二叉樹定義和種類
二叉樹是一種樹形資料結構,其中每個節點最多有兩個子節點,通常稱為“左子節點”和“右子節點”。二叉樹在電腦科學中有廣泛的應用,比如表示式解析、排序演算法、搜尋演算法等。
二叉樹的定義
一個二叉樹由一組節點組成,其中每個節點至多有兩個子節點,分別稱為左子節點和右子節點。二叉樹可以是空的(沒有節點),也可以是一個根節點加上兩個分別是左子樹和右子樹的二叉樹。
二叉樹的種類
二叉樹有多種不同的型別,每種型別都有其特定的性質和用途。常見的二叉樹種類包括:
-
滿二叉樹(Full Binary Tree):
- 每個節點要麼是葉子節點,要麼有兩個子節點。
- 滿二叉樹的所有葉子節點都在同一層次上。
-
完全二叉樹(Complete Binary Tree):
- 所有層都是完全填滿的,除了最後一層。
- 最後一層的節點儘可能靠左排列。
-
完美二叉樹(Perfect Binary Tree):
- 是一種特殊的滿二叉樹。
- 所有內部節點都有兩個子節點,所有葉子節點都在同一層次上。
-
平衡二叉樹(Balanced Binary Tree):
- 任意節點的左子樹和右子樹的高度差不超過1。
- 例如:AVL樹、紅黑樹。
-
二叉搜尋樹(Binary Search Tree, BST):
- 對於每個節點,其左子樹中所有節點的值都小於該節點的值,其右子樹中所有節點的值都大於該節點的值。
- 允許高效的查詢、插入和刪除操作。
二叉樹的表示
二叉樹可以透過多種方式表示,常見的表示方法包括:
-
鏈式儲存:
- 每個節點包含一個資料元素和兩個指標,分別指向左子節點和右子節點。
type TreeNode struct { Val int Left *TreeNode Right *TreeNode }
-
陣列表示:
- 完全二叉樹可以用陣列表示,根節點儲存在索引1的位置(或0),對於索引為 (i) 的節點,其左子節點儲存在索引 (2i)(或 (2i+1)),右子節點儲存在索引 (2i+1)(或 (2i+2))。
// 陣列表示的二叉樹 tree := []int{1, 2, 3, 4, 5, 6, 7}
二叉樹的遍歷
二叉樹的遍歷是指按照某種順序訪問樹中的每個節點。常見的遍歷方法有:
- 前序遍歷(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 }
- 中序遍歷(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 }
- 後序遍歷(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 }
- 層次遍歷(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:
- 適用於尋找最短路徑的場景,例如在無權圖中尋找兩個節點之間的最短路徑。
- 常用於層次遍歷、廣度優先搜尋樹等。