參考鏈接:
1126號注:先前有一個概念搞混了:
節點的深度 Depth 是指從根節點到當前節點的長度;
節點的高度 Height 是指從當前節點向下,到子孫中所有葉子節點的長度的最大值。
之前簡單了解過 AVL 樹,知道概念但一直沒動手實踐過。Now
AVL 樹是二叉搜索樹的一種。二叉搜索樹的規則就是:每個節點的 left child 都比自己小,right child 都比自己大。而 AVL 的在此之上又加了一個規則:每個節點的 left 深度與 right 深度只差<=1,這樣就能充分利用 二叉樹的結構,避免出現一個分支走到黑,導致搜索效率變低。如下圖:
這種樹結構,搜索起來完全沒有體現出二叉搜索樹的優勢
所以同樣多的元素,搜索樹的深度越低,就會越快的定位到搜索點。
AVL 樹為了保持樹的平衡,每次插入、刪除都會檢查 每個受影響的節點,深度差是否 < 1。最難的部分就是如果樹失衡,如何調整節點。插入的處理流程大致如下:
給定一個 value,一路比較直到插入到某個位置。然后查找哪些節點因此而失衡,針對第一個失衡的節點進行旋轉。
下面是我從另一個博客上找來的圖(
http://blog.csdn.net/gabriel1026/article/details/6311339),介紹了四種失衡的情況,以及調整方式:
注:圖中紅色的小塊 表示新插入的元素,A 點表示從插入點向上,發現的第一個失衡的點。
AVL樹的旋轉一共有四種情形,注意所有旋轉情況都是圍繞着使得二叉樹不平衡的第一個節點展開的。
1:
圖中在 Bl 下新插入一個子節點,導致 A 左側深度為 3,右側為 1,失衡。右旋。
2:
3:
4:
不知道大家看明白了調整的規則嗎?
如果您看明白了,你真是大牛!!!
沒看圖之前我大致明白了四種調整規則,看了后兩幅圖片之后,徹底迷糊了。完全不知道如何用程序來表示四種規則,也不知道怎么從一棵樹中提取到這四個特征。。。后兩張圖折磨了我一天多,才發現:后兩張圖應該是存在邏輯錯誤的。。。
AVL 樹每次插入之前,以及插入之后,都應該是平衡的。而 圖3 和圖4,插入之前就不平衡:拿圖三來說:插入之前,A 的 left 深度為3,right 深度為 1,本身就有問題
雖然上面的圖有問題,但是介紹的思路是非常正確的,只是檢查到底符合 LL、RR、LR、RL 中哪個特征時,被帶跑偏了。
所以明白了這四種調整方式后,下一步就是用程序的思維,檢查何時用哪種規則了。這一點我認為網上的博客介紹的也不太詳細,我這理解力一般的人,看過教程后,實現起來依舊是一團麻。下面是我用到的插入流程:
1:拿到新節點 newNode,根據左小右大的二叉搜索樹規則,一路定位到一個具體的節點 currNode,把 newNode 當成 currNode 的 child 插入到樹中。(不用記錄 newNode 是 currNode 的 left 還是 right )
2:檢查 currNode 是否失衡,如果沒有失衡,就檢查 currNode 的 parent,不失衡就在檢查 parent 的 parent….. 這樣直到拿到一個失衡節點(如果直到根節點依舊平衡,那當前樹就不用做任何調整),把失衡點當成 currNode
3:關鍵點的平衡來了:
如果 Depth(currNode.left) - Depth(currNode.right) = 2:說明左側深度大於右側深度,可能是 LL、LR 中的一種:此時檢查 newNode 和 currNode、
currNode.left 的大小關系
1)如果 newNode 比 currNode 小,並且 newNode 比
currNode.left
小,此時符合 LL:對 currNode 右旋;
2)如果 newNode 比 currNode 小,但是 newNode 比
currNode.left
大,此時符合 LR:先對 currNode.left 左旋,然后對 currNode 右旋;
如果 Depth(currNode.right) - Depth(currNode.left) = 2:說明右側深度大於左側深度,可能是 RR、RL 中的一種:此時檢查 newNode 和 currNode、
currNode.right 的大小關系:
1)如果 newNode 比 currNode 大,並且 newNode 比
currNode.right 大,符合 RR,對 currNode 左旋;
2)如果 newNode 比 currNode 大,並且 newNode 比
currNode.right 小,符合 RL:先對 currNode.right 右旋,然后對 currNode 左旋;
插入、調整結束。
中間有幾個容易混的地方。這樣敘述起來,用程序實現就很方便了。剩下的就是 左旋、右旋的函數實現,這兩個函數需要特別小心,每個節點的 parent、left、right 都要很小心的處理。基本上包含四個函數:
insert 負責執行 1
balance 負責執行 2、3、4
leftRotate、rightRotate 函數用來實現旋轉。
代碼我在最后統一貼出來。
插入搞定了,還有刪除操作:刪除的流程如下:
1:拿到要刪除的數字 value,從根節點開始比對,知道找到一個要刪除的節點:currNode (沒找到正好不用刪了 →_→)
2:從左子樹中找到一個最大值(左子樹中的值都比 currNode 小):targetNode(如果左子樹為空,那就直接把 right 節點上位;如果 right 也是空的,那就直接刪掉 currNode 就好了)
3:把 targetNode 放到 currNode 的位置上:因為每個 節點都有 parent、left、right 三個關聯點,要仔細處理(錯誤了好幾次才正確→_→)
4:和 插入類似,從 targetNode 開始一路向上,找到第一個失衡點。此時只有 LL 和 RR 兩種失衡情況,判斷起來相對容易些
AVL 樹最基本的插入和刪除就是這樣了。插入刪除過程中,具體為什么旋轉、為什么使用這種規則,是否覆蓋到了所有的特例等問題,都是有規律可以歸納的。對這些規律我還比較模糊,知其然不知其所以然。。。樹是一個很神奇的數據結構,諸多奇思妙想都能用樹結構來實現,還要多想想樹的規律。
JS 代碼如下:
1 /** 2 * Created by CG on 16/11/20. 3 */ 4 5 6 7 var TreeNode = function(){ 8 this.parent = null; 9 this.left = null; 10 this.right = null; 11 12 this.value = null; 13 }; 14 15 16 var AVLTree = { 17 18 insert : function (value) { 19 this.log("新加節點:new add: " + value); 20 if(this._tree == null){ 21 var node = new TreeNode(); 22 node.value = value; 23 this._tree = node; 24 return; 25 } 26 27 var newNode = new TreeNode(); 28 newNode.value = value; 29 30 var currNode = this._tree; 31 while(true){ 32 if(currNode == null){ 33 this.log(" ======== currNode: null"); 34 return; 35 } 36 37 //走向左子樹 38 if(value <= currNode.value){ 39 this.log(" to left: value: " + value + " currValue: " + currNode.value); 40 if(currNode.left){ 41 currNode = currNode.left; 42 continue; 43 } 44 else { 45 newNode.parent = currNode; 46 currNode.left = newNode; 47 this.balanceTree(currNode, newNode); 48 break; 49 } 50 } 51 //走向右子樹 52 else { 53 this.log(" to right: value: " + value + " currValue: " + currNode.value); 54 if(currNode.right){ 55 currNode = currNode.right; 56 continue; 57 } 58 else { 59 newNode.parent = currNode; 60 currNode.right = newNode; 61 this.balanceTree(currNode, newNode); 62 break; 63 } 64 } 65 } 66 }, 67 balanceTree : function (currNode, newNode) { 68 if(!currNode){ 69 return; 70 } 71 72 this.printTreeByLevel(); 73 while(currNode){ 74 this.log("---------===========--- check if adjust: " + currNode.value); 75 if(currNode.parent){ 76 this.log(" parent: " + currNode.parent.value); 77 } 78 var leftDepth = this.calcuDepth(currNode.left); 79 var rightDepth = this.calcuDepth(currNode.right); 80 this.log("leftDepth: " + leftDepth + " rightDepth: " + rightDepth); 81 if(leftDepth - rightDepth == 2){ 82 if(newNode == null){ 83 this.rightRotate(currNode); 84 } 85 else if(newNode.value < currNode.value && newNode.value < currNode.left.value){ 86 this.log("LL"); 87 this.rightRotate(currNode); 88 } 89 else if(newNode.value < currNode.value && newNode.value > currNode.left.value){ 90 this.log("LR"); 91 this.leftRotate(currNode.left); 92 this.rightRotate(currNode); 93 } 94 } 95 else if(rightDepth - leftDepth == 2){ 96 if(newNode == null){ 97 this.leftRotate(currNode); 98 } 99 else if(newNode.value > currNode.value && newNode.value > currNode.right.value){ 100 this.log("RR"); 101 this.leftRotate(currNode); 102 } 103 else if(newNode.value > currNode.value && newNode.value < currNode.right.value){ 104 this.log("RL"); 105 this.rightRotate(currNode.right); 106 this.leftRotate(currNode); 107 } 108 } 109 110 currNode = currNode.parent; 111 this.printTreeByLevel(); 112 } 113 }, 114 leftRotate : function (currNode) { 115 this.log("leftRotate: " + currNode.value); 116 var oldRight = currNode.right; 117 118 //如果當前節點就是根節點,更新外界引用的根節點 119 if(currNode == this._tree){ 120 this._tree = oldRight; 121 } 122 else { 123 //更新變動前的 currNode 的 parent 的指向 124 if(currNode.parent.left == currNode){ 125 currNode.parent.left = oldRight; 126 } 127 else if(currNode.parent.right == currNode){ 128 currNode.parent.right = oldRight; 129 } 130 } 131 132 //更新 curr 和 oldRight 的 parent 133 oldRight.parent = currNode.parent; 134 135 //更新 curr 和 oldRight 的 child 136 currNode.right = oldRight.left; 137 if(oldRight.left){ 138 oldRight.left.parent = currNode; 139 } 140 141 oldRight.left = currNode; 142 currNode.parent = oldRight; 143 144 this._tree.parent = null; 145 return oldRight; 146 }, 147 rightRotate : function (currNode) { 148 this.log("rightRotate: " + currNode.value); 149 var oldLeft = currNode.left; 150 151 //如果當前節點就是根節點,更新外界引用的根節點 152 if(currNode == this._tree){ 153 this._tree = oldLeft; 154 } 155 else { 156 //更新變動前的 currNode 的 parent 的指向 157 if(currNode.parent.left == currNode){ 158 currNode.parent.left = oldLeft; 159 } 160 else if(currNode.parent.right == currNode){ 161 currNode.parent.right = oldLeft; 162 } 163 } 164 165 //更新 curr 和 oldLeft 的 parent 166 oldLeft.parent = currNode.parent; 167 168 //更新 curr 和 oldLeft 的 child 169 currNode.left = oldLeft.right; 170 if(oldLeft.right){ 171 oldLeft.right.parent = currNode; 172 } 173 174 oldLeft.right = currNode; 175 currNode.parent = oldLeft; 176 177 this._tree.parent = null; 178 return oldLeft; 179 }, 180 181 /** 182 * 計算左右節點的深度。葉子節點的深度都是 1,依次向上加 1 183 * @param treeNode 184 * @returns {number} 185 */ 186 calcuDepth : function (treeNode) { 187 if(!treeNode){ 188 return 0; 189 } 190 if(treeNode.left == null && treeNode.right == null){ 191 return 1; 192 } 193 return 1 + Math.max(this.calcuDepth(treeNode.left), this.calcuDepth(treeNode.right)); 194 }, 195 196 /** 197 * 從樹中刪除一個節點 198 * @param value 199 */ 200 remove : function (value) { 201 this.log(" ===== 將要刪除元素:" + value); 202 if(!value){ 203 return; 204 } 205 206 //定位到節點 207 var currNode = this._tree; 208 while(currNode){ 209 if(currNode.value == value){ 210 break; 211 } 212 currNode = value > currNode.value ? currNode.right : currNode.left; 213 } 214 if(currNode.value != value){ 215 this.log("沒找到啊"); 216 return; 217 } 218 219 var targetNode = null; 220 //刪除該節點 221 if(currNode.left){ 222 //有左子樹,找到其中最大值來替代空位 223 targetNode = this.findMaxNode(currNode.left); 224 this.log(" == currNode.left: " + targetNode.value); 225 226 //更新 target 父節點的 child 指向 227 if(targetNode.parent != currNode){ 228 var newChild = targetNode.left ? targetNode.left : targetNode.right; 229 if(targetNode.parent.left == targetNode){ 230 targetNode.parent.left = newChild; 231 } 232 else { 233 targetNode.parent.right = newChild; 234 } 235 } 236 //更新 target 的 parent 指向 237 targetNode.parent = currNode.parent; 238 239 // 更新 target 的 right 指向 240 targetNode.right = currNode.right; 241 if(currNode.right){ 242 currNode.right.parent = targetNode; 243 } 244 // 更新 target 的 left 指向 、、一定要注意避免自身死循環 245 if(currNode.left != targetNode){ 246 targetNode.left = currNode.left; 247 currNode.left.parent = targetNode; 248 } 249 } 250 //沒有左子樹,但是有右子樹,直接把右子樹提上去就好了 251 else if(currNode.right){ 252 targetNode = currNode.right; 253 targetNode.parent = currNode.parent; 254 this.log(" == currNode.right: " + targetNode.value); 255 } 256 //如果 curr 是葉子節點,只要更新 curr 的 parent 就可以了,沒有額外處理 257 258 //更新 curr 父節點的 child 指向 259 if(currNode.parent && currNode.parent.left == currNode){ 260 currNode.parent.left = targetNode; 261 } 262 else if(currNode.parent && currNode.parent.right == currNode){ 263 currNode.parent.right = targetNode; 264 } 265 else { 266 this._tree = targetNode; //說明是 根節點 267 } 268 269 this.log(" +++++++++++++ "); 270 this.printTreeByLevel(); 271 this.balanceTree(targetNode == null ? currNode.parent : targetNode); 272 this.log(" +++++++++++++ "); 273 }, 274 275 276 findMaxNode : function(treeNode){ 277 while(treeNode){ 278 if(treeNode.right){ 279 treeNode = treeNode.right; 280 } 281 else { 282 return treeNode; 283 } 284 } 285 return treeNode; 286 }, 287 288 289 290 log : function (str) { 291 console.log(str); 292 }, 293 /** 294 * 按照層級打印一棵樹的各層節點名字 295 **/ 296 printTreeByLevel : function () { 297 this.log("-----------------------"); 298 if(!this._tree){ 299 this.log(" === empty ==="); 300 return; 301 } 302 var nodeList = []; 303 nodeList.push(this._tree); 304 while(nodeList.length > 0){ 305 var len = nodeList.length; 306 var value = ""; 307 for(var i=0; i<len; ++i){ 308 var currNode = nodeList[i]; 309 value += currNode.value + " "; 310 if(currNode.left){ 311 nodeList.push(currNode.left); 312 } 313 if(currNode.right){ 314 nodeList.push(currNode.right); 315 } 316 } 317 this.log(value); 318 319 nodeList = nodeList.slice(len); 320 } 321 }, 322 }; 323 324 325 AVLTree.printTreeByLevel(); 326 AVLTree.log("===================================================================================================="); 327 var list = [3,7,9,23,45, 1,5,14,25,24, 13,11, 26]; 328 for(var index in list){ 329 AVLTree.insert(list[index]); 330 } 331 AVLTree.log("===================================================================================================="); 332 AVLTree.printTreeByLevel(); 333 // AVLTree.remove(1); 334 // AVLTree.remove(25); 335 // AVLTree.printTreeByLevel();
