首页 > 其他分享 >B树删除和创建(C)

B树删除和创建(C)

时间:2023-11-29 17:11:48浏览次数:33  
标签:node 结点 删除 int 创建 keys keyNum children

不得不说,这个我写了两天。第一天晚上想移植一篇博客的,后来经过四个小时发现是错了谁懂啊!今天早上又找了一篇,大错误我都改了,有一个潜在的小bug是自己调试跳出来的,谁懂啊!得找阅读量高的才行!

先把刚刚的小错误放一下

 也不知道博主怎么想的,同时i和keynum++,害,害我好找!!!

改后就好了

由于老师要求的数据规模太大,我小小的vs承受不起,我只能想法设法减少我开辟的空间,连数组都不要了(哭)

结构(这是我采取的数据结构)

 1 //关键字索引  0(不记录)  1    2    3    4(不记录具体数值)
 2 //孩子的索引         0        1    2    3    4
 3 
 4 //树结点信息
 5 typedef struct Treenode
 6 {
 7     int level;//树的阶数
 8     int keyNum;//关键字的个数
 9     int childNum;//孩子的个数
10     int* keys;//关键字数组
11     struct Treenode** children;//孩子数组
12     struct Treenode* parent;//父亲指针
13 };

对应的初始化代码

 1 //初始化结点
 2 Treenode* initNode(int level)
 3 {
 4     Treenode* node = (Treenode*)malloc(sizeof(Treenode));
 5     node->level = level;
 6     node->keyNum = 0;
 7     node->childNum = 0;
 8     node->keys = (int*)malloc(sizeof(int) * (level+1));//索引,从1开始记录
 9     node->children = (Treenode**)malloc(sizeof(Treenode*) * level);
10     node->parent = NULL;
11     for (int i = 0; i < level; i++)
12     {
13         node->keys[i] = 0;
14         node->children[i] = NULL;
15     }
16     node->keys[level] = 0;//并没有实际用途,只是为了方便返回孩子的索引
17     return node;
18 }

 

插入

其实我感觉插入很简单,总结下来就是,找到要插入的叶子!!!结点(也就是最下面),然后如果超出m-1那就取中间进行分裂就好!

调整的步骤在代码注释中也有

//判断该结点是否进行分裂。调整主要三个方面
//1.找出中间结点,把结点前面的键值给新的左孩子,后面的键值给新的右孩子
//2.将该节点的孩子分给新的左孩子和右孩子
//3.将中间结点插入该节点的父亲关键字数组中,并将新的左孩子和右孩子插入父亲节点的孩子数组

由最底层逐层向上调整(这可能也是为什么左右子树高度相等的原因吧)

 1 //在节点处寻找合适的插入索引
 2 int findSuiteIndex(Treenode* node, int data)
 3 {
 4     int index;
 5     //索引从1开始,因为0是没有用的边界
 6     for (index = 1; index <= node->keyNum; index++)
 7         if (data < node->keys[index])
 8             break;
 9     return index;
10 }
 1 //找到合适的叶子结点,先插入叶子(没有孩子的结点)结点再做调整
 2 Treenode* findSuiteLeafNode(Treenode* T, int data)
 3 {
 4     if (T->childNum == 0)
 5         return T;
 6     else
 7     {
 8         int index = findSuiteIndex(T, data);
 9         //找到对应的(索引+1),再去该子节点去搜索,见前面注释
10         return findSuiteLeafNode(T->children[index-1], data);
11     }
12 }
1 //插入结点函数
2 void insert(Treenode** T, int data)
3 {
4     Treenode* node = findSuiteLeafNode(*T,data);
5     //找到要插入的叶子结点
6     addData(node, data, T);//插入树
7 }

