【LeetCode】二叉樹的中序遍歷(非遞迴形式:棧模擬遞迴,標記模擬遞迴,莫里斯遍歷)
阿新 • • 發佈:2022-04-11
二叉樹的中序遍歷
題目連結:https://leetcode-cn.com/problems/binary-tree-inorder-traversal/
面試的時候問這道題基本都是考察非遞迴的寫法,但還是貼一下遞迴寫法:
方法1:遞迴
var result []int func f(root *TreeNode) { if root==nil{ return } f(root.Left) result=append(result,root.Val) f(root.Right) } func inorderTraversal(root *TreeNode) []int { result=[]int{} f(root) return result }
時間複雜度:O(N)
空間複雜度:O(N)
方法2:採用棧模擬遞迴
遞迴其實也有一個棧,要求非遞迴寫法的話我們可以模擬這個棧
func inorderTraversal(root *TreeNode) []int { var stack []*TreeNode var result []int // 中序遍歷,非遞迴形式 for root != nil || len(stack) > 0 { // 當前節點不空,則一直將當前節點的左子樹入棧 if root!=nil{ stack=append(stack,root) root=root.Left }else { // 當前節點空,則取棧頂元素並列印 node:= stack[len(stack)-1] stack = stack[:len(stack)-1] result = append(result, node.Val) // 然後當前元素指向棧頂元素的右子樹 root = node.Right } } return result }
時間複雜度:O(N)
空間複雜度:O(N)
方法3:採用標記法模擬遞迴
有點類似golang的gc的三色標記法
對每個節點,都需要入棧標記兩次才能訪問其元素值,第一次入棧是不能訪問其值的,因為第一次入棧是第一次訪問該節點,需要先訪問該節點的左子樹,本身節點,右子樹分別入棧,第二次訪問時,才訪問其元素值
func inorderTraversal(root *TreeNode) []int { var stack []*TreeNode var result []int if root == nil { return result } // 中序遍歷,非遞迴形式 標記法模擬遞迴 flagMap := make(map[*TreeNode]int) stack = append(stack, root) flagMap[root] = 1 for len(stack) > 0 { node := stack[len(stack)-1] stack = stack[:len(stack)-1] flag := flagMap[node] // 第一次入棧元素 按照 右 自身 左 的順序入棧,因為棧的性質,要求中序,最先列印的最後入 if flag == 1 { // 右孩子,標記為第一次入棧 if node.Right != nil { stack = append(stack, node.Right) flagMap[node.Right] = 1 } // 自身 標記為第二次入棧 stack = append(stack, node) flagMap[node]++ // 左孩子 標記為第一次入棧 if node.Left != nil { stack = append(stack, node.Left) flagMap[node.Left] = 1 } } else if flag == 2 { // 已經是第二次入棧的元素了,直接列印 result = append(result, node.Val) } } return result }
方法4:莫里斯遍歷
遞迴,迭代和模擬發方式都使用了額外的輔助空間,而莫里斯遍歷的優點就是沒有使用任何輔助空間,缺點就是將二叉樹變成了連結串列結構
根據中序遍歷的特點,將樹轉化為一個連結串列
在中序遍歷中,根節點的前一個字元肯定是其左子樹中最右邊的那個節點
- 將黃色部分掛到5的右子樹上
- 將2和5掛到4的右子樹上
這樣整棵樹基本上就變成了一個連結串列結構,遍歷即可,結構即是中序遍歷結果
時間複雜度:O(N)
空間複雜度:O(1)
func inorderTraversal(root *TreeNode) []int {
var result []int
if root == nil {
return result
}
for root!=nil{
// 如果左節點不為空,則將當前節點連帶右子樹全部掛到 左節點的最右子樹下面
if root.Left!=nil{
// pre.right 就是左節點的最右子樹
pre:=root.Left
for pre.Right!=nil{
pre=pre.Right
}
// 掛上去
pre.Right=root
// 將root指向原來root的left
node:=root
root=root.Left
node.Left=nil
}else {
// 左子樹為空,列印節點,並向右遍歷
result=append(result,root.Val)
root=root.Right
}
}
return result
}