1. 程式人生 > 其它 >【LeetCode】二叉樹的中序遍歷(非遞迴形式:棧模擬遞迴,標記模擬遞迴,莫里斯遍歷)

【LeetCode】二叉樹的中序遍歷(非遞迴形式:棧模擬遞迴,標記模擬遞迴,莫里斯遍歷)

二叉樹的中序遍歷

題目連結: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
}