数据结构-AVL树

来源:互联网 发布:新页软件 编辑:程序博客网 时间:2024/06/05 14:41

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
原创粉丝点击