数据结构(七)——查找之平衡二叉树

本文介绍了平衡二叉树的概念,如平衡因子和AVL树,并详细阐述了在不同BF值异常情况下如何通过旋转操作保持平衡,包括LL、RR、LR和RL旋转。同时,给出了插入节点的算法思想和源代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

代码中所用到的结构体

typedef struct node
{
    int key;//关键字
    struct node*left;
    struct node*right;
    int height;//当前结点所在的高度
}BTNode;

构造平衡二叉树算法的主要思想

为了提高查找的效率,于是在二叉排序树的基础上,有了二叉平衡树的诞生。其中,先介绍几个重要的概念:

  • 平衡因子(BF):任一结点的左子树的高度减去右子树的高度所得的高度差称为该结点的平衡因子BF
  • AVL树:它的左子树和右子树都是AVL树; 且左子树和右子树的高度之差的绝对值不超过1。

重要性质:

  • 一棵平衡二叉排序树如果有n个结点,其高度可保持在O(log2n),平均查找长度也可保持在O(log2n)

算法思想

  • 通过递归的方法,来对二叉树进行构造,在BF值为正常值(即-1,0,1)的情况下,不对该二叉树进行特殊操作。
  • 由于是使用递归的算法。故BF值得计算是从下往上计算,计算方法为当前结点的左儿子高度减去右儿子高度。
  • 再通过插入关键值和BF值不正常结点的比较来决定进行哪种旋转

创建新结点

BTNode* newNode(int key)//初始化新的结点
{
    BTNode *N = (BTNode*)malloc(sizeof(BTNode));
    N->key = key;
    N->left = NULL;
    N->right = NULL;
    N->height = 1;
    return N;
}

当插入点在BF值异常点的左子树的左子树上

简称为左左插入,此时执行的是顺时针旋转

BTNode* LL_rotate(BTNode* y)//左左插入(顺旋转)
{
    BTNode* x = y->left;//申请一个新的结构指针指向y的左结点
    y->left = x->right;
    x->right = y;
    x->height = max(height(x->left),height(x->right));//对其高度进行更新
    y->height = max(height(y->left),height(y->right));
    return x;//返回根节点
}

当插入点在BF值异常点的右子树的右子树上

简称为右右插入,此时执行的是逆时针旋转

BTNode* RR_rotate(BTNode* y)//右右插入(逆旋转)
{
    BTNode* x = y->right;
    y->right = x->left;
    x->left = y;
    x->height = max(height(x->left),height(x->right));//对其高度进行更新
    y->height = max(height(y->left),height(y->right));
    return x;
}

当插入点在BF值异常点的左子树的右子树上

简称为左右插入,此时执行的是逆顺时针旋转,这个时候就调用一次逆旋转再调用一次顺旋转即可。

BTNode* LR_rotate(BTNode* y)//左右插入(逆顺旋转)
{
    y->left = RR_rotate(y->left);
    y = LL_rotate(y);
    return y;
}

当插入点在BF值异常点的右子树的左子树上

简称为右左插入,此时执行的是顺逆时针旋转,这个时候就调用一次顺旋转再调用一次逆旋转即可。

BTNode* RL_rotate(BTNode* y)//右左插入(顺逆旋转)
{
    y->right = LL_rotate(y->right);
    y = RR_rotate(y);
    return y;
}

基于顺逆旋转的理解,本人理解的是以插入路径上的点离BF值异常点最近的BF值为1或-1的点进行的顺、逆旋转

插入代码

首先执行的是插入节点的代码,要判断的情况有四种

  • 判断当前节点是否为NULL,若是,则插入
  • 判断当前节点的参数是否大于要插入的结点的参数,若是,则将当前结点的左儿子作为参数,进入下一层递归
  • 判断当前节点的参数是否小于要插入的结点的参数,若是,则将当前结点的右儿子作为参数,进入下一层递归
  • 若前三种情况均为假,则当前结点的参数等于要插入的结点的参数。若是这种情况,则直接返回,不返回任何参数。

以上的插入代码执行完后开始计算BF值,并旋转
BF值的计算是以左儿子的高度减去右儿子的高度得到的
当BF值异常时,会有四种旋转

  • 当插入结点的关键字小于当前结点的左儿子的关键字时,表示该结点插在了BF异常结点的左子树的左子树上,则进行LL旋转
  • 当插入结点的关键字大于当前结点的左儿子的关键字时,表示该结点插在了BF异常结点的左子树的右子树上,则进行LR旋转
  • 当插入结点的关键字大于当前结点的右儿子的关键字时,表示该结点插在了BF异常结点的右子树的右子树上,则进行RR旋转
  • 当插入结点的关键字小于当前结点的右儿子的关键字时,表示该结点插在了BF异常结点的右子树的左子树上,则进行RL旋转
