代码随想录笔记|C++数据结构与算法学习笔记-二叉树(一)|二叉树的递归遍历、二叉树的迭代遍历、二叉树的统一迭代法

全文基于代码随想录及相关讲解视频。

文字链接:《代码随想录》

文章目录

二叉树的递归三部曲:(非常重要,每一题几乎都要拿来分析)

  • 确定递归函数的参数和返回值
  • 确定终止条件
  • 确定单层递归的逻辑

二叉树的递归遍历

二叉树的前序遍历

前序是中左右的遍历顺序

  • 确定递归函数的参数和返回值:

​ 参数并不是要一次性确定的,在写递归函数的过程中我们需要什么样的参数,再传入什么样的参数即可,不过大多数二叉树的题目所需要的参数并不多,基本就是传入一个根结点,在传入一个数组来放我们遍历的结果返回值一般都是void,因为我们需要的结果已经放入到传入参数的数组里了

​ 就本题来说void traversal(TreeNode* cur, vector<int>& vec)

  • 确定终止条件:

​ 确定终止条件对于递归来说也非常重要,对于一个前序遍历,当他猛猛搜索一直搜索到null那肯定就往上返。

if (cur == NULL) return;

  • 确定单层递归的逻辑

​ 单层递归逻辑其实就是在递归函数中要写的东西,这里我们实现的中左右,所以我们要处理的结点首先是中间节点。所以数组要放我们遍历过的元素vec.push(cur->val);什么是左呢,就是向左遍历traversal(cur->left, val);右就是遍历它的右孩子traversal(cur->right, vec);这样我们就实现了中序遍历。

综上伪代码如下:

cpp 复制代码
void traversal(TreeNode* cur, vector<int>& vec)
  if (cur == NULL) return;
	//中
	vec.push(cur->val)
	//左
  traversal(cur->left, val)
	//右
  traversal(cur->right, vec)

写前序、中序、后序代码的原则跟我们讲的前序的顺序一致:

中序:

cpp 复制代码
	//左
  traversal(cur->left, val)
  //中
	vec.push(cur->val)
	//右
  traversal(cur->right, vec)

后序同理。

C++代码如下

cpp 复制代码
class Solution {
public:
    void traversal(TreeNode* cur, vector<int>& vec) {
        if (cur == NULL) return;
        vec.push_back(cur->val);    // 中
        traversal(cur->left, vec);  // 左
        traversal(cur->right, vec); // 右
    }
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        traversal(root, result);
        return result;
    }
};

二叉树的中序遍历

cpp 复制代码
void traversal(TreeNode* cur, vector<int>& vec) {
    if (cur == NULL) return;
    traversal(cur->left, vec);  // 左
    vec.push_back(cur->val);    // 中
    traversal(cur->right, vec); // 右
}

二叉树的后序遍历

cpp 复制代码
void traversal(TreeNode* cur, vector<int>& vec) {
    if (cur == NULL) return;
    traversal(cur->left, vec);  // 左
    traversal(cur->right, vec); // 右
    vec.push_back(cur->val);    // 中
}

二叉树的迭代遍历

看完本篇大家可以使用迭代法,再重新解决如下三道leetcode上的题目:

文章链接:非递归遍历

视频链接:写出二叉树的非递归遍历很难么?(前序和后序)

写出二叉树的非递归遍历很难么?(中序)

前序遍历

​ A

/ \

B C

/ \

D E

对于以上的二叉树,遍历顺序是ABDEC。编程语言实现递归其实就是用栈来实现的,所以我们这里用迭代来模拟递归,也是用栈这种结构。理论上来讲所有递归都能用栈来模拟。

栈底[ ]栈顶 栈入作所示,先把A入栈,然后把A弹出。为什么要弹出呢,因为我们要把A放入数组里了,因为A是我们要处理的元素。A弹出之后,我们要处理A的左右孩子。然后依次把C、B入栈。此时:栈底[C, B]栈顶。本来我们要拿到B,但是先把C入栈,这是因为栈是先进后出的,这样我们就能先拿B,再拿C,实现中左右的遍历顺序。然后我们弹出B放入数组,现在数组是[A, B]。后面对于B的子树的处理同理。具体可以看上文视频。

再一个就是碰到叶子结点就不用处理它的左右孩子了。

详细代码如下:

前序遍历C++代码

cpp 复制代码
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;
        vector<int> result;
        if (root == NULL) return result;
        st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();                       // 中
            st.pop();
            result.push_back(node->val);
            if (node->right) st.push(node->right);           // 右(空节点不入栈)
            if (node->left) st.push(node->left);             // 左(空节点不入栈)
        }
        return result;
    }
};

右序遍历

我们只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后在反转result数组,输出的结果顺序就是左右中了

右序遍历C++代码

cpp 复制代码
class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;
        vector<int> result;
        if (root == NULL) return result;
        st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            st.pop();
            result.push_back(node->val);
            if (node->left) st.push(node->left); // 相对于前序遍历,这更改一下入栈顺序 (空节点不入栈)
            if (node->right) st.push(node->right); // 空节点不入栈
        }
        reverse(result.begin(), result.end()); // 将结果反转之后就是左右中的顺序了
        return result;
    }
};

中序遍历

为什么中序遍历不同

之前代码有两个重点,一个是访问结点,一个是处理结点。访问结点就是在二叉树中从根结点开始一个结点一个结点开始访问;处理结点就是结点元素保存到数组中,这个数组就是我们要输出的顺序。

如果是中序遍历,对于上文的二叉树:

