AVL 樹的插入、刪除、旋轉概括

參考連接:
 
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 負責執行 二、三、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();
View Code
相關文章
相關標籤/搜索