当前位置: 首页 > news >正文

AVL树 c语言版本 插入部分

目录

引入平衡树

为什么要变平衡

怎么判断是否需要变平衡

怎么变平衡

LL型失衡

RR型失衡

LR型失衡

RL型失衡

补充

左旋补充

右旋补充

Code

开辟一个新节点

 初始化

获取树的高度

左旋函数

更新树高

树高的求法

右旋转函数

插入

InsertNode()

更新树高

getbalance()

根据balance判断是否失衡

RL型失衡​编辑如图(f-1)所示:

剩下的LR型失衡,RR型失衡,LL型失衡都同理,写为为如下这个样子:

测试函数

perOrder()

midOrder()

Find()

测试

代码托管


引入平衡树

假设我们有两个节点:当我们插入第三个节点,就失衡了:此刻我们就要把它平衡一下。

为什么要变平衡

为什么说它失衡了呢,又为什么要把它变平衡?

如图a,假设我们要查找30这个节点就要查3次才能找到

但是如果平衡之后(图b)就只需要2次就可以找到了,这样可以提高效率,这两个图不够明显,如果是100个节点的图就够明显了。

怎么判断是否需要变平衡

我们引入 平衡因子的概念

平衡因子 = 根节点的 左孩子高度 -  右孩子高度

当 平衡因子的绝对值 > 1 时,我们认为这个树是失衡的,需要变为平衡。

如图(c-1):

 根节点( 10 ) 的左孩子高度(0) - 右孩子高度(2)= 平衡因子 (-2)

| -2 | > 1,即该数树为失衡的树。

怎么变平衡

首先,假如有这样一个二叉搜索树:

LL型失衡

我们应该怎么平衡呢?

首先,因为根节点最小,所以把根节点变到2的右边,这也符合二叉搜索的特性,即:小了往左走

这个时候20就成了新的根节点,而原先的根节点10就成了现在根节点(20)的左孩子节点。

因此对于这种一边倒的二叉搜索树,并且是向左倒的

 也就是要往左边分点节点,我们才能保持它的平衡,

对于LL型失衡,我们可以总结有如下 左旋定理:

RR型失衡

同理,有往左边倒的就有往右边倒的,如下图:

 因为40比30大的缘故,我们可以让40到30的右边去,这样就平衡了,并且符合二叉搜索树的特性,即:大了就往右边走:

此时30就是新节点,40就为30的右孩子。

这种往右边一边倒的失衡,我们称之为RR型失衡。

对此,我们可以总结如下右旋定理:

LR型失衡

因为root节点的左孩子的右节点造成的失衡就叫LR型失衡,如图 (b-1):

因为是由于右节点造成的失衡,所以我们就让右节点到左节点去,可以用左旋定理

变为图 (b-2):

 图(b-2)我们看它的平衡因子,大于1,所以还要再变。

又因为图(b-2)是RR型失衡,所以套用  右旋定理

变为图(b-3):

此时平衡因子<1,是AVL树,因此不再变。

流程图:

RL型失衡

如图(a-1),因为root节点的右孩子的左节点造成的失衡就叫RL型失衡:

对于LR型失衡,因为是左节点造成的失衡,所以要先采用右旋定理把右节点变到左边去:

变为图(a-2):

此时平衡因子为 -2 ,仍然不平衡还需要再变

此时因为向左倾斜,即要向左部分分点,这样才平衡要调用  左旋定理

变为图 (a-3):此时平衡因子 <1,即不用再变。

流程图:

因此,我们可以只有总结:

补充

左旋补充

有如下图:

 此时应该先左旋,然后再按二叉搜索树的特性再排

旧根节点10比  新根节点原先的左子树 19小,所以19应该排到10右边:

因此,左旋定理可以得到补充,如下:

右旋补充

有图(d-1):此时应该先右旋,再按二叉搜索树特性排:

 因此,右旋定理可以得到如下补充:

Code

首先写一下树的结构

val left right是必须的,又新加了一个height,用来求平衡因子用:

typedef  struct Node
{int val;//数据域int height; //树高struct Node* left;struct Node* right;
}Node;

