AVL 树的插入、删除、旋转归纳

参考链接:

http://blog.csdn.net/gabriel1026/article/details/6311339

之前简单了解过 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();

时间: 2024-10-25 12:39:51

AVL 树的插入、删除、旋转归纳的相关文章

AVL树的插入和删除

一.AVL 树 在计算机科学中,AVL树是最早被发明的自平衡二叉查找树.在AVL树中,任一节点对应的两棵子树的最大高度差为 1,因此它也被称为高度平衡树.查找.插入和删除在平均和最坏情况下的时间复杂度都是 O(log(n)).插入和删除元素的操作则可能需要借由一次或多次树旋转,以实现树的重新平衡. 节点的平衡因子是它的左子树的高度减去它的右子树的高度(有时相反).带有平衡因子 1.0 或 -1 的节点被认为是平衡的.带有平衡因子 -2 或 2 的节点被认为是不平衡的,并需要重新平衡这个树.平衡因

AVL树的插入操作(旋转)图解

=================================================================== AVL树的概念 在说AVL树的概念之前,我们需要清楚二茬搜索树的概念.对于二叉搜索树,我们知道它可以降低查找速率,但是如果一个二叉搜索树退化成一棵只剩单支的搜索树,此时的查找速率就相当于顺序表中查找元素,效率变低,时间复杂度由原来的O(logN)变为O(N). 此时就有了AVL(高度平衡二叉搜索树),从它的名字就能知道它也是一棵二叉搜索树,只是它在插入元素的时候

PTA Root of AVL Tree (AVL树模板+四种旋转+指针)

关于AVL树(平衡二叉搜索树,高度为lgn)的讲解,双手呈上某大佬博客:https://www.cnblogs.com/zhuwbox/p/3636783.html 我从这题get到一个新的结构体写法(姿势): typedef struct treeNode { int val; treeNode *left; treeNode *right; int height; }node, *tree;//node 是treeNode结构体,*tree是结构体指针 我对AVL树的理解: 按照插入节点时旋

AVL树的插入与删除

AVL 树要在插入和删除结点后保持平衡,旋转操作必不可少.关键是理解什么时候应该左旋.右旋和双旋.在Youtube上看到一位老师的视频对这个概念讲解得非常清楚,再结合算法书和网络的博文,记录如下. 1.1 AVL 的旋转 一棵AVL树是其每个节点的左子树和右子树的高度差最多为1的二叉查找树(空树高度定义为-1).AVL树插入和删除时都可能破坏AVL的特性,可以通过对树进行修正来保证特性,修正方法称为旋转. 下面以4个插入操作为例,说明不同旋转对应的场景. 1.1.1 LL-R 插入结点为6,沿着

AVL树的创建与旋转

AVL树写的不耐烦了,索性一次性代码贴上... /** 2 * AVL树(C语言): C语言实现的AVL树. 3 * 4 * @author skywang 5 * @date 2013/11/07 6 */ #include <stdio.h> #include <stdlib.h> #define HEIGHT(p) ( (p==NULL) ? -1 : (((Node *)(p))->height) ) #define MAX(a, b) ( (a) > (b)

AVL树(平衡二叉查找树)

首先要说AVL树,我们就必须先说二叉查找树,先介绍二叉查找树的一些特性,然后我们再来说平衡树的一些特性,结合这些特性,然后来介绍AVL树. 一.二叉查找树 1.二叉树查找树的相关特征定义 二叉树查找树,又叫二叉搜索树,是一种有顺序有规律的树结构.它可以有以下几个特征来定义它: (1)首先它是一个二叉树,具备二叉树的所有特性,他可以有左右子节点(左右孩子),可以进行插入,删除,遍历等操作: (2)如果根节点有左子树,则左子树上的所有节点的值均小于根节点上的值,如果根节点有右子树,则有字数上的所有节

红黑树(RB-tree)比AVL树的优势在哪?

1. 如果插入一个node引起了树的不平衡,AVL和RB-Tree都是最多只需要2次旋转操作,即两者都是O(1):但是在删除node引起树的不平衡时,最坏情况下,AVL需要维护从被删node到root这条路径上所有node的平衡性,因此需要旋转的量级O(logN),而RB-Tree最多只需3次旋转,只需要O(1)的复杂度. 2. 其次,AVL的结构相较RB-Tree来说更为平衡,在插入和删除node更容易引起Tree的unbalance,因此在大量数据需要插入或者删除时,AVL需要rebalan

树的插入、删除、旋转归纳

AVL 树的插入.删除.旋转归纳 参考链接: http://blog.csdn.net/gabriel1026/article/details/6311339 1126号注:先前有一个概念搞混了: 节点的深度 Depth 是指从根节点到当前节点的长度: 节点的高度 Height 是指从当前节点向下,到子孙中所有叶子节点的长度的最大值. 之前简单了解过 AVL 树,知道概念但一直没动手实践过.Now AVL 树是二叉搜索树的一种.二叉搜索树的规则就是:每个节点的 left child 都比自己小,

数据结构--Avl树的创建,插入的递归版本和非递归版本,删除等操作

AVL树本质上还是一棵二叉搜索树,它的特点是: 1.本身首先是一棵二叉搜索树. 2.带有平衡条件:每个结点的左右子树的高度之差的绝对值最多为1(空树的高度为-1). 也就是说,AVL树,本质上是带了平衡功能的二叉查找树(二叉排序树,二叉搜索树). 对Avl树进行相关的操作最重要的是要保持Avl树的平衡条件.即对Avl树进行相关的操作后,要进行相应的旋转操作来恢复Avl树的平衡条件. 对Avl树的插入和删除都可以用递归实现,文中也给出了插入的非递归版本,关键在于要用到栈. 代码如下: #inclu