【算法导论】B树
一棵B树T是具有如下性质的有根树(设根为root):
1.每个节点x有一下域:
(a)num,当前存储在节点x的关键字个数,关键字以非降序存放,因此key[i]<=key[i+1]<=……key[n];
(b)isleaf,是一个bool值,如果x为叶子节点,则isleaf=true.
(c)每个节点包括num+1个指向其子女的指针p[0],p[1],……p[num]。如果x为叶子,则p=NULL
(d)每个节点包括num个关键字key[0],key[1],……key[num-1]。各关键字key[i]对存储在各子树中的关键字范围加以分隔: k1<=key[1]<=k2<=key[2]……
2.每个叶节点具有相同的深度。
3.每一个节点包含的关键字有上下界。这些界可以用一个称为B树的最小度数的固定整数M>=2来表示。每个非根节点的个数n必须满足M-1<=n<=2M-1。根节点至少包括 一个关键字。如果一个节点是满的,则它恰好有2M-1个关键字。
一棵B树可以表示如下:
B树与红黑树的相似之处在于,每棵有n个节点的B树高度为O(lgn),但可能要比一棵红黑树的高度小很多,因为它的分支比较多!因为在磁盘存储中,需要经常读取数据,所以选择一个大的分支因子,可以大大地降低树的高度,以及磁盘存取次数。这样说可能比较抽象,下面举例说明:下图为一棵分支因子为1001、高度为2的B树,可以看出它可以存储超过10亿个关键字;但是,因为根节点可以持久的保留在主存中,因此需找某个关键字至多只需要两次磁盘存取。如果用二叉树存储的话,树的深度将会很大,那么寻找位于叶子节点处的关键字将需要很多次磁盘读取!假设有n个节点,那么二叉树的高度为h<=lg(n+1),而B树为h<=log((n+1)/2)/log(M),其中M为最小度数。
B树的各种操作:
1.查找
查找b树和查找二叉树类似,就是在分支处进行判断选择正确的子树,然后递归调用。查找过程的时间复杂读为Mlgn/lgM具体程序实现如下:
/**********************************************************\ 函数功能:查找关键字所在的节点 输入: 树的根,关键字 输出: 关键字所在的节点 \**********************************************************/ BtreeNode *BtreeSearch(BtreeNode *TestNode,int keyword) { int i=0; while(i<TestNode->num&&keyword>TestNode->key[i]) i=i+1; if(i<=TestNode->num&&keyword==TestNode->key[i]) return TestNode; if(TestNode->isleaf) { printf("Not founded!\n"); return NULL; } else { return BtreeSearch(TestNode->p[i],keyword); } }
2.创建空的B树
/**********************************************************\ 函数功能:创建节点 输入:无 输出:新节点 \**********************************************************/ BtreeNode * BtreeCreate() { BtreeNode *node=(BtreeNode *)malloc(sizeof(BtreeNode)); if(NULL==node) return NULL; node->isleaf=true; node->num=0; for(int i=0;i<2*M;i++) node->p[i]=NULL; for(int i=0;i<2*M-1;i++) node->key[i]=0; return node; }
3.插入
B树的插入比二叉树的插入要复杂的多,因为二叉树的插入是插入新的节点,而B树的插入是将关键字插入到已存在的节点,而节点可能已经是满节点(前面提到过),就会破坏B树的性质。因此不能将关键字插入到满节点上。根据B树的规则,每个节点的关键字个数在[M-1, 2M-1]之间,故当keyword(要插入的关键字)要加入到某个叶子时,如果该叶子节点已经有2M-1个关键字,则再加入keyword就违反了B树的定义,这时就需要对该叶子节点进行分裂,将叶子以中间节点为界,分成两个包含M-1个关键字的子节点,同时把中间节点提升到该叶子的父节点中,如果这样使得父节点的关键字个数超过2M-1,则要继续向上分裂,直到根节点,根节点的分裂会使得树加高一层。
为了解决上面问题,我们需要不断地回溯,这显然比较复杂,我们可以未雨绸缪:我们不是等到发现是否真的需要分裂一个满节点时才做插入操作。相反地,当沿着树向下查找要插入关键字所处位置时,就分裂沿途遇到的每个满节点。这样做后,每当要分裂一个满节点时,就能保证其双亲不是满节点。
分裂满节点的过程图解如下:
我们还要考虑特殊情况:当分裂一个满的根时,需要先让根成为一个新的空根节点的孩子,这样才能被上面的分解过程分解。树的高度增加1,分裂是树增高的唯一途径!其操作如下图:
综上所述,插入过程的具体实现如下:
//////////////////////////////插入部分/////////////////////////////////////////// /**********************************************************\ 函数功能:节点分裂,防止违反B树的性质 输入: 父节点father ,子节点child,k表示子节点为父节点的哪个孩子 输出:无 \**********************************************************/ void BtreeSplitChild(BtreeNode *father,BtreeNode *child,int k) { BtreeNode *newchild=(BtreeNode *)malloc(sizeof(BtreeNode)); newchild->isleaf=child->isleaf;//newchild为child的右节点,即child分裂为child和newchild newchild->num=M-1; for(int i=0;i<M-1;i++) newchild->key[i]=child->key[i+M]; if(!child->isleaf)//当child不是叶子时,还要把指针赋给newchild { for(int j=0;j<M;j++) newchild->p[j]=child->p[j+M]; } child->num=M-1;//child的个数由2M-1变为M-1 for(int i=father->num-1;i>=k+1;i--)//改变父节点的内容 father->p[i+1]=father->p[i]; father->p[k+1]=newchild; for(int j=father->num-1;j>=k;j--) father->key[j+1]=father->key[j]; father->key[k]=child->key[M-1];//将child的中间节点提升到父节点 father->num=father->num+1; } /**********************************************************\ 函数功能:x节点不是满的情况下,插入keyword 输入:B树的根,要插入的关键字 输出:无 \**********************************************************/ void BtreeInsertNotFull(BtreeNode *x,int keyword) { int i=x->num; if(x->isleaf)//当x为叶子时,keyword插入到该节点中 { while(i>=1&&keyword<x->key[i-1]) { x->key[i]=x->key[i-1]; i=i-1; } x->key[i]=keyword; x->num=x->num+1; } else//当x不是叶子时,找到keyword要插入的节点并插入 { while(i>=1&&keyword<x->key[i-1]) { i=i-1; } if(x->p[i]->num==2*M-1)//当节点为满节点时,需要分裂 { BtreeSplitChild(x,x->p[i],i); if(keyword>x->key[i]) i=i+1; } BtreeInsertNotFull(x->p[i],keyword); } } /**********************************************************\ 函数功能:插入关键值 输入:B树的根,关键字 输出:B树的根 \**********************************************************/ BtreeNode * BtreeInsert(BtreeNode *TestNode,int keyword) { if(TestNode->num==2*M-1)//当根节点为满时,唯一增加高度的情况 { BtreeNode *newroot=(BtreeNode *)malloc(sizeof(BtreeNode)); newroot->isleaf=false;//产生新的根 newroot->num=0; newroot->p[0]=TestNode; BtreeSplitChild(newroot,TestNode,0); BtreeInsertNotFull(newroot,keyword); return newroot; } else { BtreeInsertNotFull(TestNode,keyword); return TestNode; } }
4.删除
B树的删除比插入操作更加复杂,插入操作只需考虑三种情况,而删除操作需要考虑的情况很多,情况如下:
和插入操作类似,根据B树的规则,每个节点的关键字个数在[M-1, 2M-1]之间,故当keyword(要插入的关键字)要从某个叶子删除时,如果该叶子节点只有有M-1个关键字,则再删除keyword就违反了B树的定义,这时就需要对该叶子节点进行合并。上图中各种情况中的t就是我所说的M即最小度数。具体程序实现如下:
///////////////////////////删除部分////////////////////////////////////////// /**********************************************************\ 函数功能:合并左右子节点 输入:根,左右子节点,左节点是父节点的第pos个节点 输出:无 \**********************************************************/ void BtreeMergeChild(BtreeNode *root, int pos, BtreeNode *y, BtreeNode *z) { // 将z中节点拷贝到y的后半部分 y->num = 2 * M - 1; for(int i = M; i < 2 * M - 1; i++) { y->key[i] = z->key[i-M]; } y->key[M-1] = root->key[pos]; // 将root->key[pos]下降为y的中间节点 if(false == z->isleaf)// 如果z是内节点即非叶子,需要拷贝指向子节点的指针p { for(int i = M; i < 2 * M; i++) { y->p[i] = z->p[i-M]; } } for(int j = pos + 1; j < root->num; j++) // root->key[pos]下降到y中,更新root中key和p { root->key[j-1] = root->key[j]; root->p[j] = root->p[j+1]; } root->num -= 1; free(z); } /**********************************************************\ 函数功能:删除关键字keyword 输入:树的根,关键字 输出:树的根 \**********************************************************/ BtreeNode *BtreeDelete(BtreeNode *root, int keyword) { // 唯一能降低树高的情形 if(1 == root->num) // 当根只有一个关键字,两个子女 { BtreeNode *y = root->p[0]; BtreeNode *z = root->p[1]; if(NULL != y && NULL != z &&M - 1 == y->num && M - 1 == z->num)//两个子女的关键字个数都为M-1时,合并根与两个子女 { BtreeMergeChild(root, 0, y, z); free(root);//注意释放空间 BtreeDeleteNotFull(y, keyword); return y; } else { BtreeDeleteNotFull(root, keyword); return root; } } else { BtreeDeleteNotFull(root, keyword); return root; } } /**********************************************************\ 函数功能: root至少有个M个关键字时删除关键字 输入: 树的根,关键字 输出: 无 \**********************************************************/ void BtreeDeleteNotFull(BtreeNode *root, int keyword) { if(true == root->isleaf) // 如果在叶子节点,直接删除,情况1 { int i = 0; while(i < root->num && keyword > root->key[i]) i++; if(keyword == root->key[i]) { for(int j = i + 1; j < 2 * M - 1; j++) { root->key[j-1] = root->key[j]; } root->num -= 1; } else { printf("keyword not found\n"); } } else { // 在分支中 int i = 0; BtreeNode *y = NULL, *z = NULL; while(i < root->num && keyword > root->key[i]) i++; if(i < root->num && keyword == root->key[i]) { // 如果在分支节点找到keyword y = root->p[i]; z = root->p[i+1]; if(y->num > M - 1) { // 如果左分支关键字多于M-1,则找到左分支的最右节点pre,替换keyword // 并在左分支中递归删除prev,情况2a int pre = BtreeSearchPrevious(y); root->key[i] = pre; BtreeDeleteNotFull(y, pre);//递归处理 } else if(z->num > M - 1) { // 如果右分支关键字多于M-1,则找到右分支的最左节点next,替换keyword // 并在右分支中递归删除next,情况2b int next = BtreeSearchNext(z); root->key[i] = next; BtreeDeleteNotFull(z, next); } else // 两个分支节点数都为M-1,则合并至y,并在y中递归删除keyword,情况2c { BtreeMergeChild(root, i, y, z); BtreeDelete(y, keyword); } } else// 分支中没有,在分支的子节点中的情况 { y = root->p[i]; if(i < root->num) { z = root->p[i+1];//y的右兄弟 } BtreeNode *p = NULL;//初始化 if(i > 0) { p = root->p[i-1];//y的左兄弟 } if(y->num == M - 1) { if(i > 0 && p->num > M - 1) { // 左兄弟节点关键字个数大于M-1,情况3a BtreeChangeToRchild(root, i-1, p, y); } else if(i < root->num && z->num > M - 1) { // 右兄弟节点关键字个数大于M-1,情况3a BtreeChangeToLchild(root, i, y, z); } else if(i > 0) { BtreeMergeChild(root, i-1, p, y); //左右兄弟节点都不大于M-1,情况3b y = p; } else //没有左兄弟的情况 { BtreeMergeChild(root, i, y, z); } BtreeDeleteNotFull(y, keyword); } else { BtreeDeleteNotFull(y, keyword); } } } } /**********************************************************\ 函数功能:寻找以root为根的最大关键字 输入: 树的根 输出: 最大关键字 \**********************************************************/ int BtreeSearchPrevious(BtreeNode *root) { BtreeNode *y = root; while(false == y->isleaf) { y = y->p[y->num]; } return y->key[y->num-1]; } /**********************************************************\ 函数功能:寻找以root为根的最小关键字 输入:树的根 输出:最小关键字 \**********************************************************/ int BtreeSearchNext(BtreeNode *root) { BtreeNode *z = root; while(false == z->isleaf) { z = z->p[0]; } return z->key[0]; } /**********************************************************\ 函数功能:z向y借节点,将root->key[pos]下降至z,将y的最大关键字上升至root的pos处 输入:根,左右子节点,左节点是父节点的第pos个节点 输出:无 \**********************************************************/ void BtreeChangeToRchild(BtreeNode *root, int pos, BtreeNode *y, BtreeNode *z) { z->num += 1; for(int i = z->num -1; i > 0; i--) { z->key[i] = z->key[i-1]; } z->key[0]= root->key[pos]; root->key[pos] = y->key[y->num-1]; if(false == z->isleaf) { for(int i = z->num; i > 0; i--) { z->p[i] = z->p[i-1]; } z->p[0] = y->p[y->num]; } y->num -= 1; } /**********************************************************\ 函数功能:y向借节点,将root->key[pos]下降至y,将z的最小关键字上升至root的pos处 输入:根,左右子节点,左节点是父节点的第pos个节点 输出:无 \**********************************************************/ void BtreeChangeToLchild(BtreeNode *root, int pos, BtreeNode *y, BtreeNode *z) { y->num += 1; y->key[y->num-1] = root->key[pos]; root->key[pos] = z->key[0]; for(int j = 1; j < z->num; j++) { z->key[j-1] = z->key[j]; } if(false == z->isleaf) { y->p[y->num] = z->p[0]; for(int j = 1; j <= z->num; j++) { z->p[j-1] = z->p[j]; } } z->num -= 1; }
下面用具体实例来形象地说明B树的操作:
假设初始的B树如下:
经过一系列的插入操作后:
在程序中为表示方便,将关键字由字母换成了数字,A、B、C……Y、Z对应于1、2、3……25、26.
经过上面的插入操作后,紧接在进行一系列删除操作:
具体的完整实例程序实现如下:
#include<stdio.h> #include<stdlib.h> #define M 3 //节点结构体 typedef struct BtreeNode { int num; struct BtreeNode *p[2*M]; int key[2*M-1]; bool isleaf; }BtreeNode; BtreeNode * BtreeCreate(); void BtreeSplitChild(BtreeNode *father,BtreeNode *child,int k); void BtreeInsertNotFull(BtreeNode *x,int keyword); BtreeNode * BtreeInsert(BtreeNode *TestNode,int keyword); BtreeNode *BtreeSearch(BtreeNode *TestNode,int keyword); ////// void BtreeMergeChild(BtreeNode *root, int pos, BtreeNode *y, BtreeNode *z); BtreeNode *BtreeDelete(BtreeNode *root, int keyword); void BtreeDeleteNotFull(BtreeNode *root, int keyword); int BtreeSearchPrevious(BtreeNode *root); int BtreeSearchNext(BtreeNode *root); void BtreeChangeToRchild(BtreeNode *root, int pos, BtreeNode *y, BtreeNode *z); void BtreeChangeToLchild(BtreeNode *root, int pos, BtreeNode *y, BtreeNode *z); /**********************************************************\ 函数功能:创建节点 输入:无 输出:新节点 \**********************************************************/ BtreeNode * BtreeCreate() { BtreeNode *node=(BtreeNode *)malloc(sizeof(BtreeNode)); if(NULL==node) return NULL; node->isleaf=true; node->num=0; for(int i=0;i<2*M;i++) node->p[i]=NULL; for(int i=0;i<2*M-1;i++) node->key[i]=0; return node; } //////////////////////////////插入部分/////////////////////////////////////////// /**********************************************************\ 函数功能:节点分裂,防止违反B树的性质 输入: 父节点father ,子节点child,k表示子节点为父节点的哪个孩子 输出:无 \**********************************************************/ void BtreeSplitChild(BtreeNode *father,BtreeNode *child,int k) { BtreeNode *newchild=(BtreeNode *)malloc(sizeof(BtreeNode)); newchild->isleaf=child->isleaf;//newchild为child的右节点,即child分裂为child和newchild newchild->num=M-1; for(int i=0;i<M-1;i++) newchild->key[i]=child->key[i+M]; if(!child->isleaf)//当child不是叶子时,还要把指针赋给newchild { for(int j=0;j<M;j++) newchild->p[j]=child->p[j+M]; } child->num=M-1;//child的个数由2M-1变为M-1 for(int i=father->num;i>=k+1;i--)//改变父节点的内容 { father->p[i+1]=father->p[i]; } father->p[k+1]=newchild; for(int j=father->num-1;j>=k;j--) father->key[j+1]=father->key[j]; father->key[k]=child->key[M-1];//将child的中间节点提升到父节点 father->num=father->num+1; } /**********************************************************\ 函数功能:x节点不是满的情况下,插入keyword 输入:B树的根,要插入的关键字 输出:无 \**********************************************************/ void BtreeInsertNotFull(BtreeNode *x,int keyword) { int i=x->num; if(x->isleaf)//当x为叶子时,keyword插入到该节点中 { while(i>=1&&keyword<x->key[i-1]) { x->key[i]=x->key[i-1]; i=i-1; } x->key[i]=keyword; x->num=x->num+1; } else//当x不是叶子时,找到keyword要插入的节点并插入 { i=x->num; while(i>=1&&keyword<x->key[i-1]) { i=i-1; } if(x->p[i]->num==2*M-1)//当节点为满节点时,需要分裂 { BtreeSplitChild(x,x->p[i],i); if(keyword>x->key[i]) i=i+1; } BtreeInsertNotFull(x->p[i],keyword); } } /**********************************************************\ 函数功能:插入关键值 输入:B树的根,关键字 输出:B树的根 \**********************************************************/ BtreeNode * BtreeInsert(BtreeNode *TestNode,int keyword) { if(TestNode->num==2*M-1)//当根节点为满时,唯一增加高度的情况 { BtreeNode *newroot=(BtreeNode *)malloc(sizeof(BtreeNode)); newroot->isleaf=false;//产生新的根 newroot->num=0; newroot->p[0]=TestNode; BtreeSplitChild(newroot,TestNode,0); BtreeInsertNotFull(newroot,keyword); return newroot; } else { BtreeInsertNotFull(TestNode,keyword); return TestNode; } } /**********************************************************\ 函数功能:查找关键字所在的节点 输入: 树的根,关键字 输出: 关键字所在的节点 \**********************************************************/ BtreeNode *BtreeSearch(BtreeNode *TestNode,int keyword) { int i=0; while(i<TestNode->num&&keyword>TestNode->key[i]) i=i+1; if(i<=TestNode->num&&keyword==TestNode->key[i]) return TestNode; if(TestNode->isleaf) { printf("Not founded!\n"); return NULL; } else { return BtreeSearch(TestNode->p[i],keyword); } } ///////////////////////////删除部分////////////////////////////////////////// /**********************************************************\ 函数功能:合并左右子节点 输入:根,左右子节点,左节点是父节点的第pos个节点 输出:无 \**********************************************************/ void BtreeMergeChild(BtreeNode *root, int pos, BtreeNode *y, BtreeNode *z) { // 将z中节点拷贝到y的后半部分 y->num = 2 * M - 1; for(int i = M; i < 2 * M - 1; i++) { y->key[i] = z->key[i-M]; } y->key[M-1] = root->key[pos]; // 将root->key[pos]下降为y的中间节点 if(false == z->isleaf)// 如果z是内节点即非叶子,需要拷贝指向子节点的指针p { for(int i = M; i < 2 * M; i++) { y->p[i] = z->p[i-M]; } } for(int j = pos + 1; j < root->num; j++) // root->key[pos]下降到y中,更新root中key和p { root->key[j-1] = root->key[j]; root->p[j] = root->p[j+1]; } root->num -= 1; free(z); } /**********************************************************\ 函数功能:删除关键字keyword 输入:树的根,关键字 输出:树的根 \**********************************************************/ BtreeNode *BtreeDelete(BtreeNode *root, int keyword) { // 唯一能降低树高的情形 if(1 == root->num) // 当根只有一个关键字,两个子女 { BtreeNode *y = root->p[0]; BtreeNode *z = root->p[1]; if(NULL != y && NULL != z &&M - 1 == y->num && M - 1 == z->num)//两个子女的关键字个数都为M-1时,合并根与两个子女 { BtreeMergeChild(root, 0, y, z); free(root);//注意释放空间 BtreeDeleteNotFull(y, keyword); return y; } else { BtreeDeleteNotFull(root, keyword); return root; } } else { BtreeDeleteNotFull(root, keyword); return root; } } /**********************************************************\ 函数功能: root至少有个M个关键字时删除关键字 输入: 树的根,关键字 输出: 无 \**********************************************************/ void BtreeDeleteNotFull(BtreeNode *root, int keyword) { if(true == root->isleaf) // 如果在叶子节点,直接删除,情况1 { int i = 0; while(i < root->num && keyword > root->key[i]) i++; if(keyword == root->key[i]) { for(int j = i + 1; j < 2 * M - 1; j++) { root->key[j-1] = root->key[j]; } root->num -= 1; } else { printf("keyword not found\n"); } } else { // 在分支中 int i = 0; BtreeNode *y = NULL, *z = NULL; while(i < root->num && keyword > root->key[i]) i++; if(i < root->num && keyword == root->key[i]) { // 如果在分支节点找到keyword y = root->p[i]; z = root->p[i+1]; if(y->num > M - 1) { // 如果左分支关键字多于M-1,则找到左分支的最右节点pre,替换keyword // 并在左分支中递归删除prev,情况2a int pre = BtreeSearchPrevious(y); root->key[i] = pre; BtreeDeleteNotFull(y, pre);//递归处理 } else if(z->num > M - 1) { // 如果右分支关键字多于M-1,则找到右分支的最左节点next,替换keyword // 并在右分支中递归删除next,情况2b int next = BtreeSearchNext(z); root->key[i] = next; BtreeDeleteNotFull(z, next); } else // 两个分支节点数都为M-1,则合并至y,并在y中递归删除keyword,情况2c { BtreeMergeChild(root, i, y, z); BtreeDelete(y, keyword); } } else// 分支中没有,在分支的子节点中的情况 { y = root->p[i]; if(i < root->num) { z = root->p[i+1];//y的右兄弟 } BtreeNode *p = NULL;//初始化 if(i > 0) { p = root->p[i-1];//y的左兄弟 } if(y->num == M - 1) { if(i > 0 && p->num > M - 1) { // 左兄弟节点关键字个数大于M-1,情况3a BtreeChangeToRchild(root, i-1, p, y); } else if(i < root->num && z->num > M - 1) { // 右兄弟节点关键字个数大于M-1,情况3a BtreeChangeToLchild(root, i, y, z); } else if(i > 0) { BtreeMergeChild(root, i-1, p, y); //左右兄弟节点都不大于M-1,情况3b y = p; } else //没有左兄弟的情况 { BtreeMergeChild(root, i, y, z); } BtreeDeleteNotFull(y, keyword); } else { BtreeDeleteNotFull(y, keyword); } } } } /**********************************************************\ 函数功能:寻找以root为根的最大关键字 输入: 树的根 输出: 最大关键字 \**********************************************************/ int BtreeSearchPrevious(BtreeNode *root) { BtreeNode *y = root; while(false == y->isleaf) { y = y->p[y->num]; } return y->key[y->num-1]; } /**********************************************************\ 函数功能:寻找以root为根的最小关键字 输入:树的根 输出:最小关键字 \**********************************************************/ int BtreeSearchNext(BtreeNode *root) { BtreeNode *z = root; while(false == z->isleaf) { z = z->p[0]; } return z->key[0]; } /**********************************************************\ 函数功能:z向y借节点,将root->key[pos]下降至z,将y的最大关键字上升至root的pos处 输入:根,左右子节点,左节点是父节点的第pos个节点 输出:无 \**********************************************************/ void BtreeChangeToRchild(BtreeNode *root, int pos, BtreeNode *y, BtreeNode *z) { z->num += 1; for(int i = z->num -1; i > 0; i--) { z->key[i] = z->key[i-1]; } z->key[0]= root->key[pos]; root->key[pos] = y->key[y->num-1]; if(false == z->isleaf) { for(int i = z->num; i > 0; i--) { z->p[i] = z->p[i-1]; } z->p[0] = y->p[y->num]; } y->num -= 1; } /**********************************************************\ 函数功能:y向借节点,将root->key[pos]下降至y,将z的最小关键字上升至root的pos处 输入:根,左右子节点,左节点是父节点的第pos个节点 输出:无 \**********************************************************/ void BtreeChangeToLchild(BtreeNode *root, int pos, BtreeNode *y, BtreeNode *z) { y->num += 1; y->key[y->num-1] = root->key[pos]; root->key[pos] = z->key[0]; for(int j = 1; j < z->num; j++) { z->key[j-1] = z->key[j]; } if(false == z->isleaf) { y->p[y->num] = z->p[0]; for(int j = 1; j <= z->num; j++) { z->p[j-1] = z->p[j]; } } z->num -= 1; } //按层次遍历B树 void Print(BtreeNode *root) { int front,rear; int num=0; int num1=0; int flag=0; BtreeNode *queue[100]; BtreeNode *s; if(root!=NULL) { rear=1; front=0; queue[rear]=root; while(front<rear) { front++; s=queue[front]; if(!s->isleaf) { for(int j=0;j<=s->num;j++) { if(s->p[j]!=NULL) { rear++; queue[rear]=s->p[j]; } } } } for(int k=1;k<=rear;k++)//使输出简单易看 { for(int i=0;i<queue[k]->num;i++) printf("%d ",queue[k]->key[i]); printf("| "); if(k>num) { while(flag<k) { num=num+queue[flag+1]->num+1; flag++; } printf("\n"); flag=k; } } } } //////////////////////////////////////////////////////////////////////////// void main() { /**************************初始化**************************/ BtreeNode *TestNode=BtreeCreate(); BtreeNode *Node1=BtreeCreate(); BtreeNode *Node2=BtreeCreate(); BtreeNode *Node3=BtreeCreate(); BtreeNode *Node4=BtreeCreate(); BtreeNode *Node5=BtreeCreate(); BtreeNode *root=BtreeCreate(); BtreeNode *SearchNode=BtreeCreate(); TestNode->isleaf=false; TestNode->num=4; TestNode->key[0]=7; TestNode->key[1]=13; TestNode->key[2]=16; TestNode->key[3]=24; TestNode->p[0]=Node1; TestNode->p[1]=Node2; TestNode->p[2]=Node3; TestNode->p[3]=Node4; TestNode->p[4]=Node5; Node1->isleaf=true; Node1->num=4; Node1->key[0]=1; Node1->key[1]=3; Node1->key[2]=4; Node1->key[3]=5; Node2->isleaf=true; Node2->num=2; Node2->key[0]=10; Node2->key[1]=11; Node3->isleaf=true; Node3->num=2; Node3->key[0]=14; Node3->key[1]=15; Node4->isleaf=true; Node4->num=5; Node4->key[0]=18; Node4->key[1]=19; Node4->key[2]=20; Node4->key[3]=21; Node4->key[4]=22; Node5->isleaf=true; Node5->num=2; Node5->key[0]=25; Node5->key[1]=26; root=TestNode; /*******************************初始化结束***********************/ printf("原始B树:\n"); Print(root); root=BtreeInsert(root,2); printf("\n插入关键字为2后的B树:\n"); Print(root); root=BtreeInsert(root,17); printf("\n插入关键字为17后的B树:\n"); Print(root); root=BtreeInsert(root,12); printf("\n插入关键字为12后的B树:\n"); Print(root); root=BtreeInsert(root,6); printf("\n插入关键字为6后的B树:\n"); Print(root); printf("\n\n"); //删除操作 root=BtreeDelete(root,6); printf("\n删除关键字为6后的B树:\n"); Print(root); root=BtreeDelete(root,13); printf("\n删除关键字为13后的B树:\n"); Print(root); root=BtreeDelete(root,7); printf("\n删除关键字为7后的B树:\n"); Print(root); root=BtreeDelete(root,4); printf("\n删除关键字为4后的B树:\n"); Print(root); root=BtreeDelete(root,2); printf("\n删除关键字为2后的B树:\n"); Print(root); }
程序结果如下(| 用于分开不同节点的关键字,下图显示是按树的层次遍历的,可以看出结果与上面插入和删除的图解过程完全相同!):
注:如果程序出错,可能是使用的开发平台版本不同,请点击如下链接: 解释说明
原文:http://blog.csdn.net/tengweitw/article/details/17055797
作者:nineheadedbird
【算法导论】B树的更多相关文章
- B树——算法导论(25)
B树 1. 简介 在之前我们学习了红黑树,今天再学习一种树--B树.它与红黑树有许多类似的地方,比如都是平衡搜索树,但它们在功能和结构上却有较大的差别. 从功能上看,B树是为磁盘或其他存储设备设计的, ...
- 算法导论第十八章 B树
一.高级数据结构 本章以后到第21章(并查集)隶属于高级数据结构的内容.前面还留了两章:贪心算法和摊还分析,打算后面再来补充.之前的章节讨论的支持动态数据集上的操作,如查找.插入.删除等都是基于简单的 ...
- "《算法导论》之‘树’":二叉查找树
树的介绍部分摘取自博文二叉查找树(一).二叉查找树(二).二叉查找树. 1. 树的介绍 1.1 树的定义 树是一种数据结构,它是由n(n>=1)个有限节点组成一个具有层次关系的集合. 把它叫做“ ...
- 红黑树——算法导论(15)
1. 什么是红黑树 (1) 简介 上一篇我们介绍了基本动态集合操作时间复杂度均为O(h)的二叉搜索树.但遗憾的是,只有当二叉搜索树高度较低时,这些集合操作才会较快:即当树的高度较高(甚至一种极 ...
- 基本数据结构(2)——算法导论(12)
1. 引言 这一篇博文主要介绍链表(linked list),指针和对象的实现,以及有根树的表示. 2. 链表(linked list) (1) 链表介绍 我们在上一篇中提过,栈与队 ...
- 堆排序与优先队列——算法导论(7)
1. 预备知识 (1) 基本概念 如图,(二叉)堆是一个数组,它可以被看成一个近似的完全二叉树.树中的每一个结点对应数组中的一个元素.除了最底层外,该树是完全充满的,而且从左向右填充.堆的数组 ...
- MIT算法导论——第一讲.Analysis of algorithm
本栏目(Algorithms)下MIT算法导论专题是个人对网易公开课MIT算法导论的学习心得与笔记.所有内容均来自MIT公开课Introduction to Algorithms中Charles E. ...
- MIT算法导论——第二讲.Solving Recurrence
本栏目(Algorithms)下MIT算法导论专题是个人对网易公开课MIT算法导论的学习心得与笔记.所有内容均来自MIT公开课Introduction to Algorithms中Charles E. ...
- MIT算法导论——第四讲.Quicksort
本栏目(Algorithms)下MIT算法导论专题是个人对网易公开课MIT算法导论的学习心得与笔记.所有内容均来自MIT公开课Introduction to Algorithms中Charles E. ...
- 图论——读书笔记(基于BFS广度优先算法的广度优先树)
广度优先树 对于一个图G=(V,E)在跑过BFS算法的过程中会创建一棵广度优先树. 形式化一点的表示该广度 优先树的形成过程是这样的: 对于图G=(V,E)是有向图或是无向图, 和图中的源结点s, 我 ...
随机推荐
- webpack4.1.1的使用详细教程
安装全局webpack cnpm install -g webpack 安装全局webpack-cli npm install -g webpack-cli 初始化:生成package.json文件 ...
- 安卓获取清单文件meta-data数据
<application android:icon="@drawable/ic_launcher" android:label="@string/app_name& ...
- Rails 4.0 bundle exec rspec spec/requests/xxx 测试失败的解决
rails项目没有使用默认的单元测试包,而是使用了rspec-rails来测试. 按照文档说明首先生成对应的测试文件: rails generate integration_test xxx invo ...
- Ajax原理学习
一.AJAX 简介 AJAX即"Asynchronous Javascript And XML"(异步JavaScript和XML),是指一种创建交互式网页应用的网页开发技术. A ...
- java集合循环删除
java集合循环删除,java list集合操作,java循环.分享牛,分享牛原创.java集合删除方法. 2.6.1.第一种方式 list.add("1"); list.add( ...
- Linux内核中的有关Page的算法
static inline int get_order(unsigned long size) { int order; size = (size-1) >> (PAGE_SHIFT-1) ...
- Python descriptor
class A: def __init__(self, name): self.name = name def __get__(self, ins, cls): print('call get') i ...
- React Native实现一个自定义模块
概述 在 前期介绍React Native 项目结构的时候,我们讲解过React的项目组成,其中说过 node_modules 文件夹,这是一个存放 node 模块的地方.我们知道React是用npm ...
- JVM学习之-栈
JVM栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;JVM堆解决的是数据存储的问题,即数据怎么放.放在哪儿,另外JVM堆中存的是对象.JVM栈中存的是基本数据类型和JVM堆中对象的引用. ...
- Collections类解析
最常用的排序: 需要实现Comparable接口 1.什么是Comparable接口 此接口强行对实现它的每个类的对象进行整体排序.此排序被称为该类的自然排序 ,类的 compareTo 方法被称为它 ...