​ A

/

B C

/ \

D E

我们还是要先访问根结点,但是我们先处理的却不能是根结点,中序遍历的第一个元素应该是D。这就造成了我们访问的顺序和我们处理的顺序不一样。这就是为什么我们的中序遍历是另外一套写法。

中序遍历迭代法的过程

中序遍历为:DBEAC

首先还是要用栈来记录我们遍历过的顺序,因为我们处理元素的时候其实按遍历顺序逆向输出的

我们先访问A,然后把A入栈,然后访问B把B入栈,然后是D,把D入栈。此时我们到叶子结点了(怎么知道到叶子结点了呢?因为我们再访问D的左孩子是空,所以肯定是叶子结点)。也就是因为D的左孩子为空,我们从栈取元素,就是D,D就是我们要处理的第一个元素。此时把D看成一个独立的二叉树。D是中,左右都是空,所以第一个输出的元素是D,把D加入到数组里。

然后由于D右也是空,我们就把B弹出来加入到数组。

然后B的右孩子不为空,栈记录我们遍历的元素E,然后指针还是往E的左孩子走,又是空!所以我们就把E弹出。

随后看E的右孩子,也是空!,所以我们就再从栈内弹出元素A加入到数组中。

然后就找A的右孩子C加入到数组中。然后C的左孩子为空,所以把C弹出加入数组,再看C的右孩子,也为空,理论上我们应该从栈内弹出元素,但是栈也为空了,所以我们的遍历流程就结束了。

综上,数组中的顺序为DBEAC,与答案一致

伪代码如下:

cpp 复制代码
 vector<int> traversal (root)
 {
   vector<int> result;
	 stack<TreeNode*> st;
   Treenode* cur = root; //当前结点指向根结点
   
   while (cur != NUll || !st.empty())
   {
     if (cur != Null)	//如果当前元素不为空
     {
       st.push(cur);	//吧当前方位的元素加入到栈里 
       cur = cur->left; //当前结点往左走
     }
     else
     {
       cur = st.top(); st.pop();
       result.push_back(cur->val);
       cur = cur->right;
     }
    return result;   
   }
 }

中序遍历C++代码

cpp 复制代码
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        TreeNode* cur = root;
        while (cur != NULL || !st.empty()) {
            if (cur != NULL) { // 指针来访问节点,访问到最底层
                st.push(cur); // 将访问的节点放进栈
                cur = cur->left;                // 左
            } else {
                cur = st.top(); // 从栈里弹出的数据,就是要处理的数据(放进result数组里的数据)
                st.pop();
                result.push_back(cur->val);     // 中
                cur = cur->right;               // 右
            }
        }
        return result;
    }
};

二叉树的统一迭代法

主要思想就是利用一个栈来完成遍历结点和处理结点的过程。

统一风格代码如下:

中序遍历

cpp 复制代码
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if (root != NULL) st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            if (node != NULL) {
                st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
                if (node->right) st.push(node->right);  // 添加右节点(空节点不入栈)

                st.push(node);                          // 添加中节点
                st.push(NULL); // 中节点访问过,但是还没有处理,加入空节点做为标记。

                if (node->left) st.push(node->left);    // 添加左节点(空节点不入栈)
            } else { // 只有遇到空节点的时候,才将下一个节点放进结果集
                st.pop();           // 将空节点弹出
                node = st.top();    // 重新取出栈中元素
                st.pop();
                result.push_back(node->val); // 加入到结果集
            }
        }
        return result;
    }
};

前序遍历

cpp 复制代码
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if (root != NULL) st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            if (node != NULL) {
                st.pop();
                if (node->right) st.push(node->right);  // 右
                if (node->left) st.push(node->left);    // 左
                st.push(node);                          // 中
                st.push(NULL);
            } else {
                st.pop();
                node = st.top();
                st.pop();
                result.push_back(node->val);
            }
        }
        return result;
    }
};

后序遍历

cpp 复制代码
class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if (root != NULL) st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            if (node != NULL) {
                st.pop();
                st.push(node);                          // 中
                st.push(NULL);

                if (node->right) st.push(node->right);  // 右
                if (node->left) st.push(node->left);    // 左

            } else {
                st.pop();
                node = st.top();
                st.pop();
                result.push_back(node->val);
            }
        }
        return result;
    }
};
相关推荐
知识分享小能手2 小时前
React学习教程,从入门到精通, React 属性(Props)语法知识点与案例详解(14)
前端·javascript·vue.js·学习·react.js·vue·react
汇能感知4 小时前
摄像头模块在运动相机中的特殊应用
经验分享·笔记·科技
阿巴Jun4 小时前
【数学】线性代数知识点总结
笔记·线性代数·矩阵
茯苓gao5 小时前
STM32G4 速度环开环,电流环闭环 IF模式建模
笔记·stm32·单片机·嵌入式硬件·学习
是誰萆微了承諾5 小时前
【golang学习笔记 gin 】1.2 redis 的使用
笔记·学习·golang
利刃大大5 小时前
【高并发内存池】五、页缓存的设计
c++·缓存·项目·内存池
DKPT5 小时前
Java内存区域与内存溢出
java·开发语言·jvm·笔记·学习
aaaweiaaaaaa6 小时前
HTML和CSS学习
前端·css·学习·html
ST.J6 小时前
前端笔记2025
前端·javascript·css·vue.js·笔记
C语言小火车6 小时前
【C++八股文】基础知识篇
c++·tcp/ip·const·智能指针·多线程同步·static关键字·c++内存模型