「这是我参与11月更文挑战的第16天,活动详情查看:2021最后一次更文挑战」
给定一个二叉树,返回它的 后序 遍历。
示例:
输入: [1,null,2,3]
1
\
2
/
3
输出: [3,2,1]
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
既然说递归方法很简单,那我们就先来看简单的递归方法
递归
首先,我们需要了解什么是二叉树的后序遍历,按照左→右→根节点的方式遍历这棵树,所得到的便是二叉树的后序遍历。而在访问其子树时,无论左右子树,都也应按照这个顺序遍历。直至整棵树完成。这整个遍历过程,似乎自带递归属性,让我们第一眼看到,就会想到递归方法。
我们定义一个postorder(root)表示当前遍历到的root节点。先递归遍历左子树,root.left,遍历完成之后再递归遍历右子树root.right,最后记录根节点。终止条件为root全部遍历。
function postorder(root, res=[]){
if(!root) return;
postorder(root.left,res);
postorder(root.right,res);
res.push(root.val)
return res;
}
复杂度分析
- 时间复杂度:O(n),其中 n 是二叉搜索树的节点数。每一个节点恰好被遍历一次。
- 空间复杂度:O(n),为递归过程中栈的开销,平均情况下为 O(logn),最坏情况下树呈现链状,为 O(n)。
既然递归方法已经想出来了,那么自然要挑战一下迭代方法。
迭代方法
既然题目有要求了,那我们自然是可以使用迭代方法完成的。这两种方法其实是等价的,区别就在于递归的时候是隐式的维护了一个栈,而迭代时,我们将这个栈模拟出来。
3
↙ ↘
9 4
↙ ↘
5 7
我们以上面的二叉树为例。我们按中→右→左入栈,则出栈顺序便是左→右→中。但是我们在遍历的时候,我们无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况。那我们就将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记。如何标记呢,就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。 这种方法也可以叫做标记法。
-
新建一个stack栈[],一个res = []
-
3入栈,[3]
-
入栈空节点,代表3访问过。stack=[3,null]
-
判断right存在,4入栈,stack= [3, null, 4]
-
判断left存在,9入栈,stack=[3, null, 4, 9]
-
第一次循环结束,stack不为空,则取出栈顶元素。
-
node = 9,9不为空,代表没有访问过,9入栈,加入标记stack=[3, null, 4, 9, null]
-
判断左右子节点都不存在,循环结束。
-
stack不为空,则取出栈顶元素,node=null,代表有元素访问结束,需要出栈。
-
取出栈顶元素,9,传入res中,res=[9]。循环结束。
-
stack不为空,则取出栈顶元素。node=4,代表没有访问过,4入栈。加入标记stack=[3, null, 4, null]
-
判断,左右子节点存在,入栈。stack=[3, null, 4, null,7,5]。循环结束。
-
取出栈顶元素,判断非空,入栈,加入null标记stack=[3, null, 4, null, 7, 5, null]。
-
判断左右子节点为空,进入下一个循环。
-
取出栈顶元素,判断为空,出栈栈顶元素stack=[3, null, 4, null, 7]。res=[9,5]。进入下一个循环。
-
取出栈顶元素,判断非空,入栈,加入null标记stack=[3, null, 4, null, 7, null]。
-
判断左右子节点为空,进入下一个循环。
-
取出栈顶元素,判断为空,出栈栈顶元素stack=[3, null, 4, null,]。res=[9,5,7]。进入下一个循环。
-
取出栈顶元素,判断为空,出栈栈顶元素stack=[3, null]。res=[9,5,7,4]。进入下一个循环。
-
取出栈顶元素,判断为空,出栈栈顶元素stack=[]。res=[9,5,7,4,3]。进入下一个循环。
-
stack为空,遍历结束。返回res
var postorderTraversal = function(root, res = []) { const stack = []; if (root) stack.push(root); while(stack.length) { const node = stack.pop(); if(!node) { // 拿到标记节点,处理上一个节点 res.push(stack.pop().val); continue; } stack.push(node); // 中 stack.push(null); // 节点访问过,但是还没有处理,加入空节点做为标记。 if (node.right) stack.push(node.right); // 右 if (node.left) stack.push(node.left); // 左 }; return res; };
复杂度分析
- 时间复杂度:O(n),其中 n 是二叉搜索树的节点数。每一个节点恰好被遍历一次。
- 空间复杂度:O(n),为迭代过程中显式栈的开销,平均情况下为 O(logn),最坏情况下树呈现链状,为 O(n)。