144. 二叉樹的遍歷「前序、中序、後序」 Golang實現

wochh發表於2024-11-21

題目描述:

給你二叉樹的根節點 root ,返回它節點值的 前序 遍歷。
144. 二叉樹的遍歷「前序、中序、後序」 Golang實現

思路分析:

遞迴法:

前序遍歷的順序是中左右的順序。那麼每個子樹都是這個順序,所以可以使用遞迴進行遍歷。遞迴遍歷有3部曲
	1.確定遞迴函式的引數和返回值。
		因為返回值要求儲存在一個陣列中,所以遞迴函式的引數應該包括樹的根節點和結果陣列
		` func preOrder(root *TreeNode,res *[]int) `
	2.確定遞迴函式的終止條件
		對於前序遍歷來說,當某個根節點沒有孩子節點的時候終止遍歷。
		```
		if root==nil {
                	return
				 } ```
	3.確定單層遞迴的邏輯
		按照順序進行遍歷即可。
	   *res = append(*res, root.Val)
			preOrder(root.Left,res)
			 preOrder(root.Right,res)

最終的程式碼為:

點選檢視程式碼
func preorderTraversal(root *TreeNode) []int {
//    遞迴函式3步驟:
//    1.確定遞迴函式的引數  2.確定遞迴函式的停止條件  3.確定單層遞迴的呼叫邏輯
   var res []int
   preOrder(root,&res)
   return res
}

func preOrder(root *TreeNode,res *[]int){
   if root==nil {
       return
   }
   *res = append(*res, root.Val)
   preOrder(root.Left,res)
   preOrder(root.Right,res)
}

中序遍歷:

點選檢視程式碼
func inorderTraversal(root *TreeNode) []int {
   var res []int
   if root==nil{
       return res
   }
   getOrder(root,&res)
   return res
}

func getOrder(root *TreeNode, res *[]int)  {
   if root!=nil {
       getOrder(root.Left,res)
       *res = append(*res, root.Val)
       getOrder(root.Right,res)
   }
}

後序遍歷:

點選檢視程式碼
func postorderTraversal(root *TreeNode) []int {
   var res []int
   postOrder(root,&res)
   return res
}

func postOrder(root *TreeNode,res *[]int)  {
   if root==nil{
       return
   }
   postOrder(root.Left,res)
   postOrder(root.Right,res)
   *res = append(*res, root.Val)
}

非遞迴版本

上方是三種遍歷的遞迴方式,一定要明確遞迴的3個步驟。但是遞迴一般複雜度高,很難解決資料量大的資料。在計算機中遞迴就是用棧實現的,所以我們可以用棧來實現遞迴函式的非遞迴版本。但是三種遍歷方式的寫法會有區別。

三種遍歷的非遞迴其實都是從根節點開始按照根左右的方式入棧,區別就在於什麼時候應該出棧

前序遍歷

前序遍歷的順序是根左右,由於是棧來實現,所有實際的入棧順序是根右左,當訪問到根的時候將根節點入棧--記錄其值,然後將根節點出棧,按照根節點的右左孩子的順序入棧,再根據棧頂元素作為根節點進行處理。函式結束條件則是棧的長度大於0.

點選檢視程式碼
func preorderTraversal(root *TreeNode) []int{
    var res []int
    if root==nil{
        return res
    }
    var stack []*TreeNode
    stack = append(stack, root)
    for len(stack)>0 {
        node:=stack[len(stack)-1]
        stack = stack[:len(stack)-1]
        if node!=nil {
            res = append(res, node.Val)
        }
        if node.Right != nil {
            stack = append(stack, node.Right)
        }
        if node.Left!=nil {
            stack = append(stack, node.Left)
        }
    }
    return res
}

中序遍歷

中序的遍歷順序是左中右
入棧順序:從根節點開始,首先將當前節點的左子樹一路壓入棧中,直到當前節點沒有左子樹為止。
出棧條件:當棧頂節點沒有左子樹時,出棧該節點並訪問它,然後繼續對其右子樹進行遍歷。

函式結束條件是 root!=nil || len(stack)>0 這是因為當遍歷完左子樹之後,回溯到整個樹的root的時候,棧已經空了,但是右子樹還沒有入棧,所以還需要判斷root是否為空。

點選檢視程式碼
func inorderTraversal(root *TreeNode) []int{
    if root==nil {
        return []int{}
    }
    res,stack:=[]int{},[]*TreeNode{}
    for root!=nil || len(stack)>0 {
    //    先遍歷左子樹
        for root!=nil {
            stack = append(stack, root)
            root = root.Left
        }
    //    處理當前節點,最左下方的節點
        root = stack[len(stack)-1]
        stack = stack[:len(stack)-1]
        res = append(res, root.Val)
    //    遍歷右子樹
        root = root.Right
    }
    return res
}

後序遍歷

後序遍歷的順序是:左-右-根。
入棧順序: 從根節點開始,首先將當前節點壓入棧中,然後按照“左右”的順序將節點的左孩子和右孩子壓入棧中。
出棧條件: 每次出棧棧頂元素後,僅在左右子樹都已訪問完成的情況下才訪問根節點。為了保證這一點,通常需要藉助一個額外的標記。所以我們透過pre指標來判斷左右孩子是否已經訪問過,只有他們都被訪問過才能訪問根節點。
因為入棧順序,所以棧頂元素必然是葉子節點,透過prev記錄上一次訪問的節點,如果當前節點是葉子節點,直接記錄;若不是葉子節點,如果prev是當前節點的任一孩子,說明孩子節點訪問完畢「2種情況,2個孩子和只有一個孩子,模擬一下便知」,則可以訪問根節點了。

點選檢視程式碼
func postorderTraversal(root *TreeNode) []int{
    if root==nil{
        return []int{}
    }
    res,stack := []int{},[]*TreeNode{}
    var prev *TreeNode

    stack = append(stack, root)
    for len(stack)>0{
        current:=stack[len(stack)-1]
    if(current.Left== nil && current.Right ==nil) || (prev!=nil && (prev == current.Left || prev==current.Right)){
        res = append(res, current.Val)
        stack = stack[:len(stack)-1]
        prev = current
    }else{
        if current.Right!=nil {
            stack = append(stack, current.Right)
        }
        if current.Left != nil {
            stack = append(stack, current.Left)
        }
    }
    }
    return res
}

相關文章