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. jQuery实现一个全选复选框联动效果

    类似邮件列表里的复选框 要求双向联动 ☛ [实现]: <body> <div> <input type="checkbox" name="c ...

  2. CentOS安装Nginx负载

    一.准备 1.安装Nginx 地址:http://www.cnblogs.com/rainy-shurun/p/4983260.html 二.配置负载 1.配置nginx.conf

  3. 使用 CUDA 进行计算优化的两种思路

    前言 本文讨论如何使用 CUDA 对代码进行并行优化,并给出不同并行思路对均值滤波的实现. 并行优化的两种思路 思路1: global 函数 在 global 函数中创建出多个块多个线程对矩阵每个元素 ...

  4. Bandicam视频录制技巧总结+小丸工具箱压缩视频解决视频体积问题

    1.视频录制. 录制质量建议选择100,保证原文件的质量才能更好地保证渲染转码后输出视频的质量.音效这里就一个关键点,就是编码器默认的MPEG-1 L2,会导致会声会影渲染输出出错,程序强行关闭,Ve ...

  5. UVa 10810 - Ultra-QuickSort

    题目大意:给出一个数列,每次交换相邻数字,求排成递增序的最少交换次数. 分析:求逆序数,合并排序 #include<cstdio>#include<cstring>#inclu ...

  6. App_Code 引起的 ambiguously 问题

    今天遇到了一个这样的问题,一个.net framework 4.0 的web application ,下面有一个App_Code文件夹,里面的一些公共类的build action 是 Compile ...

  7. tools/version.c

    /* *  linux/version.c * *  Copyright (C) 1992  Theodore Ts'o * *  May be freely distributed as part ...

  8. HDU 1029 Ignatius and the Princess IV --- 水题

    HDU 1029 题目大意:给定数字n(n <= 999999 且n为奇数 )以及n个数,找出至少出现(n+1)/2次的数 解题思路:n个数遍历过去,可以用一个map(也可以用数组)记录每个数出 ...

  9. POJ-2991 Crane(区间更新+向量旋转)

    题目大意:n个向量首尾相连,每次操作使某个区间中的所有向量都旋转同样的角度.每次操作后都回答最后一个向量的坐标. 题目分析:区间维护向量信息.向量旋转:x1=x0*cos(t)-y0*sin(t),y ...

  10. Kali安装

    Kali Linux 前身是 BackTrack ,不过Kali Linux是基于Debian 的 Linux 发行版,而BackTrack 则是基于Uubntu的,现在BackTrack 已经不更新 ...