不得不说,这个我写了两天。第一天晚上想移植一篇博客的,后来经过四个小时发现是错了谁懂啊!今天早上又找了一篇,大错误我都改了,有一个潜在的小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 keyNum; index++) 7 if (data 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 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 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 else73 {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 //将右兄弟移入LC13 int m = LC->keyNum;14 for (int i = 1; i 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 childNum; ++i)21 {22 LC->children[ n+ i] = RC->children[i];23 ++LC->childNum;24 }25 26 for (int i = pos + 1; i keyNum; ++i)27 {28 P->keys[i] = P->keys[i + 1];29 }30 for (int i = pos + 1; i 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 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 keyNum && T->keys[i-1] == key) 20 {// 删除键值在该节点中 21 if (T->childNum==0) 22 {// 叶子节点,删除 23 for (int k=i-1; i 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 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 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 keyNum - 1; ++k) 97 RB->keys[k] = RB->keys[k + 1]; 98 99 for (int k = 0; k 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 // 树根被借走,树高 -1118 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 keyNum; i++) 9 {10 if (data == node->keys[i])11 return 1;12 if (data keys[i])//如果比i小,查询孩子13 return enquirytree(node->children[i - 1],data);14 }15 //比最后的大16 return enquirytree(node->children[node->keyNum], data);17 }