递归本质其实就是运用了栈这个数据结构,虽然我们不使用递归,但是任然需要用到栈这个数据结构,只是这个数据结构需要我们自己维护,相比于递归,他的时间空间开销肯定会更小。
思路:
事前,先把变量定义好
具体用法,下面来慢慢讲。
因为是后序遍历,我们先让cur一直朝着左边走,走到null,同时顺带把当前的节点压入栈中
以当前树为例
执行完上述所有代码后:
因为是后序遍历,我们要想办法,访问到E结点。
这是的栈顶元素就派上了用场:
top.right就是节点E。
当cur结点一直left,到达空后,还有另一种情况,就是D的右节点是空的。
既然是后序遍历,D的两个结点都是空,那就把D结点记录到list中呗,然后从栈中pop掉D结点(因为D结点以及其子树已经全部访问完)。
整体代码如下:
//先一直遍历左子树,直到cur是空的
while (cur != null) {
stack.push(cur);//把遍历的节点,都压入栈中
cur = cur.left;
}
//到了这里,cur已经是左边的空了,不要pop栈,因为要先访问右子树,然后再访问根节点
top = stack.peek();//先得到上一个节点,进而访问右节点
if (top.right == null) {//右节点也是空的,那么就记录当前根节点,然后弹出此节点
list.add(top.val);
stack.pop();
}else{//右节点不为空
cur=top.right;
}
用下面的树来演示一遍:
top.right不是空,是E
走完代码之后,我们发现了一个尴尬的窘境,走到E,程序就结束了,list什么都没有存。
为了一直循环操作下去,还需要再整体代码最外层加上一个循环
while (cur != null || !stack.isEmpty()) {
//cur只要不为空就一直遍历
//栈不为空,需要把栈中所有的节点都访问完才行
}
在按照下面代码演示一遍(外层循环执行一次,变换一个图片):
while (cur != null || !stack.isEmpty()) {
//先一直遍历左子树,直到cur是空的
while (cur != null) {
stack.push(cur);//把遍历的节点,都压入栈中
cur = cur.left;
}
//到了这里,cur已经是左边的空了,不要pop栈,因为要先访问右子树,然后再访问根节点
top = stack.peek();//先得到上一个节点,进而访问右节点
if (top.right == null) {//右节点也是空的,那么就记录当前根节点,然后弹出此节点
list.add(top.val);
stack.pop();
}else{//右节点不为空
cur=top.right;
}
}
第一次循环:
第二次循环:
第三次循环:
唉,我在这里就没有画了。
细心的大伙儿,不知道有没有发现,第三个循环的图和上面的某一个循环的图长的很像。
没错,就是第一个循环!
除了List,存的元素不一样,其他变量都一样,如果我在画下去,其实就没有意义了,就是在这三张图里一直兜圈子,而list只记录了E,最终的结果是,list一E到底。——没错死循环了!
不要怕,有时,在最困难的时刻,在坚持一小会儿,就会看到胜利的╰(*^▽^*)╯
想要修改其实也很简单。
循环的原因很简单,就是E明明被记录了,但是程序还是会通过top.right去访问E,形成死循环。
我们只需要对记录过的节点进行标记就可以了。
方法:
定义这样一个变量:
一旦list要记录一个节点,就把这个节点赋值给pre。
然后再把上面代码的if条件多加一条
如果当前节点的左节点已经记录了,那么记录当前节点,然后pop当前节点(右遍历完,然后根节点遍历)
这样就大功告成啦
整体代码:
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();//借助栈来实现后序遍历
List<Integer> list = new ArrayList<>();//用来记录节点的顺序
TreeNode top = null;//接收peek的元素
TreeNode cur = root;//遍历整棵树
TreeNode pre = null;
while (cur != null || !stack.isEmpty()) {
//先一直遍历左子树,直到cur是空的
while (cur != null) {
stack.push(cur);//把遍历的节点,都压入栈中
cur = cur.left;
}
//到了这里,cur已经是左边的空了,不要pop栈,因为要先访问右子树,然后再访问根节点
top = stack.peek();//先得到上一个节点,进而访问右节点
if (top.right == null||pre==top.right) {//右节点也是空的,那么就记录当前根节点,然后弹出此节点
list.add(top.val);
pre=top;
stack.pop();
}else{//右节点不为空
cur=top.right;
}
}
return list;
}
}