excel 进行二叉树_专题:二叉树的遍历(迭代)

1873da9cac14ac827d57f727350fad6e.png

任何算法的递归版本都可以改成非递归版本,因为函数递归调用其实质就是压栈的过程,那么我们完全可以使用堆栈来模拟这个过程。二叉树的遍历(递归法)很容易实现,本质是采用了栈帧的实现方式,函数调用就是压栈,函数求解就是出栈的过程,那么我们完全可以手动创建栈来模拟这个过程,按照指定遍历顺序迭代的访问二叉树的所有结点。

因为前序、中序、后序三种遍历方式在当前函数体内都是先调用f(root.left),再调用f(root.right),函数再往下逐层调用,直到f(root.left)返回为空时。所以三种遍历方式的切入点都是要先遍历到二叉树的最左子结点(root.left == null),在遍历的过程中将根节点入栈,出栈的时候访问当前根节点以及其右子二叉树。创建栈的作用主要就是能够让我们存储访问顺序的那些根节点,可以回溯的对二叉树进行访问。

b86e3ae9c06176e5183fb652187935e2.png

前序遍历二叉树:

LeetCode-144. Binary Tree Preorder Traversal​zhuanlan.zhihu.com
523fd4d2c2b3b416bb2f6590a822ee04.png

前序遍历的访问顺序是 【根 左 右】,所以我们需要从二叉树的左分支入手,依次push入栈,入栈的同时访问了该结点,根节点向左更新,直到二叉树的最左边,栈中存放的结点方便了我们进行回溯父结点。入栈的顺序就是访问的顺序,为了模拟递归,函数调用是入栈,函数执行是出栈,出栈return到上一个调用它的函数。

/*
        1           如图,先访问二叉树的根节点1,同时结点1入栈,
       /           再访问二叉树的左子树,左子树的跟结点2被访问,同时入栈,
      2   3         重复上述过程,直到到达二叉树的最左结点的left指针域,为null
     /   /        需要回溯到栈中第一个弹出的结点且那根节点具有右孩子,则更新root
    4  5 6  7       让root访问结点4或结点2或结点1....的右子树,依次进行,直到根结点为空且栈空
*/
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> preOrderList = new ArrayList<>(); //存放访问顺序
        Stack<TreeNode> stack = new Stack<>();  //存放结点,用于回溯
        while(root != null || !stack.empty()) {     //迭代遍历二叉树
            while(root != null) {   //使root指向当前子二叉树的最左结点
                stack.push(root);
                preOrderList.add(root.val); //将当前子二叉树的根节点入栈,并访问
                root = root.left;
            }
            while(root == null && !stack.empty()) {
                root = stack.pop().right;   //自底向上找到栈中跟结点第一个非空右孩子
            }
        }
        return preOrderList;
    }
}

中序遍历二叉树:

LeetCode-94. Binary Tree Inorder Traversal​zhuanlan.zhihu.com
523fd4d2c2b3b416bb2f6590a822ee04.png

中序遍历的访问顺序是 【左 根 右】,所以我们需要从二叉树的左分支入手,依次push入栈,根节点向左更新,直到二叉树的最左边,即到了开始访问的时机,栈中存放的结点方便了我们进行回溯父结点。出栈的顺序就是访问的顺序,为了模拟递归,函数调用是入栈,函数执行是出栈,出栈return到上一个调用它的函数。

/*
        1           如图,先从根节点沿着左分支走到最左边,结点4处,由于结点4没有左孩子
       /           先访问结点4作为跟结点本身,结点4出栈,接着访问结点4的右孩子(作为整个子二叉树)
      2   3         (若右孩子为空则更新结点root为结点2,访问以结点2为根节点的子二叉树:依次访问其根结点和它的右孩子)
     /   /        每次循环弹出访问一个结点,更新结点root为结点8,再对以结点8为跟结点的子二叉树进行沿着左分支走到最左边
    4  5 6  7       结点8访问完之后,且无右孩子,说明当前子二叉树访问完成,则将root更新为结点2
                   简单的,root=root.right就可以完成对root的更新,若结点4.right=null, 再下一次循环从栈顶弹出应该回溯到的结点
      8             若结点4.right存在,则直接更新就好,综上,一条语句可完成.依次进行,直到根节点为空,并且栈为空。
*/
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> inOrderList = new ArrayList<>();  //存放访问顺序
        Stack<TreeNode> stack = new Stack<>();  //存放结点,用于回溯
        while(root != null || !stack.empty()) {       //迭代遍历二叉树
            while(root != null) {   //使root指向当前子二叉树的最左结点
                stack.push(root); 
                root = root.left;   
            }
            root = stack.pop();
            inOrderList.add(root.val);   //出栈当前子二叉树的根结点,并访问
            root = root.right;     //更新root结点:当前子二叉树的右孩子 or 树的父结点
        }
        return inOrderList;
    }  
}

后序遍历二叉树:

LeetCode-145. Binary Tree Postorder Traversal​zhuanlan.zhihu.com
523fd4d2c2b3b416bb2f6590a822ee04.png

在做迭代前序遍历和中序遍历的时候,发现root经过的路径都是左根右,对于前序和中序来说,访问路径基本上跟经过路径是一致的。但是在后序遍历中,我们先经过根节点,但是我们不会去访问它,而是会选择先访问它的右子节点。所以在这种情况下,我们会将根节点留在栈中不弹出,等到需要访问它的时候再出。

那我们什么情况下才能访问根节点?是从右节点回溯到根节点,而不是从左节点回溯到根节点,所以我们需要记录之前结点和当前节点的关系,来确定是否访问当前节点。总结起来,我们什么时候才能访问节点。有如下两种情况:

  • 当前经过节点是叶子节点。
  • 当前经过节点的右子节点是上一次访问的节点。

若不满足上述情况,说明是从左孩子回溯到根节点,需要先访问根节点的右孩子,root = root.right

/*
        1           
       /           
      2   3         
     /   /        
    4  5 6  7       

*/
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> postOrderList = new ArrayList<>();    //用于存放访问顺序
        Stack<TreeNode> stack = new Stack<>();  //存放结点,用于回溯
        TreeNode pre = null;    //记录之前访问过的结点
        while(root != null || !stack.empty()) { //迭代访问二叉树
            while(root != null) {   //使root指向当前子二叉树的最左结点
                stack.push(root);
                root = root.left;
            }
            root = stack.peek();
            if(root.right == null || root.right == pre) {        //当前结点为叶子结点 或者 当前结点的右孩子是上个访问结点
                pre = root;    //更新上一次访问的结点
                postOrderList.add(stack.pop().val);     //出栈,表示访问了当前结点
                root = null;    //让root到下一次循环再更新,避免发生空栈错误
            }else {
                root = root.right;     //访问当前结点的右孩子
            }
        }
        return postOrderList;    
    }
}

总结:

  • 事实上我们使用迭代对二叉树进行访问,访问的顺序都是左子树、根结点、右子树的顺序。关键在于真正访问根节点的时机(根节点的值加入到列表的时候)
  • 事实上用迭代模拟函数调用(入栈)、函数执行返回(出栈),我们就二叉树遍历这个问题上,每个结点都会访问到三次。
  • 用栈的后进先出,有效的模拟了递归中的函数调用与执行返回过程。

此外,欢迎到我的博客网站做客,请点击

以恒的个人博客​tangyiheng.top
b678471ef11852e4271804fbc9170df3.png

9625e773fb56417bf69547e7b1810c1b.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值