平衡二叉树总结二:avl树

来源:互联网 发布:安东尼·霍普金斯 知乎 编辑:程序博客网 时间:2024/05/17 22:33

  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
原创粉丝点击