开辟一个新节点


Node* newnode = (Node*)malloc(sizeof(Node));

 初始化

node->val=val;
node->height = 1;
node->left = NULL;
node->right = NULL;

 开辟和初始化我们都封装起来:

Node* newnode(int val)
{
Node* node = (Node*)malloc(sizeof(Node));
node->val=val;
node->height = 1;  //只有一个节点时,树高为1
node->left = NULL;
node->right = NULL;retur node;
}

获取树的高度

如果树高度为空,返回0

否则就把树的高度返回。

//获取树的高度
int getHeight(Node* node)
{if (node->height == NULL)return 0;elsereturn node->height;
}

接下来写左旋函数,右旋函数

左旋函数

左旋函数先按左旋定理写:

int LeftRtate(Node* root)
{//新节点为旧节点的右子树Node* newroot = root->right;//T2时新根节点的左子树Node* T2 = newroot->left;}

 开始旋转:

开始旋转://旧根节点变为新根节点的左子树newroot->left=root;//如果新根节点原来有右子树,那么新根节点原来右子树变为旧节点的右子树root->right = T2;

更新树高

经过左旋之后root和newroot的高度都变了,我们需要更新一下。

怎么更改呢?首先需要先求树高。

树高的求法

拿图(b-1)举例:

 图(b-1)的树高为3,其中,左子树的树高为2,右子树的树高为0。

我们取左右子树最大的树高+1就是整个树的树高:

int max(int A,int B)
{if (A > B ? A : B);}
int LeftRtate(Node* root)
{//新节点为旧节点的右子树Node* newroot = root->right;//T2时新根节点的左子树Node* T2 = newroot->left;//旧根节点变为新根节点的左子树newroot->left=root;//如果新根节点原来有右子树,那么新根节点原来右子树变为旧节点的右子树root->right = T2;//树高root->height = 1+max(getHeight(root->left),getHeight(root->right));newroot->height = 1+max(getHeight(newroot->left), getHeight(newroot->right));return newroot;//返回新根节点

右旋转函数

同样的道理,按照右旋定理就可以了:

Node* RightRatate(Node* root)
{//新根节点为原先旧节点的左子树Node* newroot = root->left;//新根节点的右子树Node* T2 = newroot->right;//开始旋转//旧根节点变为新根节点的右子树newroot->right = root;//3.如果新根节点原来有右子树,那么新根节点的右子树就会作为旧根节点的左子树root->left = T2;//树高root->height = 1 + max(getHeight(root->left), getHeight(root->right));newroot->height = 1 + max(getHeight(newroot->left), getHeight(newroot->right));return newroot;
}

接下来就是重头戏

插入

首先按照搜二叉的方式插入,

如果key值比当前节点值大就插在当前节点右边,否则就插在当前节点的左边。

插入完之后可能会造成失衡。

怎么判断失衡呢?根据平衡因子。

然后再去按照时哪种失衡,进行对应的调整。

欧克,一步一步来,首先插入

InsertNode()

//插入
Node* InsertNode(Node* node,int key)
{}

如果当前节点为空,那么就调用我们的newnode()函数,malloc了一个节点并且对节点进行初始化


Node* InsertNode(Node* node,int key)
{if (node==NULL)return newnode(key);}

如果key值比当前节点值大就插在当前节点右边,否则就插在当前节点的左边:

Node* InsertNode(Node* node,int key)
{if (node==NULL)return newnode(key);if (key > node->val)(node->left = InsertNode(node->right, key));if (key < node->val)(node->left = InsertNode(node->left, key));}

接下来就是用平衡因子判断插入之后是否失衡了。

写一个求平衡因子的函数:

但是在此之前需要先更新一下树高,因为平衡因子是根据树高求出来的。

更新树高

树高的求法:左右子树树高最大的那一个+1。

Node* InsertNode(Node* node,int key)
{if (node==NULL)return newnode(key);if (key > node->val)(node->left = InsertNode(node->right, key));if (key < node->val)(node->left = InsertNode(node->left, key));//更新树高//因为root改变了,所以它俩的树高需要更新node->height = 1 + max(getHeight(node->left), getHeight(node->right));
}

getbalance()

平衡因子的求法在刚开始就说过,就是左子树的树高-右子树的树高。


int getbalance(Node* node)
{return getHeight(node->left)- getHeight(node->right);}

根据balance判断是否失衡

 拿RL型失衡举例:

RL型失衡如图(f-1)所示:

根节点的 balance(平衡因子) =-2   根节点的右子树的balance=1

此时为RL型失衡。

balance<-1 && getbalance(node->right) < 0

RL失衡了就按照我们之前写的那个平衡方法就平衡一下:

先右旋,再左旋

   node->right=RightRatate(node->right);  即node链接右边的节点,让要旋转的节点旋到右边return LeftRtate(node);

剩下的LR型失衡,RR型失衡,LL型失衡都同理,写为为如下这个样子:

if (balance< -1 && getbalance(node->right)>0){//RL失衡//需要先右旋,再左旋node->right=RightRatate(node->right);return LeftRtate(node);}if (balance >1 && getbalance(node->left) <0){//LR失衡//需要先左旋旋,再右旋node->left=LeftRtate(node->left);	return RightRatate(node);}if (balance<-1 && getbalance(node->right) < 0){//RR型失衡//右旋return LeftRtate(node);}if (balance>1 && getbalance(node->right) >0){//LL失衡//左旋return RightRatate(node);}

最后把根节点返回就可以了。

return node;

至此,Insert()就全部写完了,总结为如下:

//插入
Node* InsertNode(Node* node,int key)
{if (node==NULL)return newnode(key);if (key > node->val){node->right = InsertNode(node->right, key);}else if (key < node->val){node->left = InsertNode(node->left, key);}else{return node;}//更新树高//因为root改变了,所以它俩的树高需要更新node->height = 1 + max(getHeight(node->left), getHeight(node->right));//根据balance判断是否失衡int balance = getbalance(node);if (balance< -1 && getbalance(node->right)>0){//RL失衡//需要先右旋,再左旋node->right=RightRatate(node->right);return LeftRtate(node);}if (balance >1 && getbalance(node->left) <0){//LR失衡//需要先左旋旋,再右旋node->left=LeftRtate(node->left);	return RightRatate(node);}if (balance<-1 && getbalance(node->right) < 0){//RR型失衡//右旋return LeftRtate(node);}if (balance>1 && getbalance(node->right) >0){//LL失衡//左旋return RightRatate(node);}return node;
}

然后我们来写测试函数

测试函数

我们可以用前序和后续就可以打印看看是否出错了,先写前序

perOrder()


void perOrder(Node* root)
{if (root == NULL)return;printf("%d ", root->val);perOrder(root->left);perOrder(root->right);}

midOrder()

void midOrder(Node* root)
{if (root == NULL)return;midOrder(root->left);printf("%d ",root->val);midOrder(root->right);}

Find()

find有三个参数

写一个根节点,把根节点赋值给当前节点,让当前节点去遍历。一个count参数统计查找次数。

key为要查找的值。

注意,count类型为int*而不是Int.

因为我们想让count的改变影响我们外面的实参值。

Node* Find(Node* root,int* count,int key)
{}

如果要查找的值比当前节点小,就往左走,否则如果比当前节点大,就往右走,如果不大不小那就说明要找的就是当前节点,把当前节点return。

如果找了一圈都没有找到,即当前节点跌代到空节点都找不到,那就返回NULL。

注意不是返回false,因为我们的Find()函数还要统计次数的功能,因此不是并没有给它一个bool类型。

Node* Find(Node* root,int* count,int key)
{Node* cur = root;while (cur){if (key> cur->val){cur = cur->right;(*count)++;}else if (key > cur->val){cur = cur->left;(*count)++;}else{return cur;}}return NULL;
}

测试


void test()
{Node* root = NULL;root= InsertNode(root,  10);root = InsertNode(root, 20);root = InsertNode(root, 30);root = InsertNode(root, 40);root = InsertNode(root, 50);root = InsertNode(root, 60);root = InsertNode(root, 70);int count = 0;int key = 70;Node* result=Find(root, &count, key);printf("要找的值是:%d\n",key);printf("找了%d 次\n",count);printf("前序遍历:_______ _ __  _\n");perOrder(root);printf("\n中序遍历:_______ _ __  _\n");midOrder(root);
}

报错:这是因为我们应该判断node==NULL,而不该去判断node->height;

当node为空时,node->height就是空指针指向height就会报错。

改为如下图:运行:

我们根据前序和中序打印的结果,可以画出这样一个二叉树:

 这个二叉树是一个平衡二叉树,即AVL树,并且找7这个节点找3次就能找到。

而我们打印出来的要找两次是因为我们根节点那次没算,当cur=node也让count++就可以了。

代码托管

AVLTreeinsert.c · 




 

相关文章:

  • 01-基于IDEA,Spring官网,阿里云官网,手动四种方式创建SpringBoot工程
  • 5分钟Python安装实战(MAC版本)
  • MapReduce:大数据处理的范式
  • Naocs配置中心配置映射List、Map、Map嵌套List等方式
  • Chatgpt人工智能对话源码系统分享 带完整搭建教程
  • lua脚本实现redis分布式锁(脚本解析)
  • vscode + cmake + opencv example
  • 企业防范数据安全的重要性与策略
  • Docker 用centos 编译安装apache
  • 【分布式事务】初步探索分布式事务的概率和理论,初识分布式事的解决方案 Seata,TC 服务的部署以及微服务集成 Seata
  • 【PG】PostgreSQL13主从流复制部署(详细可用)
  • 【面经】spring,springboot,springcloud有什么区别和联系
  • uni-app android picker选择默认月份
  • 【算法练习Day42】买卖股票的最佳时机 III买卖股票的最佳时机 IV
  • 独立键盘接口设计(Keil+Proteus)
  • 【挥舞JS】JS实现继承,封装一个extends方法
  • 4个实用的微服务测试策略
  • canvas 高仿 Apple Watch 表盘
  • CentOS 7 防火墙操作
  • CSS 专业技巧
  • CSS中外联样式表代表的含义
  • ES2017异步函数现已正式可用
  • IOS评论框不贴底(ios12新bug)
  • java小心机(3)| 浅析finalize()
  • Linux学习笔记6-使用fdisk进行磁盘管理
  • Making An Indicator With Pure CSS
  • springMvc学习笔记(2)
  • vue 配置sass、scss全局变量
  • vue2.0开发聊天程序(四) 完整体验一次Vue开发(下)
  • 纯 javascript 半自动式下滑一定高度,导航栏固定
  • 发布国内首个无服务器容器服务,运维效率从未如此高效
  • 关于 Cirru Editor 存储格式
  • 聚簇索引和非聚簇索引
  • 老板让我十分钟上手nx-admin
  • 如何在 Tornado 中实现 Middleware
  • 什么软件可以剪辑音乐?
  • 手机端车牌号码键盘的vue组件
  • 提醒我喝水chrome插件开发指南
  • 通过几道题目学习二叉搜索树
  • 系统认识JavaScript正则表达式
  • 主流的CSS水平和垂直居中技术大全
  • ​卜东波研究员:高观点下的少儿计算思维
  • #NOIP 2014# day.1 T2 联合权值
  • #pragma预处理命令
  • (20)目标检测算法之YOLOv5计算预选框、详解anchor计算
  • (非本人原创)史记·柴静列传(r4笔记第65天)
  • (七)Java对象在Hibernate持久化层的状态
  • (三维重建学习)已有位姿放入colmap和3D Gaussian Splatting训练
  • (转)重识new
  • .cfg\.dat\.mak(持续补充)
  • .gitignore
  • .NET C# 使用 SetWindowsHookEx 监听鼠标或键盘消息以及此方法的坑
  • .NET Entity FrameWork 总结 ,在项目中用处个人感觉不大。适合初级用用,不涉及到与数据库通信。
  • .NET Framework 和 .NET Core 在默认情况下垃圾回收(GC)机制的不同(局部变量部分)
  • .NET Framework与.NET Framework SDK有什么不同?