迭代实现遍历二叉树
用递归做二叉树的遍历是非常简单,只需要几行代码就可以搞定。
那我们知道递归的底层使用的还是栈,所以只要使用递归方式能解决的问题,都可以使用迭代(非递归)来解决。我们可以使用栈来模拟递归的过程。
迭代实现二叉树的前序遍历
思路分析:栈
前序遍历:中 左 右
- 从根节点开始遍历入栈,然后依次让右节点进栈、左节点进栈,这样出栈的时候就是先左后右了(符合前序的中左右)。
- 每次出栈,就写入返回数组中,然后将该节点的右节点进栈、左节点进栈…
- 直到栈被取空了,就代表遍历完所有节点了。
Go代码
func preorderTraversal(root *TreeNode) []int {
if root == nil {
return nil
}
ret_arr := make([]int, 0)
stack := []*TreeNode{root}
for len(stack)!= 0 {
popNode := stack[len(stack)-1]
stack = stack[:len(stack)-1]
ret_arr = append(ret_arr, popNode.Val)
if popNode.Right != nil {
stack = append(stack, popNode.Right)
}
if popNode.Left != nil {
stack = append(stack, popNode.Left)
}
}
return ret_arr
}
迭代实现二叉树的后序遍历
思路分析:栈+反转数组
后序遍历:左 右 中
- 我们可以将前序遍历的右左入栈反过来,就变成了中右左的遍历方式
- 中右左反转就是左右中,所以反转后就变成后序遍历的结果了。
Go代码
func postorderTraversal(root *TreeNode) []int {
if root == nil {
return nil
}
ret_arr := make([]int, 0)
stack := []*TreeNode{root}
for len(stack)!= 0 {
// 从栈出一个节点
popNode := stack[len(stack)-1]
stack = stack[:len(stack)-1]
ret_arr = append(ret_arr, popNode.Val)
if popNode.Left != nil {
stack = append(stack, popNode.Left)
}
if popNode.Right != nil {
stack = append(stack, popNode.Right)
}
}
// 反转
for i,j:=0,len(ret_arr)-1; i<=j; i,j=i+1,j-1 {
ret_arr[i], ret_arr[j] = ret_arr[j], ret_arr[i]
}
return ret_arr
}
迭代实现二叉树的中序遍历
思路分析:栈+指针
相信大家看了上面的前序、后序遍历之后,会认为中序遍历也差不多,可以变一变代码顺序(跟递归写法的变换一样)就能完成中序遍历,其实不然,我们没法只根据上面的思路去完成中序遍历!!
因为:前序遍历和中序遍历的遍历顺序和操作顺序是不一致的!!!
在前序遍历中:从根节点开始遍历,也是从跟节点开始操作(写入返回数组中),然后遍历到下一个左节点,同时也操作左节点…也就是遍历跟操作的节点始终一致,写起来也比较顺畅。
而在中序遍历中:从根节点开始遍历,但是我们需要一直遍历到最左的叶子节点,才要开始操作该节点(写入返回数组中),所以需要用一个指针指向我们要操作的节点,操作完了之后让该指针指向其下一个操作节点。
Go代码
func inorderTraversal(root *TreeNode) []int {
if root == nil {
return nil
}
ret_arr := make([]int, 0)
stack := make([]*TreeNode, 0)
// 操作节点指针
cur := root
for len(stack) != 0 || cur != nil {
// 如果左子树存在,一直走到左叶子节点
for cur != nil {
stack = append(stack, cur)
cur = cur.Left
}
// 出栈一个元素
cur = stack[len(stack)-1]
stack = stack[:len(stack)-1]
// 入结果数组
ret_arr = append(ret_arr, cur.Val)
cur = cur.Right
}
return ret_arr
}