avl树是第一个创造出来的平衡二叉树,之后很多的平衡树都是基于AVL树的基本操作——旋转来实现的。
avl树结构如下:
struct avl{
avl* left;
avl* right;
int val,h;//val值域,h高度。
};
typedef avl* tree;
//获取高度的函数
int height(tree t){
if(t==NULL)return -1;
else{return t->h;}
}
一、旋转操作(所有旋转操作都能保证不破坏有序性)
1.左单旋(LL)。
当左子树高度-右子树高度 >= 2 并且 左子树的左儿子高度 >= 右儿子时,可通过左单旋操作平衡树高度。
linux下面实在没找到好的画图软件,凑或看吧
上图中,旋转前,左子树高度为1,右子树高度为-1(NULL),旋转后,左子树高度为0右子树高度为1,并且旋转后依然是二叉搜索树。操作代码如下:
/*旋转操作不会改变二叉搜索树的有序性,即左<中<右。
它只会调整左右子树的高度差,高的降低,低的提高。*/
//LL旋转(左单旋)
tree LL(tree t){
tree t1 = t->left;
t->left = t1->right;
t1->right = t;
//调整后t,t1的高度都会改变,但是t的子树高度未变,先
//调整它。
t->h = max(height(t->left),height(t->right))+1;
//再调整t1
t1->h = max(height(t1->left),height(t1->right))+1;
return t1;
}
//RR旋转,LL操作的对称。
tree RR(tree t){
tree t1 = t->right;
t->right = t1->left;
t1->left = t;
//调整后t,t1的高度都会改变,但是t的子树高度未变,先
//调整它。
t->h = max(height(t->left),height(t->right))+1;
//再调整t1
t1->h = max(height(t1->left),height(t1->right))+1;
return t1;
}
2.左双旋
当左子树高度-右子树高度 >= 2 并且 左子树的左儿子高度 < 右儿子时,可通过左双旋操作平衡树高度。
如图,先把节点6的左儿子赋值给2节点的右儿子,右儿子赋值给节点8的左儿子,再将其左右儿子分别设为2节点与8节点。这样操作后,树的左右儿子高都变为了1。
说的挺复杂,但是代码却出奇的简单。
//LR旋转,左双旋,虽然我也没明白为啥LR.
tree LR(tree t){
t->left = RR(t->left);
return LL(t);
}
//RL旋转
tree RL(tree t){
t->right = LL(t->right);
return RR(t);
}
3.右单旋,右双旋。
这两个操作是左旋的镜像操作,代码已经在上面给出了。
4.插入
插入操作有些复杂,主要在二叉搜索树的插入操作基础上,需要添加调整高度的代码。
/*比较难一点的插入操作*/
tree insert(tree t,int val){
if(t==NULL){//插入到叶节点上。
t = new avl;
t->val = val;
t->left = t->right = NULL;
t->h = 0;
return t;
}
if(val > t->val){//走右边
t->right = insert(t->right,val);
if(height(t->right)-height(t->left)==2){//判断是否
//违反高度平衡条件。
if(val > t->right->val){//如果走的子树右边
t = RR(t);
}
else{//走的子树左边
t = RL(t);
}
}
}
else{
t->left = insert(t->left,val);
//判断平衡条件
if(height(t->left)-height(t->right)==2){
if(val < t->left->val){
t = LL(t);
}
else{
t = LR(t);
}
}
}
t->h = max(height(t->left),height(t->right))+1;
return t;
}
5.最难的就是删除操作了,数据量不大时还是建议使用lazy 删除。
/*永远都最难的删除。*/
tree del(tree t,int val){
//首先找到需删除的节点。
tree v;
if(t==NULL)return NULL;//为空说明没找到该值。
if(val > t->val){//往右走。
t->right = del(t->right,val);
//删除可能会破坏平衡条件,调整,往右走,右子树可能变低。
if(height(t->left) - height(t->right) == 2){
if(height(t->left->left) >= height(t->left->right)){
t = LL(t);
}
else{ t = LR(t);}
}
}
else if(val < t->val){
t->left = del(t->left,val);
//左子树可能变低。
if(height(t->right) - height(t->left) == 2){
if(height(t->right->right) >= height(t->right->left)){
t = RR(t);
}
else{ t = RL(t);}
}
}
else{
if(t->left == NULL){//只有一个儿子时,可以直接把另一个儿子接上来。
v = t->right;delete t;
return v;
}
else if(t->right == NULL){
v = t->left;delete t;
return v;
}
else{//双儿子时,查找左子树最大值。
v = findMax(t->left);
if(v == t->left){//刚好是左儿子,直接把左儿子右子树接上来。
t->left = v->left;
t->val = v->val;
delete v;//调整高度。
t->h = max(height(t->left),height(t->right))+1;
}
else{//否则删除把最大值赋值给t,递归删除最大值,因为此时
//最大值必定没有右儿子。
t->val = v->val;
del(t->left,v->val);
}
}
}
return t;
}
//avl树的删除编程起来确实异常复杂,所以实际上大家也很少用这种树,工程里更多是红黑树
//以及splay,比赛更多是编码简单的treap,sbt等。
6.最后汇总一下,加了一些测试代码。
#include<iostream>
#include<queue>
using namespace std;
struct avl{
avl* left;
avl* right;
int val,h;//val值域,h高度。
};
typedef avl* tree;
int height(tree t){
if(t==NULL)return -1;
else{return t->h;}
}
/*旋转操作不会改变二叉搜索树的有序性,即左<中<右。
它只会调整左右子树的高度差,高的降低,低的提高。*/
//LL旋转(左单旋)
tree LL(tree t){
tree t1 = t->left;
t->left = t1->right;
t1->right = t;
//调整后t,t1的高度都会改变,但是t的子树高度未变,先
//调整它。
t->h = max(height(t->left),height(t->right))+1;
//再调整t1
t1->h = max(height(t1->left),height(t1->right))+1;
return t1;
}
//RR旋转,LL操作的对称。
tree RR(tree t){
tree t1 = t->right;
t->right = t1->left;
t1->left = t;
//调整后t,t1的高度都会改变,但是t的子树高度未变,先
//调整它。
t->h = max(height(t->left),height(t->right))+1;
//再调整t1
t1->h = max(height(t1->left),height(t1->right))+1;
return t1;
}
//LR旋转,左双旋,虽然我也没明白为啥LR.
tree LR(tree t){
t->left = RR(t->left);
return LL(t);
}
//RL旋转
tree RL(tree t){
t->right = LL(t->right);
return RR(t);
}
/*比较难一点的插入操作*/
tree insert(tree t,int val){
if(t==NULL){//插入到叶节点上。
t = new avl;
t->val = val;
t->left = t->right = NULL;
t->h = 0;
return t;
}
if(val > t->val){//走右边
t->right = insert(t->right,val);
if(height(t->right)-height(t->left)==2){//判断是否
//违反高度平衡条件。
if(val > t->right->val){//如果走的子树右边
t = RR(t);
}
else{//走的子树左边
t = RL(t);
}
}
}
else{
t->left = insert(t->left,val);
//判断平衡条件
if(height(t->left)-height(t->right)==2){
if(val < t->left->val){
t = LL(t);
}
else{
t = LR(t);
}
}
}
t->h = max(height(t->left),height(t->right))+1;
return t;
}
/*寻找最大值*/
tree findMax(tree t){
while(t->right)t=t->right;
return t;
}
/*永远都最难的删除。*/
tree del(tree t,int val){
//首先找到需删除的节点。
tree v;
if(t==NULL)return NULL;//为空说明没找到该值。
if(val > t->val){//往右走。
t->right = del(t->right,val);
//删除可能会破坏平衡条件,调整,往右走,右子树可能变低。
if(height(t->left) - height(t->right) == 2){
if(height(t->left->left) >= height(t->left->right)){
t = LL(t);
}
else{ t = LR(t);}
}
}
else if(val < t->val){
t->left = del(t->left,val);
//左子树可能变低。
if(height(t->right) - height(t->left) == 2){
if(height(t->right->right) >= height(t->right->left)){
t = RR(t);
}
else{ t = RL(t);}
}
}
else{
if(t->left == NULL){//只有一个儿子时,可以直接把另一个儿子接上来。
v = t->right;delete t;
return v;
}
else if(t->right == NULL){
v = t->left;delete t;
return v;
}
else{//双儿子时,查找左子树最大值。
v = findMax(t->left);
if(v == t->left){//刚好是左儿子,直接把左儿子右子树接上来。
t->left = v->left;
t->val = v->val;
delete v;//调整高度。
t->h = max(height(t->left),height(t->right))+1;
}
else{//否则删除把最大值赋值给t,递归删除最大值,因为此时
//最大值必定没有右儿子。
t->val = v->val;
del(t->left,v->val);
}
}
}
return t;
}
void travel(tree t){
if(!t)return;
if(t->left)travel(t->left);
cout << t->val << " ";
if(t->right)travel(t->right);
}
void level(tree t){
if(!t)return;
tree now,last=t;
queue<tree> qu;
qu.push(t);
while(qu.size()){
now = qu.front();qu.pop();
if(now->left)qu.push(now->left);
if(now->right)qu.push(now->right);
cout << now->val << "(" << now->h << ")" << " ";
if(now == last && qu.size()){last = qu.back();cout << endl;}
}
cout << endl;
}
int main(){
int a[10] = {1,8,3,0,9,5,6,2,4,7};
tree t = NULL;
int i;
for(i=0;i<10;i++){
t = insert(t,a[i]);
}
travel(t);cout << endl;
level(t);
t = del(t,8);
travel(t);cout << endl;
level(t);
}
二、优化版本的avl树。
这个思路是网上看到的,其实我也一直再想因为左旋右旋操作是对称的,能不能写成一个函数。偶然还真看到了这种实现,记录下来。
主要思路是使用一个布尔值来表示左右子树,0代表左,1代表右。
#include<iostream>
#include<queue>
using namespace std;
struct avl{
avl* child[2];
int val,h;//val值域,h高度。
avl(int v,int hei):val(v),h(hei){child[0] = child[1] = NULL;}
};
typedef avl* tree;
int height(tree t){
if(t==NULL)return -1;
else{return t->h;}
}
/*旋转操作不会改变二叉搜索树的有序性,即左<中<右。
它只会调整左右子树的高度差,高的降低,低的提高。*/
//单旋
tree SR(tree t,bool c){
tree t1 = t->child[c];
t->child[c] = t1->child[!c];
t1->child[!c] = t;
//调整后t,t1的高度都会改变,但是t的子树高度未变,先
//调整它。
t->h = max(height(t->child[c]),height(t->child[!c]))+1;
//再调整t1
t1->h = max(height(t1->child[c]),height(t1->child[!c]))+1;
return t1;
}
//双旋
tree DR(tree t,bool c){
t->child[c] = SR(t->child[c],!c);
return SR(t,c);
}
/*比较难一点的插入操作*/
tree insert(tree t,int val){
if(t==NULL){//插入到叶节点上。
t = new avl(val,0);
return t;
}
bool c=0;
if(val>t->val)c=1;
t->child[c] = insert(t->child[c],val);
if(height(t->child[c])-height(t->child[!c])==2){//判断是否
//违反高度平衡条件。
if( (val < t->child[c]->val) ^ c){t = SR(t,c);}
else{t = DR(t,c);}
}
t->h = max(height(t->child[0]),height(t->child[1]))+1;
return t;
}
/*寻找最大值*/
tree findMax(tree t){
while(t->child[1])t=t->child[1];
return t;
}
/*永远都最难的删除。*/
tree del(tree t,int val){
//首先找到需删除的节点。
tree v;
if(t==NULL)return NULL;//为空说明没找到该值。
bool c=0;
if(val != t->val){
if(val > t->val)c = 1;
t->child[c] = del(t->child[c],val);
if(height(t->child[!c]) - height(t->child[c]) == 2){
if(height(t->child[!c]->child[!c]) >= height(t->child[!c]->child[c])){t = SR(t,c);}
else{ t = DR(t,c);}
}
}
else{
if(t->child[0] && t->child[1]){//双儿子时,查找左子树最大值。
v = findMax(t->child[0]);
if(v == t->child[0]){//刚好是左儿子,直接把左儿子右子树接上来。
t->child[0] = v->child[1];
t->val = v->val;
delete v;//调整高度。
t->h = max(height(t->child[0]),height(t->child[1]))+1;
}
else{//否则删除把最大值赋值给t,递归删除最大值,因为此时
//最大值必定没有右儿子。
t->val = v->val;
del(t->child[0],v->val);
}
}
else{
if(t->child[1])c=1;
v = t->child[c];
delete t;
return v;
}
}
return t;
}
void travel(tree t){
if(!t)return;
if(t->child[0])travel(t->child[0]);
cout << t->val << " ";
if(t->child[1])travel(t->child[1]);
}
void level(tree t){
if(!t)return;
tree now,last=t;
queue<tree> qu;
qu.push(t);
while(qu.size()){
now = qu.front();qu.pop();
if(now->child[0])qu.push(now->child[0]);
if(now->child[1])qu.push(now->child[1]);
cout << now->val << "(" << now->h << ")" << " ";
if(now == last && qu.size()){last = qu.back();cout << endl;}
}
cout << endl;
}
int main(){
int a[10] = {1,8,3,0,9,5,6,2,4,7};
tree t = NULL;
int i;
for(i=0;i<10;i++){
level(t);
t = insert(t,a[i]);
}
travel(t);cout << endl;
level(t);
t = del(t,8);
travel(t);cout << endl;
level(t);
}