B树的插入操作

news2025/1/21 16:35:09

我的错误插入操作的详细解析

  • 前言
  • 一、实现思路
  • 二、思路梳理
    • 1.我需要解决的问题
    • 2.具体函数实现
  • 总结


前言

本文主要记载了我在实现递归插入操作的思路历程,以及遇到的问题和梳理操作的过程。我之前的实现方法有一种很大的问题,因为不是尾递归实现,所以会造成栈溢出!!! 我后面将其改为了尾递归实现,结果也会爆栈。。。இ௰இ


一、实现思路

我制作的大概思路流程图:
在这里插入图片描述

二、思路梳理

1.我需要解决的问题

Q1:要实现插入我需要哪些功能?
A:分裂结点,剪切、将一个关键字插入到一个结点关键字数组中的合适位置,将一个儿子插入到一个结点指针数组中的合适位置、定位关键字位置。

Q2:怎么样实现结点分裂?
A:先生成一个新结点,若有父结点,则将待分裂结点的中间关键字传给其父结点随后删除它,再将其中间关键字后的关键字以及儿子剪切给新结点,使新结点的父指针指向待分裂结点的父结点;若无父结点(说明待分裂结点为根结点)开辟一个新结点其第一个关键字则为待分裂结点的中间关键字,两个儿子则为待分裂结点与新结点,同样的将中间关键字以后的关键字与儿子剪切给新结点再使新开辟的父结点成为待分裂结点与新结点的父亲。

Q3:将满结点分裂后怎么判断插入方向?
A:将待插入关键字与原结点的中间关键字进行比较,若比它大则在新结点递归插入,反之在原满结点进行递归插入。

Q4:怎么样实现新关键字插入到关键字数组中的合适位置?
A:在这个过程中我使用到了标志变量的技巧,因为关键字数组是按非降序排列的,所以我只要找到关键字数组中的元素第一次比新关键字大的位置就可以了,那个位置即是新关键字的合适位置。先创建一个大小为2T-1的关键字中间数组,再声明一个flag变量并初始化为1和两个变量记录下标每次循环时将原数组的元素赋值给中间数组两个下标自加,当flag=1且第一次有关键字比新关键字大时则将flag置0,然后其中记录中间数组的下标进一储存新关键字。结束循环后判断flag是否为1,为1则说明新关键字比关键字数组中的关键字都大,则在数组末尾插入新关键字,最后将中间数组整体覆盖原数组即可,将一个新儿子插入到指针数组中的实现类似。

Q5:怎么样实现剪切的功能?
A:这个功能主要是在分裂结点后将满结点中间关键字后的数据剪切给新结点时使用,声明两个变量,第一个初始化为T,第二个初始化为0,将原满结点的中间关键字置0(与之前的操作衔接)然后循环将满结点的数据赋给新结点,再将其数据置空,结束后再将原满结点末尾儿子释放(儿子数量比关键字数量多一),然后使两个结点指向同一个父亲。

Q6:怎么定位关键字位置?
A:基本思路与插入关键字与插入儿子相似,利用标志变量进行定位,不过判断条件有所区别,当数组第一次出现比待定位关键字大或相等的时候记录下标,若相等则说明关键字在这个结点中,反之则说明在这个下标的子树中,如果循环结束后flag还等于1则说明这个关键字在末尾子树中。

2.具体函数实现

结点声明:

typedef int KeyType;
const int T=3;
typedef struct Btreenode{
	KeyType key[2*T-1]={0};								//关键字数组 
	bool leaf=true;												//默认叶子结点为真
	int n=0;															//结点个数 
	struct Btreenode* son[2*T]={nullptr};	//子结点数组 
	struct Btreenode* father=nullptr;			//父指针 
}Btreenode;
typedef Btreenode* B_tree;

1、定位关键字:

int Find_position(B_tree p,KeyType x){
	int i=0,position=0,flag=1;					//flag=1说明还未定位到关键字位置
	for(;i<p->n;i++){
		if(flag&&(x<p->key[i]||x==p->key[i])){
			position=i;
			flag=0;													//说明已经定位到其位置 
		} 
	} 
	if(flag) position=i;								//说明其比这个结点中的关键字大 
	return position;
}

