数据结构 - 平衡二叉树 AVL

来源:互联网 发布:js获取当前时间 编辑:程序博客网 时间:2024/05/23 19:20

部分内容源自:http://blog.csdn.net/xiewenbo/article/details/8011825 和 http://blog.csdn.net/ncepustrong/article/details/8446581

总结的不错,综合一下,学习之用。

一、 平衡二叉树

在二叉查找树T中,若所有结点的平衡因子的绝对值均不超过1,则称T为一棵AVL树。

平衡二叉树又称AVL树,它是具有如下性质的二叉树:

• 左、右子树是平衡二叉树;

• 所有结点的左、右子树深度之差的绝对值≤ 1

为了方便起见,给每个结点附加一个数字,给出该结点左子树与右子树的高度差。这个数字称为结点的平衡因子balance。这样,可以得到AVL树的其它性质:

• 任一结点的平衡因子只能取:-1、0或 1;如果树中任意一个结点的平衡因子的绝对值大于1,则这棵二叉树就失去平衡,不再是AVL树;

对于一棵有n个结点的AVL树,其高度保持在O(log2n)数量级,ASL也保持在O(log2n)量级

二、  平衡调整----旋转

如果在一棵AVL树中插入一个新结点,就有可能造成失衡,此时必须重新调整树的结构,使之恢复平衡。我们称调整平衡过程为平衡旋转。无论是右旋还是左旋,都不改变二叉树的中序遍历结果。

1. 右旋(顺时针方向旋转)

结点v 是结点p 的左孩子,X和Y分别是v 的左、右子树,Z为p的右子树。所谓围绕结点p 的右旋操作,就是重新调整这些结点位置,将p作为v 的右孩子,将X 作为v 的左子树,将Y和Z分别作为p的左、右子树。围绕p右旋的结果。

                           

2. 左旋(逆时针方向旋转)

结点v 是结点p 的右孩子,Y 和Z 分别是v的左、右子树,X 为p 的左子树。所谓围绕结点p 的左旋操作,就是将p 作为v 的左孩子,将Z 作为v 的右子树,将X 和Y 分别作为p 的左、右子树。围绕p左旋的结果如图所示。

三、   插入操作

一般的,若在插入新的结点x 之后AVL 树T失去平衡,则失去平衡的结点只可能是x的祖先,且层次数小于等于x 的祖父的结点;也就是说失去平衡的结点是从x 的祖父到根的路径上的结点,但这些结点并不都是失去平衡的结点,有些结点仍然可能是平衡的。为了修正失衡的现象,可以从结点x 出发逆行向上,依次检查x 祖先的平衡因子,找到第一个失衡的祖先结点g。在从x 到g 的路径上,假设p 为g 的孩子结点,v 为p 的孩子结点。有结点p、v 必不为空,p 至少是x 的父亲,v 至少是x 本身。结点g、p、v 之间的父子关系共有4 种不同的组合,以下根据这4 种不同的组合,通过对结点g、p的旋转,使这一局部恢复平衡。


平衡旋转可以归纳为四类:

  1.   LL平衡旋转当树中节点X的左孩子的左孩子上插入新元素,且平衡因子从1变成2后,就需要绕节点X进行右旋转。
  2.   RR平衡旋转当树中节点X的右孩子的右孩子上插入新元素,且平衡因子从-1变成-2后,就需要绕节点X进行左旋转。
  3.   LR平衡旋转当树中节点X的左孩子的右孩子上插入新元素,且 平衡因子从1变成2后,就需要 先绕X的左子节点Y左旋转,接着再绕X右旋转
  4.   RL平衡旋转当树中节点X的右孩子的左孩子上插入新元素,且 平衡因子从-1变成-2后,就需要 先绕X的右子节点Y右旋转,接着再绕X左旋转

1)LL平衡旋转: p 是g的左孩子,且v是p的左孩子;

在这种情况下,必定是由于在v 的后代中插入了新结点x 而使得g不再平衡。针对这种情况,可以简单的通过结点g 的单向右旋,即可使得以g为根的子树得到平衡

 

2)RR平衡旋转: p是g的右孩子,且v 是p的右孩子;

与第一种情况对称,此时,失衡是由于在g 的右孩子的右子树中插入结点x造成的。在这种情况下需要通过结点g 的单向左旋来完成局部的平衡。

3)LR平衡旋转: p是g的左孩子,且v是p的右孩子;

如果是在g的左孩子的右子树中插入一个结点,则对应为第三种情况。在这种情况中,要使得局部的失衡重新平衡,那么需要先后进行先左后右的双向旋转:第一次是结点p的左旋,第二次是结点g 的右旋。

4)RL平衡旋转:

p是g的右孩子,且v是p的左孩子;第四种情况与第三种情况对称,其平衡调整过程也与第三种情况对称,可以通过先右后左的双向旋转来完成,其中第一次是结点p 的右旋,第二次是结点g的左旋。

四、  删除节点

按照通常的二叉查找树删除结点的方法在AVL树中删除结点x之后,如果发生失衡现象,为了重新平衡,同样从x出发逆行向上找到第一个失衡的结点g,通过旋转操作实现AVL树的重新平衡。使得结点g失衡的原因可以看成四种:

1. 失衡是由于 g 的左孩子的左子树过高造成的;

2. 失衡是由于 g 的右孩子的右子树过高造成的;

3. 失衡是由于 g 的左孩子的右子树过高造成的;

4. 失衡是由于 g 的右孩子的左子树过高造成的。

这四种情况与插入结点导致失衡的四种情况进行平衡化的旋转方法相同。但是需要注意两个特殊情况:失衡可能是由于左(右)孩子的左、右子树同时过高造成的。


c实现

代码源自:http://blog.csdn.net/q270274978/article/details/8484952


