c++ stack 遍历_用递归的思想实现二叉树前、中、后序迭代遍历

7bf7cdf0066eae11a3a0e32977bfa62c.png

先复习一下前、中、后遍历的顺序:

  1. 前序遍历顺序:中-左-右
  2. 中序遍历顺序:左-中-右
  3. 后序遍历顺序:左-右-中

用递归来写二叉树遍历是非常简单的,例如前序遍历的代码如下:

const result = []
function preorderTraversal(node) {
    if (!node) return null
    result.push(node.val)
    preorderTraversal(node.left)
    preorderTraversal(node.right)
}

preorderTraversal(root)

我们都知道,在调用函数时,系统会在栈中为每个函数维护相应的变量(参数、局部变量、返回地址等等)。

例如有 a,b,c 三个函数,先调用 a,a 又调用 b,b 最后调用 c。此时的调用栈如图所示:

2ea6855e7c6b291b184a9fbfdd890c8b.png

为什么要说这个呢?因为递归遍历的执行过程就是这样的,只不过是函数不停的调用自身,直到遇到递归出口(终止条件)。

举个例子,现在要用递归前序遍历以下二叉树:

   1
    
     2
    /
   3

它的遍历顺序为 1-2-3,调用栈如图所示:

85a0bd6e8a80a91dbb7777f320b5e1c2.png

理解了递归调用栈的情况,再来看看怎么利用递归思想实现前序迭代遍历:

function preorderTraversal(root) {
    const result = []
    // 用一个数组 stack 模拟调用栈
    const stack = []
    let node = root
    while (stack.length || node) {
        // 递归遍历节点的左子树,直到空为止
        while (node) {
            result.push(node.val)
            stack.push(node)
            node = node.left
        }
        // 跳出循环时 node 为空,由于前序遍历的特性
        // 当前 node 节点的上一个节点必定是它的父亲节点
        // 前序遍历是中-左-右,现在左子树已经到头了,该遍历父节点的右子树了
        // 所以要弹出父节点,从它的右子树开始新一轮循环
        node = stack.pop()
        node = node.right
    }

    return result
}

再看一个具体的示例,用迭代遍历跑一遍:

            1
          /    
         2      3
        /     /  
       4    5 6    7
  1. 初始节点 node 为 1
  2. while 遍历完它的左子节点
  3. 当前 stack == [1,2,4]
  4. 初始节点已经遍历完它下面的最后一个左子节点了,即节点 4 的左子节点,所以现在要开始遍历 4 的右子节点
  5. 弹出节点 4 并从它的右子节点开始新的循环
  6. 由于节点 4 的右子节点为空,所以不会进入 while 循环,然后弹出节点 4 的父节点 2
  7. 再从节点 2 的右子节点开始循环

看到这是不是已经发现了这个迭代遍历的过程和递归遍历的过程一模一样?

而且用递归的思想来实现迭代遍历,优点在于好理解,以后再遇到这种问题马上就能想起来怎么做了。

中序遍历

中序遍历和前序遍历差不多,区别在于节点出栈时,才将节点的值推入到 result 中。

function inorderTraversal(root) {
    const result = []
    const stack = []
    let node = root
    while (stack.length || node) {
        while (node) {
            stack.push(node)
            node = node.left
        }
        
        node = stack.pop()
        result.push(node.val)
        node = node.right
    }
    
    return result
}

后序遍历

前序遍历过程为中-左-右,逆前序遍历过程就是将遍历左右子树的顺序调换一下,即中-右-左。

然后再看一下后序遍历的过程左-右-中,可以看出逆前序遍历顺序的倒序就是后序遍历的顺序。

利用这一特点写出的后序遍历代码如下:

function postorderTraversal(root) {
    const result = []
    const stack = []
    let node = root
    while (stack.length || node) {
        while (node) {
            result.push(node.val)
            stack.push(node)
            node = node.right // 原来是 node.left,这里换成 node.right
        }

        node = stack.pop()
        node = node.left // 原来是 node.right,这里换成 node.left
    }
    
    return result.reverse() // 逆前序遍历顺序的倒序就是后序遍历的顺序
}

