实现Avl平衡树

 

一、介绍

  AVL树是一种自平衡的二叉搜索树,它由Adelson-Velskii和 Landis于1962年发表在论文《An algorithm for the organization of information》中。AVL树的特点是,其左右子树的高度差的绝对值小于2(空树的高度定义为 -1,无子树的树高度为0)。如下图所示,左边的二叉树为AVL树,而右边的二叉树root节点的左子树高度为2,右子树高度为0,高度差为2,不是AVL树。与普通二叉树相同的是查找和遍历;但是插入和删除操作可能会破坏AVL的平衡结构,这也是实现AVL树的难点所在。

二、定义

  AVL的特点是平衡,每个节点平衡依赖于其左右子树的高度,因此,每个树节点都有一个height值。这里的定义和实现来自于《数据结构和算法分析-C语言描述》一书,具体定义如下:

  1. /*
  2. filename: Avltree.h
  3. from: chapter 4 tree, page 87, Data Structure and Algorithm Analysis
  4. date: 2013-08-06
  5. function: declearation of AVL Tree
  6. */
  7. #ifndef _AVLTREE_H_
  8. #define _AVLTREE_H_
  9.  
  10. // 定义树节点
  11. struct AvlNode{
  12. double element; //内容元素
  13. AvlTree left; //左孩子
  14. AvlTree right; //右孩子
  15. int height; //高度,无子节点时为 0,NULL时为 -1
  16. };
  17. typedef struct AvlNode * Position;
  18. typedef struct AvlNode * AvlTree;
  19.  
  20. void InitAvl(AvlTree *T);
  21. AvlTree MakeEmpty(AvlTree T);
  22. Position Find(double X, AvlTree T);
  23. Position FindMin(AvlTree T);
  24. Position FindMax(AvlTree T);
  25. AvlTree Insert(double X, AvlTree T);
  26. AvlTree Delete(double X, AvlTree T);
  27. double Retrieve(Position P);
  28.  
  29. #endif

  对节点高度的控制是通过Height(Position P)实现的,很重要的一点是它规定了空树的高度(此时调用P->height会报错)。Height(Position P)实现如下:

  1. static int Height(Position P){
  2. if(P==NULL) return -1;
  3. else return P->height;
  4. }

三、初始化和置空树

  创建一个新树时,使用InitAvl()函数将其初始化为空。删除所有节点使用MakeEmpty()函数,代码如下:

  1. /*
  2. filename: Avltree.c
  3. */
  4. void InitAvl(AvlTree *T){
  5. *T = NULL;
  6. }
  7. AvlTree MakeEmpty(AvlTree T){
  8. if(T!=NULL){
  9. MakeEmpty(T->left);
  10. MakeEmpty(T->right);
  11. free(T);
  12. }
  13. return NULL;
  14. }

四、实现查找

  AVL树的查找与普通二叉树没有区别,可以通过递归或者非递归的方式实现,都比较简单。这里使用的是递归,需要注意,Find()、FindMin()和FindMax()方法前的return 比不可少,否则递归得出的结果无法向上传递。

  1. Position Find(double X, AvlTree T){
  2. if(T!=NULL){
  3. if(X==T->element) return T;
  4. else if(X>T->element) return Find(X, T->right);
  5. else return Find(X, T->left);
  6. }else return T;
  7. }
  8. Position FindMin(AvlTree T){
  9. if(T!=NULL && T->left!=NULL) return FindMin(T->left);
  10. else return T;
  11. }
  12. Position FindMax(AvlTree T){
  13. if(T==NULL ||T->right==NULL) return T;
  14. else return FindMin(T->right);
  15. }

五、实现插入

  在插入之前,AVL树中每个节点都是平衡的,插入元素以后,可能会导致 插入节点到根节点路径上的节点的平衡发生改变,而插入节点的子节点的平衡不变。沿着这条路径上行,对路径上的每个节点,检查其平衡状态,修正不平衡的节点,修正height,一直检查和修正到root节点。最后可以得到的是一棵平衡的AVL树。

  假设我们遍历到的节点是a,如果a满足平衡条件,则上行一个节点。如果不满足,则有四种情况:

  1. 插入到a的左儿子的左子树后,平衡被打破,(结果:左高右低,left-left型)(例子见上图)
  2. 插入到a的左儿子的右子树后,平衡被打破,(结果:左高右低,left-right型)
  3. 插入到a的右儿子的左子树后,平衡被打破,(结果:右高左低,right-left型)
  4. 插入到a的右儿子的右子树后,平衡被打破,(结果:右高左低,right-right型)

情形1和情形4对称,情形2和情形3对称。这里先看情形1。需要通过旋转来调整这几种不平衡状态

