文章目录
最近重新复习二叉树的非递归遍历方式,发现前中后序遍历实现的思想都不一致,导致要理解很多种递归的思路,有点痛苦。
对于递归遍历,思路非常明确,理解和记忆也都没有什么困难。
如下根据不同的访问顺序就可以实现先中后序的访问。
func traversal(root *TreeNode, r *[]int) {
if root==nil{
return
}
//*r = append(*r, root.Val); //先序遍历
traversal(root.Left, r)
//*r = append(*r, root.Val); //中序遍历
traversal(root.Right, r)
//*r = append(*r, root.Val); //后序遍历
}
下面把非递归的方式也归结成统一的思想:
前中序的非递归实现一般用回溯法,每次左子树遍历完之后才回溯。使用一个辅助栈,从根节点开始,如果节点不为空,就一直入栈,并更新节点为当前节点的左节点。节点为空时说明左子树处理完毕,此时将左子树出栈,处理右子树。因此:
节点在入栈的时候访问,就是前序遍历。
节点在出栈的时候访问,就是中序遍历。
type stack []*TreeNode
func pop(s *stack) *TreeNode{
node := *stack[len(stack)-1]
*stack = *stack[:len(stack)-1]
return node
}
func push(s *stack, *TreeNode) {
*stack = append(*stack, node)
}
func traversal(root *TreeNode) []int {
r := make([]int, 0, 0)
s := make(stack, 0, 0)
node := root
for len(s)!= 0 || node!=nil {
if node != nil {
//r = append(r, node.Val) //在入栈的时候访问,就是前序遍历
push(&s, node)
node = node.Left
} else {
node = pop(&s)
//r = append(r, node.Val) //在出栈的时候访问,就是中序遍历
node = node.Right
}
}
return r
}
接下来看了很多后序遍历的实现都不好理解,其实后序遍历就是 根->右->左 的逆序遍历,我们换个思路,先得到 根->右->左 的遍历结果,再反序就可以了,根->右->左就是前序遍历根->左->右的变种,上述前序遍历的代码交换以下 Left 和 Right 的入栈顺序即可,因此后序遍历的代码如下
type stack []*TreeNode
func pop(s *stack) *TreeNode{
node := *stack[len(stack)-1]
*stack = *stack[:len(stack)-1]
return node
}
func push(s *stack, *TreeNode) {
*stack = append(*stack, node)
}
func traversal(root *TreeNode) []int {
r := make([]int, 0, 0)
s := make(stack, 0, 0)
node := root
for len(s)!= 0 || node!=nil {
if node != nil {
r = append(r, node.Val)
push(&s, node)
node = node.Right
} else {
node = pop(&s)
node = node.Left
}
}
//上面已经得到了根->右->左的顺序,反转一遍就是后序遍历的结果了
r2 := make([]int, 0, len(r))
for i:=len(r)-1; i>=0 ;i--{
r2 = append(r2, r[i])
}
return r2
}
这样看来,所有的遍历都可以用一种回溯的方法解决了。