AVL树---最简单的实现
一、定义:
我们知道二叉查找树,有很强的排序、查找、删除、插入的能力,但是,随着插入和删除的次数变多,二叉查找树的工作的效率有可能的变得没那么好了,因为二叉查找树的工作效率依赖于树的高度,树越高效率越差(同样多的结点的情况下),因此,就需要尽可能地降低树的高度。引入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
高度为h
、T2、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);
leftRotation
和rightRotation
返回调整后的根结点。
4、RL
不平衡情况:和LR
不平衡对称
在原本较高的右子树(断句)的左子树高度更高了导致的不平衡,要先右单旋后左单旋。
以下假设T1、T4
高度为h
、T2、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树---最简单的实现相关推荐
- C++实践笔记(四)----AVL树的简单实现
关于AVL树的分析,请见:数据结构与算法分析学习笔记(二)--AVL树的算法思路整理 这里给出AVL树的结构定义以及insert,remove和print三种操作的例程: 1 #include & ...
- 数据结构与算法——AVL树类的C++实现
关于AVL树的简单介绍能够參考: 数据结构与算法--AVL树简单介绍 关于二叉搜索树(也称为二叉查找树)能够參考:数据结构与算法--二叉查找树类的C++实现 AVL-tree是一个"加上了额 ...
- AVL树、splay树(伸展树)和红黑树比较
AVL树.splay树(伸展树)和红黑树比较 一.AVL树: 优点:查找.插入和删除,最坏复杂度均为O(logN).实现操作简单 如过是随机插入或者删除,其理论上可以得到O(logN)的复杂度,但是实 ...
- 【从蛋壳到满天飞】JS 数据结构解析和算法实现-AVL树(一)
前言 [从蛋壳到满天飞]JS 数据结构解析和算法实现,全部文章大概的内容如下: Arrays(数组).Stacks(栈).Queues(队列).LinkedList(链表).Recursion(递归思 ...
- 纸上谈兵: AVL树
作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 二叉搜索树的深度与搜索效率 我们在树, 二叉树, 二叉搜索树中提到,一个有n个节点 ...
- AVL树入门(转载)
原文链接:http://lib.csdn.net/article/datastructure/9204 作者:u011469062 前言:本文不适合 给一组数据15分钟就能实现AV ...
- Linux内核之于红黑树and AVL树
为什么Linux早先使用AVL树而后来倾向于红黑树? 实际上这是由红黑树的实用主义特质导致的结果,本短文依然是形而上的观点.红黑树可以直接由2-3树导出,我们可以不再提红黑树,而只提2- ...
- 平衡查找树C语言程序,树4. Root of AVL Tree-平衡查找树AVL树的实现
对于一棵普通的二叉查找树而言,在进行多次的插入或删除后,容易让树失去平衡,导致树的深度不是O(logN),而接近O(N),这样将大大减少对树的查找效率.一种解决办法就是要有一个称为平衡的附加的结构条件 ...
- 从AVL树的定义出发,一步步推导出旋转的方案。
本文从AVL树的定义出发,一步步地推导出AVL树旋转的方案,这个推导是在已经清楚地知道AVL树的定义这个前提下进行的.文章注重思考的过程,并不会直接给出AVL树是怎样旋转的,用来提醒自己以后在学习的时 ...
最新文章
- matlab 画 矩阵点,在MATLAB中绘制矩阵中点之间的线
- 有了这 4 款工具,老板再也不怕我写烂SQL了
- linux复制压缩文件,Linux如何复制,打包,压缩文件
- php取月份函数,分享3个php获取日历的函数
- DNN结构演进History—CNN-GoogLeNet :Going Deeper with Convolutions
- linux curl命令验证服务器断点续传支持
- 【POJ - 2392】Space Elevator (dp,优秀的背包问题)
- iOS阶段学习第31天笔记(UINavigationBar介绍)
- oracle如何创建基表,创建本地基表的物化视图
- Atitit.sql where条件表达式的原理 attilax概括
- 十大OpenGL教程
- 基于cat12和SPM12进行大脑VBM数据分析笔记2——统计分析
- 拖动或点击CMD窗口造成程序阻塞,在bat文件中关闭cmd窗口的快速编辑模式
- 那些高中时曾经背得烂熟的古文(滕王阁序,阿房宫赋, 兰亭集序 , 师说,蜀道难 ...)再一次读读吧,慢慢的读,突然很想哭...有些岁月果真不曾忘怀
- jmeter获取上一个接口的返回值作为下一个接口的传入参数
- Linux后端开发-POSIX标准以及shell编程
- 如何控制Echarts时间轴的刻度区间
- PromptBERT: Improving BERT Sentence Embeddings with Prompts (通篇翻译)
- 软件项目管理系统-采购商品管理-采购订单一览
- 面试手册第五版更新了!(面试必备)
热门文章
- python精彩编程200例-编程语言入门经典100例【Python版】
- python学来干什么-学 Python 都用来干嘛的?
- python做excel自动化-Python控制Excel实现自动化办公
- python自动化办公入门书籍-盘点使用Python进行自动化办公所需要的知识点
- python工程师工资多少-最新 | 2019年Python工程师的平均薪资是多少?
- 零基础学python用哪本书好-零基础学python推荐几本python学习的书籍
- 远程计算机用户端口,电脑怎么开远程端口
- 漏洞payload 靶机_hackme:2 靶机攻略
- element-UI:el-table 表格排序
- 网页特效offset、client、scroll系列属性的作用