B树的原理分析及实现

来源:互联网 发布:跑跑辅助源码猴岛 编辑:程序博客网 时间:2024/05/24 02:38

    B树是为磁盘或其他直接存取的辅助设备而设计的一种平衡搜索树。许多数据库系统使用B树或B树的变种来存储信息。为何会采用这种树结构进行设计呢,《算法导论》18章讲得很到位,值得细细品味。

   下面直接开始了解B树的实现细节吧!

    一、B树的定义

    它与二叉搜索树和红黑树一样,把卫星数据和关键字都存放在同一个结点中。

    1、每个结点的属性:

     (1)x.n: 每个结点包含的关键字个数;

     (2)x.key1, x.key2, x.key3.....x.keyn, 以非降序排列。

     (3)x.leaf, 表示x结点是否是叶子结点。

     (4)x.ci: 每个结点还包含x.n+1个孩子指针x.c1, x.c2, x.c3......x.cn+1

    2、x中的关键字对存储在各子树中的关键字范围加以分割。

    3、每个叶结点具有相同的深度,为树的高度

    4、我们可以为每棵B树规定一个最小度数:T,那么每个结点所包含的关键字个数的范围:T-1 ~2T-1, 所包含的孩子结点个数为T ~ 2T。当结点关键字个数为2T - 1时,该结点满。(根结点至少包含1个关键数,而其他结点至少包含T - 1个关键字。

    5.B树的高度:h<=logt (n+1)/2。由来算法导论上很清晰地给出了证明了,容易推导而得。

二、B树上的基本操作

    1、B_Search

     B树在每个结点做的是一个多路的分支选择。对于每个内部结点x, 做的是一个(x.n+1)路的分支选择。

     B_Tree_Search(x , k)  :  x为指向子树根结点的指针,从该子树中搜索 k, 返回k 所在结点 及其在结点中的索引(y,i)

    

 i =1 while i<=x.n and k> x.keyi    i++ if i<=x.n and k = keyi   return (x,i) if x.leaf   return NULL else    Disk_Read(x,ci) //读取x的第i个孩子    return B_Tree_Search(x.ci, k)

    2、B_Insert

    区别于二叉搜索树的插入,并非简单地创建一个新的叶结点,然后将其插入。而是将新的关键字插入到一个已经存在的叶结点上。由于不能将关键字插入到一个满的叶结点中,故引入一个操作,将一个满的结点y,按照其中间关键字y.keyt分裂为各有t-1个关键字的结点, 而y.keyt则被提升到其父结点中。


3、B_Delete

   B树中,删除某个关键字后必须保证该结点的关键字个数>=T-1.。因此一个简单的删除算法,当要删除关键字的路径上结点有最少的关键字数时,类似于插入算法,也可能要向上回溯。

   分析删除过程:

  (1)、如果关键字k在结点x中, 并且x是叶结点,那么从x中删除k。

  (2)、如果关键字k在结点x中,并且x是内部结点,那么:

     a、如果x中小于k 的孩子结点y,至少包含t个关键字,那么找出k在以y为根的子树中的前驱k',将该值替换掉k,并递归地删除k'

     b、如果y有小于t(即 t-1)个关键字,但是x中大于k的孩子结点z中,至少包含t个关键字,那么找出k在以z为根的子树中的后继k',将该值替换掉k,并递归地删除k'

     c、如果y和z均只有t-1个关键字,那么合并y和z,并将k作为它们的中间关键字,同时从x中删除k, 释放掉z后,从y中递归地删除k。

  (3)、如果关键字不在结点x中。那么确定x.ci, 以它为根的子树要包含k。如果x.ci只有t-1个关键字,必须执行一些操作,以保证它降至一个至少包含t个关键字的结点。然后,通过对x的某个子结点进行递归而结束:

     a、如果x.ci中只包含t-1个关键字,而它的左相邻的兄弟x.ci-1至少包含t个关键字,那么,将x中的x.keyi-1降至x.ci中,将x.ci-1中的最大关键字升至x中。对称去处理x.ci的右相邻结点。

    b、如果x.ci的左右相邻结点均只包含t-1个关键字,那么将x.ci与一个兄弟合并,将x的一个恰当的关键字移至新的结点中,使之成为它们的中间关键字。


具体实现代码:

// B_Tree.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"#include <stdio.h>#include <iostream>using namespace std;#define  T 4typedef struct B_Tree_Node{int n;//指示该结点的关键字个数int *keys;//该结点关键字数组bool isLeaf;//该结点是否是叶子结点struct B_Tree_Node **child ;    //该结点的所有孩子结点struct B_Tree_Node *p;    //该结点的父结点}B_Tree_Node, *p_B_Tree_Node;B_Tree_Node *alloact_Node(){B_Tree_Node *newNode = new B_Tree_Node;newNode->n = 0;newNode->isLeaf = true;newNode->keys = new int[2*T-1];newNode->child = new p_B_Tree_Node[2*T];newNode->p = NULL;for(int i=0;i<2*T;i++)newNode->child[i] = NULL;return newNode;}//从以当前结点为根结点的子树中,寻找k所在的结点,/*curNode 表示该当前子树的根结点,k 是要查找的关键字, index用来保存k在该结点中的索引首先在当前结点中查找 ,若在该结点中,则直接返回该结点指针;如果没有找到,而且该结点是叶子结点,则返回NULL否则,在它的适当的孩子结点中查找*/B_Tree_Node * searchNode(B_Tree_Node *curNode, int k, int &index){int i = 0;while(i<=curNode->n && k >curNode->keys[i]) i++;if(i<curNode->n && k == curNode->keys[i])  //找到了k{index = i;return curNode;}if(curNode->isLeaf) //如果该结点是叶子结点,则k不存在return NULL;searchNode(curNode->child[i],k,index);}//splitNode_p是被分裂结点的父结点,i是索引为i的孩子为满,需要被分裂//被分裂的结点是满的,那么它的n = 2*T - 1;被分裂为两个T-1个关键字的子结点,同时它的中间元素被提升到其父节点中void BTree_Child_Split(B_Tree_Node *splitNode_p, int index_child){B_Tree_Node *newChild = alloact_Node();newChild->n = T-1;for(int i = 0;i<T-1;i++){newChild->keys[i] = splitNode_p->child[index_child]->keys[T+i];}splitNode_p->child[index_child]->n = T-1;if(splitNode_p->child[index_child]->isLeaf!=true)  //如果它的第i个孩子不是叶子结点,则将它的后T个孩子也送给newChild{newChild->isLeaf = false;for(int i=0;i<T-1;i++)newChild->child[i] = splitNode_p->child[i+T];}//将newChild 添加为splitNode_p的第i+1个孩子结点,将中间关键字提升到它中for(int i = splitNode_p->n; i>=index_child;i--){splitNode_p->child[i+1] = splitNode_p->child[i];}splitNode_p->n++;splitNode_p->child[index_child+1] = newChild;for(int i = splitNode_p->n-1; i>=index_child;i--){splitNode_p->keys[i+1] = splitNode_p->keys[i];}splitNode_p->keys[index_child] = splitNode_p->child[index_child]->keys[T-1];}void BTree_Insert_NonFull(B_Tree_Node *nonfull, int k){int i = nonfull->n - 1;if(nonfull->isLeaf){while(i>=0&&k<nonfull->keys[i]){nonfull->keys[i+1] = nonfull->keys[i];i--;}i = i+1;(nonfull->n)++;nonfull->keys[i] = k;}else{while(i>=0&&k<nonfull->keys[i])i--;i = i+1;if(nonfull->child[i]->n == 2*T-1){BTree_Child_Split(nonfull,i);if(k>nonfull->keys[i])i = i+1;}BTree_Insert_NonFull(nonfull->child[i],k);}}//在B_Tree中加入新的关键字,主要由BTree_Insert_NonFull来实现,确保每次插入时所访问的结点都是非满结点;//首先,若根结点为满,则分裂根结点void BTree_Insert_Node(p_B_Tree_Node *root,int k){B_Tree_Node *p = *root;if(p->n == 2*T - 1) //如果根结点满{B_Tree_Node *newRoot =alloact_Node();newRoot->child[0] = (*root);newRoot->isLeaf = false;*root = newRoot;BTree_Child_Split(newRoot,0);BTree_Insert_NonFull(newRoot,k);}else BTree_Insert_NonFull(*root,k);}void printBFS(B_Tree_Node *t){if(NULL == t)return;//输出当前节点所有关键字cout << "[";for(int i = 0;i < t->n;++i){cout << t->keys[i];if(t->n - 1 != i)cout << " ";}cout << "]" << endl;//递归输出所有子树for(int i = 0;i <= t->n;++i)printBFS(t->child[i]);}//delete/*subNode,当前结点,以它为根的树中删除k1.看k是否存在于以subNode为根的子树中,不存在则返回;存在则继续2.看k是否存在于当前结点:(1)存在且该节点为叶子节点,则直接删除k;(2)存在且该节点为内部节点,分情况讨论;(3)不存在,则找出以它的孩子x.ci为根的子树中包含k,Different Case:1.存在于当前结点,且该结点为内部结点:case 1:该结点中前于k的子结点y中有至少包含T个元素,则找出k'替换k,并递归地删除k';case 2:该结点中前于k的子结点z中有只包含T-1个元素,但大于k的子结点中有至少包含T个元素,同上找出k'替换k,递归地删除k';case 3:以上两个子结点均只包含T-1个元素,那么将k与z子结点中的元素均归并到y中。再递归删除k2.k不存在于当前的结点中,存在于以它的孩子x.ci为根的子树中。case 1:若x.ci中至少包含有T个元素,递归找删除kcase 2:若x.ci中只有T-1个元素,而x.ci-1或x.ci+1中至少有T个元素,刚将x中合适的元素取出来给x.ci,x.ci-1或x.ci+1中合适的元素取出来给x,递归删除kcase 3:若x.ci-1和x.ci+1中均只有T-1个元素,那么将x.ci-1与x.ci合并,或另外两个合并,并将x中合适的元素作为它们的中间关键字。*/void BTree_delete_key(B_Tree_Node *subNode, int k){int index = 0;B_Tree_Node *deleteNode = NULL;if((deleteNode = searchNode(subNode,k,index)) == NULL)return;int keyIndex = -1;for(int i=0;i<subNode->n;i++){if(k == subNode->keys[i]){keyIndex = i;break;}} //如果在当前结点,且当前结点为叶子结点,则直接删除k//OK******************************if(keyIndex != -1 && subNode->isLeaf){for(int i=keyIndex;i<subNode->n-1;i++){subNode->keys[i] = subNode->keys[i+1];}(subNode->n)--;}//如果在当前结点中,且当前结点不为叶子结点else if(keyIndex != -1 && subNode->isLeaf!= true){B_Tree_Node *processorNode = subNode->child[keyIndex];B_Tree_Node *succssorNode = subNode->child[keyIndex+1];//如果小于k的孩子结点关键字数大于Tif(processorNode->n >= T){int k1 = processorNode->keys[processorNode->n-1];subNode->keys[keyIndex] = k1;BTree_delete_key(processorNode,k1);}//如果大于k的孩子结点关键字数大于Telse if(succssorNode->n >=T){int k1 = succssorNode->keys[0];subNode->keys[keyIndex] = k1;BTree_delete_key(succssorNode,k1);}//如果两个孩子结点关键字数均不大于T,则将k与右孩子结点的关键字归并到左孩子中else{for(int j=0;j<T-1;j++){//processorNode->keys[T-1] = k;这里最好不要使用T表示,因为如果是根结点的话,可能它的关键字数不为TprocessorNode->keys[processorNode->n] = k;processorNode->keys[processorNode->n+1+j] = succssorNode->keys[j];}processorNode->n = 2*T -1 ;//将subNode->child[keyIndex+1]的孩子传给左邻孩子if(!processorNode->isLeaf){for(int j=0;j<T;j++){processorNode->child[T+j] = succssorNode->child[j];}}//修改subNode中的key值for(int j = keyIndex;j<subNode->n-1;j++){subNode->keys[j] = subNode->keys[j+1];}subNode->n = subNode->n - 1;delete succssorNode;BTree_delete_key(processorNode,k);}}else if(keyIndex == -1) //不在当前结点中{int childIndex = 0;B_Tree_Node *deleteNode = NULL;//寻找合适的子孩子,以该子孩子为根的树包含kfor(int j = 0;j<subNode->n;j++){if(k<subNode->keys[j]){childIndex = j;deleteNode = subNode->child[j];break;}}//如果该子孩子的关键字数小于T,考虑那两种情况if(deleteNode->n <= T-1){//deleteNode的左兄弟结点B_Tree_Node *LeftNode = subNode->child[childIndex-1];//deleteNode的右兄弟结点B_Tree_Node *RightNode = subNode->child[childIndex+1];//如果左兄弟结点关键字数大于T,将父结点中的第childIndex-1个元素送给deleteNode,将Left中的最大元素送给父结点,if(childIndex>=1 && LeftNode->n >= T){for(int i = deleteNode->n;i>0;i--){deleteNode->keys[i] = deleteNode->keys[i-1];}deleteNode->keys[0] = subNode->keys[childIndex];subNode->keys[childIndex] = LeftNode->keys[LeftNode->n - 1];(LeftNode->n)--;(deleteNode->n)++;BTree_delete_key(deleteNode,k);}//如果右兄弟关键字大于T,将父结点中的第childIndex个元素送给deleteNode,将Right中的最小元素送给父结点,else if(childIndex<subNode->n && RightNode->n >= T){deleteNode->keys[deleteNode->n] = subNode->keys[childIndex];subNode->keys[childIndex] = RightNode->keys[0];for(int i=0;i<RightNode->n-1;i++)RightNode[i] = RightNode[i+1];(RightNode->n)--;(deleteNode->n)++;BTree_delete_key(deleteNode,k);}//如果左兄弟和右兄弟的关键字数均不在于T,则将左兄弟或右兄弟与其合并else {if(childIndex>=1)//左兄弟存在,合并{//将keys合并for(int i=0;i<deleteNode->n;i++){LeftNode->keys[LeftNode->n+i] = deleteNode->keys[i];}//如果非叶子结点,则将叶子也合并if(!deleteNode->isLeaf){for(int i=0;i<deleteNode->n+1;i++){LeftNode->child[LeftNode->n+1+i] = deleteNode->child[i];}}LeftNode->n = LeftNode->n + deleteNode->n;//调整subNode的子节点for(int i = childIndex;i<subNode->n;i++){subNode->child[i] = subNode->child[i+1];}BTree_delete_key(LeftNode,k);}else //合并它和右兄弟{//将keys合并for(int i=0;i<RightNode->n;i++){deleteNode->keys[i+deleteNode->n] = RightNode->keys[i];}//如果非叶子结点,则将叶子合并if(!deleteNode->isLeaf){for(int i = 0;i<RightNode->n+1;i++){deleteNode->child[deleteNode->n + 1 + i] = RightNode->child[i];}}deleteNode->n = deleteNode->n + RightNode->n;//调整subNode的子节点for(int i = childIndex+1;i<subNode->n;i++){subNode->child[i] = subNode->child[i+1];}BTree_delete_key(deleteNode,k);}}}BTree_delete_key(deleteNode,k);}}void createBTree(p_B_Tree_Node *root){int a[] = {12,1,9,2,0,11,7,19,4,15,18,5,14,13,10,16,6,3,8,17};for(int i = 0;i<20;i++){BTree_Insert_Node(root,a[i]);printBFS(*root);}}int _tmain(int argc, _TCHAR* argv[]){B_Tree_Node *root = alloact_Node();createBTree(&root);cout<<"B_Tree after delete 12:"<<endl;BTree_delete_key(root,12);printBFS(root);cout<<"B_Tree after delete 1:"<<endl;BTree_delete_key(root,1);printBFS(root);cout<<"B_Tree after delete 9:"<<endl;BTree_delete_key(root,9);printBFS(root);cout<<"B_Tree after delete 2:"<<endl;BTree_delete_key(root,2);printBFS(root);return 0;}


   

原创粉丝点击