1 .基本概念

AVL树的复杂程度真是比二叉搜索树高了整整一个数量级——它的原理并不难弄懂,但要把它用代码实现出来还真的有点费脑筋。下面我们来看看:

1.1  AVL树是什么?

AVL树本质上还是一棵二叉搜索树(因此读者可以看到我后面的代码是继承自二叉搜索树的),它的特点是:

1. 本身首先是一棵二叉搜索树。

2. 带有平衡条件:每个结点的左右子树的高度之差的绝对值(平衡因子)最多为1。

例如:

5              5

/ \            / \

2   6          2   6

/ \   \        / \

1   4   7      1   4

/              /

3              3

上图中,左边的是AVL树,而右边的不是。因为左边的树的每个结点的左右子树的高度之差的绝对值都最多为1,而右边的树由于结点6没有子树,导致根结点5的平衡因子为2。

1.2  为什么要用AVL树?

有人也许要问:为什么要有AVL树呢?它有什么作用呢?

我们先来看看二叉搜索树吧(因为AVL树本质上是一棵二叉搜索树),假设有这么一种极端的情况:二叉搜索树的结点为1、2、3、4、5,也就是:

1

\

2

\

3

\

4

\

5

聪明的你是不是发现什么了呢?呵呵,显而易见——这棵二叉搜索树其实等同于一个链表了,也就是说,它在查找上的优势已经全无了——在这种情况下,查找一个结点的时间复杂度是O(N)!

好,那么假如是AVL树(别忘了AVL树还是二叉搜索树),则会是:

2

/ \

1   4

/ \

3   5

可以看出,AVL树的查找平均时间复杂度要比二叉搜索树低——它是O(logN)。也就是说,在大量的随机数据中AVL树的表现要好得多。

1.3  旋转

假设有一个结点的平衡因子为2(在AVL树中,最大就是2,因为结点是一个一个地插入到树中的,一旦出现不平衡的状态就会立即进行调整,因此平衡因子最大不可能超过2),那么就需要进行调整。由于任意一个结点最多只有两个儿子,所以当高度不平衡时,只可能是以下四种情况造成的:

1. 对该结点的左儿子的左子树进行了一次插入。

2. 对该结点的左儿子的右子树进行了一次插入。

3. 对该结点的右儿子的左子树进行了一次插入。

4. 对该结点的右儿子的右子树进行了一次插入。

情况1和4是关于该点的镜像对称,同样,情况2和3也是一对镜像对称。因此,理论上只有两种情况,当然了,从编程的角度来看还是四种情况。

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

1.3.1  单旋转

情况1:对该结点的左儿子的左子树进行了一次插入。

左边为调整前得节点,我们可以看出k2的左右子树已不再满足AVL平衡条件,调整后的为右图。

我们可以看出,解决办法是将x上移一层,并将z下移一层,由于在原树中k2 > k1,所以k2成为k1的右子树,而y是小于k2的,所以成为k2的左子树。

为了设计算法,我们这里来看一个更易理解的:插入的是节点“6”

算法设计:由于是情形1对该结点的左儿子的左子树进行了一次插入,该节点是“8”,首先我们不考虑其父节点的情况,因为我们创建节点是递归创建的,可以不用考虑其父节点与其的连接,这在后面递归创建的时候会说到,由于“8”的右孩子将不会发生变化,但是其左孩子设为“7”的右孩子,将7的右孩子设为“8”及其子树,然后返回“7”节点的指针。

实现代码:

//情形1

AVLTree SingleRotateWithLeft(PAVLNode k2)

{

PAVLNode k1;

k1 = k2->l;

k2->l = k1->r;

k1->r = k2;

k2->h = MAX( Height( k2->l ), Height( k2->r ) ) + 1;

k1->h = MAX( Height( k1->l ), k2->h ) + 1;

return k1;  /* New root */

}

情况4:对该结点的右儿子的右子树进行了一次插入。

左边为调整前得节点,我们可以看出k1的左右子树已不再满足AVL平衡条件,调整后的为右图。

我们可以看出,解决办法是将z上移一层,并将x下移一层,由于在原树中k2 > k1,所以k1成为k2的左子树,而y是大于k1的,所以成为k1的右子树。

为了设计算法,我们这里来看一个更易理解的:插入的是节点“6”

算法设计:由于是情形1对该结点的右儿子的右子树进行了一次插入,该节点为“4”,我们同第一种情形类似。

实现代码:

//情形4

AVLTree SingleRotateWithRight(PAVLNode k1)