这些基本函数封装好了,剩下就是add的实现了

 1 //插入数据到B树
 2 void addData(Treenode* node, int data,Treenode**T)
 3 {
 4     //先找到索引,将data放入关键字数组
 5     int index = findSuiteIndex(node, data);
 6     //后面元素后移动,因为本来就预留了多余的空间
 7     for (int i = node->keyNum; i >= index; i--)
 8         node->keys[i + 1] = node->keys[i];
 9     node->keys[index] = data;
10     node->keyNum++;
11 
12     //判断该结点是否进行分裂。调整主要三个方面
13     //1.找出中间结点,把结点前面的给新的左孩子,后面的给新的右孩子
14     //2.将该节点的孩子分给新的左孩子和右孩子
15     //3.将中间结点插入该节点的父亲关键字数组中,并将新的左孩子和右孩子插入父亲节点的孩子数组
16     if (node->keyNum == node->level)
17     {
18         //取中间节点
19         int mid = node->level / 2 + node->level % 2;
20 
21         //生成新的左右孩子
22         Treenode* lchild = initNode(node->level);
23         Treenode* rchild = initNode(node->level);
24 
25         //将mid左边的赋给左孩子关键字数组
26         for (int i = 1; i < mid; i++)
27             addData(lchild, node->keys[i], T);
28       
29         //将mid右边的赋给右孩子关键字数组
30         for (int i = mid + 1; i <= node->keyNum; i++)
31             addData(rchild, node->keys[i], T);
32 
33         //把该结点下面对应的孩子赋给左孩子的孩子数组
34         for (int i = 0; i < mid; i++)
35         {
36             lchild->children[i] = node->children[i];
37             //如果该孩子结点不为空,需要改变孩子的某些信息
38             if (node->children[i] != NULL)
39             {
40                 node->children[i]->parent = lchild;
41                 lchild->childNum++;
42             }
43         }
44 
45         //把该结点下面对应的孩子赋给右孩子的孩子数组
46         int k = 0;//右孩子数组的索引值
47         for (int i = mid; i < node->childNum; i++)
48         {
49             rchild->children[k++] = node->children[i];
50             //如果该孩子结点不为空,需要改变孩子的某些信息
51             if (node->children[i] != NULL)
52             {
53                 node->children[i]->parent = rchild;
54                 rchild->childNum++;
55             }
56         }
57 
58         //将中间结点插入该节点的父亲关键字数组中,并将新的左孩子和右孩子插入父亲节点的孩子数组
59         //node是根节点的时候,分裂会改变根节点
60         if (node->parent == NULL)
61         {
62             //生成新的根结点
63             Treenode* newParent = initNode(node->level);
64             addData(newParent, node->keys[mid], T);
65             newParent->children[0] = lchild;
66             newParent->children[1] = rchild;
67             newParent->childNum = 2;
68             lchild->parent = newParent;
69             rchild->parent = newParent;
70             *T = newParent;
71         }
72         else
73         {
74             //找到mid结点应该插入的node父亲的关键字数组的索引
75             int indexparent = findSuiteIndex(node->parent, node->keys[mid]);
76             
77             lchild->parent = node->parent;
78             rchild->parent = node->parent;
79             //node原来所有的元素都比index要小,所以之间把左孩子插入index-1,右孩子应该在index的位置
80             node->parent->children[indexparent - 1] = lchild;
81             //插入新的右孩子前,如果index处有,需要整体后移
82             if (node->parent->children[indexparent] != NULL)
83             {
84                 for (int i = node->parent->childNum - 1; i >= indexparent; i--)
85                     node->parent->children[i + 1] = node->parent->children[i];
86             }
87             node->parent->children[indexparent] = rchild;
88             node->parent->childNum++;
89             //将mid插入父节点位置
90             addData(node->parent, node->keys[mid],T);
91         }
92     }
93 }

删除


删除其实分两种:1.非叶子结点:就选择它左孩子最大值或右孩子最小值进行替代,然后归结为删除叶子结点

                              2.叶子结点就比较复杂了:(1)左兄弟富有或者右兄弟富有(键值个数大于【m/2】-1),就旋转一下(比如:左兄弟富有,将parent该键值给该结点1号键值处,然后将左兄弟最大键值给parent就好了!!!别看它看起来是这样,实现不容易啊!特别别忘了左兄弟的最大的孩子也要给该结点成为他的第一个孩子!!!之前博主就是少了这个)

 (2)都很穷的时候,将parent处键值,左(右)兄弟(键值和孩子),自己合并成一个结点,在插入回parent结点处,别忘了对parent相关值进行改动哦!

