一、定义:

我们知道二叉查找树,有很强的排序、查找、删除、插入的能力,但是,随着插入和删除的次数变多,二叉查找树的工作的效率有可能的变得没那么好了,因为二叉查找树的工作效率依赖于树的高度,树越高效率越差(同样多的结点的情况下),因此,就需要尽可能地降低树的高度。引入AVL树的目的就是为了提高二叉查找树的效率,在每次向二叉树插入或删除结点的时候,尽可能地使二叉查找树保持平衡,平衡状态下树的高度是最低的。
维基百科:

上图给出了,AVL树的概念和平衡因子的概念(左子树的高度-右子树的高度)

二、分析:

首先空树是AVL树,当我们先AVL树插入或删除结点的AVL树可能会失去平衡,AVL树失去平衡的情况有以下四种:

1、左左(LL)不平衡情况:
在根结点的较高的左子树的左子树上插入了结点,使得根的平衡因子大于1而失去平衡,或者是在删除一个结点后,使得原本就更高的左子树的左边更高,这种情况要作右单旋(右边矮做右单旋)操作
以下假设T1、T2、T3是高度相同的满二叉树

          y                   y                               x/ \                   / \     Right Rotation          /  \x   T3  -------->  x   T3   - - - - - - - >        T1   y / \         insert O  / \                             /    / \T1  T2             T1  T2                          O    T2  T3/O
 Node *x = y->left;y->left = x->right;x->right = y;

2、右右(RR)不平衡情况:
这个和LL对称,它是原较高的右子树的右子树更高了,这种情况要进行左单旋(左边矮做左单旋)操作
以下假设T1、T2、T3是高度相同的满二叉树

  y                            x                           x                 / \                          / \                         / \                        x   T3                          T1   y     <-------        T1   y                       / \    \   < - - - - - - -        / \     insert O           / \                             T1  T2   o  Left Rotation        T2  T3                     T2  T3                                 \O
 Node* y = x->right;x->right = y->left;y->left = x;

3、左右(LR)不平衡情况:
原本较高的左子树(断句)的右子树高度更高了,这种情况要进行先左单,后右单的双旋操作
以下假设T1、T4高度为hT2、T3的高度h-1

     z                      z                               z                           x/ \                   / \                            /   \                        /  \ y   T4   ------->     y   T4  Left Rotate (y)        x    T4  Right Rotate(z)    y      z/ \      insert O    / \      - - - - - - - - ->    /  \      - - - - - - - ->  / \    / \
T1   x                T1   x                          y    T3                    T1  T2 T3  T4/ \                     / \                        / \                             /T2   T3               T2   T3                    T1   T2                          O/                              /O                            O

上面图
假设在以z为根较高的左子树y的右子树x上插入一个结点O(x的左右子女上都可以),首先y结点的平衡因子为-2,失去平衡,这里先做个左单旋,做完后(不管做不做)z的平衡因子为2,要做个右单旋。

代码类似于:

 leftRotation(y);rightRotation(z);

因为y是z的左子女,所以可以写成:

 z->left = leftRotation(z->left);return rightRotation(z);

leftRotationrightRotation返回调整后的根结点。

4、RL不平衡情况:和LR不平衡对称
在原本较高的右子树(断句)的左子树高度更高了导致的不平衡,要先右单旋后左单旋。
以下假设T1、T4高度为hT2、T3的高度h-1

   z                            z                            z                            x/ \                         / \                          / \                          /  \
T1   y      ---------->  T1   y   Right Rotate (y)    T1   x      Left Rotate(z)   z      y/ \   insert O            / \  - - - - - - - - ->     /  \   - - - - - - - ->  / \    / \x   T4                    x   T4                      T2   y                  T1  T2  T3  T4/ \                          / \                         /    /  \                    /
T2   T3                     T2   T3                      O    T3   T4                 O/O

代码类似于:

 z->left = rightRotation(z->left);return leftRotation(z);

在插入或删除某个结点后,只有出现了这四种情况会导致AVL树暂时失去平衡,而相应的解决办法也给出了。因此,在代码实现的时候,每次插入删除的时候,都要从插入删除位置沿双亲位置向上回溯,检查以每个双亲为根时AVL树有没有失去平衡,失去了是哪种情况就对的就用那种方法调整。所以插入删除的时候可以用递归的方法来实现,插入和删除一个结点后就回溯到上一层,检查,然后继续回溯,直到没问题的时候。

小总结:

假设定义以自底向上第一个平衡因子为2或者-2为根的树为最小不平衡AVL树。那么它不平衡的原因就四种:
设根为root,这四种情况是:

不平衡情况 说明 平衡因子特点 解决办法
LL root的左子树比右子树高2,并且左子树的左子树比右子树高1或相等(插入时不会,删除时可能会) 此时root的平衡因子为2,左子树的平衡因子为1或0 右单旋
LR root的左子树比右子树高2,并且左子树的右子树比左子树高1(其实相等也可以,但我把相等的情况放上面去了) 此时root的平衡因子为2,左子树的平衡因子为-1 先左单旋后右单旋
RR root的右子树比左子树高2,并且右子树的右子树比左子树高1或相等(与LL同理) 此时,root的平衡因子为-2,右子树的平衡因子为-1或0 左单旋
RL root的右子树比左子树高2,并且右子树的左子树比右子树高1 此时,root的平衡因子为-2,root的右子树平衡因子为1 先右单旋后左单旋

这个表很重要,插入和删除时就是根据这个来调整不平衡的。
左边低左单旋,右边低右单旋。这个自己理解理解图就好。


三、代码实现:

1、结点结构:

class Node
{public:int key;int height;Node *left,*right;Node(int _key){key = _key;height = 1;left = right = NULL;}
};

2、树的高度:

int height(Node *N)
{if(N == NULL){return 0;}return N->height;
}

3、max函数:返回大的值

int max(int x,int y)
{return x > y ? x : y;
}

4、计算以某结点为根的平衡因子:左子树的高度-右子树的高度

int getBalance(Node *root)    //获得以root为根的子树的平衡因子
{if(root == NULL){return 0;} return height(root->left) - height(root->right);
}

5、左单旋:

Node* leftRotation(Node *x)
{//左单旋(左边矮),又称RR旋转(在较高的右子树的右子树插入结点导致失去平衡 ) Node* y = x->right;x->right = y->left;y->left = x;y->height = max(height(y->left), height(y->right)) + 1;x->height = max(height(x->left), height(x->right)) + 1;return y;   //返回调整后的根结点
}

6、右单旋:

Node* rightRotation(Node *y)
{ //右单旋(右边矮),又称LL旋转(在较高的左子树的左子树插入结点导致失去平衡) Node *x = y->left;y->left = x->right;x->right = y;y->height = max(height(y->left), height(y->right)) + 1;x->height = max(height(x->left), height(x->right)) + 1;return x;   //返回调整后的根结点
}

7、插入:
插入完后执行以下步骤:
1)当前节点如果是插入结点的祖先之一,那么要更新当前结点的高度。
2)获取当前结点的平衡因子(左子树的高度–右子树的高度)。
3)如果平衡因子大于1,则当前结点不平衡,我们处于“左左”情况或“左右”情况。要检查是左左情况还是左右情况,请获取左子树的平衡因子。如果左子树的平衡因子大于或等于0,则为LL情况[因为大于等于0说明左子树的左子树的高度大于或等于左子树的右子树的高度,大于0时是LL情况没问题,等于0时,为方面当作LL情况来处理(LL一次左单旋就够了,LR得先左旋后右旋)],否则为LR情况。下同
4)如果平衡因子小于-1,则当前节点不平衡,我们处于“右右”情况或“右左”情况。要检查是右右情况还是右左情况,请获取右子树的平衡因子。如果右子树的平衡因子小于或等于0,则为RR情况,否则为RL情况。

Node* insert(Node *root,int key)
{if(root == NULL){return new Node(key);} if(key < root->key){root->left = insert(root->left, key); }else if(key > root->key){root->right = insert(root->right, key);}else   //key == root->key不插入 {return root;}root->height = 1 + max(height(root->left),height(root->right));int balance = getBalance(root);     //检查平衡因子//LL情况 -->右单旋 if(balance > 1 && getBalance(root->left) >= 0){//LL ---> 右单旋 return rightRotation(root); } if(balance > 1 && getBalance(root->left) < 0){//LR ----->先左单旋后右单旋root->left = leftRotation(root->left);return rightRotation(root); } if(balance < -1 && getBalance(root->right) <= 0){//RR---->左单旋return leftRotation(root);       }if(balance < -1 && getBalance(root->right) > 0){//RL---->先右单旋后左单旋 root->right = rightRotation(root->right);return leftRotation(root);}return root;  //没有失去平衡
}