{

PAVLNode k2;

k2 = k1->r;

k1->r = k2->l;

k2->l = k1;

k1->h = MAX( Height( k1->l ), Height( k1->r ) ) + 1;

k2->h = MAX( Height( k2->r ), k1->h ) + 1;

return k2;  /* New root */

}

1.3.2  双旋转

情况2:对该结点的左儿子的右子树进行了一次插入。

这种情况是单旋转调整不回来的,如下图:

图(1)

左--右双旋转如下:

图(2)

这里我们将图(1)的Y子树看成如图(2),以k2为子树根节点的树,我们将其子树分成比D低,这里我我先对k3的左子树进行一次情形四的右旋转,然后在进行一次情形1的左旋转,详细步骤如下:(红色框里面的即是要进行单旋转的)

实现代码:

//情形2

AVLTree DoubleRotateWithLeft( PAVLNode k3 )

{

/* Rotate between K1 and K2 */

k3->l = SingleRotateWithRight( k3->l );

/* Rotate between K3 and K2 */

return SingleRotateWithLeft(k3);

}

情况3:对该结点的右儿子的左子树进行了一次插入。

右—左双旋转如下:

我们先对k1的右子树进行一次左旋转(情形1,然后再对k1进行一次右旋转(情形4)。

实现代码:

//情形3

AVLTree DoubleRotateWithRight( PAVLNode k1 )

{

/* Rotate between K3 and K2 */

k1->r = SingleRotateWithLeft( k1->r );

/* Rotate between K1 and K2 */

return SingleRotateWithRight( k1 );

}

1.3  插入操作

插入的核心思路是通过递归找到合适的位置,插入新结点,然后看新结点是否平衡(平衡因子是否为2),如果不平衡的话,就分成三种大情况以及两种小情况:

1. 在结点的左儿子(X < T->item)

o 在左儿子的左子树 (X < T->l-> item),“外边”,要做单旋转。

o 在左儿子的右子树 (X > T->l-> item),“内部”,要做双旋转。

2. 在结点的右儿子(X > T->item)

o 在右儿子的左子树(X < T->r-> item),“内部”,要做双旋转。

o 在右儿子的右子树(X > T->r-> item),“外边”,要做单旋转。

3. (X == T->item) ,对该节点的计数进行更新。

当进行了旋转之后,必定会有结点的“父结点”是需要更新的,例如:

2

/ \

1   4

/ \

3   5

\

6

上图是调整前的,下图是调整后的:

4

/ \

2   5

/ \   \

1   3   6

可以看出,根结点2不平衡,是由于它的右儿子的右子树插入了新的结点6造成的。因此,这属于“外边”的情况,要进行一次单旋转。于是我们就把结点4调整上来作为根结点,再把结点2作为4的左儿子,最后把结点2的右儿子修改为原来的结点4的左儿子。

实现代码:

AVLTree Insert(Item X, AVLTree T )

{

if( T == NULL )

{

/* Create and return a one-node tree */

T = (PAVLNode)malloc( sizeof(AVLNode ) );

if( T == NULL )

perror("malloc failed");

else

{

T->item = X;

T->h = 0;

T->l = T->r = NULL;

T->count = 1;

}

}

else if(compare(&X,&T->item) == -1)//插入情况1

{

T->l = Insert( X, T->l );

if( Height( T->l ) - Height( T->r ) == 2 )

if(compare(&X, &T->l->item ) == -1)//左边左子树 单旋转

T = SingleRotateWithLeft( T );

else

T = DoubleRotateWithLeft( T );//左边右子树

}

else if( compare(&X,&T->item) == 1 ) //插入情况2

{

T->r = Insert( X, T->r );

if( Height( T->r ) - Height( T->l ) == 2 )

if(compare(&X , &T->r->item) == 1)//右边右子树 单旋转

T = SingleRotateWithRight( T );

else

T = DoubleRotateWithRight( T );//右边左子树

}

else//插入情况3

T->count++;

/* Else X is in the tree already; we'll do nothing */

T->h = MAX( Height( T->l ), Height( T->r ) ) + 1;

return T;

}

 转自:http://blog.chinaunix.net/uid-25324849-id-2182877.html

[算法] 数据结构之AVL树的更多相关文章

  1. 数据结构与算法(九):AVL树详细讲解

    数据结构与算法(一):基础简介 数据结构与算法(二):基于数组的实现ArrayList源码彻底分析 数据结构与算法(三):基于链表的实现LinkedList源码彻底分析 数据结构与算法(四):基于哈希 ...

  2. 数据结构与算法——平衡二叉树(AVL树)

    目录 二叉排序树存在的问题 基本介绍 单旋转(左旋转) 树高度计算 旋转 右旋转 双旋转 完整代码 二叉排序树存在的问题 一个数列 {1,2,3,4,5,6},创建一颗二叉排序树(BST) 创建完成的 ...

  3. linux 内核数据结构之 avl树.

    转载: http://blog.csdn.net/programmingring/article/details/37969745 https://zh.wikipedia.org/wiki/AVL% ...

  4. 数据结构之AVL树

    AVL树是高度平衡的而二叉树.它的特点是:AVL树中任何节点的两个子树的高度最大差别为1. 旋转 如果在AVL树中进行插入或删除节点后,可能导致AVL树失去平衡.这种失去平衡的可以概括为4种姿态:LL ...

  5. D&F学数据结构系列——AVL树(平衡二叉树)

    AVL树(带有平衡条件的二叉查找树) 定义:一棵AVL树是其每个节点的左子树和右子树的高度最多差1的二叉查找树. 为什么要使用AVL树(即为什么要给二叉查找树增加平衡条件),已经在我之前的博文中说到过 ...

  6. [javaSE] 数据结构(AVL树基本概念)

    AVL树是高度平衡的二叉树,任何节点的两个子树的高度差别<=1 实现AVL树 定义一个AVL树,AVLTree,定义AVLTree的节点内部类AVLNode,节点包含以下特性: 1.key——关 ...

  7. 算法数据结构(一)-B树

    介绍 B树的目的为了硬盘快速读取数据(降低IO操作次树)而设计的一种平衡的多路查找树.目前大多数据库及文件索引,都是使用B树或变形来存储实现. 目录 为什么B树效率高 B树存储 B树缺点 为什么B树效 ...

  8. 大话数据结构—平衡二叉树(AVL树)

    平衡二叉树(Self-Balancing Binary Search Tree/Height-Balanced Binary Search Tree),是一种二叉排序树,当中每个节点的左子树和右子树的 ...

  9. [算法] 数据结构 splay(伸展树)解析

    前言 splay学了已经很久了,只不过一直没有总结,鸽了好久来写一篇总结. 先介绍 splay:亦称伸展树,为二叉搜索树的一种,部分操作能在 \(O( \log n)\) 内完成,如插入.查找.删除. ...

随机推荐

  1. IDataReader转换成list通用方法

    public static IList<T> ReaderToList<T>(this IDataReader dr) { //DateTime dt = DateTime.N ...

  2. 第二个Sprint冲刺第二天

    讨论地点:宿舍 讨论成员:邵家文.李新.朱浩龙.陈俊金 任务:解决了第二个Sprint冲刺第一天遇到的错误. 燃尽图: 遇到的问题: 解决之后: 开发感悟:最近一直在写代码,都很少外出活动了,不知不觉 ...

  3. 4 Values whose Sum is 0

      Time Limit:15000MS     Memory Limit:228000KB     64bit IO Format:%I64d & %I64u Submit Status D ...

  4. Word embedding

    https://en.wikipedia.org/wiki/Word_embedding 简言之,就是讲词汇或短语映射成实值特征向量.

  5. CentOS7.0关于libguestfs的bug

    libguestfs,libguestfs-tools是用来在不启动虚拟机的情况下,快速简单访问虚拟机磁盘的工具. 今天在CentOS7.0系统上通过guestmount命令去mount虚拟机磁盘的时 ...

  6. 右键菜单添加程序,指定图标, Notepad2、Sublime Text 2

    右键添加Sublime Text 打开方式 Windows Registry Editor Version 5.00 [HKEY_CLASSES_ROOT\*\shell\Sublime Text] ...

  7. Java-->实现群聊功能(C/S模式--TCP协议)

    --> Java 对TCP协议的支持: --> java.net包中定义了两个类ServerSocket 和Socket ,分别用来实现双向连接的server 端和client 端. -- ...

  8. 第六课,T语言表达式(版本5.0)

    TC综合开发工具里的表达式大体分为:计算表达式.条件表达式 计算表达式: 它一般是用在赋值过程中,或者是和条件表达式混合使用这样的表达式里只有数字运算符(如:+.-.+=.*=等等运算符),没有关系运 ...

  9. mysql之数据库连接的方法封装及防sql注入

    一.定义数据库和表 create database animal; CREATE TABLE `pet` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `name ...

  10. Apache的https协议配置

    一.http协议和https协议的传输格式 http:文本格式的协议 https:二进制格式的协议 二.x509.3证书格式: 证书格式的版本号 证书序列号 证书签名算法 证书颁发者 有效期 持有者的 ...