AVL平衡树 - 二叉搜索树的推广

    • AVL树的介绍
    • 实现
      • 1 旋转

        • LL左左
        • RR右右
        • LR左右
        • RL右左
      • 2 插入
      • 3 删除
    • 性能
    • 完整代码和参考资料

1. AVL树的介绍

AVL树是根据它的发明者G.M. Adelson-Velsky和E.M. Landis命名的。

它是最先发明的自平衡二叉查找树,也被称为高度平衡树。相比于”二叉查找树”,它的特点是:AVL树中任何节点的两个子树的高度最大差别为1

如上图(左):非AVL树,如对节点9,左子树高度为0,右子树高度为2

如上图(由):AVL树,对任意节点的两个子树高度最大差1

2. 实现

当因为插入或删除导致AVL的平衡被破坏时,需要旋转处理来修复。

如果在AVL树中进行插入或删除节点后,可能导致AVL树失去平衡。这种失去平衡的可以概括为4种姿态:LL(左左),LR(左右),RR(右右)和RL(右左)

对于左右的情况为什么不是直接以3节点为轴向左旋转? 因为旋转只能处理根节点和它的左节点(或右节点),来把它的左节点(右节点)提升到根。换句话说,旋转的输入参数(轴)必须是根,提升的只能是字节点,下降的只能是根。

(多级旋转,必须是从底层开始,到顶层结束)

2.1 旋转

LL左左

因为插入或删除,使得根节点的左节点的左节点非空,导致根的左子树比右子树高度大2;只需以根为输入,一次右旋,将原根的左节点旋转到根

//LL情况时,一次右旋--k2是根,k1是k2的左子树,将k1旋转成根 -- 以k2为原点向右旋
AVLTree rotLL(AVLTree k2)
{
    AVLTree k1 = k2->left;
    k2->left = k1->right;
    k1->right = k2;
    k2->height = MAX(AVLTreeHeight(k2->left), AVLTreeHeight(k2->right)) + 1;
    k1->height = MAX(AVLTreeHeight(k1->left), k2->height) + 1;
    return k1;
}

RR右右

因为插入或删除,使得根节点的右节点的右节点非空,导致根的右子树比左子树高度大2;只需以根为输入,一次左旋,将原根的右节点旋转到根

//RR情况时,一次左旋---k2是根,k1是k2的右子树,(k1的右子树非空)将k1旋转成根 -- 以k2为原点向左旋
AVLTree rotRR(AVLTree k2)
{
    AVLTree k1 = k2->right;
    k2->right = k1->left;
    k1->left = k2;
    k2->height = MAX(AVLTreeHeight(k2->left), AVLTreeHeight(k2->right)) + 1;
    k1->height = MAX(k2->height, AVLTreeHeight(k1->right)) + 1;
    return k1;
}

LR左右

插入或删除一个节点后,根节点的右子树的左子树还有非空子节点,导致”根的右子树的高度”比”根的左子树的高度”大2,导致AVL树失去了平衡。需要2次旋转,先以根的右节点为输入做一次左旋(即RR的情况),此时树的形状为LL,再做一次右旋(即LL)。

//LR情况时,先左旋在右旋:k3是根,k1是k3的左子树,k2是k1的右子树,
// 那么先以k1为原点左旋,再以k3为原点右旋,最终把k2旋转到根(相当于先RR旋转,再LL旋转)
AVLTree rotLR(AVLTree k3)
{
    k3->left = rotRR(k3->left); //从底层开始到顶层结束
    return rotLL(k3);
}

AVLTree rotRL(AVLTree k3)
{
    k3->right = rotLL(k3->right); //从底层开始到顶层结束
    return rotRR(k3);
}

RL右左

类似于LR情况,不做赘述

2.2 插入

首先,AVL的插入和搜索二叉树的插入相同。不同的是插入会影响AVL的平衡,当因为插入导致不平衡时(左右子树高度差2),同样存在上述的四种情况,需要对应的旋转处理。

