背景
很多场景下都需要将元素存储到已排序的集合中。用数组来存储,搜索效率非常高: O(log n),但是插入效率比较低:O(n)。用链表来存储,插入效率和搜索效率都比较低:O(n)。如何能提供插入和搜索效率呢?这就是二叉搜索树的由来,本文先介绍非平衡二叉搜索树。
非平衡二叉搜索树
规则
所有节点的左节点小于节点,所有节点的右节点大于等于自身,即:node.value > node.left.value && node.value <= node.right.value。
示例
根据上面的规则,我们也很容易找到最大值和最小值,后面也会用到这种算法。最大值可以通过递归方法 node.right 得到,最小值可以递归 node.left 得到。
为什么叫非平衡?
说明:下图变成链表了,这会导致效率非常低,后面找机会再介绍平衡算法。
实现
搜索、遍历(前序、中序和后序)、添加算法都比较简单,可以直接看后面的代码,这里重点介绍一下删除算法。
如何删除元素?
第一步:要找到删除的元素(current)。
第二步:判断 current 满足如下哪种场景:
- current.Right == null
示例
代码
1 if (parent == null) 2 { 3 this.Root = current.Left; 4 } 5 else if (isLeft) 6 { 7 parent.Left = current.Left; 8 } 9 else 10 { 11 parent.Right = current.Left; 12 }
结果
- current.Right != null && current.Right.Left == null
示例
代码
1 current.Right.Left = current.Left; 2 3 if (parent == null) 4 { 5 this.Root = current.Right; 6 } 7 else if (isLeft) 8 { 9 parent.Left = current.Right; 10 } 11 else 12 { 13 parent.Right = current.Right; 14 }
结果
- current.Right != null && current.Right.Left != null
示例
代码
1 Node<T> currentRightSmallestParent = current.Right; 2 var currentRightSmallest = current.Right.Left; 3 4 this.FindSmallest(ref currentRightSmallestParent, ref currentRightSmallest); 5 6 currentRightSmallestParent.Left = currentRightSmallest.Right; 7 currentRightSmallest.Left = current.Left; 8 currentRightSmallest.Right = current.Right; 9 if (parent == null) 10 { 11 this.Root = currentRightSmallest; 12 } 13 else if (isLeft) 14 { 15 parent.Left = currentRightSmallest; 16 } 17 else 18 { 19 parent.Right = currentRightSmallest; 20 }
结果
说明
这里的重点是 FindSmallest,找出 current.Right.Left 子树中最小的元素,然后用它替换 current。
完整代码
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace DataStuctureStudy.Trees 8 { 9 class UnBalancedBinarySearchTree 10 { 11 class Node<T> 12 where T : IComparable<T> 13 { 14 public T Value { get; set; } 15 16 public Node<T> Left { get; set; } 17 18 public Node<T> Right { get; set; } 19 20 public void InOrderTraverse(Action<T> action) 21 { 22 if (this.Left != null) 23 { 24 this.Left.InOrderTraverse(action); 25 } 26 27 action(this.Value); 28 29 if (this.Right != null) 30 { 31 this.Right.InOrderTraverse(action); 32 } 33 } 34 35 public int Depth() 36 { 37 var leftDepth = 0; 38 var rightDepth = 0; 39 40 if (this.Left != null) 41 { 42 leftDepth = this.Left.Depth(); 43 } 44 if (this.Right != null) 45 { 46 rightDepth = this.Right.Depth(); 47 } 48 49 return 50 leftDepth > rightDepth 51 ? leftDepth + 1 52 : rightDepth + 1; 53 } 54 } 55 56 public class Tree<T> 57 where T : IComparable<T> 58 { 59 private Node<T> Root { get; set; } 60 61 public void Display() 62 { 63 Console.WriteLine(); 64 65 if (this.Root == null) 66 { 67 return; 68 } 69 70 var depth = this.Root.Depth(); 71 var buffers = new string[depth][]; 72 for (int i = 0; i < buffers.Length; i++) 73 { 74 buffers[i] = new string[(int)(Math.Pow(2, depth) - 1)]; 75 } 76 77 this.BuildArray(this.Root, depth, buffers, 0, 0); 78 79 for (int i = 0; i < buffers.Length; i++) 80 { 81 for (int j = 0; j < buffers[i].Length; j++) 82 { 83 if (buffers[i][j] == null) 84 { 85 Console.Write(new string(' ', 5)); 86 } 87 else 88 { 89 var leftPad = (5 - buffers[i][j].Length) / 2; 90 Console.Write(buffers[i][j] 91 .PadLeft(leftPad + buffers[i][j].Length) 92 .PadRight(5)); 93 } 94 } 95 Console.WriteLine(); 96 Console.WriteLine(); 97 } 98 } 99 100 private void BuildArray(Node<T> node, int nodeDepth, string[][] buffers, int row, int startColumn) 101 { 102 if (node == null) 103 { 104 return; 105 } 106 107 var nodeWidth = Math.Pow(2, nodeDepth) - 1; 108 var column = (int)(startColumn + nodeWidth / 2); 109 110 buffers[row][column] = node.Value.ToString(); 111 112 this.BuildArray(node.Left, nodeDepth - 1, buffers, row + 1, startColumn); 113 this.BuildArray(node.Right, nodeDepth - 1, buffers, row + 1, column + 1); 114 } 115 116 public bool Contains(T item) 117 { 118 var current = this.Root; 119 120 while (current != null) 121 { 122 if (item.CompareTo(current.Value) == 0) 123 { 124 return true; 125 } 126 else if (item.CompareTo(current.Value) < 0) 127 { 128 current = current.Left; 129 } 130 else 131 { 132 current = current.Right; 133 } 134 } 135 136 return false; 137 } 138 139 public void InOrderTraverse(Action<T> action) 140 { 141 if (this.Root != null) 142 { 143 this.Root.InOrderTraverse(action); 144 } 145 } 146 147 public void Insert(T item) 148 { 149 var node = new Node<T> { Value = item }; 150 151 Node<T> parent = null; 152 var current = this.Root; 153 var isLeft = false; 154 155 while (current != null) 156 { 157 parent = current; 158 159 if (item.CompareTo(current.Value) < 0) 160 { 161 current = current.Left; 162 isLeft = true; 163 } 164 else 165 { 166 current = current.Right; 167 isLeft = false; 168 } 169 } 170 171 if (parent == null) 172 { 173 this.Root = node; 174 } 175 else if (isLeft) 176 { 177 parent.Left = node; 178 } 179 else 180 { 181 parent.Right = node; 182 } 183 } 184 185 public bool Delete(T item) 186 { 187 Node<T> parent = null; 188 var current = this.Root; 189 var isLeft = false; 190 191 this.Find(item, ref parent, ref current, ref isLeft); 192 193 if (current == null) 194 { 195 return false; 196 } 197 198 if (current.Right == null) 199 { 200 if (parent == null) 201 { 202 this.Root = current.Left; 203 } 204 else if (isLeft) 205 { 206 parent.Left = current.Left; 207 } 208 else 209 { 210 parent.Right = current.Left; 211 } 212 } 213 else if (current.Right != null && current.Right.Left == null) 214 { 215 current.Right.Left = current.Left; 216 217 if (parent == null) 218 { 219 this.Root = current.Right; 220 } 221 else if (isLeft) 222 { 223 parent.Left = current.Right; 224 } 225 else 226 { 227 parent.Right = current.Right; 228 } 229 } 230 else 231 { 232 Node<T> currentRightSmallestParent = current.Right; 233 var currentRightSmallest = current.Right.Left; 234 235 this.FindSmallest(ref currentRightSmallestParent, ref currentRightSmallest); 236 237 currentRightSmallestParent.Left = currentRightSmallest.Right; 238 currentRightSmallest.Left = current.Left; 239 currentRightSmallest.Right = current.Right; 240 if (parent == null) 241 { 242 this.Root = currentRightSmallest; 243 } 244 else if (isLeft) 245 { 246 parent.Left = currentRightSmallest; 247 } 248 else 249 { 250 parent.Right = currentRightSmallest; 251 } 252 } 253 254 return true; 255 } 256 257 private void Find(T item, ref Node<T> parent, ref Node<T> current, ref bool isLeft) 258 { 259 while (current != null) 260 { 261 if (item.CompareTo(current.Value) == 0) 262 { 263 break; 264 } 265 266 parent = current; 267 268 if (item.CompareTo(current.Value) < 0) 269 { 270 current = current.Left; 271 isLeft = true; 272 } 273 else 274 { 275 current = current.Right; 276 isLeft = false; 277 } 278 } 279 } 280 281 private void FindSmallest(ref Node<T> parent, ref Node<T> current) 282 { 283 while (current != null) 284 { 285 if (current.Left == null) 286 { 287 break; 288 } 289 290 parent = current; 291 current = current.Left; 292 } 293 } 294 } 295 } 296 }
备注
学完这个树的最大收获就是,找到了一种输出树形结构相对高效的方法,比我之前用的高效,这种算法可以用在组织结构图的生成中。