数据结构-AVL树

AVL树

【概念】

1、AVL树性质:是带有平衡条件这个平衡条件必须容易保持,必须保证树的平均深度为O(logN))二叉查找树

         一颗AVL树是其每个节点的左子树和右子树的高度最多差1的二叉查找树。(空树的高度定义为-1)

         插入一个节点可能会破坏AVL树的特性。如果发生这种情况,那么就要把性质恢复后才认为这一步插入完成。

2、旋转

和其他的树结构一样,AVL树也有插入、删除等操作,这些操作都有可能破坏AVL树的性质,对于经过操作后不满足AVL树性质的,我们通过旋转来恢复其性质。

经过旋转后满足AVL树性质:

  1. 对于树中的每个节点X,它的左子树中所有关键字值小于X的关键字值,而它的右子树中的所有关键字值大于X的关键字值。
  2. 每个节点的左子树和右子树的高度最多差1

我们通过两种旋转来恢复AVL树的性质。

单旋转

经过在X子树上的插入操作后,节点K2不满足AVL树的平衡特性,左子树比右子树深2层。为了恢复AVL树的平衡性质,我们需要将X上移一层,而旋转操作还将Z下移了一层(此时其实超出了AVL特性的要求)。

如图为左左单旋转(对称情形类似)。

                           

但是,有些情形通过单旋转并不能修复:

                        

如果新插入的节点在子树Y上,就算经过了上图中的左左单旋转K1节点仍然不平衡。此时我们需要利用双旋转(本质是两次单旋转)将其修复。

双旋转

如图是左右双旋转(对称情形类似)先在K1处经过右右单旋转,再在K3处经过左左单旋转,最后恢复AVL树的平衡性质。

                          


【程序】

        #include "avltree.h"
        #include <stdlib.h>
        #include "fatal.h"

        struct AvlNode
        {
            ElementType Element;
            AvlTree  Left;
            AvlTree  Right;
            int      Height;
        };

        AvlTree
        MakeEmpty( AvlTree T )
        {
            if( T != NULL )
            {
                MakeEmpty( T->Left );
                MakeEmpty( T->Right );
                free( T );
            }
            return NULL;
        }

        Position
        Find( ElementType X, AvlTree T )
        {
            if( T == NULL )
                return NULL;
            if( X < T->Element )
                return Find( X, T->Left );
            else
            if( X > T->Element )
                return Find( X, T->Right );
            else
                return T;
        }

        Position
        FindMin( AvlTree T )
        {
            if( T == NULL )
                return NULL;
            else
            if( T->Left == NULL )
                return T;
            else
                return FindMin( T->Left );
        }

        Position
        FindMax( AvlTree T )
        {
            if( T != NULL )
                while( T->Right != NULL )
                    T = T->Right;

            return T;
        }

//计算AVL节点的高度的函数
        static int
        Height( Position P )
        {
            if( P == NULL )
                return -1;
            else
                return P->Height;
        }

//返回俩数中较大的一个
        static int
        Max( int Lhs, int Rhs )
        {
            return Lhs > Rhs ? Lhs : Rhs;
        }
//当插入发生在“外边”的情况,我们可以对树进行一次单旋转恢复其AVL性质
//单旋转分两种:左左单旋转,右右单旋转


//左左单旋转情形,当在K2节点处不满足AVL树性质。
        static Position
        SingleRotateWithLeft( Position K2 )
        {
            Position K1;

            K1 = K2->Left;
            K2->Left = K1->Right;
            K1->Right = K2;

            K2->Height = Max( Height( K2->Left ), Height( K2->Right ) ) + 1;
            K1->Height = Max( Height( K1->Left ), K2->Height ) + 1;

            return K1;  /* New root */
        }



//右右单旋转情形:在K1处不满足AVL树的性质
        static Position
        SingleRotateWithRight( Position K1 )
        {
            Position K2;

            K2 = K1->Right;
            K1->Right = K2->Left;
            K2->Left = K1;

            K1->Height = Max( Height( K1->Left ), Height( K1->Right ) ) + 1;
            K2->Height = Max( Height( K2->Right ), K1->Height ) + 1;

            return K2;  /* New root */
        }

//当插入发生在“内部”的情况,我们可以对树进行双旋转来恢复其AVL性质
//双旋转分两种:左右双旋转和右左双旋转


//左右双旋转情形:
//先右右单旋转再左左单旋转。
        static Position
        DoubleRotateWithLeft( Position K3 )//左右
        {
            /* Rotate between K1 and K2 */
            K3->Left = SingleRotateWithRight( K3->Left );

            /* Rotate between K3 and K2 */
            return SingleRotateWithLeft( K3 );
        }


//右左双旋转情形:
//先左左单旋转再右右单旋转
        static Position
        DoubleRotateWithRight( Position K1 )//右左
        {
            /* Rotate between K3 and K2 */
            K1->Right = SingleRotateWithLeft( K1->Right );

            /* Rotate between K1 and K2 */
            return SingleRotateWithRight( K1 );
        }