AVLTree AVLTreeInsert(AVLTree tree, Item key)
{
    if (tree == NULL)
        return NewNode(key, NULL, NULL);
    if (key < tree->key) {
        //插入到左子树
        tree->left = AVLTreeInsert(tree->left, key);
        //由于插入到左子树上的节点导致不平衡
        if (Height(tree->left) - Height(tree->right) > 1) {
            //插入到左子树的左侧。则是LL,反之则是LR
            if (key < tree->left->key)
                tree = rotLL(tree);
            else
                tree = rotLR(tree);
        }
    } else if (key > tree->key) {
        tree->right = AVLTreeInsert(tree->right, key);
        if (Height(tree->right) - Height(tree->left) > 1) {
            if (key > tree->right->key)
                tree = rotRR(tree);
            else
                tree = rotRL(tree);
        }
    } else {
        printf("\nExsited key = %d, Insert Failed\n", key);
        // return tree;
    }

    tree->height = MAX(Height(tree->left), Height(tree->right)) + 1;
    return tree;
}

2.3 删除

删除同样可能影响AVL的平衡。

- 比如说,如果因为删除左子树中的节点,导致右子树比左子树高度大2,那么此时再观察右子树的左右子树的高度,若右子树的右子树比左子树高,那么则是RR的情况,反之则是RL的情况。

- 而针对于匹配到待删除节点 : 如果恰好是根节点:那么删除根后,左右子树高度还是最大差1,不需旋转;如果不是根(即待删除的在左右子树中),那么在上述两种情况已经判断了是否旋转。

- 对于删除节点后的连接:如果左右子树都非空

若左子树高,则找出左子树的最大值替换根的值,然后在左子树中删除该最大值的节点;

若右子树高,则找出右子树的最小值替换根的值,然后在右子树中删除该最小值的节点;

这样保证了AVL以更大的可能维持平衡。

AVLTree AVLTreeDelete(AVLTree tree, Item key)
{
    if (tree == NULL)
        return NULL;
    //待删除节点在左子树中
    if (key < tree->key) {
        tree->left = AVLTreeDelete(tree->left, key);
        //左子树中删除,若右子树高度比左大2(失去平衡),将右子树中的某节点旋转到根,降低高度
        if (Height(tree->right) - Height(tree->left) > 1) {
            if (Height(tree->right->right) > Height(tree->right->left))
                tree = rotRR(tree);
            else
                tree = rotRL(tree);
        }

    } else if (key > tree->key) {
        tree->right = AVLTreeDelete(tree->right, key);
        //右子树中删除,左子树高度比右大2
        if (Height(tree->left) - Height(tree->right) > 1) {
            if (Height(tree->left->left) > Height(tree->left->right))
                tree = rotLL(tree);
            else
                tree = rotLR(tree);

        }
    }  else {//匹配到待删除
        //待删除节点两字树非空
        if (tree->left && tree->right) {
            if (Height(tree->left) > Height(tree->right)) {
                //左高,找到左子树中的最大值,替换根的值
                AVLTree avl_max = findAVLMax(tree->left);
                tree->key = avl_max->key;
                AVLTreeDelete(tree->left, avl_max->key);
            } else {
                //右高,找到右子树中的最小值,替换根的值
                AVLTree avl_min = findAVLMin(tree->right);
                tree->key = avl_min->key;
                AVLTreeDelete(tree->right, avl_min->key);
            }
        } else {
            //有一个为空,或者2个为空
            AVLTree tmp = tree;
            tree = (tree->left == NULL) ? (tree->right) : (tree->left);
            free(tmp);
            return tree;
        }
    }
    return tree;
}

3. 性能

  1. 向AVL树插入,可以透过如同它是未平衡的二叉查找树一样,把给定的值插入树中,接着自底往上向根节点折回,于在插入期间成为不平衡的所有节点上进行旋转来完成。因为折回到根节点的路途上最多有1.44乘log n个节点,即O(log n),而每次AVL旋转都耗费固定的时间,所以插入处理在整体上的耗费为O(logn)时间。
  2. 从AVL树中删除,可以透过把要删除的节点向下旋转成一个叶子节点,接着直接移除这个叶子节点来完成。因为在旋转成叶子节点期间最多有log n个节点被旋转,而每次AVL旋转耗费固定的时间,所以删除处理在整体上耗费O(log n) 时间。本文没有采用
  3. 搜索,可以像普通二叉查找树一样的进行,所以耗费O(log n)时间,因为AVL树总是保持平衡的。不需要特殊的准备,树的结构不会由于查找而改变。(这是与伸展树搜寻相对立的,它会因为搜寻而变更树结构。)

