程式碼隨想錄day13 || 樹定義以及遍歷

周公瑾55發表於2024-07-29

二叉樹定義和種類

二叉樹是一種樹形資料結構,其中每個節點最多有兩個子節點,通常稱為“左子節點”和“右子節點”。二叉樹在電腦科學中有廣泛的應用,比如表示式解析、排序演算法、搜尋演算法等。

二叉樹的定義

一個二叉樹由一組節點組成,其中每個節點至多有兩個子節點,分別稱為左子節點和右子節點。二叉樹可以是空的(沒有節點),也可以是一個根節點加上兩個分別是左子樹和右子樹的二叉樹。

二叉樹的種類

二叉樹有多種不同的型別,每種型別都有其特定的性質和用途。常見的二叉樹種類包括:

  1. 滿二叉樹(Full Binary Tree)

    • 每個節點要麼是葉子節點,要麼有兩個子節點。
    • 滿二叉樹的所有葉子節點都在同一層次上。
  2. 完全二叉樹(Complete Binary Tree)

    • 所有層都是完全填滿的,除了最後一層。
    • 最後一層的節點儘可能靠左排列。
  3. 完美二叉樹(Perfect Binary Tree)

    • 是一種特殊的滿二叉樹。
    • 所有內部節點都有兩個子節點,所有葉子節點都在同一層次上。
  4. 平衡二叉樹(Balanced Binary Tree)

    • 任意節點的左子樹和右子樹的高度差不超過1。
    • 例如:AVL樹、紅黑樹。
  5. 二叉搜尋樹(Binary Search Tree, BST)

    • 對於每個節點,其左子樹中所有節點的值都小於該節點的值,其右子樹中所有節點的值都大於該節點的值。
    • 允許高效的查詢、插入和刪除操作。

二叉樹的表示

二叉樹可以透過多種方式表示,常見的表示方法包括:

  1. 鏈式儲存

    • 每個節點包含一個資料元素和兩個指標,分別指向左子節點和右子節點。
    type TreeNode struct {
        Val   int
        Left  *TreeNode
        Right *TreeNode
    }
    
  2. 陣列表示

    • 完全二叉樹可以用陣列表示,根節點儲存在索引1的位置(或0),對於索引為 (i) 的節點,其左子節點儲存在索引 (2i)(或 (2i+1)),右子節點儲存在索引 (2i+1)(或 (2i+2))。
    // 陣列表示的二叉樹
    tree := []int{1, 2, 3, 4, 5, 6, 7}
    

二叉樹的遍歷