1、Left-left case

  这里,K2是当前要处理的节点。可以看到K2->left 比 K2->right 要高两层,而X比Y要高一层。而插入之前,k2满足平衡条件。插入新元素导致 X 长出一层,使其比Z高两层。重新调整平衡的方法是使X上移一层,Z下移一层,同时将K1设置为当前节点。

实现如下:

  1. /*
  2. filename: AvlTree.c
  3. */
  4. static Position SingleRotationWithLeft(Position K2){ //左孩子的Height太大
  5. Position K1 = K2->left;
  6. K2->left = K1->right;
  7. K1->right = K2;
  8. K2->height = Max(Height(K2->left), Height(K2->right)) + 1;
  9. K1->height = Max(Height(K1->left), Height(K2))+1;
  10. return K1;
  11. }

2、right-right case

  这种情况和与前一种没有本质差别,这里仅仅附上图示和代码

  1. // filename: Avltree.c
  2. static Position SingleRotationWithRight(Position K2){ //右孩子的Height太大,且大的分支在其右孩子
  3. Position K1 = K2->right;
  4. K2->right = K1->left;
  5. K1->left = K2;
  6. K2->height = Max(Height(K2->left), Height(K2->right)) + 1;
  7. K1->height = Max(Height(K1->right), K2->height)+1;
  8. return K1;
  9. }

3、left-right case

这种情况下,使用单次旋转后,可以看出当前节点(K1)仍然是不平衡的,因此我们需要进行两次旋转。旋转方法见图

等价于

  

  这里使用了两次单旋转,先旋转K1和K2,再把K2置为当前节点。(通过图可以直观地看出K1->element < K2->element < K3->element)

  1. // filename: Avltree.c
  2. static Position DoubleRotationWithLeft(Position K2){ // 左孩子太大,且大的分支在左孩子的右孩子上
  3. K2->left = SingleRotationWithRight(K2->left);
  4. K2 = SingleRotationWithLeft(K2);
  5.  
  6. return K2;
  7. }

4、right-left case

  这种情形与情形3类似,不多解释,代码如下

  1. // filename: Avltree.c
  2. static Position DoubleRotationWithRight(Position K2){ //右孩子太大,且大的分支是右孩子的左孩子
  3. K2->right = SingleRotationWithLeft(K2->right);
  4. K2 = SingleRotationWithRight(K2);
  5. return K2;
  6. }

  这四种旋转实现了平衡AVL树的功能,下面就插入元素分情况进行讨论。

  如果T为空树,则插入比较简单,直接分配内存,并存储即可。如果T不为空树,则需要分情况讨论。这里再强调一次,

  “在插入之前,AVL树中每个节点都是平衡的,插入元素以后,可能会导致 插入节点到根节点路径上的节点的平衡发生改变,而插入节点的子节点的平衡不变。沿着这条路径上行,对路径上的每个节点,检查其平衡状态,修正不平衡的节点,修正height,一直检查和修正到root节点。最后可以得到的是一棵平衡的AVL树。”

  每个节点的平衡状态是由其左右子节点的高度差决定的,使用递归可以保证更新完子节点以后,上溯到父节点进行检查更新。

  1. // filename: Avltree.c
  2. AvlTree Insert(double X, AvlTree T){
  3. if(T==NULL){ // T 为空树
  4. T = (AvlTree)malloc(sizeof(struct AvlNode));
  5. if(T==NULL){
  6. fprintf(stderr, "no space available!\n");
  7. abort();
  8. }else{
  9. T->element = X;
  10. T->left = T->right = NULL;
  11. T->height = 0;
  12. }
  13. }else if(X<T->element){
  14. T->left = Insert(X, T->left);
  15. if(Height(T->left) - Height(T->right)==2){
  16. if(X<T->left->element) // X被插入到其左孩子的左子树中,left-left case
  17. T = SingleRotationWithLeft(T);
  18. else // X被插入到其左孩子的右子树中,left-right case
  19. T = DoubleRotationWithLeft(T);
  20. }
  21. }else if(X>T->element){
  22. T->right = Insert(X, T->right);
  23. if(Height(T->right)-Height(T->left)==2){
  24. if(X<T->right->element) // X被插入到右孩子的左子树中, right-left case
  25. T = DoubleRotationWithRight(T);
  26. else // X 被插入到其右孩子的右子树中, right-right case
  27. T = SingleRotationWithRight(T);
  28. }
  29. }
  30. // else if x== T->left->element, no need to know
  31.  
  32. T->height = Max(Height(T->left),Height(T->right)) + 1; // 更新节点的高度 Max()为辅助函数
  33. return T;
  34. }