8、删除:这个和二叉查找树一样,当有待删结点有两个子女时,要把它转换成删一个子女的情况(找右子树下值关键码最小的结点来代替被删(中序后继),或者左子树下关键码最大的(中序前继))。
删完后还有以下步骤:
1)当前节点如果是已删除结点的祖先之一,那么要更新当前结点的高度。
2)获取当前结点的平衡因子(左子树的高度–右子树的高度)。
3)如果平衡因子大于1,则当前结点不平衡,我们处于“左左”情况或“左右”情况。要检查是左左情况还是左右情况,请获取左子树的平衡因子。如果左子树的平衡因子大于或等于0,则为LL情况[因为大于等于0说明左子树的左子树的高度大于或等于左子树的右子树的高度,大于0时是LL情况没问题,等于0时,为方面当作LL情况来处理(LL一次左单旋就够了,LR得先左旋后右旋)],否则为LR情况。下同
4)如果平衡因子小于-1,则当前节点不平衡,我们处于“右右”情况或“右左”情况。要检查是右右情况还是右左情况,请获取右子树的平衡因子。如果右子树的平衡因子小于或等于0,则为RR情况,否则为RL情况。

Node *minValueNode(Node *node)
{//中序右子树下第一个结点(右子树下key值最小) Node *current = node;while(current->left != NULL){current = current->left;}return current;
}
Node* deleteNode(Node* root,int key)
{if(root == NULL){return root;} if(key < root->key){root->left = deleteNode(root->left, key);}else if(key > root->key){root->right = deleteNode(root->right, key); }else{if((root->left == NULL) || (root->right == NULL)){Node* temp = root->left?root->left:root->right; if(temp == NULL)  //如果没有子女 {temp = root;  //temp 保存要删除结点 root = NULL;    // root 置空 }else        //有一个子女 {*root = *temp;        //把temp的值拷给root,temp代替被删 }delete temp; }else    //如果有两个子女-->找个只有一个子女或者没有子女的子孙, {     //把它的值存到当前结点,然后把这个子孙删了 Node *temp = minValueNode(root->right);root->key = temp->key;root->right = deleteNode(root->right,temp->key);} }//删完后root == NULL就不用检查以root为根的子树平不平衡了if(root == NULL){return root;} root->height = 1 + max(height(root->left),height(root->right));int balance = getBalance(root);if(balance > 1 && getBalance(root->left) >= 0){//LL ---> 右单旋 return rightRotation(root); } if(balance > 1 && getBalance(root->left) < 0){//LR ----->先左单旋后右单旋root->left = leftRotation(root->left);return rightRotation(root); } if(balance < -1 && getBalance(root->right) <= 0){//RR---->左单旋return leftRotation(root);      }if(balance < -1 && getBalance(root->right) > 0){//RL---->先右单旋后左单旋 root->right = rightRotation(root->right);return leftRotation(root);}return root;      //返回删完后调整后的根
}

9、测试代码:

#include<iostream>
#include<vector>
#include<string>using namespace std;class Node
{public:int key;int height;Node *left,*right;Node(int _key){key = _key;height = 1;left = right = NULL;}
};
int height(Node *N)
{if(N == NULL){return 0;}return N->height;
}
int max(int x,int y)
{return (x > y )? x : y;
}
int getBalance(Node *root)    //获得以root为根的子树的平衡因子
{if(root == NULL){return 0;} return height(root->left) - height(root->right);
}
/*y                               x/ \     Right Rotation          /  \x   T3   - - - - - - - >        T1   y / \       < - - - - - - -            / \T1  T2     Left Rotation            T2  T3
*/
Node* leftRotation(Node *x)
{//左单旋(左边矮),又称RR旋转(在较高的右子树的右子树插入结点导致失去平衡 ) Node* y = x->right;x->right = y->left;y->left = x;//更新x,y的高度,必须先x,后y,因为x是y的子树 x->height = max(height(x->left), height(x->right)) + 1;y->height = max(height(y->left), height(y->right)) + 1;return y;    //返回调整后的根结点
}
Node* rightRotation(Node *y)
{ //右单旋(右边矮),又称LL旋转(在较高的左子树的左子树插入结点导致失去平衡) Node *x = y->left;y->left = x->right;x->right = y;//更新x,y的高度,必须先y,后x,因为y是x的子树 y->height = max(height(y->left), height(y->right)) + 1;x->height = max(height(x->left), height(x->right)) + 1;return x;    //返回调整后的根结点
}
Node* insert(Node *root,int key)
{if(root == NULL){return new Node(key);} if(key < root->key){root->left = insert(root->left, key); }else if(key > root->key){root->right = insert(root->right, key);}else   //key == root->key不插入 {return root;}root->height = 1 + max(height(root->left),height(root->right));int balance = getBalance(root);     //检查平衡因子//LL情况 -->右单旋 if(balance > 1 && getBalance(root->left) >= 0){//LL ---> 右单旋 return rightRotation(root); } if(balance > 1 && getBalance(root->left) < 0){//LR ----->先左单旋后右单旋root->left = leftRotation(root->left);return rightRotation(root); } if(balance < -1 && getBalance(root->right) <= 0){//RR---->左单旋return leftRotation(root);       }if(balance < -1 && getBalance(root->right) > 0){//RL---->先右单旋后左单旋 root->right = rightRotation(root->right);return leftRotation(root);}return root;  //没有失去平衡
}
Node *minValueNode(Node *node)
{//中序右子树下第一个结点(右子树下key值最小) Node *current = node;while(current->left != NULL){current = current->left;}return current;
}Node* deleteNode(Node* root,int key)
{if(root == NULL){return root;} if(key < root->key){root->left = deleteNode(root->left, key);}else if(key > root->key){root->right = deleteNode(root->right, key); }else{if((root->left == NULL) || (root->right == NULL)){Node* temp = root->left?root->left:root->right; if(temp == NULL)  //如果没有子女 {temp = root;  //temp 保存要删除结点 root = NULL;    // root 置空 }else        //有一个子女 {*root = *temp;        //把temp的值拷给root,temp代替被删 }delete temp; }else    //如果有两个子女-->找个只有一个子女或者没有子女的子孙, {     //把它的值存到当前结点,然后把这个子孙删了 Node *temp = minValueNode(root->right);root->key = temp->key;root->right = deleteNode(root->right,temp->key);} }//删完后root == NULL就不用检查以root为根的子树平不平衡了if(root == NULL){return root;} root->height = 1 + max(height(root->left),height(root->right));int balance = getBalance(root);if(balance > 1 && getBalance(root->left) >= 0){//LL ---> 右单旋 return rightRotation(root); } if(balance > 1 && getBalance(root->left) < 0){//LR ----->先左单旋后右单旋root->left = leftRotation(root->left);return rightRotation(root); } if(balance < -1 && getBalance(root->right) <= 0){//RR---->左单旋return leftRotation(root);      }if(balance < -1 && getBalance(root->right) > 0){//RL---->先右单旋后左单旋 root->right = rightRotation(root->right);return leftRotation(root);}return root;}
void printBST(Node *root, int k)
{if(root != NULL){printBST(root->right,k+5);for(int i = 0; i < k; i++){cout<<" ";}cout<<root->key<<endl;printBST(root->left,k+5);}
}
int main()
{Node *root = NULL;root = insert(root,10); root = insert(root,5); root = insert(root,11); root = insert(root,3); root = insert(root,6);root = insert(root,12); root = insert(root,2); root = insert(root,9);     printBST(root,0);cout<<"------LL---------"<<endl;root = deleteNode(root,12);printBST(root,0);  return 0;
}

结果图:

特意找个删除后是root的左子树的平衡结点是0(左子树的左右子树高度相等),当LL处理没问题。


写完了,刚了3个下午应该算搞懂了。如果哪里错了,还请各位指出哦!

AVL树---最简单的实现相关推荐

  1. C++实践笔记(四)----AVL树的简单实现

    关于AVL树的分析,请见:数据结构与算法分析学习笔记(二)--AVL树的算法思路整理 这里给出AVL树的结构定义以及insert,remove和print三种操作的例程:   1 #include & ...

  2. 数据结构与算法——AVL树类的C++实现

    关于AVL树的简单介绍能够參考: 数据结构与算法--AVL树简单介绍 关于二叉搜索树(也称为二叉查找树)能够參考:数据结构与算法--二叉查找树类的C++实现 AVL-tree是一个"加上了额 ...

  3. AVL树、splay树(伸展树)和红黑树比较

    AVL树.splay树(伸展树)和红黑树比较 一.AVL树: 优点:查找.插入和删除,最坏复杂度均为O(logN).实现操作简单 如过是随机插入或者删除,其理论上可以得到O(logN)的复杂度,但是实 ...

  4. 【从蛋壳到满天飞】JS 数据结构解析和算法实现-AVL树(一)

    前言 [从蛋壳到满天飞]JS 数据结构解析和算法实现,全部文章大概的内容如下: Arrays(数组).Stacks(栈).Queues(队列).LinkedList(链表).Recursion(递归思 ...

  5. 纸上谈兵: AVL树

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 二叉搜索树的深度与搜索效率 我们在树, 二叉树, 二叉搜索树中提到,一个有n个节点 ...

  6. AVL树入门(转载)

    原文链接:http://lib.csdn.net/article/datastructure/9204           作者:u011469062 前言:本文不适合 给一组数据15分钟就能实现AV ...

  7. Linux内核之于红黑树and AVL树

    为什么Linux早先使用AVL树而后来倾向于红黑树?        实际上这是由红黑树的实用主义特质导致的结果,本短文依然是形而上的观点.红黑树可以直接由2-3树导出,我们可以不再提红黑树,而只提2- ...

  8. 平衡查找树C语言程序,树4. Root of AVL Tree-平衡查找树AVL树的实现

    对于一棵普通的二叉查找树而言,在进行多次的插入或删除后,容易让树失去平衡,导致树的深度不是O(logN),而接近O(N),这样将大大减少对树的查找效率.一种解决办法就是要有一个称为平衡的附加的结构条件 ...

  9. 从AVL树的定义出发,一步步推导出旋转的方案。

    本文从AVL树的定义出发,一步步地推导出AVL树旋转的方案,这个推导是在已经清楚地知道AVL树的定义这个前提下进行的.文章注重思考的过程,并不会直接给出AVL树是怎样旋转的,用来提醒自己以后在学习的时 ...

最新文章

  1. matlab 画 矩阵点,在MATLAB中绘制矩阵中点之间的线
  2. 有了这 4 款工具,老板再也不怕我写烂SQL了
  3. linux复制压缩文件,Linux如何复制,打包,压缩文件
  4. php取月份函数,分享3个php获取日历的函数
  5. DNN结构演进History—CNN-GoogLeNet :Going Deeper with Convolutions
  6. linux curl命令验证服务器断点续传支持
  7. 【POJ - 2392】Space Elevator (dp,优秀的背包问题)
  8. iOS阶段学习第31天笔记(UINavigationBar介绍)
  9. oracle如何创建基表,创建本地基表的物化视图
  10. Atitit.sql where条件表达式的原理  attilax概括
  11. 十大OpenGL教程
  12. 基于cat12和SPM12进行大脑VBM数据分析笔记2——统计分析
  13. 拖动或点击CMD窗口造成程序阻塞,在bat文件中关闭cmd窗口的快速编辑模式
  14. 那些高中时曾经背得烂熟的古文(滕王阁序,阿房宫赋, 兰亭集序 , 师说,蜀道难 ...)再一次读读吧,慢慢的读,突然很想哭...有些岁月果真不曾忘怀
  15. jmeter获取上一个接口的返回值作为下一个接口的传入参数
  16. Linux后端开发-POSIX标准以及shell编程
  17. 如何控制Echarts时间轴的刻度区间
  18. PromptBERT: Improving BERT Sentence Embeddings with Prompts (通篇翻译)
  19. 软件项目管理系统-采购商品管理-采购订单一览
  20. 面试手册第五版更新了!(面试必备)

热门文章

  1. python精彩编程200例-编程语言入门经典100例【Python版】
  2. python学来干什么-学 Python 都用来干嘛的?
  3. python做excel自动化-Python控制Excel实现自动化办公
  4. python自动化办公入门书籍-盘点使用Python进行自动化办公所需要的知识点
  5. python工程师工资多少-最新 | 2019年Python工程师的平均薪资是多少?
  6. 零基础学python用哪本书好-零基础学python推荐几本python学习的书籍
  7. 远程计算机用户端口,电脑怎么开远程端口
  8. 漏洞payload 靶机_hackme:2 靶机攻略
  9. element-UI:el-table 表格排序
  10. 网页特效offset、client、scroll系列属性的作用