树 - B树的简单实现
来源:互联网 发布:手机千牛淘宝客插件 编辑:程序博客网 时间:2024/06/05 10:03
二叉查找树和平衡二叉树都是典型的二叉查找树结构,其查找的时间复杂度与树的高度相关,降低树的高度自然对查找效率有所帮助,B树正是这样的树。
定义
一棵m阶B树,或为空树,满足以下特性:
- 树中每个结点最多有m棵子树
- 若根结点不是叶子节点,则至少有2个子树
- 除根结点之外的所有非终端结点至少有
⌈m/2⌉ 棵子树 - 每个非终端结点中包含信息(n , A0 , K1 , A1 , K2 , .. , Kn , An)。其中
- Ki(1 ≤ i ≤ n)为关键字,且按升序排序
- 指针Ai(0 ≤ i ≤ n)指向子树的根结点,Ai-1 指向子树中所有结点的关键字均小于Ki,且大于Ki-1
- 关键字的个数n必须满足
⌈m/2⌉ -1 ≤ n ≤ m-1
- 所有叶子结点都出现在同一层,叶子结点不包含任何信息
这里我发现跟定义跟算法导论一书中似乎不一样,有待研究呀!
结构
#define M 7 //阶数#define SUCCESS 1#define ERROR 0const int MIN = ceil(M/2.0)-1;const int MAX = M-1;typedef struct BTNode{ int size; // 当前关键字大小 int keys[M+1]; // 关键字数组,0号单元未使用,按升序排序 struct BTNode *parent; // 双亲结点指针 struct BTNode *childs[M+1]; // 孩子结点数组 }BTNode,*BTree;typedef struct { BTree btree;// 指向找到的结点 int i; // 1<= i <= M,在结点中关键字数组的位置 int tag; // 标记查找是否成功,1为成功,2为失败 }Result;
接口
基本的接口有查找、插入、删除,下面分别讲解
查找
由B树的定义,孩子结点指针Ai中的关键字均小于Ki,大于Ki-1。先从根结点开始查找,若找到要查找的关键字,则查找成功;否则则根据比较进入孩子结点Ai查找,如此反复,直到找到或者查找到叶子结点为止。
下面给出相关代码:
// 比较关键字a与b的大小// 1: a>b// 0: a==b// -1 a<b int CompareTo(int a, int b){ return a>b? 1 : a == b ? 0 : -1;}// 在结点t中查找关键字key// 返回 的index表示keys[index]之前的关键字都小于key int SearchKey(BTree &t,int key){ int index = 1; // 从1开始 // 保证index小于关键字数组的大小,同时保证keys[index]之前的关键字都小于key while( index <= t->size && CompareTo(key,t->keys[index]) == 1 ) index++; return index;}// 查找关键字key // 返回结果res 查找成功则返回位置,否则则返回待插入位置void Search(BTree &t,int key,Result &res){ BTree p,q; // p表示当前节点,q表示上一个结点 p = t; q = NULL; int isFound = 0; // 表示是否查找成功,0为不成功,1为成功 int index = 0; while(p!=NULL&&isFound==0){ // 在当前节点查找 index = SearchKey(p,key); if( index <= p->size && CompareTo(key, p->keys[index]) == 0 ) isFound = 1; else{ q = p; p = p->childs[index-1]; } } if(isFound == 1){ res.btree = p; res.tag = isFound; res.i = index; }else{ // 查找不成功,返回key的插入位置 res.btree = q; // 待插入结点 res.i = index; // 待插入的位置 res.tag = isFound; }}
插入
利用前述的查找结果,若找到,则直接返回,否则,根据待插入位置插入。插入后,若其关键字总数size为达到m,结束,否则分裂结点
分裂结点:
- 生成一个新结点,从中间位置把结点(不包括中间位置结点)分成两部分。
- 前半部分留在旧结点中,后半部分复制到新节点中,中间位置连同新结点插入到父结点中。
- 如果父结点的关键字个数也超过m-1,再分裂。如果持续到根结点分裂,则会产生新的根结点
下面是代码,有点多,不过逻辑还是挺清晰的,而且还有注释:
// 创建以t为根的根结点 void NewRoot(BTree &t,BTree p,int key,BTree ap){ t = (BTNode*)malloc(sizeof(BTNode)); t->keys[1] = key; t->childs[0] = p; t->childs[1] = ap; t->size = 1; t->parent = NULL; if(p!=NULL) p->parent = t; if(ap!=NULL) ap->parent = t; }// 将关键字插入结点t的index位置,ap为其子结点 void InsertToNode(BTree &t,int key,int index,BTree &ap){ int n = t->size; for(int i = n; i >= index; i--){ t->keys[i+1] = t->keys[i]; t->childs[i+1] = t->childs[i]; } t->keys[index] = key; t->childs[index] = ap; if( ap!=NULL ) ap->parent = t; t->size++;}// 将结点t分裂成两个结点,前一半保留,后一半移入新结点ap void Split(BTree &t,int s,BTree &ap){ int n = t->size; ap = (BTNode*)malloc(sizeof(BTNode)); int i, j; for( i = s+1, j = 1; i <= n; i ++, j++ ){ ap->keys[j] = t->keys[i]; } for( i = s, j = 0; i <= n; i++, j++){ ap->childs[j] = t->childs[i]; if(ap->childs[j]!=NULL) ap->childs[j]->parent = ap; } ap->size = n - s; ap->parent = t->parent; t->size = s - 1; } // 在B树中插入关键字key// q:B树上结点,又查找失败得到,待插入位置为q->keys[index-1]和q->keys[index]之间 void InsertToTree(BTree &t,int key,BTree q,int index){ int mFinished = 0; // 标记是否插入完成 int isNeedNewRoot = 0; // 标记是否需要创建新的根结点 int mKey = key; // 关键字 if( q == NULL ) NewRoot(t,NULL,mKey,NULL); else{ BTree ap = NULL; while(isNeedNewRoot == 0 && mFinished == 0){ InsertToNode(q,mKey,index,ap); if(q->size < M) mFinished = 1; else{ // 需要分裂结点q int s = (M+1)/2; mKey = q->keys[s]; Split(q,s,ap); if(q->parent != NULL){ // 将结点被分裂出来的关键字mKey插入父结点中 q = q->parent; index = SearchKey(q,mKey); }else{ // 此时q原本是根结点,被分裂成结点q和ap isNeedNewRoot = 1; } } } if(isNeedNewRoot == 1) NewRoot(t,q,mKey,ap); }}// 在B树中插入关键字keyint Insert(BTree &bt,int key){ Result result; Search(bt,key,result); if( result.tag == ERROR ){ InsertToTree(bt,key,result.btree,result.i); return SUCCESS; } return ERROR; }
删除
B树的删除操作就有点复杂,要分多种情况考虑,最重要的还是对定义要有深刻的理解,特别是在删除过程,必须明确现在的B树是满足定义的,因为在插入跟删除都不断维持着B树的状态,所以不要做无谓的判断,这一点我自己确实做得不好
思路:
先通过查找操作查找待删除关键字位置,再根据所在结点是否最下层非终端结点处理
- 若该结点为最下层非终端结点,有三种可能情况:
- 待删除的关键字所在结点关键字个数 n ≥
⌈m/2⌉ ,直接删除 - 待删除的关键字所在结点关键字个数 n ==
⌈m/2⌉ - 1,删除后需要调整(下面说明)
- 待删除的关键字所在结点关键字个数 n ≥
- 若该结点不是最下层非终端结点,且被删关键字为该结点中第 i 个关键字,则可从孩子结点child[i]所指子树中找到最下层非终端结点的最小关键字key,代替key[i],然后删除key,这样就回到删除最下层非终端结点的操作
调整过程:
当最下层非终端结点被删除某关键字后,需要进行调整
- 如果其左右兄弟结点中有“富余”的关键字,则可将右(左)兄弟结点中最小(大)关键字上移至双亲结点。而将双亲结点中的小(大)与该上移关键字下移至被删关键字所在结点中。(注意这里左右兄弟结点指的是间隔为1的兄弟结点)
- 如果其左右兄弟结点中没有“富余”的关键字,需要把要删除关键字结点与其左(或右)兄弟结点以及双亲结点中分割二者的关键字合并成一个结点,即在删除关键字后,加上双亲结点间隔兄弟结点的关键字,合并到兄弟结点中去。如果因此双亲结点中关键字个数小于
⌈m/2⌉ - 1,则对双亲结点做同样处理。甚至可能对根结点做这样处理使得树减少一层。
下面是代码,代码有点长,最重要还是思路:
// 移除结点t中pos位置的关键字 和child[pos-1]void RemoveKey(BTree &t,int pos){ int size = t->size; int i; for(i = pos; i < size; i ++){ t->keys[i] = t->keys[i+1]; // 孩子结点为空,可以不用管 , // 不能不管 , 可能在Restore2中调用非终端结点的 // t->childs[i] = t->childs[i+1]; // 但是这样不行,另外下面方法调用 t->childs[i-1] = t->childs[i]; // pos >= 1 } t->childs[i-1] = t->childs[i]; t->size --; }// 查找父结点parent中子结点t的位置,查找不到则返回-1 int SearchChildPos(BTree parent,BTree t){ if(t == NULL){ return -1; } int size = parent->size; for(int i=0; i <= size; i ++){ if(parent->childs[i] == t){ return i; } } return -1;}// 删除树上结点t的pos个位置 void DeleteBTree(BTree &t,int pos){ if( t==NULL ) return ; if(t->childs[pos] != NULL){ // 不是最下层终端结点 // 查找孩子子树最下层非终端结点最小关键字 BTree child = t->childs[pos]; while( child->childs[0] != NULL){ child = child->childs[0]; } int key = child->keys[1]; t->keys[pos] = key; // 调用delete DeleteBTree(child,1); } else{ // 下层终端结点 RemoveKey(t,pos); if( t->size < MIN ){ Restore(t,pos); } }}// 删除B树t上的关键字key int Remove(BTree &t,int key){ Result res; Search(t,key,res); if(res.tag == 1){ DeleteBTree(res.btree,res.i); return 1; }else{ printf("无该关键字\n"); return 0; }} // 查看兄弟结点是否富余 // 若富余,调整后返回 SUCCESS 1// 否则 返回 ERROR 0 int Restore2(BTree &t){ if( t == NULL ) return ERROR; if( t->size >= MIN ) return SUCCESS; BTree parent = t->parent; if( parent == NULL ) return ERROR; int sizeOfParent = parent->size; int posInParent; if((posInParent = SearchChildPos(parent,t)) == -1 ) return ERROR; if( posInParent > 0 && parent->childs[posInParent-1] != NULL && parent->childs[posInParent-1]->size > MIN ){ // 查找左兄弟,仅仅左兄弟 // 如果有,则获取左兄弟最大关键字 BTree lbt = parent->childs[posInParent-1]; int leftMaxKey = lbt->keys[lbt->size]; // 注意这里移动后,结点的父结点的处理 InsertToNode(t,parent->keys[posInParent],1,t->childs[0]); t->childs[0] = lbt->childs[lbt->size]; if(t->childs[0]) t->childs[0]->parent = t; lbt->childs[lbt->size] = NULL; lbt->size--; parent->keys[posInParent] = leftMaxKey; return SUCCESS; }else if( posInParent < sizeOfParent && parent->childs[posInParent+1] != NULL && parent->childs[posInParent+1]->size > MIN ){// 查找右兄弟 // 如果有,则获取右兄弟最小关键字 BTree rbt = parent->childs[posInParent+1]; int rightMinKey = rbt->keys[1]; // 注意这里移动后,结点的父结点的处理 InsertToNode(t,parent->keys[posInParent+1],t->size+1,rbt->childs[0]); RemoveKey(rbt,1); // 插入父结点 parent->keys[posInParent+1] = rightMinKey; return SUCCESS; } return ERROR;}// 与兄弟结点合并操作,操作后递归对父结点进行调整 void Restore3(BTree &t){ if( t == NULL ) return ; if(t->size>=MIN){ return; } BTree parent = t->parent; if( parent == NULL ){ // ????为什么这样不行 // t = t->childs[0]; if( t->size > 0 ){ return ; } BTree tt = t->childs[0]; if( tt == NULL ) return ; if(t->childs[0]) t->childs[0]->parent = NULL; t->childs[0] = NULL; int size = tt->size; for(int i = 1; i <= size; i ++){ t->keys[i] = tt->keys[i]; } for(int i = 0; i <= size; i++){ t->childs[i] = tt->childs[i]; if( t->childs[i] ) t->childs[i]->parent = t; // T^T ... } t->size = size; t->parent = NULL; return ; } int posInParent; if((posInParent = SearchChildPos(parent,t)) == -1) return ; int sizeOfParent = parent->size; int mIsFinished = 0; // 与父结点和左结点结合 if( posInParent > 0 && mIsFinished == 0){ // .......................................可能刚刚好该结点为空 ,不用考虑T^T,还是去看看B树的性质吧。。 BTree lbt = parent->childs[posInParent-1]; if(lbt == NULL) { lbt = (BTNode*)malloc(sizeof(BTNode)); if( lbt == NULL ) return; lbt->parent = parent; parent->childs[posInParent-1] = lbt; lbt->size = 0; } if(lbt != NULL ){ lbt->keys[++lbt->size] = parent->keys[posInParent]; lbt->childs[lbt->size] = NULL; // ..........................因为lbt结点结合t结点,而t结点为终端结点,自然没有孩子 int i,j; for(j = lbt->size+1, i = 1; i <= t->size;i++, j++){ lbt->keys[j] = t->keys[i]; // lbt->childs[j] = NULL; } for( int k = lbt->size, i = 0; i <= t->size; i ++, k++){ lbt->childs[k] = t->childs[i]; if(t->childs[i]) t->childs[i]->parent = NULL; t->childs[i] = NULL; if(lbt->childs[k]!=NULL) lbt->childs[k]->parent = lbt; // 居然还漏了 。。。 } lbt->size+=t->size; t->size = 0; } // 移除父结点中所结合的结点 、移除孩子 if(parent->childs[posInParent]) parent->childs[posInParent]->parent = NULL; parent->childs[posInParent] = NULL; for(int i = posInParent; i < parent->size; i ++){ parent->keys[i] = parent->keys[i+1]; parent->childs[i] = parent->childs[i+1]; } parent->size --; mIsFinished = 1; } // 与父结点和右结点结合 if( posInParent < sizeOfParent && mIsFinished == 0 ){ BTree rbt = parent->childs[posInParent+1]; // 应该移入有结点中,为什么?? 因为t为终端结点 t->keys[++t->size] = parent->keys[posInParent+1]; t->childs[t->size] = NULL; int i, j; if(rbt!=NULL){ for(i = 1, j = t->size+1; i <= rbt->size; i ++,j ++){ t->keys[j] = rbt->keys[i]; } for( int k = t->size, i = 0; i <= rbt->size; i ++, k ++){ t->childs[k] = rbt->childs[i]; if(rbt->childs[i]) rbt->childs[i]->parent = NULL; rbt->childs[i] = NULL; if(t->childs[k]!=NULL) t->childs[k]->parent = t; // 居然还漏了 。。。 } t->size += rbt->size; rbt->size = 0; } // 移除父结点中所结合的结点 、移除孩子// RemoveKey(parent,posInParent); if(parent->childs[posInParent+1]) parent->childs[posInParent+1]->parent = NULL; parent->childs[posInParent+1] = NULL; for(i = posInParent+1; i < parent->size; i ++){ parent->keys[i] = parent->keys[i+1]; parent->childs[i] = parent->childs[i+1]; } parent->size --; mIsFinished = 1; // 不能直接调用下面方法删除,因为该结点不是终端结点时,会获取最小结点替代 // int kk = parent->keys[posInParent+1];// DeleteBTree(parent,posInParent+1); } // 调整父结点 Restore2(parent); Restore3(parent);} // 调整 void Restore(BTree &t,int pos){ if( Restore2(t) == ERROR ) { Restore3(t); }}
总结
这个B树的抽象数据类型是之前写的,都是泪啊,定义不熟悉,而且代码不严谨,在移动或者调整的时候,孩子结点经常忘了其父结点等等错误,这就有了我当时两三天的调试T^T,醉了。
理解好定义,写代码之前一定思考
这段时间考试,各种预习,关于编程,所以只看了下《Android开发艺术探索》,简单的就直接划书,难的又看不懂,唉,博客总结也落下了
最后如果本文有哪个地方不足或者错误,还请指正!
- 树 - B树的简单实现
- B+树插入C++的简单实现
- 【寒江雪】B树的一般知识与简单实现
- B+树的实现
- B+树的实现
- B树的实现
- B树的实现
- b树的实现
- B树的实现
- B树的实现
- B树、B+树的java实现
- B树----B-TREE的实现
- B树和B树的实现 B-Tree
- B树、B-树、B+树、B*树的简单理解
- B树的java实现
- B-树的C++实现
- B树的C实现
- java b+树的实现
- js实现全选与反选
- Centos6.5部署ftp文件服务器
- Ext.js5表单—(自定义错误信息)(formBind)(53)
- asp.net mvc情况下使用jqery ajax的方法进行json数据传递
- CSS pointer-events属性
- 树 - B树的简单实现
- Collection接口和Collections类的区别
- 纯java代码布局android RadioButton 遇见的问题
- cocos2dx win32修改鼠标指针图案
- Cling魅族机子奇葩问题java.lang.NoSuchMethodError
- Android中的RecyclerView的使用(一)
- 关于精度计算的问题
- 安卓开发学习之021 Canvas之drawPoint
- 最大稳定极值区域(MSER)检测