二叉树先序、中序、后序遍历 递归+迭代详解

二叉树遍历

  • 定义
  • 前序遍历
    • 递归实现
    • 迭代实现
      • 思路
      • 代码
  • 中序遍历
    • 递归实现
    • 迭代实现
    • 思路
  • 后序遍历
    • 递归实现
    • 迭代实现

定义

二叉树的遍历( traversing binary tree)是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。

这里有两个关键词:访问和次序。

二叉树的遍历次序不同于线性结构,最多也就是从头至尾、循环、双向等简单的遍历方式。树的结点之间不存在唯一的前驱和后继关系,在访问一个结点后,下一个被访问的结点面临着不同的选择。

因此也就产生了不同的遍历方式,主要分为4种:前序遍历,中序遍历,后序遍历,层序遍历

这里先定义一下本文中树节点的结构,方便后续实现

struct TreeNode
{
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

前序遍历

首先是前序遍历,规则是若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。

如图:
二叉树先序、中序、后序遍历 递归+迭代详解_第1张图片

递归实现

void PreOrderTraverse(TreeNode* root)
{
    if (root == NULL)//若二叉树为空则返回
        return ;
    PreOrderTraverse(root->left);//接着遍历左子树
    PreOrderTraverse(root->right);//最后右子树
}

可以看出起点是二叉树中的根节点
递归方法比较好理解,让我们看看迭代实现:

迭代实现

思路

  1. 首先,我们利用辅助栈来保存树节点【TreeNode】
  2. 每次先处理中间节点,先将根节点放入栈中,然后将右孩子加入栈,再加入左孩子。

这里要解释一下:为什么要先加入右孩子,再加入左孩子呢?

因为这样出栈的时候才是中左右的顺序。

代码

class Solution
{
public:
    void *PreOrderTraverse(TreeNode *root)
    {
        stack<TreeNode *> stk;
        stk.push(root);
        //迭代法前序遍历二叉树
        while (!stk.empty())
        {
            TreeNode *temp=stk.top();
            stk.pop();
            if(temp->right)         //先将右子树入栈
            stk.push(temp->right);
            if(temp->left)          //再将左子树入栈,这样先出栈进行处理的就是左子树
            stk.push(temp->left);
        }
    }
};

来个样例讲解一下
二叉树先序、中序、后序遍历 递归+迭代详解_第2张图片
根据上图代码,

  1. 首先先将 A 节点入栈,然后进入循环,用 temp 接收出栈的元素 A (这里A是出栈了),然后先将右子树入栈,接着是左子树,此时左子树 B 是栈顶元素。 二叉树先序、中序、后序遍历 递归+迭代详解_第3张图片

  2. 如果满足条件继续循环,接着用 temp 接收出栈的元素 B,即遍历了中间节点 ,接着再往栈中插入 B 的右子树和左子树
    二叉树先序、中序、后序遍历 递归+迭代详解_第4张图片

  3. 以此类推到最后 H 的左子树不存在,仅入栈 K,进入下一层循环,此时用 temp 接收到的是栈顶元素 K并将 K 出栈,此时栈顶元素则是 E .

  4. 再次以此类推直至栈空,则可以遍历完整个二叉树

二叉树先序、中序、后序遍历 递归+迭代详解_第5张图片

中序遍历

让我们以任意一个当前节点为基准点,前序,中序,后序都是用来形容当前节点

  • 前序遍历,即当前节点为左 中 右三个节点最先遍历
    中序遍历,即当前节点为左 中 右三个节点中间顺序进行遍历,
    后续遍历,即当前节点为左 中 右三个节点中最后进行遍历的。

那么二叉树的中序遍历算法是如何呢?哈哈,别以为很复杂,它和前序遍历算法仅仅只是代码的顺序上的差异。

递归实现

void InOrderTraverse(TreeNode* root)
{
	InOrderTraverse(root->left);//先遍历左子树
    if (root == NULL)//若二叉树为空则返回
        return ;
    InOrderTraverse(root->right);//最后右子树
}

换句话说,它等于是把调用左孩子的递归函数提前了,就这么简单

迭代实现

思路

中序遍历的迭代实现和前序遍历的代码实现则有所不同
因为前序遍历的顺序是中左右,先访问的元素是中间节点,要处理的元素也是中间节点,所以刚刚才能写出相对简洁的代码。
那么再看看中序遍历,中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点。
那么怎么处理呢?

  • 因为栈后进先出的特性,首先我们从根结点出发,不断向左走的过程中,从根开始将途径节点push入栈,这也就意味这后续访问节点的顺序一定是左子节点在根节点之前的
  • 将每次pop出栈的节点T当成是一棵子树的根节点,接下来要去访问的就是T的右子树(如果存在的话),访问完毕后再进入下一轮迭代

具体的代码如下:

class Solution
{
public:
    stack<TreeNode *> stk;
    void goToLeft(TreeNode *root) //将所有左子树入栈
    {
        while (root != NULL)
        {
            stk.push(root);
            root = root->left;
        }
    }
    void *InorderSuccessor(TreeNode *root)  //看这里
    {

        goToLeft(root);					//将所有左子树入栈
        while (!stk.empty())
        {
            TreeNode *temp = stk.top();
            stk.pop()
            visit(temp->val); //遍历该节点
            if (temp->right != NULL)
                goToLeft(temp->right); //如果右子树存在,因为右子树也是一棵独立子树
                   					   //所以也要对它进行一遍这个操作进行中序遍历
        }
    }
};

后序遍历

那么同样的,后序遍历也就很容易想到应该如何写代码了。
首先是递归代码

递归实现

void PostOrderTraverse(TreeNode* root)
{
	PostOrderTraverse(root->left);//先遍历左子树
    PostOrderTraverse(root->right);//再右子树
     if (root == NULL)//最后若二叉树为空则返回
        return ;
}

迭代实现

后序遍历是:左右根,前序遍历是:跟左右,可以先将前序遍历换成跟右左,就和前序遍历一样了

class Solution {
public:
    vector<int> PostorderTraversal(TreeNode* root) {
        vector<int> res;
        if(root==NULL) return res;
        stack<TreeNode*> st;
        st.push(root);
        while(!st.empty()){
            TreeNode* top=st.top();
            st.pop();
            res.push_back(top->val);
            if(top->left!=NULL) st.push(top->left);//因为用栈存储,所以先左后右先遍历到右子结点,最终得到后序遍历的逆序列
            if(top->right!=NULL) st.push(top->right);
        }
        reverse(res.begin(),res.end());
        return res;
    }
};

你可能感兴趣的:(leetcode,算法,c++)