2、将一个新关键字插入结点:

void Sortkey(B_tree p,KeyType newkey){
	KeyType temp[2*T-1]={0};
	int i=0,j=0,flag=1;										//flag=1代表新关键字未插入
	for(;i<p->n;i++,j++){
		if(flag&&p->key[i]>newkey){
			temp[j]=newkey;
			temp[++j]=p->key[i];
			flag=0;
		}else if(p->key[i]==newkey){
			cout<<"The key youo have input."<<endl;
			return;
		}else temp[j]=p->key[i];
	} 
	if(flag) temp[j]=newkey;
	for(i=0;i<=j;i++) p->key[i]=temp[i];
	p->n++;
}

3、将一个新儿子插入到儿子指针数组中:

void	Sortson(B_tree father,B_tree newson){
	B_tree temp[2*T]={nullptr};
	int i=0,j=0,flag=1;										//flag=1代表新儿子未插入父结点的儿子数组
	for(;father->son[i];i++,j++){
		if(flag&&father->son[i]->key[0]>newson->key[0]){
			temp[j]=newson;
			temp[++j]=father->son[i];
			flag=0;
		}else temp[j]=father->son[i];
	} 
	if(flag) temp[j]=newson;
	for(i=0;i<2*T&&temp[i];i++) father->son[i]=temp[i];
}

4、将满结点的后半部分剪切给新结点:

void Cat(B_tree first,B_tree second){
	int i=T,j=0;
	first->key[T-1]=0;								//因为这个关键字已经被传给了父结点
	for(;i<2*T-1;i++,j++){
		second->key[j]=first->key[i];
		first->key[i]=0;
		second->son[j]=first->son[i];
		first->son[i]=nullptr;
	} 
	second->son[j]=first->son[i];			//因为儿子结点比关键字多一 
	first->son[i]=nullptr;
	second->n=first->n=T-1;
	second->leaf=first->leaf;
	second->father=first->father;
}

5、将满结点分裂:

void Split(B_tree p){
	B_tree newson=new Btreenode;
	KeyType new_father_key=p->key[T-1];
	Cat(p,newson);
	if(p->father){										//p有父结点
		Sortkey(p->father,new_father_key);
		Sortson(p->father,newson); 
	}else{														//没有父结点,说明待分裂结点为根结点 
		B_tree newfather=new Btreenode;
		p->father=newfather;
		newson->father=newfather;
		newfather->son[0]=p;
		newfather->key[0]=new_father_key;
		newfather->son[1]=newson;				//因为newson继承了原来结点的右半边比原来结点大 
		newfather->leaf=false;
	}
}

6、插入函数:
(插入后需要检查根结点有没有父结点,因为分裂后原根结点就不是根结点了,每次插入后要检查,若根结点有父结点则其父结点成为新根结点)

B_tree Insert(B_tree root,KeyType newkey){
	int flag=0; 
	B_tree newson=nullptr;											//分裂结点后会产生新儿子 
	int position=Find_position(root,newkey);
	if(root->n==2*T-1){												//满结点 
		KeyType temp=root->key[T-1];
		newson=Split(root);		  									//newson是继承了满结点的后半部分的新儿子结点 
		if(newkey<temp) flag=1;
		else  flag=2;
	}else{
		if(root->leaf==false) flag=3;								//内部结点 
		else Sortkey(root,newkey);									//叶子结点 
	}
	if(flag==1) return Insert(root,newkey);							//在分裂后的当前结点中插入 
	else if(flag==2) return Insert(newson,newkey);					//在分裂后产生的新儿子中进行插入 
	else if(flag==3) return Insert(root->son[position],newkey);		//当前结点为内部结点,递归查找合适的叶结点进行插入 
	else return root;															//已经将关键字插入到叶结点中了 
}

总结