六、实现删除

  节点的删除比Insert麻烦一些,我们分情形处理。

  1. T为空树
  2. 要删除的元素不存在
  3. 要删除的元素位于叶子节点上
  4. 要删除的元素位于非叶子节点上

  情形1 和情形2不需要太多处理,直接返回T;情形3则需要递归删除,并逐级上溯纠正树的平衡性;情形4比较复杂,这里将详细讨论。

  假设要删除的节点为 a,如果a->left 或者 a->right为空树,则将 a = a->right(假设a->left==NULL), 释放原来a节点的空间,即可。然后逐个上溯到父节点,并调整其平衡性。如果 a的左右子树均不为空,则用其右子树的最小值取代 a中的值(a->element = FindMin(a->right)),递归删除右子树的最小值,然后调整a的平衡。最后逐个上溯到父节点,并调整其父节点的平衡性。

  1. if(!T->left){ // 第一种情况: 左子树为NULL, 删除后需要重新计算高度(减一),但不需要调节平衡
  2. pos = T;
  3. T = T->right;
  4. free(pos);
  5. }else if(!T->right){ //第二种情况:右子树为NULL,删除后需要重新计算高度(减一),但不需要调节平衡
  6. pos = T;
  7. T = T->left;
  8. free(pos);
  9. }else{ // 第三种情况,左右子树均不为空
  10. // 用右子树的最小值代替X,删除右子树最小值,并调节其平衡
  11. pos = FindMin(T->right);
  12. T->element = pos->element;
  13. Delete(pos->element, T->right); // 递归删除最小值,计算高度,并调节平衡至 T->right
  14. // 但无法保证 T 的平衡,因此需要调节
  15. if(Height(T->left) - Height(T->right)==2){
  16. if(Height(T->left->right)-Height(T->left->left)==1) // left-right型
  17. T = DoubleRotationWithLeft(T);
  18. else T = SingleRotationWithLeft(T); // left-left型
  19. }
  20. }

  Delete的全部实现如下:

  1. AvlTree Delete(double X, AvlTree T){
  2. Position pos;
  3. if(!T) return NULL; // when T ==NULL
  4.  
  5. // 递归删除
  6. if(X<T->element){
  7. T->left = Delete(X, T->left);// 删除后,左子树元素个数减少,可能为 NULL
  8. if(Height(T->right)-Height(T->left)==2){ // 平衡被打破,右子树高度过高
  9. if(T->left==NULL){
  10. // need to identify them
  11. if(T->right->right==NULL) T = DoubleRotationWithRight(T);
  12. else T = SingleRotationWithRight(T);
  13. }else if(X<T->left->element) // X小于左子树的元素,为 left-right型
  14. T = DoubleRotationWithLeft(T);
  15. else // X 大于左子树的元素,左子树的右子树的元素被删除,为left-left型
  16. T = SingleRotationWithLeft(T);
  17. }
  18. }else if(X>T->element){
  19. T->right = Delete(X, T->right); //删除后,右子树的元素个数减少, 可能为 NULL
  20. if(Height(T->left)-Height(T->right)==2){ //平衡被打破,左子树高度过高
  21. if(T->right==NULL){
  22. //left-left or left-right
  23. if(T->left->left==NULL) T = DoubleRotationWithLeft(T);
  24. else T = SingleRotationWithLeft(T);
  25. }else if(X<T->right->element) //X小于右子树元素,,为right-right型
  26. T = SingleRotationWithRight(T);
  27. else //X 大于右子树元素,为right-left型
  28. T = DoubleRotationWithRight(T);
  29. }
  30. }else{ // X == T->element
  31.  
  32. if(!T->left){ // 第一种情况: 左子树为NULL, 删除后需要重新计算高度(减一),但不需要调节平衡
  33. pos = T;
  34. T = T->right;
  35. free(pos);
  36. }else if(!T->right){ //第二种情况:右子树为NULL,删除后需要重新计算高度(减一),但不需要调节平衡
  37. pos = T;
  38. T = T->left;
  39. free(pos);
  40. }else{ // 第三种情况,左右子树均不为空
  41. // 用右子树的最小值代替X,删除右子树最小值,并调节其平衡
  42. pos = FindMin(T->right);
  43. T->element = pos->element;
  44. Delete(pos->element, T->right); // 递归删除最小值,计算高度,并调节平衡至 T->right
  45. // 但无法保证 T 的平衡,因此需要调节
  46. if(Height(T->left) - Height(T->right)==2){
  47. if(Height(T->left->right)-Height(T->left->left)==1) // left-right型
  48. T = DoubleRotationWithLeft(T);
  49. else T = SingleRotationWithLeft(T); // left-left型
  50. }
  51. }
  52. }
  53. // things does not work when T ==NULL
  54. if(T) T->height = Max(Height(T->left),Height(T->right)) + 1;
  55. return T;
  56. }

参考引用: 《数据结构与算法分析--C语言描述》

