您的位置:首页 > 其它

AVL树

2012-11-12 22:22 169 查看
AVL(Adelson-Velskii and Landis)树是带有平衡条件(balance condition)的二叉查找树。这个平衡条件必须容易保持,而且必须保证树的深度是O(logN)。AVL树规定其每个结点的左子树和右子树的高度最多差1。如下图,左边的树是AVL树,右边的则不是。



AVL树的每个结点(在其结点结构中)保留高度信息。可以证明,一般情况下,一棵AVL树的高度最多为1.44log(N+2) – 1.328,并且实际上的高度只比logN稍微多一点。

下图显示了一棵具有最少结点(143)高度为9的AVL树。这棵树的左子树是高度为7且结点数最少的AVL树,右子树是高度为8且结点数最少的AVL树。可以看出在高度为h的AVL树中,最少结点数S(h)=S(h-1) + S(h-2) + 1。对于h=0,S(h) = 1;h=1,S(h)=2。函数S(h)与斐波那契数密切相关,由此可以推出上面的关于AVL树的高度的界。



除了插入操作(假设删除是懒惰删除),所有的AVL树操作都可以以时间O(logN)执行。当进行插入操作时,需要更新通向根节点路径上的那些结点的平衡信息,因为插入操作可能会破坏AVL树的平衡特性。如果AVL树的平衡被破坏,就要进行修正,这里称其为旋转(rotation)。

假设结点a的左子树和右子树失去平衡(高度差2),则可能的情况有下面4种:

1. 对a的左儿子的左子树进行一次插入。

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

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

4. 对a的右儿子的右子树进行一次插入。

情形1和4关于结点a对称,情形2和3关于结点a对称,所以理论上只有两种情况,不过从编程的角度来看还是4种情形。

第一种情况是插入发生在“外边”的情形(即“左-左”或“右-右”),该情况通过对树的一次单旋转(single rotation)完成调整。第二种情况是插入发生在“内部”的情形(即“左-右”或“右-左”),该情况通过稍微复杂些的双旋转(double rotation)完成调整。

单旋转

下图显示了对于情形1如何进行单旋转。左边是旋转前,右边是旋转后。在左边的树中,子树X中新插入了一个结点使得结点k2不再满足AVL平衡性质,因为它的左子树比右子树深2层(图中间的虚线代表树的各层)。



上图中为了使树恢复平衡,把子树X上移了一层,把子树Z下移了一层。这其实已经超额完成了AVL性质的要求。抽象的形容一下单旋转的过程:把树看成是柔软灵活的,抓住子结点k1,用力摇动它,在重力的作用下,k1就变成了新的根。X和Z仍然分别是k1的左儿子和k2的右儿子。子树Y包含原树中介于k1和k2之间的那些结点,现在把它放在新树中k2的左儿子的位置上。这样,所有对顺序的要求都得到满足,旋转后得到的新树是一棵AVL树。不仅如此,其实上图中经过单旋转所得到的新树的高度,和插入新节点(导致原树不平衡)之前原树的高度相同,所以通向根节点的路径上的那些结点不需要进一步调整(它们仍然是平衡的)。下图是一次单旋转的例子:



下图演示应于“情形4”的单旋转修正:



下面演示一个更长一些的例子,从初始的空AVL树开始插入3、2和1,然后依序插入4到7。在插入1时,AVL性质在根处被破坏。于是在根结点与其左儿子之间执行一次单旋转以修正这个问题。



图中以虚线连接的那两个结点是旋转的主体。接着插入4和5,在插入5时破坏了结点3处的AVL性质,通过单旋转将其修正。在编程的时候必须记住:结点2的右儿子必须重新设置以链接到4而不是3,这一点很容易忘记,从而导致树被破坏。



接着插入6,根节点处失去平衡,因为它的左子树高度为0而右子树高度为2。在根节点2和结点4之间执行一次单旋转。



接着插入7,再次执行单旋转。



双旋转

对于平衡二叉树失衡的情况2、3,使用单旋转是无效的。



此时需要使用双旋转(两次旋转:“左-右”或“右-左”)


上图中,树B或树C中有一棵比D深两层。为了重新平衡,选择k2作为新的根,k1作k2的左儿子,k3作k2的右儿子。与单旋转一样,把树恢复到插入新节点之前的水平。下图是“右-左”双旋转。



继续单旋转时的示例,以倒序插入10-16,接着插入8、9。插入16时并不破坏树的平衡。插入15时引起结点7的不平衡,这属于情况3,需要做一次“右-左”双旋转。



接着插入14,同样也需要做一次“右-左”双旋转。结合上面的“右-左双旋转修正情形3”示意图,子树A的根结点为5,子树B为空树,子树C的根结点为14,子树D的根结点为16。



插入13,在根结点处失衡。由于13不在4和7之间,所以是上面介绍的失衡情况4(对失衡结点4的右儿子的右子树进行一次插入),这里需要做一次单旋转(左旋)。



插入12也需要做一次单旋转(右旋)。



接着插入11、10、8(未作旋转图示),得到下面一棵近乎理想的平衡树。



最后插入9,需要在8、9、10三个结点之间做“左-右”双旋转。



在编程时,需要计算子树的高度差。有些程序员会在结点的存储结构中,增加一个BF成员(Balance Factor:平衡因子)。这样可以避免平衡因子的重复计算,提高程序的性能,不过却丧失了程序的某些简明性。

下面是一部分例程:

struct AvlNode
{
Comparable element;
AvlNode   *left;
AvlNode   *right;
int       height;

AvlNode( const Comparable & theElement, AvlNode *lt,
AvlNode *rt, int h = 0 )
: element( theElement ), left( lt ), right( rt ), height( h ) { }
};

/**
* Return the height of node t or -1 if NULL.
*/
int height( AvlNode *t ) const
{
return t == NULL ? -1 : t->height;
}

/**
* Internal method to insert into a subtree.
* x is the item to insert.
* t is the node that roots the subtree.
* Set the new root of the subtree.
*/
void insert( const Comparable & x, AvlNode * & t )
{
if( t == NULL )
t = new AvlNode( x, NULL, NULL );
else if( x < t->element )
{
insert( x, t->left );
if( height( t->left ) - height( t->right ) == 2 )
if( x < t->left->element )
rotateWithLeftChild( t );
else
doubleWithLeftChild( t );
}
else if( t->element < x )
{
insert( x, t->right );
if( height( t->right ) - height( t->left ) == 2 )
if( t->right->element < x )
rotateWithRightChild( t );
else
doubleWithRightChild( t );
}
else
;  // Duplicate; do nothing
t->height = max( height( t->left ), height( t->right ) ) + 1;
}


下面是单旋转函数rotateWithLeftChild的示意图和源码。rotateWithRightChild是对称的,所以未贴出。



/**
* Rotate binary tree node with left child.
* For AVL trees, this is a single rotation for case 1.
* Update heights, then set new root.
*/
void rotateWithLeftChild( AvlNode * & k2 )
{
AvlNode *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;
k2 = k1;
}


下面是“左-右”双旋转函数doubleWithLeftChild示意图和源代码,doubleWithRightChild是对称的。



/**
* Double rotate binary tree node: first left child
* with its right child; then node k3 with new left child.
* For AVL trees, this is a double rotation for case 2.
* Update heights, then set new root.
*/
void doubleWithLeftChild( AvlNode * & k3 )
{
rotateWithRightChild( k3->left );
rotateWithLeftChild( k3 );
}


Reference:

[1] 《数据结构与算法分析c++描述》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: