我的错误插入操作的详细解析
- 前言
- 一、实现思路
- 二、思路梳理
- 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; //已经将关键字插入到叶结点中了
}
总结
我之前设计时没有考虑到不恰当的递归操作会导致栈溢出的问题,后面将其修改为尾递归实现,但还是没有解决问题,泪目了,留给以后非递归实现解决,从此能不用递归就不用递归。这个过程费了我很多时间,但让我以后设计递归函数时会考虑的更全面,会更详细考虑边界条件和异常情况。