实现Avl平衡树的更多相关文章

  1. Python与数据结构[3] -> 树/Tree[2] -> AVL 平衡树和树旋转的 Python 实现

    AVL 平衡树和树旋转 目录 AVL平衡二叉树 树旋转 代码实现 1 AVL平衡二叉树 AVL(Adelson-Velskii & Landis)树是一种带有平衡条件的二叉树,一棵AVL树其实 ...

  2. 数据结构学习-AVL平衡树

    环境:C++ 11 + win10 IDE:Clion 2018.3 AVL平衡树是在BST二叉查找树的基础上添加了平衡机制. 我们把平衡的BST认为是任一节点的左子树和右子树的高度差为-1,0,1中 ...

  3. AVL平衡树的插入例程

    /* **AVL平衡树插入例程 **2014-5-30 11:44:50 */ avlTree insert(elementType X, avlTree T){ if(T == NULL){ T = ...

  4. AVL 平衡树

    AVL是一种平衡二叉树,它通过对二叉搜索树中的节点进行旋转使得二叉搜索树达到平衡.AVL在所有的平衡二叉搜索树中具有最高的平衡性. 定义 平衡二叉树或者为空树或者为满足如下性质的二叉搜索树: 左右子树 ...

  5. AVL平衡树(非指针实现)

    看了网上三四篇博客,学习了AVL树维护平衡的方式.但感觉他们给出的代码都有一点瑕疵或者遗漏,懂得了思想之后,花了一些时间把他们几篇的长处结合起来,没有使用指针,实现了一下.每个小逻辑功能都抽象成了函数 ...

  6. 数据结构——AVL平衡树

    1.是二叉搜索树(Binary Search Tree) 2.树和所有左右子树高度之差为-1,0,1 平衡因子(balance factor) =右子树高度-左子树高度 平衡化旋转: 1.从插入位置向 ...

  7. My implementation of AVL tree

    C++实现的avl平衡树 #include <stdlib.h> #include <time.h> #include <string.h> #include &l ...

  8. [SinGuLaRiTy] 平衡树

    [SinGuLaRiTy-1009] Copyright (c) SinGuLaRiTy 2017. All Rights Reserved. 二叉查找树 二叉查找树是指具有下列性质的非空二叉树: ⑴ ...

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

    AVL树本质上还是一棵二叉搜索树,它的特点是: 1.本身首先是一棵二叉搜索树.   2.带有平衡条件:每个结点的左右子树的高度之差的绝对值最多为1(空树的高度为-1).   也就是说,AVL树,本质上 ...

随机推荐

  1. C#5.0新特性

    C#5.0新特性 C#5.0最大的新特性,莫过于Async和Parallel. 以往我们为了让用户界面保持相应,我们可以直接使用异步委托或是System.Threading命名空间中的成员,但Syst ...

  2. 在线预览Excel

    遇到的问题各种多 <system.web>        <identity impersonate="true" userName="Administ ...

  3. XShell删除键之类的不正常

    异常处理汇总-开发工具  http://www.cnblogs.com/dunitian/p/4522988.html 一图解决 异常处理汇总:http://www.cnblogs.com/dunit ...

  4. Unofficial Microsoft SQL Server Driver for PHP (sqlsrv)非官方的PHP SQL Server 驱动

    原文 Unofficial Microsoft SQL Server Driver for PHP (sqlsrv) Here are unofficial modified builds of Mi ...

  5. C++中出现的计算机术语2

    C-style strings(C 风格字符串) C 程序把指向以空字符结束的字符数组的指针视为字符串.在 C++ 中,字符串字面值就是 C 风格字符串.C 标准库定义了一系列处理这样的字符串的库函数 ...

  6. Oracle执行计划——Oracle 如何启用执行计划

    AUTOTRACE是一项SQL*Plus功能,自动跟踪为SQL语句生成一个执行计划并且提供与该语句的处理有关的统计.SQL*Plus AUTOTRACE可以用来替代SQL Trace使用,AUTOTR ...

  7. How to write own add-in for SSMS 2012 (Final release version)

    原文 How to write own add-in for SSMS 2012 (Final release version) Reading internet forums I have noti ...

  8. python进程池剖析(二)

    之前文章中介绍了python中multiprocessing模块中自带的进程池Pool,并对进程池中的数据结构和各个线程之间的合作关系进行了简单分析,这节来看下客户端如何对向进程池分配任务,并获取结果 ...

  9. [翻译]用Dart塑造Android未来

    明天回家,今天下午瞅时间翻译了Cyril Mottier的另外一篇有关Android前景的文章. 原谅地址是:http://cyrilmottier.com/2014/06/12/shaping-th ...

  10. springmvc3.1.1+hibernate4

    上篇介绍了基本的配置,这篇着重介绍与hibernate4整合. 1.web.xml文件中加入spring-hibernate的配置.新的web.xml文件内容如下: <?xml version= ...