4. 数据结构 - AVL 树

这篇文章收录在我的 Github 上 algorithms-tutorial,另外记录了些算法题解,感兴趣的可以看看,转载请注明出处。

(一) AVL 树概念

AVL 树(也可以称为平衡树)是一类数据结构,是改进后的二叉查找树。一般的二叉树的查询复杂度是跟目标结点到树根的距离 (即树深) 有关,因此如果当结点的深度普遍比较大时,查询的成本就会上升,所以 AVL 树就应运而生。

AVL 树可以理解为平衡树树的一种具体实现,两者并无二致。这里介绍的平衡树跟老师说的AVL 树不一样。

一、AVL 树特性:

  1. AVL 树本身首先得是一棵二叉搜索树
  2. 每个结点的左右子树的高度之差的绝对值 (平衡因子) 不超过 1
  3. 每个结点的左右子树也要满足特性 2,也是平衡二叉树

平衡因子:某结点的左子树与右子树的高度(深度)差即为该结点的平衡因子

AVL 树上所有结点的平衡因子只可能是 -1,0 或 1。

比如:

AVL 树:

1.png

non-AVL 树:根节点无左子树

2.png

二、为什么使用 AVL 树?

3.png

我们都知道二叉搜索树可以具有良好的查询,插入等操作,但是如果二叉树退化成了链式树(上图右),那么其空间复杂度也会从原本 O(logn) 退化成了 O(n),这样显然是不想看到的结果。

如果可以让树维持矮矮胖胖的好身材, 也就是让高度维持在 O(log n)左右, 完成上述工作就很省时间。能够一直维持好身材, 不因新增删除而长歪的搜寻树, 叫做 AVL 树。

(二) AVL 树基本操作

相对于二叉查找树的节点来说,我们需要用一个属性二叉树的高度,目的是维护插入和删除过程中的旋转算法。

JAVA 代码实现:

public class AVLTree{
    TreeNode leftChild = null;
    TreeNode rightChild = null;
    int height;
    String data;
}

那么我们在对一棵二叉查找树进行插入和删除操作的时候,如何让其保持 AVL 树的特性呢?

一、旋转:

假设有一个结点的平衡因子为 2(在 AVL 树中,因为结点是一个个插入到树中,一旦出现不平衡的状态就会立即调整,因此平衡因子最大不可能超过 2)

那么这时候就要进行调整了,由于任意结点最多只有两个儿子,所以当高度不平衡时候,无非以下情况所造成:

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

情况 1 和 4 是关于该点的镜像对称,同样,情况 2 和 3 也是一对镜像对称。

当那个结点高度出现不平衡,我们就要对那个结点进行操作,这里我们称这个不平衡结点为当前节点。

二、单旋转:

情况 1 和情况 4类似,我们可以理解为发生在 “外边” 的情况 (即左-左,右-右的情况) 该情况只需要通过一次旋转来完成调整

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

4.png

此时结点 k2 出现了不平衡,所以我们以 k2 为当前节点进行右旋,并将 Y 移到 k2 左子结点,从而变成右图(因为左边高度高,所以我们将左子树上移,来平衡其高度差,很好理解)

代码实现:

public AVLTree singleRotateWithRight(AVLTree unbalancedNode){
    AVLTree node = unbalancedNode.leftChild;
    unbalancedNode.leftChild = node.rightChild;
    node.rightChild = unbalancedNode;
    node.height = MAX(node.leftChild.height, node.rightChild.height) + 1;
    unbalancedNode.height = MAX(unbalancedNode.leftChild.height, unbalancedNode.rightChild.height) + 1;
    return node;   //返回新的根结点
}

实例:插入结点 6

1.gif

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

6.png

思路和情况 1 类似,操作刚好相反过来

代码实现:

public AVLTree singleRotateWithLeft(AVLTree unbalancedNode){
    AVLTree node = unbalancedNode.rightChild;
    unbalancedNode.rightChild = node.leftChild;
    node.leftChild = unbalancedNode;
    node.height = MAX(node.leftChild.height, node.rightChild.height) + 1;
    unbalancedNode.height = MAX(unbalancedNode.leftChild.height, unbalancedNode.rightChild.height) + 1;
    return node;   //返回新的根结点
}

实例:插入结点 6

2.gif

三、双旋转:

情况 2 和情况 3 (由于其形状可以称为 dog-leg)是插入发生在“内部”的情况(即左-右的情况或右-左的情况),该情况要通过稍微复杂些的双旋转来处理。

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

这种情况是单旋转调整不回来的,如下图,旋转后还是不平衡

8.png

这时候需要旋转两次来达到平衡效果:

9.png

以 k1 为当前结点进行左旋,再以 k3 为当前结点进行右旋

代码实现:

public AVLTree doubleRotateWithRight(PAVLNode unbalancedNode){    //之所以称为 Right,是因为最后不平衡结点变为右子树,这样比较好记
    /* Rotate between K1 and K2 */
    AVLTree node = singleRotateWithLeft(unbalancedNode.leftChild); //返回值为 K2 
    node.parent = unbalancedNode;
    /* Rotate between K3 and K2 */
    return singleRotateWithRight(unbalancedNode);  //最后返回的是 k2 结点,即根结点
}

例如:往该树中插入结点 9

4.gif

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

类似需要旋转两次来达到平衡效果:

10.png

以 k3 为当前结点进行右旋,再以 k2 为当前结点进行左旋

代码实现:

public AVLTree doubleRotateWithLeft(PAVLNode unbalancedNode){   
    /* Rotate between K3 and K2 */
    AVLTree node = singleRotateWithRight(unbalancedNode.rightChild); //返回值为 K2 
    node.parent = unbalancedNode;
    /* Rotate between K1 and K2 */
    return singleRotateWithLeft(unbalancedNode);  //最后返回的是 k2 结点,即根结点
}

例如:往该树中插入结点 11

3.gif

插入操作:

插入的核心思路是通过递归找到合适的位置,插入新结点,然后看新结点是否平衡(平衡因子是否为2),如果不平衡的话,就对其进行旋转操作:

  1. 在结点的左儿子(X < T->item):
    在左儿子的左子树(X < T->l-> item): “外边”,要做单旋转。
    在左儿子的右子树(X > T->l-> item): “内部”,要做双旋转。
  2. 在结点的右儿子(X > T->item):
    在右儿子的左子树(X < T->r-> item),“内部”,要做双旋转。
    在右儿子的右子树(X > T->r-> item),“外边”,要做单旋转。
  3. (X == T->item) ,对该节点的计数进行更新。

课上的两个例题:构建一棵 AVL 树

往树中按顺序插入 342, 206, 444, 523, 607, 301, 142, 183, 102, 157, 149

5.gif

往树中按顺序插入 47, 23, 52, 14, 59, 29, 36, 38, 26

6.gif

参考链接:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容