AVL平衡二叉查找树

概述

本文主要分成两部分:

  • AVL平衡二叉树介绍
  • 源代码递归逻辑分析

一、AVL(Adelson-Velskii-Landis)平衡二叉查找树介绍

为了简便起见,后面我们把AVL平衡二叉查找树简称为AVL树。
什么是AVL树,一句话:一棵AVL树是每个节点的左子树和右子树的高度最多相差1的二叉查找树。
在下图中,左边是AVL树,右边不是,因为根结点的左子树的高度为2,但右子树的高度为0,两者相差大于1。
在这里插入图片描述
这是一种很好的二叉树,看下图,是不是很好看?,不仅好看,在结点相同的情况下,它的时间复杂度也是比较小的。
在这里插入图片描述
但是,当我们在进行插入或者删除操作的时候就出现了一些问题,插入或者删除一个结点后,原有的AVL树可能就不满足AVL树定义中的平衡条件了,那么怎么办呢?我们需要通过一些简单的旋转的操作来解决这个问题。下面我们就来解决这个问题。

我们先来确认几个事实,自己思考一下为什么?。
一、在插入以后,只有那些从插入点到根结点的路径上的结点的平衡可能会被改变。
二、我们沿着插入点往上寻找,找到第一个不平衡的点,只要这个点被旋转平衡,那么整棵树也就重新平衡。
三、我们把第一个不平衡的结点叫做 α \alpha α,容易看出,这种不平衡是由下列四种情况之一导致的。
1、对 α \alpha α的左儿子的左子树进行一次插入。
2、对 α \alpha α的左儿子的右子树进行一次插入。
3、对 α \alpha α的右儿子的左子树进行一次插入。
4、对 α \alpha α的右儿子的右子树进行一次插入。
第一种和第四种是对称的,我们只讨论第一种,第二种和第三种是对称的,我们只讨论第二种。对这两种情况,我们分别有两种方法,一种叫单旋转,一种叫双旋转

单旋转

在第一种情况下,我们把这种情况进行普适化,图示如下,图中的红色虚线表示树的各层
在这里插入图片描述
我们可以看到X所到达的层比Z要深两层,所以K2是不满足平衡条件的。所以需要调整,很简单,看到那两条带颜色的线条没,现在把它们的终点互换。然后拽着K1使劲地抖动,把K1抖成根结点。操作过程如下图所示。
在这里插入图片描述
这便是单旋转的普适思想。下面找几个例子操作一下,看看这种方法的效果。
话不多说,看图
在这里插入图片描述
再看图
在这里插入图片描述
应该说得很明白了吧,对称问题可以类推。

双旋转

首先我们来看看在第二种情况下,单旋转法能否解决。
第二种情况是什么:对 α \alpha α的左儿子的右子树进行一次插入。看下图
在这里插入图片描述
采用单旋转法试试看,看下图
在这里插入图片描述
嘿嘿?,是不是相当于变到了第三种情况,并没有让它恢复平衡,那怎么办呢,下面我们来介绍双旋转法。
首先是和单旋转法一样:普适性原理介绍。下面是第二种情况的普适化。
在这里插入图片描述
那么具体的操作步骤是什么样呢?如果你前面的单旋转理解得很好的话,那么这里应该很容易就能理解,看下图
在这里插入图片描述
就是一个左旋-右旋的操作,对称地,有右旋-左旋操作。

二、源代码递归逻辑分析

是时候展现真正的技术了,下面我们就要用代码来实现我们上面的思想。
头文件

#ifndef AVLTREE_H_INCLUDED
#define AVLTREE_H_INCLUDED

typedef struct Avl_Tree{
    int data;
    int height;//New member variable used to save height of the Node
    struct Avl_Tree *left;
    struct Avl_Tree *right;
}Avl_Tree;

Avl_Tree *Insert(Avl_Tree *T, int X);
static Avl_Tree *Single_Rotate_With_Left(Avl_Tree *K2);
static Avl_Tree *Single_Rotate_With_Right(Avl_Tree *K1);
static Avl_Tree *Double_Rotate_With_Left(Avl_Tree *K3);
static Avl_Tree *Double_Rotate_With_Right(Avl_Tree *K3);
int max(int a,int b);

#endif // AVLTREE_H_INCLUDED

返回结点高度

static int Height(Avl_Tree *T)
{
    if(T == NULL)
        return -1;
    else
        return T->height;
}

在左外插入,再来回顾一下那张图
在这里插入图片描述

static Avl_Tree *Single_Rotate_With_Left(Avl_Tree *K2)
{
    Avl_Tree *K1;

    K1 = K2->left;
    K2->left = K1->right;
    K1->right = K2;
    K2->height = max(Height(K2->left),Height(K2->right)) + 1;
    K1->height = max(Height(K1->left), K2->height) + 1;

    return K1;
}

在右外插入,对称地进行。
代码如下

static Avl_Tree *Single_Rotate_With_Right(Avl_Tree *K1)
{
    Avl_Tree *K2;

    K2 = K1->left;
    K1->right = K2->left;
    K2->left = K1;
    K1->height = max(Height(K1->left), Height(K1->right)) + 1;
    K2->height = max(Height(K2->right), K1->height) + 1;

    return K2;
}

在左内插入,再来看一下图
在这里插入图片描述
代码如下

static Avl_Tree *Double_Rotate_With_Left(Avl_Tree *K3)
{
    K3->left = Single_Rotate_With_Right(K3->left);

    return Single_Rotate_With_Left(K3);

}

在右内插入,对称地进行。
代码如下

static Avl_Tree *Double_Rotate_With_Right(Avl_Tree *K1)
{
    K1->right = Single_Rotate_With_Left(K1->right);
    return Single_Rotate_With_Right(K1);
}

插入元素

Avl_Tree *Insert(Avl_Tree *T, int X)
{
    if(T == NULL)
    {
        T = (Avl_Tree *)malloc(sizeof(Avl_Tree));
        if(T == NULL)
        {
            printf("Out of space !!");
            exit(1);
        }
        else
        {
            T->data = X;
            T->height = 0;
            T->left = T->right = NULL;
        }
    }
    else if(X < T->data)
    {
        T->left = Insert(T->left, X);
        if(Height(T->left) - Height(T->right) == 2)
        {
            if(X < T->left->data)
                T = Single_Rotate_With_Left(T);
            else
                T = Double_Rotate_With_Right(T);
        }
    }
    else if(X > T->data)
    {
        T->right = Insert(X, T->right);
        if(Height(T->right) - Height(T->right) == 2)
        {
            if(X > T->right->data)
                T = Single_Rotate_With_Left(T);
            else
                T = Double_Rotate_With_Left(T);
        }
    }

    T->height = max(Height(T->left), Height(T->right)) + 1;
    return T;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值