数据结构-平衡二叉树
对于一般的二叉搜索树,搜索树结点不同插入次序,将导致不同的深度和平均查找长度ASL。甚至在极端的情况下,二叉搜索树会退化称线性的链表,导致插入和查找的复杂度下降到O(n),所以便提出了平衡二叉树的概念。
基本概念
平衡因子(Balance Factor, BF):BF(T)=hL-hR,其中hL、hR分别是T的左、右子树的高度
平衡二叉树(Balanced Binary Tree, AVL树):一棵二叉树,可以为空;或者任一结点左、右子树高度差的绝对值不超过1,即|BF(T)|<=1
给定结点数为n的AVL树的最大高度为O(logn)
抽象数据类型描述
类型名称:平衡二叉树
数据对象集:一棵二叉树,任一结点左、右子树高度差的绝对值不超过1
操作集:
- Position Rotation(Position A):二叉树位置A的平衡调整
- AVLTree Insert(AVLTree T, ElementType X):在平衡二叉树T中插入元素X
结构体定义
typedef struct AVLNode *Position;
typedef Position AVLTree;
struct AVLNode{
ElementType Data; //结点数据
AVLTree Left; //指向左子树
AVLTree Right; //指向右子树
int Height; //树的高度
};
平衡二叉树的调整
在插入一个新的结点后,那些从插入点到根结点的路径上的结点的平衡可能会被改变,导致平衡二叉树的不平衡。
我们把平衡因子大于1且高度最低(深度最深)的结点叫做不平衡的“发现者”,把插入后导致不平衡的新结点叫做“麻烦结点”。那么,不平衡一共有4种不同的情况:
- 对发现者的左孩子的左子树进行一次插入
- 对发现者的左孩子的右子树进行一次插入
- 对发现者的右孩子的左子树进行一次插入
- 对发现者的右孩子的右子树进行一次插入
其中,情况1和4是关于不平衡发现者的镜像对称,而2和3是关于发现者的镜像对称。前一种情况是插入发生在“外边”的情况(即左-左或右-右),该情况通过对树的一次单旋转完成调整。后一种情况是插入发生在“内部”的情况(即左-右或右-左),该情况通过稍微复杂些的双旋转来处理。
LL旋转
对于情况1,麻烦结点是发现者的左子树的左孩子,需要LL旋转来调整(实际上旋转方向是右旋转)。
int Max(int a, int b){
return a>b?a:b;
}
int GetHeight(Position P){
if(P==NULL)
return -1;
else
return P->Height;
}
Position SingleLeftRotation(Position A){ //A必须要有左子结点B
//将A与B做左单旋,更新A与B的高度,返回新的根结点B
Position B=A->Left;
A->Left=B->Right; //开始旋转
B->Right=A;
A->Height=Max(GetHeight(A->Left),GetHeight(A->Right))+1; //更新高度
B->Height=Max(GetHeight(B->Left),A->Height)+1;
return B;
}
RR旋转
对于情况4,麻烦结点是发现者的右子树的右孩子,需要RR旋转来调整(实际上旋转方向是左旋转)。
Position SingleRightRotation(Position A){ //A必须要有右子结点B
//将A与B做右单旋,更新A与B的高度,返回新的根结点B
Position B=A->Right;
A-Right=B->Left; //开始旋转
B->Left=A;
A->Height=Max(GetHeight(A->Left),GetHeight(A->Right))+1; //更新高度
B->Height=Max(A->Height,GetHeight(B->Right))+1;
return B;
}
LR旋转
对于情况2,麻烦结点是发现者的左子树的右孩子,需要LR旋转来调整(先RR旋转,再LL旋转)
Position DoubleLeftRightRotation(Position A){ //A必须要有左子结点B,B必须要有右子结点C
//将A、B与C做两次单旋,返回新的根结点C
A->Left=SingleRightRotation(A->Left); //将B与C做RR旋转(右单旋),C被返回
return SingleLeftRotation(A); //将A与C做LL旋转(左单旋),C被返回
}
RL旋转
对于情况3,麻烦结点是发现者的右子树的左孩子,需要RL旋转来调整(先LL旋转,再RR旋转)
Position DoubleRightLeftRotation(Position A){ //A必须要有右子结点B,B必须要有左子结点C
//将A、B与C做两次单旋,返回新的根节点C
A->Right=SingleLeftRotation(A->Right); //将B与C做LL旋转(左单旋),C被返回
return SingleRightRotation(A); //将A与C做RR旋转(右单旋),C被返回
}
插入
在平衡二叉树中插入新结点,通过比较新元素与原有结点元素的大小,确定插入位置(通过递归),创建新结点,在调整平衡二叉树(上文4种情况)。
AVLTree Insert(AVLTree T, ElementType X){ //将X插入AVL树T中,并返回调整后的AVL树
if(T==NULL){
T=(AVLTree)malloc(sizeof(struct AVLTree)); //空子树,则创建一个结点的树
T->Data=X;
T->Left=T->Right=NULL;
T->Height=0;
}
else if(X<T->Data){ //如果插入到左子树
T->Left=Insert(T->Left, X); //向下递归
if(GetHeight(T->Left)-GetHeight(T->Right)==2) //如果不平衡
if(X<T->Left->Data) //插入结点是T的左孩子的左结点
T=SingleLeftRotation(T); //左单旋
else //插入结点是T的左孩子的右结点
T=DoubleLeftRightRotation(T); //左-右双旋
}
else if(X>T->Data){ //如果插入到右子树
T->Right=Insert(T->Right, X); //向下递归
if(GetHeight(T->Left)-GetHeight(T->Right)==-2) //如果不平衡
if(X>T->Right->Data) //插入结点是T的右孩子的右结点
T=SingleRightRotation(T); //右-左双旋
else //插入结点是T的右孩子的左结点
T=DoubleRightLeftRotation(T); //右单旋
}
//else X==T->Data,什么也不做
T->Height=Max(GetHeight(T->Left),GetHeight(T->Right))+1; //更新树高
return T;
}