和插入一样,需要从底层往跟进行调整,我采用栈比较好理解啦

      我的实现是,将跟到待删除的叶子!!!结点入栈,然后一个一个取出来(叶子----跟)进行判断是否需要调整就好了

 1 // 合并操作
 2 void merge(Treenode* P, int pos)
 3 {
 4     //          pos+1(P)
 5     // pos(LC)        pos+1(RC)
 6     Treenode* LC = P->children[pos];
 7     Treenode* RC = P->children[pos + 1];
 8 
 9     LC->keys[LC->keyNum + 1] = P->keys[pos + 1];
10     ++LC->keyNum;
11 
12     //将右兄弟移入LC
13     int m = LC->keyNum;
14     for (int i = 1; i <= RC->keyNum; ++i)
15     {
16         LC->keys[m + i] = RC->keys[i];
17         ++LC->keyNum;
18     }
19     int n = LC->childNum;
20     for (int i = 0; i < RC->childNum; ++i)
21     {
22         LC->children[ n+ i] = RC->children[i];
23         ++LC->childNum;
24     }
25 
26     for (int i = pos + 1; i < P->keyNum; ++i)
27     {
28         P->keys[i] = P->keys[i + 1];
29     }
30     for (int i = pos + 1; i < P->childNum-1; ++i)
31     {
32         P->children[i] = P->children[i + 1];
33     }
34     --P->keyNum;
35     --P->childNum;
36 }
  1 //删除键值
  2 bool deleteKey(int key, Treenode** Root)
  3 {
  4     //如果根节点为空或者没有键值时
  5     if (NULL == *Root || 0 == (*Root)->keyNum) return false;
  6 
  7     Treenode* T = *Root;//复制一份
  8 
  9     stack<Treenode*> NODE;//存储调整的那棵子树
 10 
 11     while (T) 
 12     {
 13         NODE.push(T);
 14         //找到第一个比他大的索引
 15         int i = findSuiteIndex(T, key);
 16         //         i-1(可能相等的点)     i(key比它小)
 17         //孩子  i-2            i-1                    i
 18 
 19         if (i-1 <= T->keyNum && T->keys[i-1] == key) 
 20         {// 删除键值在该节点中
 21             if (T->childNum==0) 
 22             {// 叶子节点,删除
 23                 for (int k=i-1; i <= T->keyNum - 1; ++i) 
 24                     T->keys[k] = T->keys[k + 1];
 25                 --T->keyNum;
 26                 break;
 27             }
 28             else 
 29             {// 非叶子节点,找后继/也可以找前驱(必存在)
 30                 Treenode* RC = T->children[i - 1];// 右孩子
 31                 //找右孩子最小值
 32                 while (RC->childNum!=0) RC = RC->children[0];
 33 
 34                 T->keys[i-1] = RC->keys[1];//替换
 35                 key = RC->keys[1];//改变待删除值
 36                 T = T->children[i - 1];
 37             }
 38         }
 39         else // 删除节点不在该节点中
 40             T = T->children[i-1];
 41     }
 42     // 删除后调整
 43     Treenode* P = NODE.top();
 44     NODE.pop();
 45 
 46     while (!NODE.empty()) 
 47     {
 48         T = P;
 49         P = NODE.top();//该节点根树
 50         NODE.pop();
 51 
 52         //比最小键值数小,做调整
 53         if (T->keyNum < 1) 
 54         {
 55             //找到该结点属于该根节点的第几个孩子
 56             int i = 0;
 57             for (; i <T->childNum; ++i) 
 58                 if (T == P->children[i]) break;
 59             
 60             //找该节点两边的兄弟结点
 61             Treenode* LB = i > 0 ? P->children[i - 1] : NULL;
 62             Treenode* RB = i < P->childNum-1 ? P->children[i + 1] : NULL;
 63 
 64             if (LB && LB->keyNum > 1) 
 65             {// 左兄弟存在且键值富余
 66              //        i(P)
 67              // i-1(LB)    i(T)
 68                 //T中键值往后移动,便于插入
 69                 for (int k = T->keyNum + 1; k > 1; --k)
 70                     T->keys[k] = T->keys[k - 1];
 71                     
 72                 for (int k = T->childNum; k > 0; --k)
 73                     T->children[k] = T->children[k - 1];
 74              
 75                 //旋转
 76                 T->keys[1] = P->keys[i];
 77                 T->children[0] = LB->children[LB->childNum - 1];
 78                 ++T->keyNum;
 79                 ++T->childNum;
 80 
 81                 P->keys[i] = LB->keys[LB->keyNum];
 82                 //LB->children[LB->childNum - 1] = NULL;
 83                 --LB->childNum;
 84                 --LB->keyNum;
 85             }
 86             else if (RB && RB->keyNum > 1) 
 87             {// 右兄弟存在且键值富余
 88                 //        i+1(P)
 89                 //   i(T)      i+1(RB)
 90                 T->keys[T->keyNum+1] = P->keys[i+1];
 91                 T->children[T->childNum] = RB->children[0];
 92                 ++T->keyNum;
 93                 ++T->childNum;
 94 
 95                 P->keys[i+1] = RB->keys[1];
 96                 for (int k = 1; k <= RB->keyNum - 1; ++k)
 97                     RB->keys[k] = RB->keys[k + 1];
 98                 
 99                 for (int k = 0; k < RB->childNum - 1; ++k)
100                     RB->children[k] = RB->children[k + 1];
101                 
102                 --RB->keyNum;
103                 --RB->childNum;
104             }
105             else if (LB) 
106             { // 左兄弟存在但不富余
107                 merge(P, i - 1);
108                 T = P->children[i - 1];
109             }
110             else if (RB) 
111             {// 右兄弟存在但不富余
112                 merge(P, i);
113                 T = P->children[i];
114             }
115         }
116     }
117     // 树根被借走,树高 -1
118     if (*Root == P && P->keyNum == 0)
119     {
120         *Root = P->children[0];
121         
122     }
123     return true;
124 }

