一道很常规的二叉树遍历题,相信大家都在课上学习过。
但是题目要求是不能用递归调用的方法,也就是课上讲过的方法。要用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