4. 完整代码和参考资料

#include <stdio.h>
#include <stdlib.h>
#define MAX(A, B) ((A > B) ? A : B)
#define Height(tree) ((tree == NULL) ? 0 : (tree->height))
typedef int Item;
typedef struct AVLTreeNode AVLTreeNode;
typedef AVLTreeNode* AVLTree;
struct AVLTreeNode {
    Item key;
    AVLTree left;
    AVLTree right;
    int height;
};
static int g_error = 0; //错误代码
AVLTree NewNode(Item key, AVLTree left, AVLTree right)
{
    AVLTree x = (AVLTree)malloc(sizeof(*x));
    if (x == NULL) {
        g_error = 1;
        exit(-1);
    }
    x->key = key;
    x->left = left;
    x->right = right;
    x->height = MAX(Height(x->left), Height(x->right)) + 1;
    return x;
}

AVLTree AVLTreeInit()
{
    return NewNode(10, NULL, NULL);
}

int AVLTreeHeight(AVLTree tree)
{
    return (tree == NULL) ? 0 : (tree->height);
}

//RR情况时,一次左旋---k2是根,k1是k2的右子树,(k1的右子树非空)将k1旋转成根 -- 以k2为原点向左旋
AVLTree rotRR(AVLTree k2)
{
    AVLTree k1 = k2->right;
    k2->right = k1->left;
    k1->left = k2;
    k2->height = MAX(AVLTreeHeight(k2->left), AVLTreeHeight(k2->right)) + 1;
    k1->height = MAX(k2->height, AVLTreeHeight(k1->right)) + 1;
    return k1;
}

//LL情况时,一次右旋--k2是根,k1是k2的左子树,将k1旋转成根 -- 以k2为原点向右旋
AVLTree rotLL(AVLTree k2)
{
    AVLTree k1 = k2->left;
    k2->left = k1->right;
    k1->right = k2;
    k2->height = MAX(AVLTreeHeight(k2->left), AVLTreeHeight(k2->right)) + 1;
    k1->height = MAX(AVLTreeHeight(k1->left), k2->height) + 1;
    return k1;
}

//LR情况时,先左旋在右旋:k3是根,k1是k3的左子树,k2是k1的右子树,
// 那么先以k1为原点左旋,再以k3为原点右旋,最终把k2旋转到根(相当于先RR旋转,再LL旋转)
AVLTree rotLR(AVLTree k3)
{
    k3->left = rotRR(k3->left); //从底层开始到顶层结束
    return rotLL(k3);
}

AVLTree rotRL(AVLTree k3)
{
    k3->right = rotLL(k3->right); //从底层开始到顶层结束
    return rotRR(k3);
}

AVLTree AVLTreeInsert(AVLTree tree, Item key)
{
    if (tree == NULL)
        return NewNode(key, NULL, NULL);
    if (key < tree->key) {
        tree->left = AVLTreeInsert(tree->left, key);
        if (Height(tree->left) - Height(tree->right) > 1) {
            if (key < tree->left->key)
                tree = rotLL(tree);
            else
                tree = rotLR(tree);
        }
    } else if (key > tree->key) {
        tree->right = AVLTreeInsert(tree->right, key);
        if (Height(tree->right) - Height(tree->left) > 1) {
            if (key > tree->right->key)
                tree = rotRR(tree);
            else
                tree = rotRL(tree);
        }
    } else {
        printf("\nExsited key = %d, Insert Failed\n", key);
        // return tree;
    }

    tree->height = MAX(Height(tree->left), Height(tree->right)) + 1;
    return tree;
}

void traversal(AVLTree tree)
{
    if (tree == NULL) {
        printf("NIL\t");
        return;
    }
    printf("%d\t", tree->key);
    traversal(tree->left);
    traversal(tree->right);
    return;
}
AVLTree findAVLMin(AVLTree tree)
{
    if(tree == NULL || tree->left == NULL)
        return tree;
    return findAVLMin(tree->left);
}
AVLTree findAVLMax(AVLTree tree)
{
    if (tree == NULL || tree->right == NULL)
        return tree;
    return findAVLMax(tree->right);
}

