转载于https://www.cnblogs.com/leihui/p/6002418.html
这篇文章用来复习AVL的平衡操作,分别会介绍其旋转操作的递归与非递归实现,但是最终带有插入示例的版本会以递归呈现。
下面这张图绘制了需要旋转操作的8种情况。(我要给做这张图的兄弟一个赞)后面会给出这八种情况对应平衡实现。
[1]
情况1-2:
这种需要旋转的结构一般称之为为LL型,需要右旋(顺时针旋转)。
我用一个图来抽象一下这两个情况,画的不好,我尽量表达吧。
此时需要对一个进行平衡操作,方法为:
- 将甲的左子树换为乙的右子树。
- 乙的右子树换为A.
- 非递归实现的代码为:
非递归的操作在旋转前会充分考虑所有的旋转情况,目的是提早调整甲下面各节点的高度。
之后再进行旋转操作,这一点与递归的不同,可见递归是平衡完后再进行的高度调整。
- 递归实现代码为:
情况3-4:
这种需要旋转的结构一般称之为RR型,需要左旋(逆时针旋转)。
需要对一个进行平衡操作,方法为:
- 将甲的右子树换为乙的左子树;
- 乙的左子树换为甲
- 非递归的实现为:
- 递归实现为:
情况5-6:
这种需要旋转的结构一般称之为LR型,需要双旋转,即两次单旋。分别为左旋和右旋。
需要对一个进行平衡操作,方法为:
- 对B(A->左)做左旋
- 对一个做右旋
这个递归与非递归的方式都是一样的。
- 非递归:
- 递归:
但是有没有一次性到位的方法呢?有的
我把非递归的两个函数展开:
发现最后一步都是确定与父节点的关系,并不是旋转中的具体过程,于是可以简化为这样:
情况7-8:
这种需要旋转的结构一般称之为RL型,需要双旋转,即两次单旋。分别为右旋和左旋。
需要对一个进行平衡操作,方法为:
- 对乙进行右旋
- 对一个进行左旋
同样,递归与非递归版本是一样的。
- 非递归:
- 递归:
同样,也有一次性到位的方法:
下面是实现部分:
0.结构声明 [2] :
1.类中提供的API
2.获取高度:
因为在MAX()函数获取结束后需要1,所以这里的目的是将叶节点的高度想办法为0。
3.插入操作:
- 递归
通过回溯的方式找到插入的位置,先平衡后调整高度;
哈哈,有一个很有趣的细节为什么同时判断高度差一个是
if(getHeight(_T-> left) - getHeight(_T-> right)== 2)
而另一个是
if(getHeight(_T-> right) - getHeight(_T-> left)== 2)
因为这里已经知道了插入发生在哪边了,所以肯定是插入的那边会有破坏平衡的可能,不会造成尴尬的(小 - 大)的局面。
- 非递归[3]:
可以发现,非递归的实现是先调整高度再平衡,但是要提前考虑所有情况。
考虑左子树的情况:
考虑右子树的情况:
总结:
递归真是神奇啊,对子树的处理递归的很漂亮,代码量是一方面,代码逻辑的清晰性也是非递归程序鲜有的。
用这个来学习递归算法真是好工具,希望对于我后面复习图论有帮助。
这篇文章中所述的非递归程序我并没有实现,肯定有疏忽的地方,欢迎大家指正。
完整示例中还有一个showThisTree(),它可以打印出漂亮的平衡二叉树。
代码相关请见我的github上
[1] AVL树的旋转操作图解最详细
[2]左等价leftChild,同理,右也等价rightChild。
[4]参考教材 数据结构与算法分析:C语言描述(原书第2版)[美] MarkAllenWeiss着