94 Binary Tree Inorder Traversal 【递归和迭代的对比较分析】

来源:互联网 发布:宽带网络套餐十机顶盒 编辑:程序博客网 时间:2024/06/03 17:41

一道很常规的二叉树遍历题,相信大家都在课上学习过。

但是题目要求是不能用递归调用的方法,也就是课上讲过的方法。要用iterative迭代的方法,也就是一个一个找,通过while循环来输出。

先把递归的方法代码写出来,如下。要注意的是,我连递归调用都不能立马想起来,只想到了当node是null时,什么都不用做。那么当不是null的时候呢?其实就是按照preorder的顺序,摆放的三行代码即可,怎么记忆呢?以单一root为例,就是先输出左子节点,其为空,则不处理,然后到本身,然后到右子节点。

课程中一般只需要打印,但是这题要求把所有节点数值收集到一个集合里,所以设计一个helper函数,不返回任何值 (void 函数在这里比较好用来递归调用)。

/** * Definition for a binary tree node. * public class TreeNode { *     int val; *     TreeNode left; *     TreeNode right; *     TreeNode(int x) { val = x; } * } */public class Solution {    public List<Integer> inorderTraversal(TreeNode root) {        List<Integer> result= new ArrayList<>();        helper(root, result);        return result;    }    // 课程中一般只需要打印出来,但是这里需要返回一个list,那么就写一个helper函数,加入一个输入的参数即能解决问题。    public void helper(TreeNode root, List<Integer> list){/*        if(root!=null){            helper(root.left, list);            list.add(root.val);            helper(root.right, list);        }*/        if(root=null) return; // 这句code其实并不是很intuitive,意思是一样的,如果为空,则不做任何处理,刚进入就返回。        helper(root.left, list);        list.add(root.val);        helper(root.right, list);    }}

那么用迭代的方法,思路是比较有意思的,同时也比较固定,没有其他解法。

参考代码来理解整个逻辑:

1,从root开始要往深度走,找到最左边的节点,那么中间走过的这些节点都需要一次记录下来,而且顺序的话是要反向输出的。用stack合适!

2,一直往最左走到了null,那么stack中的top位置就是要弹出并且记入结果的node。然而这个node的右端是紧接着要读取的部分,所以curr要指向这个node的右子节点,然后开始重复上面的往左走到最左,并且一直往stack装node的过程。

大概的过程就是这样,其实本质上是什么?就是递归调用方法中,一模一样的过程,因为在递归调用中,这些访问过的节点也都是存放在系统中的stack里,一个个压入,然后当遇到return时再一个个弹出,只不过递归调用是把所有过程都隐藏起来了,而迭代的话,是显示的一一写出了。

代码中一个需要好好理解的就是第一个while的条件:当curr不为null,或者stack里还有node时,就一直循环操作。只有当curr指向null,同时stack里也没有东西,那么才代表整个树遍历完毕。在每一个循环中,curr还是变化的中心:1 往左走到底;2 指向最后左节点;3,指向最左节点的右子节点;然后再次进入循环。

以上的过程,最好是通过画图在脑海中形成印象:一棵树,从根到最左,到右子,依然到最左,如果没有,则指向stack中弹出来top element,等于是往上面爬了一层。具体画面看ppt中的图吧,文字也表示不清楚。

代码如下:

/** * Definition for a binary tree node. * public class TreeNode { *     int val; *     TreeNode left; *     TreeNode right; *     TreeNode(int x) { val = x; } * } */public class Solution {    public List<Integer> inorderTraversal(TreeNode root) {        List<Integer> result = new ArrayList<>();                Stack<TreeNode> stack = new Stack<>();        TreeNode curr=root;        while(curr!=null || !stack.isEmpty()){            while(curr!=null){                stack.push(curr);                curr=curr.left;            } // curr is null here after            curr=stack.pop();            result.add(curr.val);            curr=curr.right;        } // 想象一下最后curr的完结情况,最右端的节点,因此按照while循环,找到其最左子节点(null),然后pop弹出该节点        // 写入result,curr指向右节点,为null,此时stack也为空,整个树遍历完毕,循环也结束。                return result;    }}

最后加一个 递归调用,但不需要helper方法的解法,之前说了如果是有return类型的方法递归调用,那么可能不好操作,因为返回结果每次要new 一个list,但是其实把list里所有的数全部add进去也是可以的,就用addAll就行:

/** * Definition for a binary tree node. * public class TreeNode { *     int val; *     TreeNode left; *     TreeNode right; *     TreeNode(int x) { val = x; } * } */public class Solution {    public List<Integer> inorderTraversal(TreeNode root) {        List<Integer> result = new ArrayList<>();        if(root==null) return result;        result.addAll(inorderTraversal(root.left)); // 两步写作一行代码,有点意思,哈哈        result.add(root.val);        result.addAll(inorderTraversal(root.right)); // 同理        return result;    }}

以上三组代码,性能效率是一样的,充分说明了之前的判断,recursive和iterative的本质是一样的,递归只是系统自建stack,而迭代是把建stack,压入,弹出这些过程全部显示的写成代码。

通过这题把二叉树的遍历算是搞的比较透彻。

之后两篇分析 preorder 和 postorder


0 0