B树完整实现(附详细注释)

一定要结合图形和书来看,注释是根据算法导论写的,有些太绕了,直接上书看好得多。
可以结合具体例子看,根据例子来理解代码要轻松一些
代码是抄的这个博主的(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");
	}
}

在写各种树的时候最重要的还是要学会写结构体和指针。

猜你喜欢

转载自blog.csdn.net/alike_meng/article/details/83020299