二叉树(三)之AVL树

转自:http://www.cnblogs.com/skywang12345/p/3576969.html

目录

一、AVL树的概念

二、AVL树的实现

三、测试代码


一、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;
}

猜你喜欢

转载自blog.csdn.net/qq_39826163/article/details/82146217