一定要结合图形和书来看,注释是根据算法导论写的,有些太绕了,直接上书看好得多。
可以结合具体例子看,根据例子来理解代码要轻松一些
代码是抄的这个博主的(B树我自己写不出来…),有少许改动,原博文除了没有注释之外,分析写的还是很好的,主要是代码结构很简洁,大概也是按照导论来的。
https://blog.csdn.net/geek_jerome/article/details/78895289
看以下代码中要注意的地方:
1.输出的形式很奇怪,但是用递归输出的,也不好改。输出形式就是从根开始向下递归,然后 把当前指针所在的位置的结点的孩子从左到右全部输出。再到下一层。如果是内部结点就是0,叶子结点就是1。虽然不好看,但是逻辑还是在那里能看懂。
2.delete那部分函数写注释的时候很绕,直接看变量名搞清楚父子关系,兄弟关系可能还要清楚一些。
3.我这边编译通过了,结果目前看来没问题,and,搞不定的话就体会一下思想就好,加油!
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#define MinDegree 2 //最小度c
typedef int ElementType; //定义数据类型
typedef int* PtrElementType; //定义指针类型
typedef struct TreeNode *PtrBTNode; //定义一个结点和一个指向结点的指针
typedef struct TreeNode BTNode;
typedef struct TreeNode { //结点的结构体,包含位置,判断是否为叶子结点,关键字,孩子结点的指针
int Num; //Num是当前结点的关键字的个数
bool IsLeaf;
PtrElementType Key;
PtrBTNode *Child;
};
typedef struct Tree *PtrBT; //定义指向树根的指针
typedef struct Tree {
PtrBTNode Root;
};
//函数列表
PtrBTNode BTAllocateNode();
PtrBTNode BTSearch(PtrBTNode Root, ElementType Val, int* Index);
void BTChildSplit(PtrBTNode SplitNodeP, int ChildIndex);
void BTInsertNonFull(PtrBTNode Root, ElementType Val);
void BTInsert(PtrBT T, ElementType Val);
void BTDelete(PtrBT T, PtrBTNode Root, ElementType Val);
void Merge(PtrBT T, PtrBTNode ParentNode, int LeftIndex, int RightIndex);
void ShiftKey(PtrElementType Key, bool Direction, int Begin, int End);
void ShiftChild(PtrBTNode *Child, bool Direction, int Begin, int End);
int GetIndex(PtrElementType Key, int Size, ElementType Val);
void BTPrintTree(PtrBTNode Root);
void BTCreateTree(PtrBT T);
//A test program
int main() {
PtrBT T = (PtrBT)malloc(sizeof(struct Tree));
T->Root = BTAllocateNode();
BTCreateTree(T);
printf("B_Tree after delete 8:\n");
BTDelete(T, T->Root, 8);
BTPrintTree(T->Root);
printf("B_Tree after delete 16:\n");
BTDelete(T, T->Root, 16);
BTPrintTree(T->Root);
printf("B_Tree after delete 15:\n");
BTDelete(T, T->Root, 15);
BTPrintTree(T->Root);
printf("B_Tree after delete 4:\n");
BTDelete(T, T->Root, 4);
BTPrintTree(T->Root);
return 0;
}
PtrBTNode BTAllocateNode() {
int i;
PtrBTNode NewNode = (PtrBTNode)malloc(sizeof(BTNode)); //新建一个结点,分配空间
NewNode->Num = 0; //初始化新建结点
NewNode->IsLeaf = true; //如果是叶子结点
NewNode->Key = (PtrElementType)malloc(sizeof(ElementType) * (MinDegree * 2 - 1)); //分配叶子结点关键字的最大空间(内结点至多有2*最小度-1的关键字个数)
NewNode->Child = (PtrBTNode*)malloc(sizeof(PtrBTNode) * (MinDegree * 2)); //分配孩子结点指针的空间(内结点至多有2*最小度的空间)
for (i = 0; i < MinDegree * 2; i++) {
NewNode->Child[i] = NULL; //初始化指针
}
return NewNode;
}
PtrBTNode BTSearch(PtrBTNode Root, ElementType Val, int* Index) { //传入的指向某子树的根节点的指针和要在该子树查找的关键字
int i=0;
while (i <= Root->Num && Val > Root->Key[i]) //找出使Val<Key[i]的最小下标i,如果找不到就把i的位置设为Num+1
i++;
if (i <= Root->Num && Root->Key[i] == Val) { //检查是否找到,找到则返回
*Index = i;
return Root;
}
else if (true == Root->IsLeaf) { //如果根是叶子结点,子树就是空的
return NULL;
}
else {
return BTSearch(Root->Child[i], Val, Index);
}
}
//分裂前x为父节点,Child[i]为子节点,Child[i]中有2*MinDgree-1个关键字,有2*MinDegree个子女
//在分裂后Child[i]有MinDegree-1个关键字,MinDegree个子女
void BTChildSplit(PtrBTNode x, int i) {
int j;
PtrBTNode z = BTAllocateNode(); //新建一个结点储存被分裂出来的元素
PtrBTNode FullNode = x -> Child[i]; //父节点x的孩子中下标为i(也就是第i个孩子)赋值给结点FullNode
for (j = 0; j<= MinDegree - 1; j++) { //把FullNode中的后一半元素的关键字赋给z
z->Key[j] = FullNode->Key[MinDegree + j];
}
if (false == FullNode->IsLeaf) { //如果FullNode不是叶子结点
z->IsLeaf = false; //那z也不是叶子结点
for (j = 0; j < MinDegree; j++) { //z的子女就等于FullNode的后一半的子女
z->Child[j] = FullNode->Child[MinDegree + j];
}
}
z->Num = FullNode->Num = MinDegree-1; //Child[i]分裂后,FullNode和z都有MinDegree-1个关键字
ShiftKey(x->Key,1,i, x->Num - 1); //把正中间的那个元素移到父节点上面去,ShiftKey会找到它的位置,并把它的位置空出来
x->Key[i] = FullNode->Key[MinDegree - 1]; //将需要上移的关键字移到空出来的位置
ShiftChild(x->Child, 1, i + 1, x->Num); //现在多了一个元素,x的指针域都需要往后移动一位
x->Child[i + 1] = z; //将新分裂出来的结点插入其父节点。
(x->Num)++; //因为Child中有一个移到了x,所以多了一个元素
}
void BTInsertNonFull(PtrBTNode CurrentNode, ElementType Val) { //插入一个非满的结点
int Index = GetIndex(CurrentNode->Key, CurrentNode->Num, Val); //用GetIndex找到插入的下标i
if (true == CurrentNode->IsLeaf) { //如果当前结点是一个叶子结点
ShiftKey(CurrentNode->Key, 1, Index, CurrentNode->Num - 1); //使用shiftkey函数空位置出来
CurrentNode->Key[Index] = Val; //插进空位
(CurrentNode->Num)++; //个数++
}
else { //如果不是叶子结点,应该将结点插到CurrentNode为根的适当叶节点中去,需要确定递归朝哪个子节点下降
if (MinDegree * 2 - 1 == CurrentNode->Child[Index]->Num) { //检查递归是否降至一个满结点上
BTChildSplit(CurrentNode, Index); //如果到了一个满结点就分裂
if (CurrentNode->Key[Index] < Val) { //确定从哪个孩子结点下降
Index++;
}
}
BTInsertNonFull(CurrentNode->Child[Index], Val); //递归的将Val插入到合适的子树上去
}
}
void BTInsert(PtrBT T, ElementType Val) {
PtrBTNode NewNode;
if (MinDegree * 2 - 1 == T->Root->Num) { //如果B树T的根节点关键字的个数满了的话
NewNode = BTAllocateNode(); //分配一个新的结点
NewNode->IsLeaf = false; //如果新建的结点不是叶子结点
NewNode->Child[0] = T->Root; //新建结点的第一个孩子是原来的B树的根
T->Root = NewNode; //新的B树的根是新建的结点
BTChildSplit(NewNode, 0); //newnode的child[0]分裂
}
BTInsertNonFull(T->Root, Val); //沿树向下递归
}
//变量关系:currentnode是父节点。Child[index]、Child[index-1]、Child[index+1]都是它的子节点。
//当要删除的结点不在当前结点的子树下面的时候定义一个subnode是currentnode的子节点,并且是删除结点的父节点
void BTDelete(PtrBT T, PtrBTNode CurrentNode, ElementType Val) {
int Index;
PtrBTNode Precursor=NULL, Successor=NULL, SubNode;
Index = GetIndex(CurrentNode->Key, CurrentNode->Num, Val); //找到value的下标
SubNode = CurrentNode->Child[Index]; //初始化subnode是包含关键字Val的子树的根的指针
if (Index < CurrentNode->Num && CurrentNode->Key[Index] == Val) { //如果关键字在当前结点中
if (true == CurrentNode->IsLeaf) { //是叶节点
ShiftKey(CurrentNode->Key, 0, Index + 1, CurrentNode->Num - 1); //shiftkey在false的时候能实现把它删了
(CurrentNode->Num)--; //减一
return;
}
else { //不是叶节点
Precursor = CurrentNode->Child[Index]; //初始化两个指针指向指向关键字val之前的某左子节点Child[index]、和之后的右子节点Child[index+1]
Successor = CurrentNode->Child[Index + 1];
if (Precursor->Num > MinDegree - 1) { //如果左子节点至少有MinDegree个关键字
CurrentNode->Key[Index] = Precursor->Key[Precursor->Num - 1]; //原来的位置被前驱取代
BTDelete(T, Precursor, Precursor->Key[Precursor->Num - 1]); //原来的关键字被删除
}
else if (Successor->Num > MinDegree - 1) { //如果右子结点至少有MinDegree个关键字
CurrentNode->Key[Index] = Successor->Key[0]; //后继
BTDelete(T, Successor, Successor->Key[0]); //删除
}
else { //如果左右子结点都只含有MinDegree-1个关键字
Merge(T, CurrentNode, Index, Index + 1); //把左右子结点合并,把关键字从父节点拖到子节点
BTDelete(T, CurrentNode->Child[Index], Val); //递归删掉关键字
}
}
}
else { //关键字不在当前结点,要确定包含关键字的子树的根
if (true == CurrentNode->IsLeaf) { //当前结点是叶子结点
return;
}
else {
if (SubNode->Num > MinDegree - 1) { //这个当前子树的根至少有MinDegree个关键字
BTDelete(T, SubNode, Val); //删除
}
else { //当前子树的根少于MinDegree个关键字,也就是包含MinDegree-1个关键字
if (Index > 0) {
Precursor = CurrentNode->Child[Index - 1];
}
if (Index < CurrentNode->Num) {
Successor = CurrentNode->Child[Index + 1];
}
if (Index > 0 && Precursor->Num > MinDegree - 1) { //相邻的左兄弟结点至少有MinDegree个关键字
ShiftKey(SubNode->Key, 1, 0, SubNode->Num - 1); //往后移动
ShiftChild(SubNode->Child, 1, 0, SubNode->Num); //指针也往后移动
SubNode->Key[0] = CurrentNode->Key[Index - 1]; //从父结点中移动一个关键字到自己这里
SubNode->Child[0] = Precursor->Child[Precursor->Num]; //把指针也移动过来
(SubNode->Num)++;
CurrentNode->Key[Index - 1] = Precursor->Key[Precursor->Num - 1]; //左兄弟结点的一个关键字移到父节点也就是CurrentNode
(Precursor->Num)--;
BTDelete(T, SubNode, Val); //递归
}
else if (Index < CurrentNode->Num && Successor->Num > MinDegree - 1) { //右子树至少有MinDegree个关键字
SubNode->Key[SubNode->Num] = CurrentNode->Key[Index];
SubNode->Child[SubNode->Num + 1] = Successor->Child[0];
(SubNode->Num)++;
CurrentNode->Key[Index] = Successor->Key[0];
ShiftKey(Successor->Key, 0, 1, Successor->Num - 1);
ShiftChild(Successor->Child, 0, 1, Successor->Num);
(Successor->Num)--;
BTDelete(T, CurrentNode->Child[Index], Val);
}
else { //当前子树的根和他的左右兄弟结点都只有MinDgree-1个关键字
if (Index > 0) {
Merge(T, CurrentNode, Index - 1, Index); //合并递归
BTDelete(T, Precursor, Val);
}
else { //要删的关键字是他那个结点的第一个元素
Merge(T, CurrentNode, Index, Index + 1);
BTDelete(T, SubNode, Val);
}
}
}
}
}
}
void Merge(PtrBT T, PtrBTNode ParentNode, int LeftIndex, int RightIndex) {
PtrBTNode LeftNode = ParentNode->Child[LeftIndex], RightNode = ParentNode->Child[RightIndex];
int i;
for (i = 0; i < MinDegree - 1; i++) { //把右边结点的元素和指针拿过来合并
LeftNode->Key[MinDegree + i] = RightNode->Key[i];
}
if (false == LeftNode->IsLeaf) {
for (i = 0; i < MinDegree; i++) {
LeftNode->Child[MinDegree + i] = RightNode->Child[i];
}
}
LeftNode->Key[MinDegree - 1] = ParentNode->Key[LeftIndex]; //父节点的一个元素移动下来
LeftNode->Num = MinDegree * 2 - 1; //个数+1
ShiftKey(ParentNode->Key, 0, LeftIndex + 1, ParentNode->Num - 1); //parentnode 下移的那个元素在父节点里面删了
ShiftChild(ParentNode->Child, 0, RightIndex + 1, ParentNode->Num); //指针也删了
(ParentNode->Num)--;
if (ParentNode == T->Root && 0 == ParentNode->Num) { //如果刚刚那个父节点是根节点的话且移动过后已经没有元素了
T->Root = LeftNode; //他的子节点就是新的根
}
}
void ShiftKey(PtrElementType Key, bool Direction, int Begin, int End) {
int i;
if (true == Direction) { //true关键字就往后移动
for (i = End; i >= Begin; i--) {
Key[i + 1] = Key[i];
}
}
else { //否则关键字往前移动
for (i = Begin; i <= End; i++) {
Key[i - 1] = Key[i];
}
}
}
void ShiftChild(PtrBTNode *Child, bool Direction, int Begin, int End) {
int i;
if (true == Direction) { //child往后
for (i = End; i >= Begin; i--) {
Child[i + 1] = Child[i];
}
}
else {
for (i = Begin; i <= End; i++) { //child往前
Child[i - 1] = Child[i];
}
}
}
int GetIndex(PtrElementType Key, int Size, ElementType Val) {
int i;
for (i = 0; i < Size; i++) {
if (Key[i] >= Val) {
break;
}
}
return i; //获取下标
}
void BTPrintTree(PtrBTNode Root) {
int i;
if (NULL == Root) {
return;
}
putchar('[');
for (i = 0; i < Root->Num; i++) {
printf("%d", Root->Key[i]);
if (i != Root->Num - 1) {
putchar(' ');
}
}
putchar(']');
printf("%d", Root->IsLeaf); //显示1的是叶子,显示0的不是叶子
putchar('\n');
for (i = 0; i <= Root->Num; i++) {
BTPrintTree(Root->Child[i]);
}
}
void BTCreateTree(PtrBT T) {
int i;
int a[] = { 6,7,11,4,8,13,17,9,16,20,3,12,14,15 };
for (i = 0; i < 14; i++) {
BTInsert(T, a[i]);
BTPrintTree(T->Root);
printf("The End\n");
}
}
在写各种树的时候最重要的还是要学会写结构体和指针。