转自:http://www.cnblogs.com/skywang12345/p/3576969.html
目录
一、AVL树的概念
AVL树即平衡二叉查找树,顾名思义它既符合二叉查找树的定义,也符合平衡二叉树的定义,AVL树中任何节点的两个子树的高度最大差别为1,且左子树的节点比根节点小,右子树的节点比根节点大。
AVL树的查找、插入和删除在平均和最坏情况下都是O(logn)。
在高度为h的AVL树种,最少节点数S(h)=S(h-1)+S(h-2)+1 (h=0,S(h)=1;h=1,S(h)=2)
二、AVL树的实现
1、节点定义
typedef struct Node
{
int val;
struct Node *left, *right;
}Node,*ANode;
2、树的高度
int getHeight(ANode root)
{
if(root==NULL)return 0;
return max(getHeight(root->left),getHeight(root->right))+1;
}
3、旋转
前面说过,如果在AVL树中进行插入或删除节点后,可能导致AVL树失去平衡。这种失去平衡的可以概括为4种姿态:LL(左左),LR(左右),RR(右右)和RL(右左)。下面给出它们的示意图:
上图中的4棵树都是"失去平衡的AVL树",从左往右的情况依次是:LL、LR、RL、RR。除了上面的情况之外,还有其它的失去平衡的AVL树,如下图:
上面的两张图都是为了便于理解,而列举的关于"失去平衡的AVL树"的例子。总的来说,AVL树失去平衡时的情况一定是LL、LR、RL、RR这4种之一,它们都由各自的定义:
3.1、LL的旋转
(1) LL:LeftLeft,也称为"左左"。插入或删除一个节点后,根节点的左子树的左子树还有非空子节点,导致"根的左子树的高度"比"根的右子树的高度"大2,导致AVL树失去了平衡。
例如,在上面LL情况中,由于"根节点(8)的左子树(4)的左子树(2)还有非空子节点",而"根节点(8)的右子树(12)没有子节点";导致"根节点(8)的左子树(4)高度"比"根节点(8)的右子树(12)"高2。
LL失去平衡的情况,可以通过一次旋转让AVL树恢复平衡。如下图:
图中左边是旋转之前的树,右边是旋转之后的树。从中可以发现,旋转之后的树又变成了AVL树,而且该旋转只需要一次即可完成。
对于LL旋转,你可以这样理解为:LL旋转是围绕"失去平衡的AVL根节点"进行的,也就是节点k2;而且由于是LL情况,即左左情况,就用手抓着"左孩子,即k1"使劲摇。将k1变成根节点,k2变成k1的右子树,"k1的右子树"变成"k2的左子树"。
ANode llrotation(ANode root)
{
ANode k1;
k1=root->left;
root->left=k1->right;
k1->right=root;
return k1;
}
3.2、RR的旋转
(4) RR:RightRight,称为"右右"。插入或删除一个节点后,根节点的右子树的右子树还有非空子节点,导致"根的右子树的高度"比"根的左子树的高度"大2,导致AVL树失去了平衡。
例如,在上面RR情况中,由于"根节点(8)的右子树(12)的右子树(14)还有非空子节点",而"根节点(8)的左子树(4)没有子节点";导致"根节点(8)的右子树(12)高度"比"根节点(8)的左子树(4)"高2。
理解了LL之后,RR就相当容易理解了。RR是与LL对称的情况!RR恢复平衡的旋转方法如下:
图中左边是旋转之前的树,右边是旋转之后的树。RR旋转也只需要一次即可完成。
ANode rrrotation(ANode root)
{
ANode k2;
k2=root->right;
root->right=k2->left;
k2->left=root;
return k2;
}
3.3、LR的旋转
(2) LR:LeftRight,也称为"左右"。插入或删除一个节点后,根节点的左子树的右子树还有非空子节点,导致"根的左子树的高度"比"根的右子树的高度"大2,导致AVL树失去了平衡。
例如,在上面LR情况中,由于"根节点(8)的左子树(4)的左子树(6)还有非空子节点",而"根节点(8)的右子树(12)没有子节点";导致"根节点(8)的左子树(4)高度"比"根节点(8)的右子树(12)"高2。
LR失去平衡的情况,需要经过两次旋转才能让AVL树恢复平衡。如下图:
第一次旋转是围绕"k1"进行的"RR旋转",第二次是围绕"k3"进行的"LL旋转"。
ANode lrrotation(ANode root)
{
root->left=rrrotation(root->left);
return llrotation(root);
}
3.4、RL的旋转
(3) RL:RightLeft,称为"右左"。插入或删除一个节点后,根节点的右子树的左子树还有非空子节点,导致"根的右子树的高度"比"根的左子树的高度"大2,导致AVL树失去了平衡。
例如,在上面RL情况中,由于"根节点(8)的右子树(12)的左子树(10)还有非空子节点",而"根节点(8)的左子树(4)没有子节点";导致"根节点(8)的右子树(12)高度"比"根节点(8)的左子树(4)"高2。
前面说过,如果在AVL树中进行插入或删除节点后,可能导致AVL树失去平衡。AVL失去平衡之后,可以通过旋转使其恢复平衡,下面分别介绍"LL(左左),LR(左右),RR(右右)和RL(右左)"这4种情况对应的旋转方法。
RL是与LR的对称情况!RL恢复平衡的旋转方法如下:
第一次旋转是围绕"k3"进行的"LL旋转",第二次是围绕"k1"进行的"RR旋转"。
ANode rlrotation(ANode root)
{
root->right=llrotation(root->right);
return rrrotation(root);
}
4、插入节点
ANode insertnode(ANode root,int val)
{
if(root==NULL)
{
root=new Node();
root->val=val;
root->left=root->right=NULL;
}
else if(val<root->val)
{
root->left=insertnode(root->left,val);
if(getHeight(root->left)-getHeight(root->right)==2)
root=(val<root->left->val)?llrotation(root):lrrotation(root);
}
else if(val>root->val)
{
root->right=insertnode(root->right,val);
if(getHeight(root->right)-getHeight(root->left)==2)
root=(val>root->right->val)?rrrotation(root):rlrotation(root);
}
return root;
}
5、查找最大最小值
ANode findMin(ANode root)//查找最小值所在的结点
{
if(root==NULL)
return NULL;
else if(root->left==NULL)
return root;
return findMin(root->left);
}
ANode findMax(ANode root)//查找最大值所在的结点
{
if(root!=NULL)
{
while(root->right!=NULL)
root=root->right;
}
return root;
}
6、删除节点
类似二叉查找树,但要考虑删除节点后,AVL树是去平衡,应该进行相应的调节
(1)需要删除的是叶节点(没有子节点的节点),直接把节点删除即可。
(2)需要删除的是链节点(只有一个子节点的节点),为了删除这个节点而不影响它的子树,需要用它的子节点代替它的位置,然后把它删除。
若左子树比右子树高,应该选左子树的最大节点代替它的位置,这样可以不破坏高度;若右子树比左子树高,应该选右子树的最小节点代替它的位置。
(3)需要删除的节点由两个非空子节点。由于情况比较复杂,一般用它右子树的最小值来代替它,然后把它删除。
ANode deletenode(ANode root,int val)
{
if(root==NULL)//根为空
return root;
if(val<root->val)//待删除的节点在左子树
{
root->left=deletenode(root->left);
//删除节点后,若AVL树失去平衡,则进行相应的调节
if(getHeight(root->right)-getHeight(root->left)==2)
{
ANode r=root->right;
//删除左子树的节点后,右子树的左子树若比右子树高,则进行RL旋转,反之进行RR旋转
root=(getHeight(r->left)>getHeight(r->right))?rlrotation(root):rrrotation(root);
}
}
else if(val>root->val)//待删除的节点在右子树
{
root->right=deletenode(root->right);
//删除节点后,若AVL树失去平衡,则进行相应的调节
if(getHeight(root->left)-getHeight(root->right)==2)
{
ANode r=root->left;
//删除右子树的节点后,左子树的左子树若比右子树高,则进行LL旋转,反之进行LR旋转
root=(getHeight(r->left)>getHeight(r->right))?llrotation(root):lrrotation(root);
}
}
else//root是对应删除的节点
{
//root的左右孩子都非空
if((root->left) && (root->right))
{
if(getHeight(root->left)>getHeight(root->right))
{
// 如果tree的左子树比右子树高;
// 则(01)找出tree的左子树中的最大节点
// (02)将该最大节点的值赋值给tree。
// (03)删除该最大节点。
// 这类似于用"tree的左子树中最大节点"做"tree"的替身;
// 采用这种方式的好处是:删除"tree的左子树中最大节点"之后,AVL树仍然是平衡的。
ANode Max=findMax(root->left);
root->val=Max->val;
root->left=deletenode(root->left,Max);
}
else
{
// 如果tree的左子树不比右子树高(即它们相等,或右子树比左子树高1)
// 则(01)找出tree的右子树中的最小节点
// (02)将该最小节点的值赋值给tree。
// (03)删除该最小节点。
// 这类似于用"tree的右子树中最小节点"做"tree"的替身;
// 采用这种方式的好处是:删除"tree的右子树中最小节点"之后,AVL树仍然是平衡的。
ANode Min=findMin(root->right);
root->val=Min->val;
root->right=deletenode(root->right,Min);
}
}
else//如果其中有一个孩子为空,只需直接释放root节点
{
ANode temp=root;
root=(root->left)?root->left:root->right;
delete(temp);
}
}
return root;
}
三、测试代码
#include<bits/stdc++.h>
using namespace std;
typedef struct Node
{
int val;
struct Node *left, *right;
}Node,*ANode;
int getHeight(ANode root)
{
if(root==NULL)return 0;
return max(getHeight(root->left),getHeight(root->right))+1;
}
ANode llrotation(ANode root)
{
ANode k1;
k1=root->left;
root->left=k1->right;
k1->right=root;
return k1;
}
ANode rrrotation(ANode root)
{
ANode k2;
k2=root->right;
root->right=k2->left;
k2->left=root;
return k2;
}
ANode lrrotation(ANode root)
{
root->left=rrrotation(root->left);
return llrotation(root);
}
ANode rlrotation(ANode root)
{
root->right=llrotation(root->right);
return rrrotation(root);
}
ANode findMin(ANode root)//查找最小值所在的结点
{
if(root==NULL)
return NULL;
else if(root->left==NULL)
return root;
return findMin(root->left);
}
ANode findMax(ANode root)//查找最大值所在的结点
{
if(root!=NULL)
{
while(root->right!=NULL)
root=root->right;
}
return root;
}
ANode insertnode(ANode root,int val)
{
if(root==NULL)
{
root=new Node();
root->val=val;
root->left=root->right=NULL;
}
else if(val<root->val)
{
root->left=insertnode(root->left,val);
if(getHeight(root->left)-getHeight(root->right)==2)
root=(val<root->left->val)?llrotation(root):lrrotation(root);
}
else if(val>root->val)
{
root->right=insertnode(root->right,val);
if(getHeight(root->right)-getHeight(root->left)==2)
root=(val>root->right->val)?rrrotation(root):rlrotation(root);
}
return root;
}
void preorder(ANode root)
{
if(root==NULL)return;
printf("%d ",root->val);
preorder(root->left);
preorder(root->right);
}
ANode deletenode(ANode root,int val)
{
if(root==NULL)//根为空
return root;
if(val<root->val)//待删除的节点在左子树
{
root->left=deletenode(root->left,val);
//删除节点后,若AVL树失去平衡,则进行相应的调节
if(getHeight(root->right)-getHeight(root->left)==2)
{
ANode r=root->right;
//删除左子树的节点后,右子树的左子树若比右子树高,则进行RL旋转,反之进行RR旋转
root=(getHeight(r->left)>getHeight(r->right))?rlrotation(root):rrrotation(root);
}
}
else if(val>root->val)//待删除的节点在右子树
{
root->right=deletenode(root->right,val);
//删除节点后,若AVL树失去平衡,则进行相应的调节
if(getHeight(root->left)-getHeight(root->right)==2)
{
ANode r=root->left;
//删除右子树的节点后,左子树的左子树若比右子树高,则进行LL旋转,反之进行LR旋转
root=(getHeight(r->left)>getHeight(r->right))?llrotation(root):lrrotation(root);
}
}
else//root是对应删除的节点
{
//root的左右孩子都非空
if((root->left) && (root->right))
{
if(getHeight(root->left)>getHeight(root->right))
{
// 如果tree的左子树比右子树高;
// 则(01)找出tree的左子树中的最大节点
// (02)将该最大节点的值赋值给tree。
// (03)删除该最大节点。
// 这类似于用"tree的左子树中最大节点"做"tree"的替身;
// 采用这种方式的好处是:删除"tree的左子树中最大节点"之后,AVL树仍然是平衡的。
ANode Max=findMax(root->left);
root->val=Max->val;
root->left=deletenode(root->left,Max->val);
}
else
{
// 如果tree的左子树不比右子树高(即它们相等,或右子树比左子树高1)
// 则(01)找出tree的右子树中的最小节点
// (02)将该最小节点的值赋值给tree。
// (03)删除该最小节点。
// 这类似于用"tree的右子树中最小节点"做"tree"的替身;
// 采用这种方式的好处是:删除"tree的右子树中最小节点"之后,AVL树仍然是平衡的。
ANode Min=findMin(root->right);
root->val=Min->val;
root->right=deletenode(root->right,Min->val);
}
}
else//如果其中有一个孩子为空,只需直接释放root节点
{
ANode temp=root;
root=(root->left)?root->left:root->right;
delete(temp);
}
}
return root;
}
int main()
{
int arr[]={3,2,1,4,5,6,7,16,15,14,13,12,11,10,8,9};
ANode root=NULL;
//添加节点
for(int i=0;i<16;i++)
root=insertnode(root,arr[i]);
printf("先序遍历:");
preorder(root);
printf("\n");
//删除节点
root=deletenode(root,8);
printf("删除节点后先序遍历:");
preorder(root);
printf("\n");
return 0;
}