参考资料

他来了,他带着他的三兄弟来了,前中后序遍历统一的算法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
二叉树的创建及遍历有很多种实现方式,下面我分别介绍一下递归非递归的方式。 1. 递归实现二叉树的创建及遍历 定义二叉树节点的结构体: ```cpp struct TreeNode { int val; TreeNode* left; TreeNode* right; TreeNode(int x) : val(x), left(NULL), right(NULL) {} }; ``` 二叉树的创建采用递归方式,每次递归处理左右子树: ```cpp TreeNode* createBinaryTree(vector<int>& nums, int index) { if (index >= nums.size() || nums[index] == -1) { return NULL; } TreeNode* root = new TreeNode(nums[index]); root->left = createBinaryTree(nums, index * 2 + 1); root->right = createBinaryTree(nums, index * 2 + 2); return root; } ``` 遍历采用递归方式,先访问根节点,再访问左子树,最后访问右子树: ```cpp void preorderTraversal(TreeNode* root) { if (root == NULL) return; cout << root->val << " "; preorderTraversal(root->left); preorderTraversal(root->right); } ``` 遍历采用递归方式,先访问左子树,再访问根节点,最后访问右子树: ```cpp void inorderTraversal(TreeNode* root) { if (root == NULL) return; inorderTraversal(root->left); cout << root->val << " "; inorderTraversal(root->right); } ``` 后序遍历采用递归方式,先访问左子树,再访问右子树,最后访问根节点: ```cpp void postorderTraversal(TreeNode* root) { if (root == NULL) return; postorderTraversal(root->left); postorderTraversal(root->right); cout << root->val << " "; } ``` 2. 非递归实现二叉树遍历 遍历采用非递归方式,使用栈进行迭代,先将根节点压入栈,然后循环处理栈节点,每次处理一个节点时,先访问该节点,再将右子节点压入栈,最后将左子节点压入栈,这样可以保证左子节点先于右子节点被访问: ```cpp void preorderTraversal(TreeNode* root) { if (root == NULL) return; stack<TreeNode*> st; st.push(root); while (!st.empty()) { TreeNode* node = st.top(); st.pop(); cout << node->val << " "; if (node->right != NULL) st.push(node->right); if (node->left != NULL) st.push(node->left); } } ``` 遍历采用非递归方式,使用栈进行迭代,先将所有的左子节点压入栈,然后循环处理栈节点,每次处理一个节点时,先访问该节点,然后将右子节点压入栈,这样可以保证左子树先于根节点被访问,右子树后于根节点被访问: ```cpp void inorderTraversal(TreeNode* root) { if (root == NULL) return; stack<TreeNode*> st; TreeNode* cur = root; while (cur != NULL || !st.empty()) { while (cur != NULL) { st.push(cur); cur = cur->left; } cur = st.top(); st.pop(); cout << cur->val << " "; cur = cur->right; } } ``` 后序遍历采用非递归方式,使用栈进行迭代,需要使用两个栈,先将根节点压入第一个栈,然后循环处理第一个栈节点,每次处理一个节点时,将该节点的左子节点和右子节点分别压入第一个栈,然后将该节点压入第二个栈,这样可以保证左子树和右子树都先于根节点被访问,最后从第二个栈依次弹出节点,即为后序遍历的结果: ```cpp void postorderTraversal(TreeNode* root) { if (root == NULL) return; stack<TreeNode*> st1, st2; st1.push(root); while (!st1.empty()) { TreeNode* node = st1.top(); st1.pop(); st2.push(node); if (node->left != NULL) st1.push(node->left); if (node->right != NULL) st1.push(node->right); } while (!st2.empty()) { cout << st2.top()->val << " "; st2.pop(); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值