二叉樹的遍歷是指按照某種順序訪問樹中的每個節點。常見的遍歷方法有:

  1. 前序遍歷(Pre-order Traversal)
    • 訪問根節點 -> 前序遍歷左子樹 -> 前序遍歷右子樹。
    func preorderTraversal(root *TreeNode) []int {
    	// 三種遍歷順序指的是根節點在“左右”兩個節點中的順序
    	// 前序遍歷 根左右
    	// 中序遍歷 左根右
    	// 後續遍歷 左右根
    	var res []int
    	var traversal func(node *TreeNode)
    	traversal = func(node *TreeNode) {
    		if node == nil {
    			return
    		}
    		res = append(res,node.Val)
    		traversal(node.Left)
    		traversal(node.Right)
    	}
    	traversal(root)
    	return res
    }
    
    func preorderTraversal(root *TreeNode) []int {
    // 考慮使用迭代法解決 根左右,所以先遍歷根,然後入棧右左,實現出棧順序是左右
    
    	if root == nil {
    		return nil
    	}
    
    	st := list.New() // go包雙向連結串列
    	var ans []int
    	st.PushBack(root)
    	for st.Len() > 0 {
    		node := st.Remove(st.Back()).(*TreeNode)
    
    		ans = append(ans, node.Val)
    		if node.Right != nil {
    			st.PushBack(node.Right)
    		}
    		if node.Left != nil {
    			st.PushBack(node.Left)
    		}
    	}
    	return ans
    }
    
  2. 中序遍歷(In-order Traversal)
    • 中序遍歷左子樹 -> 訪問根節點 -> 中序遍歷右子樹。
    func postorderTraversal(root *TreeNode) []int {
    	var res []int
    	traversal(root, &res)
    	return res
    }
    
    func traversal(root *TreeNode, res *[]int) {
    	if root == nil {
    		return
    	}
    	traversal(root.Left, res)
    	traversal(root.Right, res)
    	*res = append(*res, root.Val)
    }
    
    func inorderTraversal(root *TreeNode) []int {
    	// 中序迭代有點難理解的
    	if root == nil {
    		return nil
    	}
    
    	var res []int
    	st := list.New()
    	cur := root
    	for cur != nil || st.Len() > 0{
    		if cur != nil{
    			st.PushBack(cur)
    			cur = cur.Left
    		} else {
    			cur = st.Remove(st.Back()).(*TreeNode)
    			res = append(res, cur.Val)
    			cur = cur.Right
    		}
    	}
    	return res
    }
    
  3. 後序遍歷(Post-order Traversal)
    • 後序遍歷左子樹 -> 後序遍歷右子樹 -> 訪問根節點。
    func inorderTraversal(root *TreeNode) []int {
    	res := []int{}
    	traversal(root, &res)
    	return res
    }
    func traversal(root *TreeNode, res *[]int) {
    	if root == nil {
    		return
    	}
    	traversal(root.Left, res)
    	*res = append(*res, root.Val)
    	traversal(root.Right, res)
    }
    
    func postorderTraversal(root *TreeNode) []int {
    	// 思路,左右中,反轉之後就是中右左, 那麼和前序遍歷zhong左右只是區別在於入棧順序,然後最終將res翻轉即可
    	if root == nil {
    		return nil
    	}
    
    	var res = []int{}
    	st := list.New()
    	st.PushBack(root)
    
    	for st.Len() > 0 {
    		node := st.Remove(st.Back()).(*TreeNode)
    		res = append(res, node.Val)
    		if node.Left != nil {
    			st.PushBack(node.Left)
    		}
    		if node.Right != nil {
    			st.PushBack(node.Right)
    		}
    	}
    
    	return reverse(res)
    }
    
    func reverse(l []int) []int {
    	left, right := 0, len(l) - 1
    	for left < right {
    		l[left], l[right] = l[right], l[left]
    		left++
    		right--
    	}
    
    	return l
    }
    
  4. 層次遍歷(Level-order Traversal)
    • 按照層次從上到下、從左到右依次訪問每個節點。
    func levelOrder(root *TreeNode) [][]int {
    	// 透過佇列實現廣度優先遍歷
    	if root == nil {
    		return nil
    	}
    
    	var res [][]int
    	queue := list.New() // 同樣使用go包雙向連結串列實現佇列,push = pushback pop = remove(front())
    	queue.PushBack(root)
    	size := 1
    	// 對於每一層的遍歷,都要維護一個size變數,方便記錄彈出元素個數
    	for queue.Len() > 0{
    		var (
    			count int
    			r []int
    		)
    		for i := 0; i < size; i++ { // 對於每一層的遍歷次數
    			// 1, 插入節點元素值
    			node := queue.Remove(queue.Front()).(*TreeNode)
    			r = append(r, node.Val)
    
    			// 左右子節點入隊, 順序不能顛倒,佇列先入先出
    			if node.Left != nil {
    				queue.PushBack(node.Left)
    				count++
    			}
    			if node.Right != nil {
    				queue.PushBack(node.Right)
    				count++
    			}
    		}
    		// 此時上一層已經遍歷完成,將size賦值為下一層的節點數量
    		size = count
    		res = append(res, r)
    	}
    	return res
    }
    

其中前序遍歷,中序遍歷,後序遍歷都是深度優先遍歷,通常採用遞迴方法實現,層序遍歷為廣度優先遍歷,採用佇列方法實現

深度優先搜尋(DFS)和廣度優先搜尋(BFS)

深度優先搜尋(DFS)和廣度優先搜尋(BFS)是兩種基本的圖遍歷演算法,它們也常用於樹的遍歷。以下是它們的詳細介紹:

深度優先搜尋(DFS)

深度優先搜尋是一種優先探索儘可能深的節點的遍歷策略。它沿著每一個分支儘可能深地搜尋,直到不能再繼續為止,然後回溯並繼續探索下一個分支。DFS可以使用遞迴或顯式的棧來實現。

廣度優先搜尋(BFS)

廣度優先搜尋是一種優先探索離起始節點最近的節點的遍歷策略。它按層次逐層訪問節點,先訪問完一層的所有節點,再繼續訪問下一層的節點。BFS通常使用佇列來實現。

DFS:

  • 適用於需要遍歷到某個特定深度的場景,例如解決迷宮問題、尋找路徑等。
  • 常用於圖的連通性檢測、拓撲排序等。

BFS:

  • 適用於尋找最短路徑的場景,例如在無權圖中尋找兩個節點之間的最短路徑。
  • 常用於層次遍歷、廣度優先搜尋樹等。

相關文章