AVLTree AVLTreeDelete(AVLTree tree, Item key)
{
    if (tree == NULL)
        return NULL;
    //待删除节点在左子树中
    if (key < tree->key) {
        tree->left = AVLTreeDelete(tree->left, key);
        //左子树中删除,右子树高度比左大2,将右子树中的某节点旋转到根,降低高度
        if (Height(tree->right) - Height(tree->left) > 1) {
            if (Height(tree->right->right) > Height(tree->right->left))
                tree = rotRR(tree);
            else
                tree = rotRL(tree);
        }

    } else if (key > tree->key) {
        tree->right = AVLTreeDelete(tree->right, key);
        //右子树中删除,左子树高度比右大2
        if (Height(tree->left) - Height(tree->right) > 1) {
            if (Height(tree->left->left) > Height(tree->left->right))
                tree = rotLL(tree);
            else
                tree = rotLR(tree);

        }
    }  else {       //匹配到待删除节点 : 如果恰好是根节点:那么删除跟后,左右子树高度还是最大差1,不需旋转;如果不是根,那么在上述两种情况判断是否旋转
        //待删除节点两字树非空
        if (tree->left && tree->right) {
            if (Height(tree->left) > Height(tree->right)) {
                //左高,找到左子树中的最大值,替换根的值
                AVLTree avl_max = findAVLMax(tree->left);
                tree->key = avl_max->key;
                AVLTreeDelete(tree->left, avl_max->key);
            } else {
                //右高,找到右子树中的最小值,替换根的值
                AVLTree avl_min = findAVLMin(tree->right);
                tree->key = avl_min->key;
                AVLTreeDelete(tree->right, avl_min->key);
            }
        } else {
            AVLTree tmp = tree;
            tree = (tree->left == NULL) ? (tree->right) : (tree->left);
            free(tmp);
            return tree;
        }
    }
    return tree;
}

int main()
{
    AVLTree avl_tree = NULL;
    for (int i = 0; i < 10; i++) {
        int key = rand()%100;
        avl_tree = AVLTreeInsert(avl_tree, key);
        printf("%d\t", key);
    }
    printf("\nTraversal\n");
    traversal(avl_tree);

    AVLTreeDelete(avl_tree, 41);
    printf("\nDeleted Traversal\n");
    traversal(avl_tree);
    getchar();
}

参考资料:

1. AVL树(一)之 图文解析 和 C语言的实现:http://www.cnblogs.com/skywang12345/p/3576969.html

2. AVL树-维基百科:https://zh.wikipedia.org/wiki/AVL%E6%A0%91

时间: 2024-12-17 05:18:48

AVL平衡树 - 二叉搜索树的推广的相关文章

【算法学习】AVL平衡二叉搜索树原理及各项操作编程实现(C语言)

#include<stdio.h> #include "fatal.h" struct AvlNode; typedef struct AvlNode *Position; typedef struct AvlNode *AvlTree; typedef int ElementType ; AvlTree MakeEmpty(AvlTree T); Position Find(ElementType X,AvlTree T); Position FindMin(AvlTre

数据结构中常见的树(BST二叉搜索树、AVL平衡二叉树、RBT红黑树、B-树、B+树、B*树)

树 即二叉搜索树: 1.所有非叶子结点至多拥有两个儿子(Left和Right): 2.所有结点存储一个关键字: 非叶子结点的左指针指向小于其关键字的子树,右指针指向大于其关键字的子树: 如: BST树的搜索,从根结点开始,如果查询的关键字与结点的关键字相等,那么就命中: 如果BST树的所有非叶子结点的左右子树的结点数目均保持差不多(平衡),那么B树 的搜索性能逼近二分查找:但它比连续内存空间的二分查找的优点是,改变BST树结构 插入与删除结点)不需要移动大段的内存数据,甚至通常是常数开销: 如:

数据结构之二叉搜索树、AVL自平衡树