查询

搜索到位空就好

 

 1 //查询该节点
 2 bool enquirytree(Treenode* node, int data)
 3 {
 4     //如果该节点没有值就没有这个data
 5     if (node == NULL)
 6         return 0;
 7     //查询该结点的关键字索引
 8     for (int i = 1; i <= node->keyNum; i++)
 9     {
10         if (data == node->keys[i])
11             return 1;
12         if (data < node->keys[i])//如果比i小,查询孩子
13             return enquirytree(node->children[i - 1],data);
14     }
15     //比最后的大
16     return enquirytree(node->children[node->keyNum], data);
17 }

 

标签:node,结点,删除,int,创建,keys,keyNum,children
From: https://www.cnblogs.com/saucerdish/p/17865332.html

相关文章

  • vue 创建 项目方式
    使用webpack创建vuecreatepageName资料https://cli.vuejs.org/zh/guide/使用vite创建npmcreatevue@latest资料https://cn.vitejs.dev/guide/clihttps://github.com/vuejs/create-vue......
  • C#删除.git文件夹
    C#在通常情况下删除文件只需要调用下面的方法即可:Directory.Delete(dir.FullName,true);上面的代码会删除指定的文件夹及文件夹下面所有的子文件夹和文件。但是用上面的代码去删除.git文件夹的目录时,貌似会失败,报异常。具体的失败原因不是特别清楚,也没有去仔细钻研。可能的......
  • linux 使用crontab 创建定时任务
    转载请注明出处:在服务器中需要创建一个定时任务,每天执行去清理很早之前备份的文件,所以想到在linux上创建一个shell脚本,通过linux的crontab命令定时去执行该shell脚本,从而实现定时清理服务器文件。crontab是Linux系统中用于调度任务的命令,它允许用户在固定的间隔时间执行......
  • 19.删除链表的倒数第N个节点
    leetcode题目链接题目描述给你一个链表,删除链表的倒数第n个结点,并且返回链表的头结点。示例1:输入:head=[1,2,3,4,5],n=2输出:[1,2,3,5]示例2:输入:head=[1],n=1输出:[]示例3:输入:head=[1,2],n=1输出:[1]提示:链表中结点的数目为sz1<=sz<=300<=Node.val<=10......
  • 创建numpy
    一维#使用列表创建一维数组my_list=[1,2,3,4,5]#将列表转换为NumPy数组my_array=np.array(my_list)[12345]<class'numpy.ndarray'>二维#二维数组,3行4列arr_2d=np.array([[1,2,3,4],[5,6,7,8],[......
  • wsl 创建服务 启动停止, init
    ////////////////////////////////////////////////运行//////////////////////////////////////////////////////////////////#!/bin/bash/opt/odoo16env/bin/python3/opt/www/odoo16pro/odoo-bin-c/opt/www/odoo16pro/odoo.conf-ibase/opt/odoo17env/bin/pyth......
  • 邻接矩阵存储创建有向图
    #include<iostream>usingnamespacestd;//邻接矩阵需要顶点表,二维矩阵,还有点数边数#defineMVNum100typedefstruct{charvexs[MVNum]; //顶点表intarcs[MVNum][MVNum]; //矩阵intvexnum,arcnum; //顶点数、边数}AMGraph;intLocateVex(AMGraphG,charv){//......
  • 绝了!利用“定义名称”,创建动态的数据透视表。
    1职场实例有的小伙伴给群主反映了这样的一个Excel问题:我在创建完成了Excel数据透视表之后,如果数据透视表的数据源中增加了新的行或列数据,在刷新数据透视表后,新增的数据仍然不能在数据透视表里面更新呈现。我该如何解决这个问题呢?如下图所示:左表为数据源,右表为数据透视表汇总结果。......
  • Rust Tauri系列: 项目创建
    创建Rust-Tauri##创建rustTauri项目pnpmcreatetauri-app->项目名称test-app->选择TypeScript/JavaScript(pnpm,yarn,npm,bun)->选择包管理工具(熟悉那个就用那个)->选择vue(熟悉那个就用那个)->选择TypeScript或者js#运行启动cdtest-apppnpmins......
  • 删除表记录 修改表记录
    删除表记录:#删除的两种方式#第一种:queryset的delete方法#res=models.Book.objects.all().delete()#print(res)#第二种:对象自己的delete方法#book=models.Book.objects.all().filter(name='红楼梦').first()#print(type(book))#res=book......