题面
给定一个二叉树,返回它的 后序 遍历。
示例
输入: [1,null,2,3]
1
\
2
/
3
输出: [3,2,1]
分析
定义 在二叉树中,先左后右再根,即首先遍历左子树,然后遍历右子树,最后访问根结点
- 已知前序遍历和中序遍历,就能确定后序遍历
递归遍历
- 后序遍历左子树
- 后序遍历右子树
- 访问根结点
- 二叉树为空则结束返回
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
func postorderTraversal(root *TreeNode) []int {
if root == nil {
return []int{}
}
l :=[]int{}
left := postorderTraversal(root.Left)
right:=postorderTraversal(root.Right)
if len(left)>0 {
l = append(l,left...)
}
if len(right)>0 {
l = append(l,right...)
}
l = append(l,root.Val)
return l
}
非递归遍历
核心思想:用栈来保存先前走过的路径,以便可以在访问完子树后,可以利用栈中的信息,回退到当前节点的双亲节点,进行下一步操作
与递归区别
- 在非递归算法中,利用栈回退到时,并不知道是从左子树回退到根节点,还是从右子树回退到根节点
- 如果从左子树回退到根节点,此时就应该去访问右子树,而如果从右子树回退到根节点,此时就应该访问根节点。
- 相比前序和后序,必须得在压栈时添加信息,以便在退栈时可以知道是从左子树返回,还是从右子树返回进而决定下一步的操作
import "container/list"
type Element struct {
Bt *TreeNode
Direct bool
}
func postorderTraversal(root *TreeNode) []int {
l :=[]int{}
stack := list.New()
e :=&Element{Bt:root}
stack.PushBack(e)
for stack.Len()>0 {
top := stack.Back().Value.(*Element)
// 不可直接访问
if !top.Direct {
rt:= top.Bt
stack.Remove(stack.Back())
// 入栈顺序与后序遍历相反,确保出栈顺序为左->右->根
if rt != nil {
// 根节点
stack.PushBack(&Element{Bt:rt,Direct:true})
// 右节点
stack.PushBack(&Element{Bt:rt.Right})
// 左节点
stack.PushBack(&Element{Bt:rt.Left})
}
}
// 为右节点, 可直接访问
if top.Direct {
l = append(l,top.Bt.Val)
stack.Remove(stack.Back())
}
}
return l
}
标志位分析
- 栈的进进出出
- 当左子树处理完毕出栈,返回根节点时,不能立即处理当前根节点
- 给根节点打标志,表明左已处理完,此时需要根指向的右子树节点入栈
本质栈进进出出,弹出的栈元素,需要对其相关节点的入栈进行可行性评估
python 巧妙版
- 充分复用层序遍历特点
- 栈的反序,结合上步自顶向下构建后序非递归遍历,无须使用标志位
- 使用标志位的关键在于区分途经节点,访问节点,与节点出入栈,用标志表明是否已经来过,再次遇到意味着访问,即取值,彻底出栈
class Solution:
def postorderTraversal(self , root ):
st,rs = [],[]
while root:
rs.insert(0,root.val)
root.left and st.append(root.left)
root.right and st.append(root.right)
root = st.pop() if st else None
return rs