二叉树详解 本文详细介绍二叉树的相关知识,包括二叉树的基本概念、遍历方法、平衡二叉树和线索化等内容。
什么是二叉树? 二叉树(Binary Tree)是每个节点最多有两个子节点的树结构。通常子节点被称作”左子节点”和”右子节点”。
二叉树的特点
每个节点最多有两个子节点
左子树和右子树是有顺序的,不能颠倒
即使某个节点只有一个子节点,也要区分它是左子树还是右子树
二叉树的分类 flowchart TD
Root["二叉树"]
Full["满二叉树 所有节点都有两个子节点"]
Complete["完全二叉树 除了最后一层,其他层都是满的"]
Perfect["完美二叉树 所有叶子节点在同一层"]
Balanced["平衡二叉树 左右子树高度差不超过1"]
BST["二叉搜索树 左子树 < 根 < 右子树"]
Root --> Full
Root --> Complete
Root --> Perfect
Root --> Balanced
Root --> BST
style Root fill:#e1f5ff
style Full fill:#e8f5e9
style Complete fill:#fff3e0
style Perfect fill:#f3e5f5
style Balanced fill:#e0f2f1
style BST fill:#fce4ec
二叉树节点定义 1 2 3 4 5 6 type TreeNode struct { Val int Left *TreeNode Right *TreeNode }
遍历 二叉树的遍历是指按照某种规则访问树中的每个节点,且每个节点只访问一次。主要有四种遍历方式:前序遍历、中序遍历、后序遍历和层序遍历。
前序遍历 算法原理 前序遍历(Preorder Traversal)的访问顺序是:根节点 → 左子树 → 右子树
算法流程 flowchart TD
Start["开始"] --> Input["输入根节点"]
Input --> Check{节点为空?}
Check -->|是| End["返回"]
Check -->|否| Visit["访问根节点"]
Visit --> Left["递归遍历左子树"]
Left --> Right["递归遍历右子树"]
Right --> End
style Start fill:#e1f5ff
style End fill:#e8f5e9
style Visit fill:#fff3e0
实现代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 package mainimport "fmt" type TreeNode struct { Val int Left *TreeNode Right *TreeNode } func PreorderTraversal (root *TreeNode) []int { var result []int preorderHelper(root, &result) return result } func preorderHelper (node *TreeNode, result *[]int ) { if node == nil { return } *result = append (*result, node.Val) preorderHelper(node.Left, result) preorderHelper(node.Right, result) } func PreorderTraversalIterative (root *TreeNode) []int { if root == nil { return []int {} } var result []int stack := []*TreeNode{root} for len (stack) > 0 { node := stack[len (stack)-1 ] stack = stack[:len (stack)-1 ] result = append (result, node.Val) if node.Right != nil { stack = append (stack, node.Right) } if node.Left != nil { stack = append (stack, node.Left) } } return result } func main () { root := &TreeNode{ Val: 1 , Left: &TreeNode{ Val: 2 , Left: &TreeNode{Val: 4 }, Right: &TreeNode{Val: 5 }, }, Right: &TreeNode{Val: 3 }, } fmt.Println("前序遍历(递归):" , PreorderTraversal(root)) fmt.Println("前序遍历(迭代):" , PreorderTraversalIterative(root)) }
复杂度分析
时间复杂度 : O(n) - 需要访问每个节点一次
空间复杂度 :
递归: O(h) - h 为树的高度,递归栈的深度
迭代: O(h) - 栈的最大深度
中序遍历 算法原理 中序遍历(Inorder Traversal)的访问顺序是:左子树 → 根节点 → 右子树
对于二叉搜索树,中序遍历可以得到有序序列。
算法流程 flowchart TD
Start["开始"] --> Input["输入根节点"]
Input --> Check{节点为空?}
Check -->|是| End["返回"]
Check -->|否| Left["递归遍历左子树"]
Left --> Visit["访问根节点"]
Visit --> Right["递归遍历右子树"]
Right --> End
style Start fill:#e1f5ff
style End fill:#e8f5e9
style Visit fill:#fff3e0
实现代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 func InorderTraversal (root *TreeNode) []int { var result []int inorderHelper(root, &result) return result } func inorderHelper (node *TreeNode, result *[]int ) { if node == nil { return } inorderHelper(node.Left, result) *result = append (*result, node.Val) inorderHelper(node.Right, result) } func InorderTraversalIterative (root *TreeNode) []int { var result []int stack := []*TreeNode{} node := root for node != nil || len (stack) > 0 { for node != nil { stack = append (stack, node) node = node.Left } node = stack[len (stack)-1 ] stack = stack[:len (stack)-1 ] result = append (result, node.Val) node = node.Right } return result }
复杂度分析
应用场景
二叉搜索树排序 : 中序遍历二叉搜索树可以得到有序序列
表达式树求值 : 中序遍历表达式树可以得到中缀表达式
后序遍历 算法原理 后序遍历(Postorder Traversal)的访问顺序是:左子树 → 右子树 → 根节点
算法流程 flowchart TD
Start["开始"] --> Input["输入根节点"]
Input --> Check{节点为空?}
Check -->|是| End["返回"]
Check -->|否| Left["递归遍历左子树"]
Left --> Right["递归遍历右子树"]
Right --> Visit["访问根节点"]
Visit --> End
style Start fill:#e1f5ff
style End fill:#e8f5e9
style Visit fill:#fff3e0
实现代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 func PostorderTraversal (root *TreeNode) []int { var result []int postorderHelper(root, &result) return result } func postorderHelper (node *TreeNode, result *[]int ) { if node == nil { return } postorderHelper(node.Left, result) postorderHelper(node.Right, result) *result = append (*result, node.Val) } func PostorderTraversalIterative (root *TreeNode) []int { if root == nil { return []int {} } var result []int stack := []*TreeNode{root} for len (stack) > 0 { node := stack[len (stack)-1 ] stack = stack[:len (stack)-1 ] result = append ([]int {node.Val}, result...) if node.Left != nil { stack = append (stack, node.Left) } if node.Right != nil { stack = append (stack, node.Right) } } return result } func PostorderTraversalIterative (root *TreeNode) []int { if root == nil { return []int {} } var result []int stack := []*TreeNode{} var lastVisited *TreeNode node := root for node != nil || len (stack) > 0 { if node != nil { stack = append (stack, node) node = node.Left } else { top := stack[len (stack)-1 ] if top.Right != nil && top.Right != lastVisited { node = top.Right } else { stack = stack[:len (stack)-1 ] result = append (result, top.Val) lastVisited = top } } } return result }
复杂度分析
应用场景
删除树 : 后序遍历可以确保在删除父节点之前先删除子节点
计算目录大小 : 先计算子目录大小,再计算父目录大小
表达式树求值 : 后序遍历表达式树可以直接计算表达式的值
层序遍历 算法原理 层序遍历(Level Order Traversal)按照树的层次,从上到下、从左到右访问每个节点。通常使用队列来实现。
算法流程 flowchart TD
Start["开始"] --> Input["输入根节点"]
Input --> Queue["将根节点入队"]
Queue --> Loop{队列不为空?}
Loop -->|否| End["结束"]
Loop -->|是| Dequeue["出队一个节点"]
Dequeue --> Visit["访问该节点"]
Visit --> EnqueueLeft{左子节点存在?}
EnqueueLeft -->|是| EnqueueL["左子节点入队"]
EnqueueLeft -->|否| EnqueueRight{右子节点存在?}
EnqueueL --> EnqueueRight
EnqueueRight -->|是| EnqueueR["右子节点入队"]
EnqueueRight -->|否| Loop
EnqueueR --> Loop
style Start fill:#e1f5ff
style End fill:#e8f5e9
style Visit fill:#fff3e0
实现代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 func LevelOrderTraversal (root *TreeNode) []int { if root == nil { return []int {} } var result []int queue := []*TreeNode{root} for len (queue) > 0 { node := queue[0 ] queue = queue[1 :] result = append (result, node.Val) if node.Left != nil { queue = append (queue, node.Left) } if node.Right != nil { queue = append (queue, node.Right) } } return result } func LevelOrderTraversalWithLevels (root *TreeNode) [][]int { if root == nil { return [][]int {} } var result [][]int queue := []*TreeNode{root} for len (queue) > 0 { levelSize := len (queue) level := []int {} for i := 0 ; i < levelSize; i++ { node := queue[0 ] queue = queue[1 :] level = append (level, node.Val) if node.Left != nil { queue = append (queue, node.Left) } if node.Right != nil { queue = append (queue, node.Right) } } result = append (result, level) } return result }
复杂度分析
时间复杂度 : O(n)
空间复杂度 : O(w) - w 为树的最大宽度
应用场景
打印树的结构 : 按层打印可以直观地看到树的结构
查找树的宽度 : 层序遍历可以找到树的最大宽度
Z字形遍历 : 在层序遍历基础上实现 Z 字形遍历
平衡二叉树 平衡二叉树(Balanced Binary Tree)是一种特殊的二叉树,它的左右子树的高度差不超过 1,这样可以保证树的高度为 O(log n),从而保证各种操作的时间复杂度为 O(log n)。
AVL树 算法原理 AVL 树(Adelson-Velsky and Landis Tree)是最早发明的自平衡二叉搜索树。在 AVL 树中,任何节点的两个子树的高度最大差别为 1。
平衡因子 平衡因子(Balance Factor)定义为:左子树高度 - 右子树高度
平衡因子 = -1, 0, 1:节点平衡
平衡因子 < -1:右子树过高,需要左旋
平衡因子 > 1:左子树过高,需要右旋
旋转操作 左旋(Left Rotation) 当右子树过高时,进行左旋操作。
flowchart TD
Before["左旋前 A / \ B C / \ D E"]
Before --> After["左旋后 C / \ A E / \ B D"]
style Before fill:#ffebee
style After fill:#e8f5e9
右旋(Right Rotation) 当左子树过高时,进行右旋操作。
flowchart TD
Before["右旋前 A / \ B C / \ D E"]
Before --> After["右旋后 B / \ D A / \ E C"]
style Before fill:#ffebee
style After fill:#e8f5e9
实现代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 type AVLNode struct { Val int Left *AVLNode Right *AVLNode Height int } func getHeight (node *AVLNode) int { if node == nil { return 0 } return node.Height } func getBalanceFactor (node *AVLNode) int { if node == nil { return 0 } return getHeight(node.Left) - getHeight(node.Right) } func max (a, b int ) int { if a > b { return a } return b } func updateHeight (node *AVLNode) { node.Height = 1 + max(getHeight(node.Left), getHeight(node.Right)) } func leftRotate (x *AVLNode) *AVLNode { y := x.Right T2 := y.Left y.Left = x x.Right = T2 updateHeight(x) updateHeight(y) return y } func rightRotate (y *AVLNode) *AVLNode { x := y.Left T2 := x.Right x.Right = y y.Left = T2 updateHeight(y) updateHeight(x) return x } func InsertAVL (root *AVLNode, val int ) *AVLNode { if root == nil { return &AVLNode{Val: val, Height: 1 } } if val < root.Val { root.Left = InsertAVL(root.Left, val) } else if val > root.Val { root.Right = InsertAVL(root.Right, val) } else { return root } updateHeight(root) balance := getBalanceFactor(root) if balance > 1 && val < root.Left.Val { return rightRotate(root) } if balance < -1 && val > root.Right.Val { return leftRotate(root) } if balance > 1 && val > root.Left.Val { root.Left = leftRotate(root.Left) return rightRotate(root) } if balance < -1 && val < root.Right.Val { root.Right = rightRotate(root.Right) return leftRotate(root) } return root }
复杂度分析
时间复杂度 :
查找: O(log n)
插入: O(log n)
删除: O(log n)
空间复杂度 : O(n)
应用场景
需要频繁查找的场景 : AVL 树保证 O(log n) 的查找时间
需要有序遍历的场景 : 中序遍历可以得到有序序列
数据库索引 : 某些数据库使用 AVL 树作为索引结构
红黑树 算法原理 红黑树(Red-Black Tree)是一种自平衡二叉搜索树,它通过颜色标记和旋转操作来保持平衡。红黑树比 AVL 树更宽松的平衡条件,使得插入和删除操作更高效。
红黑树的性质
每个节点要么是红色,要么是黑色
根节点是黑色
所有叶子节点(NIL)都是黑色
如果一个节点是红色,那么它的两个子节点都是黑色 (不能有两个连续的红色节点)
从任意节点到其每个叶子的所有简单路径都包含相同数目的黑色节点 (黑高相同)
实现代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 type RBNode struct { Val int Color string Left *RBNode Right *RBNode Parent *RBNode } const ( RED = "RED" BLACK = "BLACK" ) func InsertRB (root *RBNode, val int ) *RBNode { node := insertBST(root, val) node.Color = RED return fixInsertRB(node) } func insertBST (root *RBNode, val int ) *RBNode { if root == nil { return &RBNode{Val: val, Color: RED} } if val < root.Val { root.Left = insertBST(root.Left, val) root.Left.Parent = root } else if val > root.Val { root.Right = insertBST(root.Right, val) root.Right.Parent = root } return root } func fixInsertRB (node *RBNode) *RBNode { var parent, grandparent, uncle *RBNode for node.Parent != nil && node.Parent.Color == RED { parent = node.Parent grandparent = parent.Parent if parent == grandparent.Left { uncle = grandparent.Right if uncle != nil && uncle.Color == RED { parent.Color = BLACK uncle.Color = BLACK grandparent.Color = RED node = grandparent } else { if node == parent.Right { node = parent node = leftRotateRB(node) } parent.Color = BLACK grandparent.Color = RED node = rightRotateRB(grandparent) } } else { uncle = grandparent.Left if uncle != nil && uncle.Color == RED { parent.Color = BLACK uncle.Color = BLACK grandparent.Color = RED node = grandparent } else { if node == parent.Left { node = parent node = rightRotateRB(node) } parent.Color = BLACK grandparent.Color = RED node = leftRotateRB(grandparent) } } } for node.Parent != nil { node = node.Parent } node.Color = BLACK return node } func leftRotateRB (x *RBNode) *RBNode { y := x.Right x.Right = y.Left if y.Left != nil { y.Left.Parent = x } y.Parent = x.Parent if x.Parent == nil { } else if x == x.Parent.Left { x.Parent.Left = y } else { x.Parent.Right = y } y.Left = x x.Parent = y return y } func rightRotateRB (y *RBNode) *RBNode { x := y.Left y.Left = x.Right if x.Right != nil { x.Right.Parent = y } x.Parent = y.Parent if y.Parent == nil { } else if y == y.Parent.Left { y.Parent.Left = x } else { y.Parent.Right = x } x.Right = y y.Parent = x return x }
复杂度分析
时间复杂度 :
查找: O(log n)
插入: O(log n)
删除: O(log n)
空间复杂度 : O(n)
AVL树 vs 红黑树
特性
AVL树
红黑树
平衡条件
严格平衡(高度差≤1)
相对平衡(最长路径≤2倍最短路径)
查找性能
更好(更平衡)
稍差
插入/删除性能
较慢(需要更多旋转)
更快(旋转次数少)
应用场景
查找频繁的场景
插入/删除频繁的场景
应用场景
C++ STL : std::map 和 std::set 使用红黑树实现
Java : TreeMap 和 TreeSet 使用红黑树实现
Linux 内核 : 进程调度器使用红黑树
数据库索引 : MySQL 的 InnoDB 存储引擎使用红黑树
线索化 线索化(Threading)是在二叉树的基础上,利用空指针域存储前驱和后继节点的信息,从而可以不用递归或栈就能遍历二叉树。
线索二叉树节点 1 2 3 4 5 6 7 8 type ThreadedNode struct { Val int Left *ThreadedNode Right *ThreadedNode LTag bool RTag bool }
前序线索化 算法原理 前序线索化按照前序遍历的顺序,将空指针域指向前驱或后继节点。
实现代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 var prev *ThreadedNode func PreorderThreading (root *ThreadedNode) { prev = nil preorderThreadingHelper(root) } func preorderThreadingHelper (node *ThreadedNode) { if node == nil { return } if node.Left == nil { node.Left = prev node.LTag = true } else { node.LTag = false } if prev != nil && prev.Right == nil { prev.Right = node prev.RTag = true } else if prev != nil { prev.RTag = false } prev = node if !node.LTag { preorderThreadingHelper(node.Left) } if !node.RTag { preorderThreadingHelper(node.Right) } } func PreorderTraversalThreaded (root *ThreadedNode) []int { var result []int node := root for node != nil { result = append (result, node.Val) if !node.LTag { node = node.Left } else { node = node.Right } } return result }
中序线索化 算法原理 中序线索化按照中序遍历的顺序,将空指针域指向前驱或后继节点。中序线索化是最常用的线索化方式。
实现代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 func InorderThreading (root *ThreadedNode) { prev = nil inorderThreadingHelper(root) } func inorderThreadingHelper (node *ThreadedNode) { if node == nil { return } inorderThreadingHelper(node.Left) if node.Left == nil { node.Left = prev node.LTag = true } else { node.LTag = false } if prev != nil && prev.Right == nil { prev.Right = node prev.RTag = true } else if prev != nil { prev.RTag = false } prev = node inorderThreadingHelper(node.Right) } func InorderTraversalThreaded (root *ThreadedNode) []int { var result []int node := root for node != nil && !node.LTag { node = node.Left } for node != nil { result = append (result, node.Val) if node.RTag { node = node.Right } else { node = node.Right for node != nil && !node.LTag { node = node.Left } } } return result }
后序线索化 算法原理 后序线索化按照后序遍历的顺序,将空指针域指向前驱或后继节点。
实现代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 func PostorderThreading (root *ThreadedNode) { prev = nil postorderThreadingHelper(root) } func postorderThreadingHelper (node *ThreadedNode) { if node == nil { return } postorderThreadingHelper(node.Left) postorderThreadingHelper(node.Right) if node.Left == nil { node.Left = prev node.LTag = true } else { node.LTag = false } if prev != nil && prev.Right == nil { prev.Right = node prev.RTag = true } else if prev != nil { prev.RTag = false } prev = node } func PostorderTraversalThreaded (root *ThreadedNode) []int { var result []int node := root var prev *ThreadedNode for node != nil { for node.Left != nil && !node.LTag { node = node.Left } result = append (result, node.Val) prev = node if node.RTag { node = node.Right } else { if node.Right == prev { node = node.Parent } else { node = node.Right } } } return result }
复杂度分析
时间复杂度 :
空间复杂度 : O(1) - 不需要额外的栈空间
应用场景
节省空间 : 利用空指针域,不需要额外的栈空间
提高遍历效率 : 可以 O(1) 时间找到前驱和后继
嵌入式系统 : 内存受限的场景
总结
遍历方式 : 前序、中序、后序、层序各有不同的应用场景
平衡二叉树 : AVL 树和红黑树都是自平衡二叉搜索树,各有优势
线索化 : 利用空指针域存储前驱和后继信息,提高遍历效率
实际应用 : 二叉树广泛应用于数据库索引、文件系统、表达式求值等领域
选择合适的二叉树结构和遍历方式可以显著提升程序性能!