//插入操作(所有的插入都是在叶子节点上进行的)
        AvlTree
        Insert( ElementType X, AvlTree T )
        {
            if( T == NULL )//如果树为空,则直接插入
            {
                /* Create and return a one-node tree */
                T = malloc( sizeof( struct AvlNode ) );
                if( T == NULL )
                    FatalError( "Out of space!!!" );
                else
                {
                    T->Element = X; T->Height = 0;
                    T->Left = T->Right = NULL;
                }
            }
            else
            if( X < T->Element )
            {
                T->Left = Insert( X, T->Left );
                if( Height( T->Left ) - Height( T->Right ) == 2 )//判断AVL条件是否满足
                    if( X < T->Left->Element )//X < T->Right->Element与上边的X < T->Element来共同确定是单旋转还是双旋转
                        T = SingleRotateWithLeft( T );//左左
                    else
                        T = DoubleRotateWithLeft( T );//左右
            }
            else
            if( X > T->Element )// X > T->Element
            {
                T->Right = Insert( X, T->Right );
                if( Height( T->Right ) - Height( T->Left ) == 2 )
                    if( X > T->Right->Element )//X > T->Right->Element与上边的X > T->Element来共同确定是单旋转还是双旋转
                        T = SingleRotateWithRight( T );//右右
                    else
                        T = DoubleRotateWithRight( T );//右左,T为失衡节点
            }
            /* Else X is in the tree already; we'll do nothing */

            T->Height = Max( Height( T->Left ), Height( T->Right ) ) + 1;
            return T;
        }

        ElementType
        Retrieve( Position P )//取位置P处的元素
        {
            return P->Element;
        }

以上为部分关键代码,完整程序代码可以在https://github.com/mazilaile下载

【程序分析】

结合插入程序分析单双旋转例程:

单旋转程序分析:

                     

向上图树中插入6,插入程序AvlTree Insert( ElementType X, AvlTree T )先判断树不为空。([1层]为递归调用的层次

[1层]再判断插入的元素大于元素T(即根处的元素5),递归调用Insert( XT->Right )

[2层]再次进行判断树不为空,插入的元素小于元素T(即元素8),递归调用Insert( XT->Left )

[3层]再次进行判断树不为空,插入的元素小于元素T(即元素7),递归调用Insert( XT->Left )

[3层]此时,树空,直接将6插入,更新高度为0,返回上一层(即元素7所在的一层)。

[2层]进行判断,并没有破坏AVL树性质,更新高度为1,返回上一层(即元素8所在的一层)。

[1层]进行判断,破坏了AVL树性质,右子树高度比左子树高度大二。所以要进行旋转恢复AVL树的性质。那么采用单旋转还是双旋转?之前判断过插入元素是小于该层元素8的,再将插入   元素与该层元素的左子树上的根元素比较ifX < T->Left->Element ),如果小于则属于左左情形,进行左左单旋转恢复AVL树的性质,如果大于属于左右双旋转情形,则先进行右右旋转再进行左左旋转进行恢复AVL树的性质。很明显,这里属于左左情形,我们进行单旋转将AVL树的性质进行恢复:

此时,程序中的T即为8(也即SingleRotateWithLeft( Position K2 )函数中的K2),我们先将K2(即8)的左儿子7保存在K1中,再将K2(即8)的左儿子赋值为K1的右儿子(即为空),最后将K1的右儿子赋值为K2

最后,我们再将节点高度进行更新,这时我们便恢复了AVL树的性质。

                                                         

双旋转程序分析

再来看,插入以后需要进行双旋转恢复AVL树的平衡性质。

                                         

向上图树中插入14,插入程序AvlTree Insert( ElementType X, AvlTree T )先判断树不为空。

[1层]再判断插入的元素大于元素T(即根处的元素4),递归调用Insert( XT->Right )

[2层]再次进行判断树不为空,插入的元素大于元素T(即元素6),递归调用Insert( XT->Right )

[3层]再次进行判断树不为空,插入的元素小于元素T(即元素15),递归调用Insert( XT->Left )

[4层]再次进行判断树不为空,插入的元素大于元素T(即元素7),递归调用Insert( XT->Right )

[4层]此时,树空,直接将14插入,更新高度为0,返回上一层(即元素7所在的一层)。

[3层]进行判断,并没有破坏AVL树性质,更新高度为1,返回上一层(即元素15所在的一层)。

[2层]进行判断,并没有破坏AVL树性质,更新高度为2,返回上一层(即元素6所在的一层)。

[1层]进行判断,破坏了AVL树性质,元素6的右子树高度比左子树高度大二。所以要进行旋转恢复AVL树的性质。那么采用单旋转还是双旋转?之前判断过插入元素是大于该层元素6的,再将插 入元素与该层元素的右子树上的根元素比较ifX < T->Right->Element ),如果大于则属于右右情形,进行右右单旋转恢复AVL树的性质,如果大于属于右左双旋转情形,则先进行左左单旋转再进行右右单旋转来恢复AVL树的性质。很明显,这里属于右左情形,我们进行双旋转将AVL树的性质进行恢复:

此时,程序中的T即为6(也即DoubleRotateWithRight( Position K1 )函数中的K1),我们先对K16)的右子树(15)进行左左单旋转:调用程序SingleRotateWithLeft( Position K2 )图中的15(标号K3)即为这里函数的形参K2,我们先将K2(即15)的左儿子7保存在K1中,再将K2的左儿子赋值为K17)的右儿子(即为14),最后将K17)的右儿子赋值为K2。即得到下图:

         

                                           

再对K1进行右右单旋转:SingleRotateWithRight( Position K1 )6即为这里的K1,我们先将K1(即6)的右儿子7保存在K2中,再将K1(即为6)的右儿子(即为7)赋值为K2的左儿子(即为空),最后再将K2(即为7)的左儿子赋值为K1(即为6)。

最后,我们再将节点高度进行更新,这时我们便恢复了AVL树的性质。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值