我之前设计时没有考虑到不恰当的递归操作会导致栈溢出的问题,后面将其修改为尾递归实现,但还是没有解决问题,泪目了,留给以后非递归实现解决,从此能不用递归就不用递归。这个过程费了我很多时间,但让我以后设计递归函数时会考虑的更全面,会更详细考虑边界条件和异常情况。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/527321.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智能高效的Go IDE——GoLand v2023.1全新发布,引入gRPC 导航

GoLand 使 Go 代码的阅读、编写和更改变得非常容易。即时错误检测和修复建议&#xff0c;通过一步撤消快速安全重构&#xff0c;智能代码完成&#xff0c;死代码检测和文档提示帮助所有 Go 开发人员&#xff0c;从新手到经验丰富的专业人士&#xff0c;创建快速、高效、和可靠的…

多语言APP的外包开发流程及注意事项

近些年国内越来越多的公司走向海外&#xff0c;有些互联网项目即可以为国内用户使用&#xff0c;也可以为国外用户使用&#xff0c;尤其是一些智力类小游戏&#xff0c;这些小游戏不需要特别的运营&#xff0c;只要在设计和玩法上把握好&#xff0c;那就可以推广到全球用户。今…

MySQL 中 CONCAT 函数使用

1&#xff1a;创建数据表&#xff1a; CREATE TABLE user ( id int NOT NULL AUTO_INCREMENT, code varchar(255) NOT NULL, name varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT3 DE…

KubeSphere 社区双周报 | 开源之夏已启动 | 2023.04.28-05.11

KubeSphere 社区双周报主要整理展示新增的贡献者名单和证书、新增的讲师证书以及两周内提交过 commit 的贡献者&#xff0c;并对近期重要的 PR 进行解析&#xff0c;同时还包含了线上/线下活动和布道推广等一系列社区动态。 本次双周报涵盖时间为&#xff1a;2023.04.28-2023.…

数据结构学习分享之堆的详解以及TopK问题

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:数据结构学习分享⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你了解更多数据结构的知识   &#x1f51d;&#x1f51d; 数据结构第七课 1. 前言&a…

未来已来,时代颠覆者ChatGPT你真的了解吗?

文章目录 什么是ChatGPTchatgpt与自然语言处理从gpt1.0到chatgpt&#xff0c;经历了什么chatgpt是一个语言模型chatgpt是如何处理文字输入的写在最后 什么是ChatGPT ChatGPT是美国OpenAI研发的聊天机器人程序&#xff0c;2022年11月30日发布。ChatGPT是人工智能技术驱动的自然语…

网络基础知识(4)——建立与关闭连接

建立 TCP 连接&#xff1a;三次握手 前面我们提到过&#xff0c;TCP 协议是一个面向连接的协议&#xff0c;双方在进行网络通信之间&#xff0c;都必须先在双方之间建立一条连接&#xff0c;俗称“握手”&#xff0c;可能在学习网络编程之前&#xff0c;大家或多或少都听过“…

SpringSecurity安全权限框架及其原理

1. 基础使用 首先创建最基本的SpringBoot项目&#xff0c;默认都会。主要是引入依赖和创建Controller进行测试。 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://w…

十五天MySQL学习计划(运维篇-完结)读写分离-第十五天

十五天MySQL学习计划&#xff08;运维篇-完结&#xff09;读写分离-第十五天 读写分离 1.读写分离 ​ 读写分离&#xff0c;简单的说是把对数据库的读和写操作分开&#xff0c;以对应不同的数据库服务器。主服务器提供写操作&#xff0c;从数据库提供读操作&#xff0c;这样…

linux0.12-8-10-sys.c

[369页] 1、 这个文件需要配合其他文件一起看才能明白函数的作用&#xff1b; 2、 进程ID和进程组ID是描述进程之间的关系&#xff0c;与用户ID和组ID等其他无关系&#xff1b; 3、 用户ID和组ID和文件&#xff08;程序&#xff09;属性有关&#xff0c;当进程执行&#xff08;…

首发!车联网前装搭载率破70%,本土供应商抢下半壁江山

