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. OLAP的一些知识——接下去的项目需要的背景

    1.维是人们观察主题的特定角度,每一个维分别用一个表来描述,称为“维表”(Dimension Table),它是对维的详细描述. 2.事实表示所关注的主题,亦由表来描述,称为“事实表”(Fact Ta ...

  2. javascript js写特效日历

    <!doctype html public "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  3. LeetCode Populating Next Right Pointers in Each Node (技巧)

    题意: 给一棵满二叉树,要求将每层的节点从左到右用next指针连起来,层尾指向NULL即可. 思路: 可以递归也可以迭代.需要观察到next的左孩子恰好就是本节点的右孩子的next啦. (1)递归:这 ...

  4. 解决chrome同步问题

    原方法为 修改文件:“C:\WINDOWS\system32\drivers\etc\hosts”,但是经常失败,需要重新修改,很是麻烦. 更好的方法是使用 “谷歌访问助手(chrome版)” 具体参 ...

  5. NetCDF 格式化数据概述

    前言 马上要开始新的项目了.气象局方面要求处理的数据是 NetCDF 格式的,这是一种封装好了的数据结构,需要连接一些库,调用特定的 API 进行操作. 可这方面我知之甚少,遂利用周日上午的时间学习这 ...

  6. ZOJ Problem Set - 3329 One Person Game

    题目大意:有三个骰子,分别有k1,k2,k3个面. 每次掷骰子,如果三个面分别为a,b,c则分数置0,否则加上三个骰子的分数之和. 当分数大于n时结束.求游戏的期望步数.初始分数为0分析  设 E[i ...

  7. 快排算法(C++版)

    #include <iostream> using namespace std; /** Quick Sort * * split: cmp && swap * left ...

  8. Linux主机硬盘的主要规划

    系统对于硬盘的需求跟刚刚提到的主机开放的服务有关,那么除了这点之外,还有没有其他的注意事项?当然有,那就是数据的分类与数据安全性的考虑,即当主机系统的硬件出现问题时,你的档案数据能否安全的保存. 前篇 ...

  9. 2015GitWebRTC编译实录

    整体解决思路1 编译完成后2~3天,对之前编译的lib库进行测试.目前阶段至少保证真机测试是ok的,模拟器先放到一边.2015.06.24完成编译状况编译完成libjsoncpp编译完成libsyst ...

  10. H5标签-canvas实现颜色拾取功能

    HTML5 <canvas> 标签是用于绘制图像,不过,<canvas> 元素本身并没有绘制能力(它仅仅是图形的容器),必须使用脚本(通常是 JS)来完成实际的绘图任务. &l ...