前言 最近在帮公司校招~~ 所以来整理一些数据结构方面的知识,这些知识呢,光看一遍理解还是很浅的,看过跟动手做过一遍的同学还是很容易分辨的哟~ 一直觉得数据结构跟算法,就好比金庸小说里的<九阳神功>,学会九阳神功后,有了内功基础,再去学习其他武功,速度就有质的提升 内容大概包含这些,会分多篇文章来整理: 二叉搜索树 平衡二叉树(AVL) 二叉堆 堆排序 四叉树 八叉树 图,深度优先DFS.广度优先BFS 最短路径 二叉树 二叉树,也就是每个节点最多有两个孩子的树.多用于搜索,查找,还有可以用来

【数据结构】第9章 查找! (二叉搜索树BST AVL树 B-(+)树 字典树 HASH表)

难产的笔记...本来打算用1天 结果前前后后拖了5天 §9.1 静态查找表 9.1.1 顺序表的查找 各种扫 自己脑补吧 复杂度O(n) 9.1.2 有序表的查找 若表是单调的,则可以利用二分查找.复杂度O(logn) 9.1.3 静态树表的查找 见 http://blog.csdn.net/area_52/article/details/43795837 9.1.4 索引顺序表的查找 建立索引表查找 §9.2 动态查找表 动态查找表的特点是,表结构本身是在查找过程中动态生成的,即对于给定值ke

平衡二叉搜索树(AVL树)的原理及实现源代码(有图文详解和C++、Java实现代码)

一.AVL树(平衡二叉搜索树)是什么? AVL树是根据它的发明者G.M. Adelson-Velsky和E.M. Landis命名的.AVL树本质上还是一棵二叉搜索树,它的特点是: 1.本身首先是一棵二叉搜索树. 2.带有平衡条件:每个非叶子结点的左右子树的高度之差的绝对值(平衡因子)最多为1. 例如: 5             5 / \            /  \ 2   6         2   6 / \    \         / \ 1  4   7       1  4

算法二叉搜索树之AVL树

最近学习了二叉搜索树中的AVL树,特在此写一篇博客小结. 1.引言 对于二叉搜索树而言,其插入查找删除等性能直接和树的高度有关,因此我们发明了平衡二叉搜索树.在计算机科学中,AVL树是最先发明的自平衡二叉搜索树.在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树.对于N个节点的AVL树,由于树高被限制为lgN,因此其插入查找删除操作耗时为O(lgN). 2.旋转 在讲解关键步骤插入与删除以前,首先我们先定义一些辅助用的操作:旋转.旋转分为左旋和右旋,其示意图如下: 相信上

PAT树_层序遍历叶节点、中序建树后序输出、AVL树的根、二叉树路径存在性判定、奇妙的完全二叉搜索树、最小堆路径、文件路由

<pre class="code"><span style="font-family: %value; font-size: 14px;">03-树1. List Leaves (25) Given a tree, you are supposed to list all the leaves in the order of top down, and left to right. Input Specification: Each inpu

二叉树、二叉搜索树、AVL树的java实现

数据结构一直都是断断续续的看,总是觉得理解的不够深入,特别是对树的理解,一直都很浅显,今儿又看了一遍,来做个总结吧. 首先,树中的一些概念: 1.树的节点包含一个数据元素,以及若干指向其子树的分支.节点拥有的子树的数量称为节点的度.节点的最大层次称为树的深度或高度. 2.二叉树是一种树形结构,其特点是每个节点至多有两棵子树,且子树有左右之分,次序不能随意颠倒. 3.满二叉树:一棵深度为k且有2^k - 1个节点的二叉树,称之为满二叉树. 4.完全二叉树:对一个深度为k,节点个数为n的二叉树,当且

数据结构 - 从二叉搜索树说到AVL树(一)之二叉搜索树的操作与详解(Java)

二叉搜索树(Binary Search Tree),简称BST,顾名思义,一颗可以用于搜索的二叉树.BST在数据结构中占有很重要的地位,一些高级树结构都是其的变种,例如AVL树.红黑树等,因此理解BST对于后续树结构的学习有很好的作用.同时利用BST可以进行排序,称为二叉排序,也是很重要的一种思想. 二叉树的性质:任意一个节点的所有左子树元素都比该元素值要小,而所有右子树元素都比该元素值要大. 符合该性质的二叉树就是一颗二叉搜索树,当然前提下是树中不允许有重复元素. 所有的二叉搜索树的中序遍历序