对于汽车智能化来说&#xff0c;网联化是相辅相成的角色&#xff0c;从传统3G、4G到5G的进化&#xff0c;提升座舱信息娱乐的体验&#xff1b;到C-V2X落地为辅助驾驶及自动驾驶提供冗余感知。 同时&#xff0c;车联网的普及&#xff0c;也进一步驱动互联网生态内容、服务以及类…

【AUTOSAR】【以太网】SoAd

目录 一、概述 二、限制与约束 三、依赖模块 5.1 TCPIP模块 5.2 通用上层 四、功能描述 4.1 套接字连接 4.2 PDU传输 4.3 PDU Header option 4.4 PDU 接收 4.5 最佳匹配算法 4.6 消息接受策略 4.7 TP PDU取消 4.8 路由组 4.9 PDU fan-out 五、API接口 5.1 API…

SpringBoot实现登录拦截的实现

对于管理系统或其他需要用户登录的系统&#xff0c;登录验证都是必不可少的环节&#xff0c;在SpringBoot开发的项目中&#xff0c;通过实现拦截器来实现用户登录拦截并验证。 1、SpringBoot实现登录拦截的原理 SpringBoot通过实现HandlerInterceptor接口实现拦截器&#xff…

如何完成GNSS接收器的定时校准

背景 GNSS以其提供亚米级精度定位的能力而闻名。然而鲜为人知的是&#xff0c;GNSS还提供了一种非常便捷的方法&#xff0c;可以通过GNSS接收器获得纳秒&#xff08;甚至亚纳秒&#xff09;的定时精度。事实上&#xff0c;除了三个空间维度之外&#xff0c;GNSS还使用户能够计…

iPhone照片导入电脑的图文教程,批量上传的3个方法!

案例&#xff1a;苹果手机照片怎么批量上传到电脑&#xff1f; 【友友们&#xff0c;手机照片太多&#xff0c;占用了我很多内存。想要把照片上传批量上传到电脑上进行保存&#xff0c;该怎么做&#xff1f;】 随着iPhone的普及和摄影功能的提升&#xff0c;越来越多的用户希望…

Rave Reports v2022 for Delphi 7-11

Rave Reports v2022 for Delphi 7-11 Rave Reports是来自Nevrona的一组公司&#xff0c;从Delphi和CBuilder中的数据库中报告进度。您可以使用专用工具轻松设计自己的报告。如果您需要将报告更改为您的用户&#xff0c;例如要发布的订单报告&#xff0c;此工具也提供了此功能&a…

Java | 一分钟掌握定时任务 | 3 - 单机定时之Timer

作者&#xff1a;Mars酱 声明&#xff1a;本文章由Mars酱原创&#xff0c;部分内容来源于网络&#xff0c;如有疑问请联系本人。 转载&#xff1a;欢迎转载&#xff0c;转载前先请联系我&#xff01; 介绍 这个是个JDK远古时代的api了&#xff0c;据考证&#xff0c;可以追溯到…

CSS小技巧之圆形虚线边框

虚线相信大家日常都用的比较多&#xff0c;常见的用法就是使用 border-style 控制不同的样式&#xff0c;比如设置如下边框代码&#xff1a; border-style: dotted dashed solid double;这将设置顶部的边框样式为点状&#xff0c;右边的边框样式为虚线&#xff0c;底部的边框样…

视频采集到录制 - 采集到显示碰到一些难点

项目中用到相机后端处理&#xff0c;走了一些弯路&#xff0c;也遇到不少问题&#xff08;解决了不少问题&#xff09;&#xff0c;特意写下本文记录下当时点点滴滴。 讲一下背景&#xff0c;公司自研相机&#xff0c;用于一些高端场合&#xff0c;因此对后端处理也非常讲究 …

网络基本知识分享

目录 1.IP地址 2.端口号 3.协议 4.协议分层 5.Tcp/Ip五层网络模型 5.1 应用层 5.2 传输层 5.3 网络层 5.4 数据链路层 5.5 物理层 6.封装和分用 6.1 封装 6.1.1 应用层拿到数据 6.1.2 向下传递给传输层 6.1.3 继续向下传递给网络层 6.1.4 继续向下传递给数据链…