#include <stdio.h>#include<stdlib.h>#define  Ok 1#define  Error 0#define max 20#define Status int #define LH 1#define EH 0#define RH -1typedef struct Btree{Btree *lchild;Btree *rchild;int   data;int bf;//用来记录该节点所处的状态0,1或-1}Btree,*Bitree;//平衡二叉树节点的结构体Status inital(Bitree &b)//初始化树{b=(Btree*)malloc(sizeof(Btree));//不先初始化给树一个地址的话,在主函数里b是传递不进去的,会报错b=NULL;return 1;}bool  EQ(int i,int j){   if(i==j)   return 1;   else    return 0;}bool LT(int i,int j){if(i<j)return 1;else return 0;}void R_Rotate(Bitree &p)//单项右旋处理{Bitree lc;lc=p->lchild; //需要将p与他的左孩子节点旋转p->lchild=lc->rchild;//将左孩子的右节点给根节点做左孩子节点lc->rchild=p;//让根节点做它以前左孩子结点的右孩子节点p=lc;//转换根节点}void L_Rotate(Bitree &p)//单项左旋处理{Bitree lc;lc=p->rchild; //道理同上p->rchild=lc->lchild;lc->lchild=p;p=lc;}void leftBalance(Bitree &t)//当以t为根节点已经左子树比右子树多1个高度的情况下再向左子树叶子节点插入一个节点{Bitree lc,rd;lc=t->lchild;switch(lc->bf){//查看插入后t的左孩子的状态case LH://如果插入后左子树比右子树高t->bf=lc->bf=EH;//重置t和他左子树的状态R_Rotate(t);//进行单向右旋处理break;case RH://如果插入后右子树比左子树高,须进行双旋处理rd=lc->rchild;switch(rd->bf)//这一步得根据分析的图来进行旋转后的赋值{   case LH://根节点左孩子的右孩子为1,则平衡后,根节点为-1,根节点左孩子为0;   t->bf=RH; lc->bf=EH;break;   case RH://根节点左孩子的右孩子为-1,则平衡后,根节点为0,根节点左孩子为1;   t->bf=EH; lc->bf=LH;   break;   case EH://根节点左孩子的右孩子为0,则平衡后,根节点为0,根节点左孩子为0;   t->bf=lc->bf=EH;   break;}break;   rd->bf=EH;//平衡后新的根节点为0;   L_Rotate(t->lchild);//先对t的左孩子节点做单向做平衡处理   R_Rotate(t);//再对t做单向右平衡处理}}void RightBalance(Bitree &t)//当以t为根节点已经右子树比左子树多1个高度的情况下再向右子树叶子节点插入一个节点{Bitree rc,ld;rc=t->rchild;switch(rc->bf){case RH:rc->bf=t->bf=EH;L_Rotate(t);break;               //同上case LH:ld=rc->lchild;switch(ld->bf){case RH:  t->bf=LH; rc->bf=EH;break;case LH:   t->bf=EH; rc->bf=RH;break;case EH:   t->bf=rc->bf=EH;break;}ld->bf=EH;R_Rotate(t->rchild);L_Rotate(t);break;}}Status InsertAVL(Bitree &t,int e,bool &taller)//插入平衡二叉树的节点函数{if(!t){                              //插入节点成功  t=(Bitree)malloc(sizeof(Btree));  t->data=e;  t->lchild=t->rchild=NULL;   t->bf=EH;   taller=true;}else {if(EQ(e,t->data))   //如果插入的节点在树中已经是存在的了,那么不用查了,{taller=false; return 0;}if(LT(e,t->data))//如果插入的节点是在左子树中{if(!InsertAVL(t->lchild,e,taller))  return 0;//一直递归到要插入的叶子节点的位置if(taller)//如果成功插入一个叶子节点,需要看它是否符合平衡二叉树要求,再修改{switch(t->bf){case LH://如果它的父节点在插入前就是左子树比右子树大的话leftBalance(t);  taller=false;  //当以t为根节点已经左子树比右子树多1个高度的情况下再向左子树叶子节点插入一个节点break;case EH://如果它的父节点在插入前就是左子树与右子树高度相等时t->bf=LH;  taller=true; //记录父节点左子树比右子树高1break;case RH://如果它的父节点在插入前就是左子树比右子树小1的话t->bf=EH;  taller=false;//此时父节点左右子树相等break;}}}    else {  //当插入的节点在右子树的话。。。。。道理同上if(!InsertAVL(t->rchild,e,taller))  return 0;if(taller){switch(t->bf){case LH:t->bf=EH;  taller=false;break;case EH:t->bf=RH; taller =true;break;case RH:RightBalance(t);  taller=false;break;}}}}return 1;}Status Showtree(Bitree t){if(t){if(Showtree(t->lchild)){printf("%d  ",t->data);if(Showtree(t->rchild))return Ok;//printf("\n");}}else return Ok;}void main(){Bitree t;int e;  bool taller;/*printf("请输入根节点");creattree(t);*/    inital(t); printf("请输入要插入的节点(输入0结束)");scanf("%d",&e);while(e!=0){ InsertAVL(t,e,taller); scanf("%d",&e);}printf("平衡二叉树的中序序列为:\n");Showtree(t);getchar();getchar();}//算法分析:1.平衡二叉树的要求:(1)它的左右子树都是平衡二叉树(2)它的左右子树的深度只差的绝对值不超过1          //2.在平衡二叉树上进行查找过程和排序树相同,进行比较的关键字个数不超过树的深度          //3.进行查找的时间复杂度为O(logn)


另外,链接:http://blog.csdn.net/hnust_xiehonghao/article/details/7938869

该blog添加了删除操作,值得学习。mark一下。





原创粉丝点击