平衡二叉树总结二:avl树

  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);
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值