AVL树

AVL树是一种带有自平衡性质的的二叉查找树,也就是说,在多次插入删除或者极端数据情况下依然可以保证O(logn)的操作复杂度。文章同步发布在个人博客,链接:AVL树

自平衡的二叉查找树

上一篇中的二叉查找树在多次插入删除后,树的节点会向其中一边下沉,操作(插入/删除/查找)的复杂度会从O(logn)渐渐增大。在数据量很大时,操作时间可能变得不可接受。而且当建树的一组数据是已经排好序的极端情况时,普通的二叉查找树就会退化为代价高昂的链表,而时间复杂度将会退化为线性的O(n)。

而要避免以上问题,我们需要为二叉查找树建立一个平衡的附加结构条件。条件不同,实现不同。当附加平衡条件后,二叉树便可以实现自平衡,即当大量插入删除时仍然可以保持O(logn)的最坏操作时间复杂度,我们将其称作平衡二叉搜索树(Self-balancing binary search tree)。平衡二叉树常见的实现有AVL树、红黑树(Red Black Tree)、伸展树(Splay tree)等。工程中大量使用红黑树,以后有机会讨论。这里简要实现AVL树。

AVL Tree

AVL树是以它的发明者(G.M. Adelson-Velsky 和 E.M. Landis)来命名的,也是最先发明的自平衡二叉查找树,于1962年发明。
AVL树的平衡条件是:每个节点的左子树与右子树高度相差不超过1。其中只有一个根节点的高度定义为0,空树高度定义为-1。

这里需要区分两个概念,节点的深度与高度,深度是从根节点到该节点的唯一路径的长度,高度是从该节点到其后代的树叶的最长路径。则根节点深度为0,对一棵树来说,树的高等于根节点的高,也等于该树最深的树叶的深度,即树的深度。

因为插入和删除可能会破坏AVL树的平衡条件,所以插入和删除后如果失衡则必须对其进行修正/平衡化。我们称其为旋转
首先考虑插入,我们把必须要重新平衡的节点就做a,因此高度不平衡时,a的左子树与右子树高度相差2。这可以分为以下4种情况:

  1. 对a的左儿子的左子树进行了一次插入。
  2. 对a的左儿子的右子树进行了一次插入。
  3. 对a的右儿子的左子树进行了一次插入。
  4. 对a的右儿子的右子树进行了一次插入。

情形1和4是对称的,2和3也是对称的。因为需要通过节点的高度来判断是否平衡,所以我们在节点中增加一个域height来表示该节点的高度。所以节点定义如下。

#define Max(a,b) (a>b?a:b)
typedef int ElemType;
typedef struct TreeNode
{
    ElemType element;
    TreeNode * left;
    TreeNode * right;
    int height;
} * pNode, * AvlTree;

操作声明:

int Height(pNode p);
void Insert(AvlTree & T,ElemType x);
void Delete(AvlTree & T,ElemType x);
pNode Single_rotate_left(pNode p); //从左向右单旋
pNode Single_rotate_right(pNode p);//从右向左单旋
pNode Double_rotate_left(pNode p);
pNode Double_rotate_right(pNode p);
AvlTree CreateTree(ElemType A[],int n);//从数组建树

由于需要通过计算高度来判断平衡,所以必须定义一个计算节点高度的函数:

int Height(pNode p)
{
    if(p == NULL)
        return -1;
    else
        return p->height;
}

从数组建树:

AvlTree CreateTree(ElemType A[],int n)
{
    AvlTree T = NULL;
    for(int i = 0; i < n; i++)
        Insert(T,A[i]);
    return T;
}

单旋转

考虑情形1,如果对a的左儿子的左子树进行了一次插入,导致a的左子树比右子树高度大2的话,那么我们可以通过如下的单旋转来使其重新平衡。

此处输入图片的描述

旋转后,返回tmp,很容易看出旋转后,依然满足二叉查找树的性质,而且重新达到了平衡。这一过程的实现:

pNode Single_rotate_left(pNode p) 
{
    pNode tmp = p->left;
    p->left = tmp->right;
    tmp->right = p;
    p->height = Max(Height(p->left),Height(p->right)) + 1;
    tmp->height = Max(Height(tmp->left),p->height) + 1;
    return tmp;
}

类似的情形4,则如图所示。

此处输入图片的描述

实现(与情形1镜像对称):

pNode Single_rotate_right(pNode p)
{
    pNode tmp = p->right;
    p->right = tmp->left;
    tmp->left = p;
    p->height = Max(Height(p->left),Height(p->right)) + 1;
    tmp->height = Max(Height(tmp->right),p->height) + 1;
    return tmp; 
}

上面的实现中,Single_rotate_left表示的是从左向右单旋,Single_rotate_right是从右向左单旋。

双旋转

情形2和3则无法通过单旋转实现平衡,但可以通过一次双旋转来完成。就情形2来说,对a的左儿子的右子树进行了一次插入导致a的左子树比右子树高度大了2。则可以通过如下的旋转使得再次达到平衡。

此处输入图片的描述

可以很明显的看出双旋转可以通过两次单旋转来完成,上图中,首先对子树k1进行可一次从右向左的单旋,然后对k3进行了一次从左向右的单旋。所以实现时调用上述函数即可。

pNode Double_rotate_left(pNode p)
{
    p->left = Single_rotate_right(p->left);
    return Single_rotate_left(p);
}

类似,情形3,如下。

此处输入图片的描述

实现:

pNode Double_rotate_right(pNode p)
{
    p->right = Single_rotate_left(p->right);
    return Single_rotate_right(p);
}

上面的1,2,3,4四种情形也经常因为插入位置被称为LL,LR,RL,RR。
一个描述了这几种旋转的动图:
此处输入图片的描述

插入

旋转实现了,则插入就可以实现了,注意旋转中必须对节点高度进行必要的更新。AVL树插入节点的实现:

void Insert(AvlTree & T,ElemType x)
{
    if(T == NULL)
    {
        T = new TreeNode;
        T->element = x;
        T->height = 0;
        T->left = T->right = NULL;
    }
    else if(x < T->element)
    {
        Insert(T->left,x);                 //在T的左子树插入       
        if(Height(T->left) - Height(T->right) == 2) //左子树失衡,需要平衡 
        {
            if(x < T->left->element)       //插入在T的左孩子的左子树,单旋转,情形1
                T = Single_rotate_left(T);
            else                           //插入在T的左孩子的右子树,双旋转,情形2
                T = Double_rotate_left(T); 
        }
    }
    else if(x > T->element)         
    {
        Insert(T->right,x);               //在T的右子树插入
        if(Height(T->right) - Height(T->left) == 2) //右子树失衡,需要平衡
        {
            if(x > T->right->element)     //插入在T的右孩子的右子树,单旋转,情形4
                T = Single_rotate_right(T);
            else                          //插入在T的右孩子的左子树,双旋转,情形3
                T = Double_rotate_right(T);
        } 
    }
    //else x is aleardy exist in the tree, we'll do nothing.
    //Don't forget to update the height of root 
    T->height = Max(Height(T->left),Height(T->right)) + 1;
}

删除

比较复杂,暂时未实现。对于这种情况,采用懒惰删除也许会是一个好的选择,节点中添加一个域,查找到后将其状态修改为deleted就Ok。略。

源码

点击下载

参考资料:数据结构与算法分析——C语言描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值