BTNode* insert(BTNode* T,BTNode *N)//二叉平衡树的插入操作
{
    int BF;
    if(T==NULL)//当前结点为空
    {
        return N;
    }
    else if(T->key > N->key)
    {
        T->left = insert(T->left,N);
    }
    else if(T->key < N->key)
    {
        T->right = insert(T->right,N);
    }
    else
    {
        return;
    }
    T->height = 1 + max(height(T->left),height(T->right));//计算当前节点的高度
    BF = getBlance(T);//得到BF值
    if(BF>1&&N->key<T->left->key)
    {
        T = LL_rotate(T);
    }
    else if(BF>1&&N->key>T->left->key)
    {
        T = LR_rotate(T);
    }
    else if(BF<-1&&N->key>T->right->key)
    {
        T = RR_rotate(T);
    }
    else if(BF<-1&&N->key<T->right->key)
    {
        T = RL_rotate(T);
    }
    return T;
}

源代码

#include <stdio.h>
#include <stdlib.h>

typedef struct node
{
    int key;//关键字
    struct node*left;
    struct node*right;
    int height;//当前结点所在的高度
}BTNode;

int max(int a,int b)//返回最大值
{
    if(a>b)
        return a;
    else
        return b;
}

int height(BTNode *N)//返回当前节点所在高度
{
    if(N==NULL)
    {
        return 0;
    }
    else
        return N->height;
}

BTNode* newNode(int key)//初始化新的结点
{
    BTNode *N = (BTNode*)malloc(sizeof(BTNode));
    N->key = key;
    N->left = NULL;
    N->right = NULL;
    N->height = 1;
    return N;
}

BTNode* LL_rotate(BTNode* y)//左左插入(顺旋转)
{
    BTNode* x = y->left;//申请一个新的结构指针指向y的左结点
    y->left = x->right;
    x->right = y;
    x->height = max(height(x->left),height(x->right));//对其高度进行更新
    y->height = max(height(y->left),height(y->right));
    return x;//返回根节点
}

BTNode* RR_rotate(BTNode* y)//右右插入(逆旋转)
{
    BTNode* x = y->right;
    y->right = x->left;
    x->left = y;
    x->height = max(height(x->left),height(x->right));//对其高度进行更新
    y->height = max(height(y->left),height(y->right));
    return x;
}

BTNode* LR_rotate(BTNode* y)//左右插入(逆顺旋转)
{
    y->left = RR_rotate(y->left);
    y = LL_rotate(y);
    return y;
}

BTNode* RL_rotate(BTNode* y)//右左插入(顺逆旋转)
{
    y->right = LL_rotate(y->right);
    y = RR_rotate(y);
    return y;
}

int getBlance(BTNode *N)//或得当前结点的BF值
{
    if(N==NULL)
        return 0;
    return height(N->left)-height(N->right);
}

BTNode* insert(BTNode* T,BTNode *N)//二叉平衡树的插入操作
{
    int BF;
    if(T==NULL)//当前结点为空
    {
        return N;
    }
    else if(T->key > N->key)
    {
        T->left = insert(T->left,N);
    }
    else if(T->key < N->key)
    {
        T->right = insert(T->right,N);
    }
    else
    {
        return;
    }
    T->height = 1 + max(height(T->left),height(T->right));//计算当前节点的高度
    BF = getBlance(T);//得到BF值
    if(BF>1&&N->key<T->left->key)
    {
        T = LL_rotate(T);
    }
    else if(BF>1&&N->key>T->left->key)
    {
        T = LR_rotate(T);
    }
    else if(BF<-1&&N->key>T->right->key)
    {
        T = RR_rotate(T);
    }
    else if(BF<-1&&N->key<T->right->key)
    {
        T = RL_rotate(T);
    }
    return T;
}
void inOrderTraverse(BTNode *p)//中序遍历
{
    if(p->left!=NULL)//判断是否存在左儿子
    {
        inOrderTraverse(p->left);//向左儿子走一步
    }
    printf("%d ",p->key);
    if(p->right!=NULL)//判断是否存在右儿子
    {
        inOrderTraverse(p->right);
        return;
    }
    else if(p->right==NULL)
    {
        return;
    }
}
int main()
{
    int n,key;
    int i;
    BTNode* N;
    BTNode* T;
    T=NULL;
    printf("请输入平衡二叉树的点的个数:\n");
    scanf("%d",&n);
    getchar();
    printf("请输入数据:\n");
    for(i=0;i<n;i++)
    {
        scanf("%d",&key);
        getchar();
        N = newNode(key);
        T = insert(T,N);
    }
    inOrderTraverse(T);

    return 0;
}

测试样例
10
25
27
30
12
11
18
14
20
15
22
测试结果
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值