最近阅读java的TreeMap源码,发觉是使用红黑树实现内部的数据管理,所以开始学习编写红黑树。然后发现要想理解红黑树还是要从基础的二叉平衡树的开始。于是,最终决定拿AVL树开刀。
网络上有很多AVL树的各种语言实现版本,个人偏爱c的绝对控制性编程,所以选择了c实现。令人庆幸的是,一个非常简易的AVL树c实现被我找到。现在就在其基础上加上个人理解把AVL树实现描述如下:
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
int height;
struct Node *lchild,*rchild;
} Node;
int Max(int a,int b)
{
return a>b?a:b;
}
int height(Node* pNode)
{
if(pNode == NULL)
{
return -1;
}
return pNode->height;
}
Node* single_rotate_with_left(Node* pNode)
{
Node* tmp;
tmp = pNode->lchild;
pNode->lchild = tmp->rchild;
tmp->rchild = pNode;
//结点的位置改变,需更新结点的高度
//结点的高度根据孩子的计算得到:
//pNode的左右孩子分别是pNode->lchild,pNode->rchild
//tmp的孩子:tmp->lchild,pNode
pNode->height = Max(height(pNode->lchild),height(pNode->rchild)) + 1;
tmp->height = Max(height(tmp->lchild),pNode->height) + 1;
return tmp;
}
Node* single_rotate_with_right(Node* pNode)
{
Node* tmp;
tmp = pNode->rchild;
pNode->rchild = tmp->lchild;
tmp->lchild = pNode;
//原理同上
pNode->height = Max(height(pNode->lchild),height(pNode->rchild)) + 1;
tmp->height = Max(height(tmp->rchild),pNode->height) + 1;
return tmp;
}
Node* double_l(Node* pNode)
{
pNode->lchild = single_rotate_with_right(pNode->lchild);
return single_rotate_with_left(pNode);
}
Node* double_r(Node* pNode)
{
pNode->rchild = single_rotate_with_left(pNode->rchild);
return single_rotate_with_right(pNode);
}
Node* insert(int d,Node* pNode)
{
if(pNode == NULL)
{//如果删除判断平衡因子的语句和更新树高度的语句,则此insert完全是一个BST的插入操作
//所以从此函数,可以看出AVL树脱胎自BST
pNode = (Node*)malloc(sizeof(Node));
pNode->data = d;
pNode->height = 0;
pNode->lchild = pNode->rchild = NULL;
} else if (d < pNode->data) { //插入左子树
pNode->lchild = insert(d,pNode->lchild);
if(height(pNode->lchild) - height(pNode->rchild) == 2) //not balence
{
if(d < pNode->lchild->data)
{
//插入到左子树,做单旋转
pNode = single_rotate_with_left(pNode);
} else {
pNode = double_l(pNode);
}
}
} else if (d > pNode->data) {
pNode->rchild = insert(d,pNode->rchild);
if(height(pNode->rchild) - height(pNode->lchild) == 2) {
if(d > pNode->rchild->data)
{
pNode = single_rotate_with_right(pNode);
} else {
pNode = double_r(pNode);
}
}
}
pNode->height = Max(height(pNode->lchild),height(pNode->rchild)) + 1;
return pNode;
}
void print(Node* root)
{//中根遍历二叉树
if(root == NULL)
{
return ;
}
print(root->lchild);
fprintf(stdout,"%d ",root->data);
print(root->rchild);
}
int main(void)
{
int i;
Node* root = NULL;
int a[] = {1,2,3,4,5};
const int len = sizeof(a)/sizeof(int);
for(;i<len;i++)
{
root = insert(a[i],root);
}
print(root);
return 0;
}<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
以上代码通过测试。
详解依次插入1,2,3,4,5时的建树过程:
1、插入1:
1
2、插入2:
1
\
2
3、插入3:
1 1 2
\ ==> \ ==> / \
2 2 1 3
\
3
4、插入4:
2
/ \
1 3
\
4
5 、插入5:
2 2
/ \ / \
1 3 ==> 1 4
\ / \
4 3 5
\
5
此时构建出AVL树,通过gdb调试,我们可以验证最终的树即是第5步最后的那棵树。从图中,我们可知道建树过程中共进行够两次左旋转。
旋转过程中,如果能够理解树的高度计算,那么可以说AVL树已经理解。
下面使用一个实例来说明树的高度计算过程:
例:顺序插入3,2,1,构建AVL树。
3 (h3=1) 3(h3=1) 2 (h2=1) 2(h2=1)
3 (h3=0) ==> / ==> / ==> / \ ==> / \
2 (h2=0) 2 (h2=1) 1(h1=0) 3(h3=1) 1(h1=0) 3(h3=0)
/
1(h1=0)
过程详解:
刚插入的结点高度默认为0, 插入2后insert方法返回,程序进入3结点的insert方法,因为平衡,所以程序直接跳到更新高度的代码行,更新3这个结点的高度为1(具体方法,参见代码);插入1结点后,返回到2结点的insert方法,此时平衡因子为1,所以直接更新2的高度为1;接着进入3结点的insert方法,此时3的高度还是1,但是3结点的左结点高度1,减去右结点高度-1(参见height函数),平衡因子等于2,
此时平衡被打破,需进行右旋转;旋转后的图,即是过程3之后的图。根据右旋转方法,可知:先计算3结点的高度h3,而其高度是左孩子和右孩子中最大值加1,所以此时h3=-1+1=0;然后计算2结点的高度h2,
2结点的高度由它的左右孩子值加1决定(父结点的高度一定等于最大孩子高度加1),此时h1和h3都等于0,所以h2=0+1=1。
至此,整个高度的更新过程描述完毕。