数据结构-AVL树
来源:互联网 发布:新页软件 编辑:程序博客网 时间:2024/06/05 14:41
AVL树
【概念】
1、AVL树性质:是带有平衡条件(这个平衡条件必须容易保持,必须保证树的平均深度为O(logN))的二叉查找树。
一颗AVL树是其每个节点的左子树和右子树的高度最多差1的二叉查找树。(空树的高度定义为-1)
插入一个节点可能会破坏AVL树的特性。如果发生这种情况,那么就要把性质恢复后才认为这一步插入完成。
2、旋转:
和其他的树结构一样,AVL树也有插入、删除等操作,这些操作都有可能破坏AVL树的性质,对于经过操作后不满足AVL树性质的,我们通过旋转来恢复其性质。
经过旋转后满足AVL树性质:
- 对于树中的每个节点X,它的左子树中所有关键字值小于X的关键字值,而它的右子树中的所有关键字值大于X的关键字值。
- 每个节点的左子树和右子树的高度最多差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( X, T->Right )。
[2层]再次进行判断树不为空,插入的元素小于元素T(即元素8),递归调用Insert( X, T->Left )。
[3层]再次进行判断树不为空,插入的元素小于元素T(即元素7),递归调用Insert( X, T->Left )。
[3层]此时,树空,直接将6插入,更新高度为0,返回上一层(即元素7所在的一层)。
[2层]进行判断,并没有破坏AVL树性质,更新高度为1,返回上一层(即元素8所在的一层)。
[1层]进行判断,破坏了AVL树性质,右子树高度比左子树高度大二。所以要进行旋转恢复AVL树的性质。那么采用单旋转还是双旋转?之前判断过插入元素是小于该层元素8的,再将插入 元素与该层元素的左子树上的根元素比较if( X < 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( X, T->Right )。
[2层]再次进行判断树不为空,插入的元素大于元素T(即元素6),递归调用Insert( X, T->Right )。
[3层]再次进行判断树不为空,插入的元素小于元素T(即元素15),递归调用Insert( X, T->Left )。
[4层]再次进行判断树不为空,插入的元素大于元素T(即元素7),递归调用Insert( X, T->Right )。
[4层]此时,树空,直接将14插入,更新高度为0,返回上一层(即元素7所在的一层)。
[3层]进行判断,并没有破坏AVL树性质,更新高度为1,返回上一层(即元素15所在的一层)。
[2层]进行判断,并没有破坏AVL树性质,更新高度为2,返回上一层(即元素6所在的一层)。
[1层]进行判断,破坏了AVL树性质,元素6的右子树高度比左子树高度大二。所以要进行旋转恢复AVL树的性质。那么采用单旋转还是双旋转?之前判断过插入元素是大于该层元素6的,再将插 入元素与该层元素的右子树上的根元素比较if( X < T->Right->Element ),如果大于则属于右右情形,进行右右单旋转恢复AVL树的性质,如果大于属于右左双旋转情形,则先进行左左单旋转再进行右右单旋转来恢复AVL树的性质。很明显,这里属于右左情形,我们进行双旋转将AVL树的性质进行恢复:
此时,程序中的T即为6(也即DoubleRotateWithRight( Position K1 )函数中的K1),我们先对K1(6)的右子树(15)进行左左单旋转:调用程序SingleRotateWithLeft( Position K2 )图中的15(标号K3)即为这里函数的形参K2,我们先将K2(即15)的左儿子7保存在K1中,再将K2的左儿子赋值为K1(7)的右儿子(即为14),最后将K1(7)的右儿子赋值为K2。即得到下图:
再对K1进行右右单旋转:SingleRotateWithRight( Position K1 )6即为这里的K1,我们先将K1(即6)的右儿子7保存在K2中,再将K1(即为6)的右儿子(即为7)赋值为K2的 左儿子(即为空),最后再将K2(即为7)的左儿子赋值为K1(即为6)。
最后,我们再将节点高度进行更新,这时我们便恢复了AVL树的性质。
更多内容请关注:麻子来了 | 博客 技术分享
- AVL 树数据结构
- 数据结构之AVL树
- 数据结构之AVL树
- 数据结构之AVL树
- 数据结构之AVL树
- 数据结构之AVL树
- 数据结构之AVL树
- 数据结构之AVL树
- 数据结构之AVL树
- 数据结构之AVL树
- 数据结构 - AVL树
- 数据结构:avl树
- 数据结构之AVL树
- 数据结构之AVL树
- 数据结构之AVL树
- 数据结构: AVL树
- 数据结构之AVL树
- 数据结构之AVL树
- android listView 相关
- ssi整合开发层次总结
- JAVA多线程实现的三种方式
- Git 远程仓库/ssh
- CSV文件
- 数据结构-AVL树
- Python小记04
- 关于使用新版本的BufferKnife出现NullPointerException的问题
- Android Button 总是在最上层的问题
- PermGen Space的解决办法
- Nmap速查手册
- set
- Http 知识(一)
- 获取spring中的ApplicationContext最简单的方式