浅析B-树

来源:互联网 发布:php教材推荐 编辑:程序博客网 时间:2024/05/22 16:55
  B树是一种适合与外查找的的搜索树,他是一种平衡的多叉树。在B树的每个结点中包含一组指针recptr[M]指向实际记录的存放地址。key[i]与recptr[i]形成一个索引项,通过key[i]可以找到某个记录的存储地址recptr[i]。

M阶的B树具有以下特点:
1、根节点要么是叶子结点,要么至少具有两个孩子。
2、非根结点(叶结点除外)都具有[M/2,M]个孩子。
3、非根结点都具有[M/2-1,M-1]个关键码,关键码以升序排列。
4、key[i]和key[i+1]之间的结点的值都介于key[i]与key[i+1]之间
5、数据项都保存在叶子结点中,且叶子结点都在同一层。


M/2是向上取整。
  B树的搜索过程是在一个结点内搜索和循某一条路径向下一层搜索交替进行的过程,因此B树的搜索时间与B树的阶数M(决定内部搜索次数)和B树的高度h有关。在B树上进行搜索,搜索成功的时间取决于关键码所在的层次,搜索不成功取决于树的高度。

  当数据太大,不能一次放入内存中,磁盘存取的数目就变成关键了。这时候我们就要使用外查找。B树是用于磁盘级查找最流行的数据结构。在B树中结点必须是半数填满的,这就保证了树不会退化成一颗简单的二叉树。


B树的插入:
  首先我们要在B树中寻找这个关键码,如果已经存在则插入失败,如果不存在又分为以下两种情况:
  1、如果要插入的叶子结点中的关键码的个数小于M-1,则直接插入,然后再重新组织叶子结点中的数据项。
  2、如果要插入的叶子结点中的关键码的个数就是M-1,表示叶子结点已满,这时候我们就要经这片叶子分裂为两片叶子。

实现分裂的原则是:
设结点p中已经有了M-1个关键码,当再插入一个关键码后结点中的状态为
(m,p0,k1,p1,k2,p2,k3,....,km,pm)
因为关键码的个数必须是在[M/2-1,M-1]之间,所以我们必须将p分裂为两个叶子结点p,q。
p:([m/2]-1,p0,k1,p1,....,k[m/2]-1,p[m/2]-1)
q:(m-[m/2],p[m/2],k[m/2]+1,p[m/2]+1,....,km,pm)
  关键码k[m/2]与指向新结点q的指针形成一个二元组插入到这两个结点p,q的父节点中去。如果父节点也满了则就再分裂,一直到根节点。如果分裂了根,那么我们就创建一个新根,将分裂的这两个根作为新创建的根的孩子,这也是B树增加高度唯一的方法。虽然分裂结点是比较费时的,但是分裂一次我们可以插入多次。
处理孩子溢出还有其他方法,一种方法就是将孩子结点让给具有空间的兄弟收养,但是这样的话会影响到键,所这种方法需要修改父亲,但是它会倾向于让叶子结点更加饱满,会更加节省空间。

B树的删除:
  删除的过程与插入的过程相反,如果某叶子丢了孩子,他可能需要和另一片叶子进行合并,我们必须一直沿着树向上合并下去(一直向上合并出现的几率很小),但是再最坏的情况下,根结点丢失了一孩子,然后我们就要删除根,并使用另一个孩子作为根,这也是B树降低高度的唯一方法。
  如果想要删除一个关键码我们首先要找到这个关键码,如果B树中没有这个关键码,那么就删除失败。
  如果找到了这个关键码,但是这个关键码不再叶子结点中,则应该采用交换删除的思想,应该以结点P所指向的子树中的最小的关键码X来代替要删除的关键码,问题就又转换成了删除X了,直到X出现在叶子中间,我们才真正的删除X。在叶子结点之中删除关键码有以下四种情况。

  1、要删除的关键码所在的叶子结点就是根节点,那么直接删除之后再修改根节点中放入数据项即可。

  2、要被删除的关键码所在的叶节点不是根节点,且被删除前该叶节点中关键码的个数大于最低限度,即大于M/2-1。则直接删除即可。

  3、要被删除的关键码所在的叶节点不是根节点,且被删除前该叶子结点中关键码的个数已经等于最小限度M/2-1,但是这个叶子结点的左兄弟或者右兄弟的关键码个数大于最低限M/2-1。
这时候就可以向他的兄弟收养一个孩子,以达到新的平衡。
收养的方法如下(以收养右兄弟的孩子为例):
   3.1、将父亲结点中刚刚大于被删除关键码的关键码K下移到被删除的位置。
   3.2、将右兄弟中最小的关键码上移到父节点中k的位置
   3.3、将右兄弟中的最左子树的指针平移到被删除关键码所在结点中的最后位置
   3.4、在右兄弟的结点中再将被移走的关键码和指针位置用剩余的关键码和指针填补,调整。

  4、被删除的关键码所在的叶节点删除前的关键码个数已经达到最低限度M/2-1,并且它的右兄弟(或左兄弟)也是最低限度M/2-1,则这时候就要将这两个结点合并。
合并方法如下:
  4.1、将父结点p中相应的关键码下移到选定保留的结点中,若要合并p子树指针p1和p2所指向的结点,且要保留p1所指结点,则把p中关键码K2下移到p1所指的结点之中。
  4.2、把p中子树p2所指结点中的全部指针和关键码都搬到p1所指结点的后面,删除p2所指结点。
  4.3、在父节点p中用后面剩余的关键码和指针填补关键码K2和指针p2。
  4.4、在修改父节点p和选定保留的结点
在合并的过程中,父节点的关键码个数减少了,所以还要向上继续判断满不满足平衡,如果父节点不满足,则一直重复上面的步骤。最坏的情况下,这种自下而上的处理要到根节点。

//代码实现
#pragma once#include<utility>using namespace std;template<typename K,int M>struct BTreeNode{K _keys[M];                         //M是孩子的个数,keys是关键值数组,多开辟一个便于处理BTreeNode<K, M> *_subs[M + 1];      //孩子指针数组BTreeNode<K, M> *_parent;           //指向父亲的指针size_t _size;                       //记录关键值的个数 BTreeNode():_parent(NULL), _size(0){for (int i = 0; i < M; i++){_keys[i] = K();_subs[i] = NULL;}_subs[M] = NULL;}};template<typename K,int M>class BTree{typedef BTreeNode<K, M> Node;public:BTree():_root(NULL){}pair<Node*,int>  Find(const K& key){Node* cur = _root;Node* parent = NULL;while (cur){int i = 0;while (i<(int)cur->_size){if (cur->_keys[i] < key){i++;}else if (cur->_keys[i]>key){break;}else{return pair<Node*, int>(cur,i);      //已经存在了}}parent = cur;cur = cur->_subs[i];}returnpair<Node*, int>(parent, -1);      //没找到,返回-1}bool Insert(const K& key){if (_root==NULL)       //如果是空树的话{_root = new Node;_root->_keys[0] = key;_root->_size++;_root->_parent = NULL;}//如果不是空树的话,先找这个key存在还是不存在pair<Node*, int>  product= Find(key);if (product.second != -1)       //表示这个key已经存在了{return false;               //已经存在,插入失败}Node* cur = product.first;Node* sub = NULL;K newKey = key;while (1)               {_InsertKey(cur, newKey, sub);             //将关键值插入if (cur->_size == M)                   //如果key已经大于M-1,则需要分裂{Node* tmp = new Node;              //创建一个分裂后的结点int mid = cur->_size / 2;        //找到keys数组的中间位置的下标int j = 0;int i = mid+1;                     //从中间位置的下一个位置开始复制for (; i <(int)cur->_size; ++i, ++j){tmp->_keys[j] = cur->_keys[i];        //将关键字复制到tmp中tmp->_subs[j] = cur->_subs[i];        //将孩子也复制到tmp中if (tmp->_subs[j])                    //如果孩子不为空,则让它指向分裂的结点tmp->_subs[j]->_parent= tmp;cur->_keys[i] = K();cur->_subs[i] = NULL;tmp->_size++;}//将最后一个右孩子也复制过来tmp->_subs[j] = cur->_subs[i];if (tmp->_subs[j])tmp->_subs[j]->_parent = tmp;cur->_subs[i] = NULL;newKey = cur->_keys[mid];cur->_keys[mid] = K();sub = tmp;cur->_size = mid;if (cur == _root)                //如果分裂的结点是根节点{_root = new Node;_root->_keys[0] = newKey;_root->_subs[0] = cur;_root->_subs[1] = sub;cur->_parent = _root;sub->_parent = _root;_root->_size = 1;return true;}cur = cur->_parent;}else                                //不需要分裂,已经平衡break;}return true;}bool Remove(const K& key){if (_root == NULL)               //树为空树,则删除失败{return false;}pair<Node*, int> product = Find(key);if (product.second == -1)              //如果key不再树中,则删除失败{return false;}Node* cur = product.first;           int pos = product.second;                //记录要删除的位置//将判断要删除的关键码所在结点是不是叶子结点,如果不是的话,要先将关键码交换到叶子结点中if (cur->_subs[pos + 1] != NULL)    //如果要删除的关键码的右子树不为空,则是非叶子结点的删除{Node* minkey = cur->_subs[pos + 1];          //用来记录要删除的关键码的右子树的最小的键值while (minkey->_subs[0]){minkey = minkey->_subs[0];}//转换成删除这个最小的关键码cur->_keys[pos] = minkey->_keys[0];cur = minkey;_MoveLeft(cur, 0);           //把交换后的关键码删除掉}else_MoveLeft(cur, pos);//判断是否满足B树的条件,不满足的话就要调整int mid =(M+1)/2-1;        //求出关键码个数的下限,关键码最少为M/2-1,向上取整while (1){if ((int)cur->_size < mid)   //关键码的个数小于上限值,则要进行调整{if (cur == _root)break;Node* parent = cur->_parent;pos = 0;while (parent->_subs[pos] != cur&&pos < (int)parent->_size)pos++;if (pos == 0)   //进行左调整_LeftAdjust(parent, cur, mid, pos);else_RightAdjust(parent, cur, mid, pos-1);cur = parent;}else                     //如果不小于上限值的话,则表示已经满足B树的条件,则直接退出break;}if (_root->_size == 0)         //如果调整之后根的关键码个数已经减为0,则要把当前根删除掉,把他的孩子当做根{Node* del = _root;            //记录要删的这个根_root = _root->_subs[0];if (_root)    _root->_parent = NULL;        //将新根的父亲置空delete del;                   }return true;}void InOder(){_InOder(_root);}~BTree(){_Destory(_root);}protected:void _LeftAdjust(Node* parent, Node* cur, int mid, int pos)      //当前结点cur在他父亲的左边,cur与他的右兄弟进行调整{//如果cur的右兄弟的关键码的个数已经大于关键码的上限,则就通过收养解决Node* right = parent->_subs[pos+1];       //right指向cur的左兄弟if ((int)right->_size>mid)      //大于关键码的上限,通过收养解决{cur->_size++;cur->_keys[cur->_size - 1] = parent->_keys[pos];       //把父节点的相应关键码下移parent->_keys[pos] = right->_keys[0];                  //把右兄弟的最小关键码上移到父亲关键码位置//把右兄弟的最左孩子移动到cur的最右孩子处cur->_subs[cur->_size] = right->_subs[0];if (right->_subs[0]){right->_subs[0]->_parent = cur;            //让这个孩子的父亲指向cur}right->_subs[0] = right->_subs[1];_MoveLeft(right, 0);                                    //把右兄弟中剩余的关键码向左移动}else              //只能通过合并解决_Merge(parent,cur,right,pos);}void _RightAdjust(Node* parent, Node* cur,int mid, int pos)  //当前结点cur在他父亲的右边,cur与他的左兄弟进行调整{Node* left = parent->_subs[pos];          //left指向cur的左兄弟if ((int)left->_size>mid)               //左兄弟的关键码个数大于关键码上限的值{//cur先把最左边的位置空出来,再把父亲结点的相应关键码下移_MoveRight(cur,0);cur->_keys[0] = parent->_keys[pos];      //父亲相应关键码下移parent->_keys[pos] = left->_keys[left->_size-1];    //将左兄弟中的最大关键码上移到父节点位置cur->_subs[0] = left->_subs[left->_size];           //将左兄弟中的最后一个孩子移到cur的左边if (left->_subs[left->_size]){left->_subs[left->_size]->_parent = cur;         //将这个孩子结点的父亲指向cur}left->_keys[left->_size - 1] = K();left->_subs[left->_size] = NULL;left->_size--;}else_Merge(parent,left,cur,pos);}void _Merge(Node* parent,Node* cur,Node* brother,int pos)        //保留cur,合并兄弟结点{int i = cur->_size;     //要插入的位置cur->_keys[i] = parent->_keys[pos];        //先把父亲结点相应的关键码的值移动到左孩子中 cur->_subs[i + 1] = brother->_subs[0];      //把右兄弟的最左孩子移动过来if (brother->_subs[0])brother->_subs[0]->_parent = cur;i++;cur->_size++;for (int j = 0; j < (int)brother->_size; ++i, ++j)     //将兄弟结点指针拷贝过来{cur->_keys[i] = brother->_keys[j];cur->_subs[i + 1] = brother->_subs[j + 1];if (brother->_subs[j+1])brother->_subs[j+1]->_parent = cur;cur->_size++;}if (parent->_subs[pos] == brother)parent->_subs[pos] = NULL;elseparent->_subs[pos + 1] = NULL;_MoveLeft(parent,pos);delete brother;}void _Destory(Node* cur){if (cur == NULL)return;int i = 0;for (i = 0; i < (int)cur->_size; i++){_Destory(cur->_subs[i]);delete cur->_subs[i];}_Destory(cur->_subs[i]);                  //遍历最右边的孩子delete cur->_subs[i];}void _InOder(Node* cur){if (cur == NULL)return;int i = 0;for (i = 0; i < (int)cur->_size; i++){_InOder(cur->_subs[i]);cout << cur->_keys[i] << "  ";}_InOder(cur->_subs[i]);                  //遍历最右边的孩子}void _InsertKey(Node* cur,const K& key,Node* sub){int i = cur->_size-1;while (i >= 0)        //将key插入到B树中{if (key < cur->_keys[i]){cur->_keys[i + 1] = cur->_keys[i];cur->_subs[i + 2] = cur->_subs[i+1];--i;} else                                 //key比当前关键值小,则进行插入break;}//key是这组keys中最小的,放在第0个位置cur->_keys[i+1] = key;cur->_subs[i+2] = sub;if (sub != NULL)                  //孩子不为空sub->_parent = cur;          //孩子的parent指向curcur->_size++;}void _MoveLeft(Node* cur,int pos)                  //在叶子结点中删除一个关键码{if (cur == NULL)return;int i = 0;for (i = pos; i < (int)cur->_size; ++i){cur->_keys[i]=cur->_keys[i+1];                 //将要删除的关键码覆盖掉cur->_subs[i+ 1] = cur->_subs[i+ 2];}cur->_size--;                                    }void _MoveRight(Node* cur,int pos){int i = cur->_size-1;       //当前结点最右关键码下标for (; i >=pos; --i){cur->_keys[i+1] = cur->_keys[i];cur->_subs[i+2] = cur->_subs[i+1];}cur->_subs[1] = cur->_subs[0];cur->_size++;}private:Node* _root;};


6 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 行驶证过了两个多月没年检怎么办 驾照没满一年扣了3分怎么办 6年免检但是行驶证到期怎么办 驾驶证和行驶证一起丢了怎么办 身份证驾驶证行驶证一起丢了怎么办 在银行柜圆机取款时忘了取卡怎么办 中国建设银银行取款密码忘了怎么办 买到证件不全的二手电动车怎么办 光大乐惠金卡信用卡还了怎么办 增驾期间被扣3分怎么办 a2驾驶证被扣12分后怎么办 我手机换号码了驾驶证档案怎么办 手机号码换了查不到驾照分了怎么办 金牛区源泉幼儿园摇不到号怎么办 b2驾驶证被扣12分后怎么办 福建省超过一年驾照未年审要怎么办 为缓解交通拥堵现状人们应该怎么办 告对方不知道对方出身日期怎么办 上海业余围棋4进3老不过怎么办 孩子想上学但又怕同学议论怎么办 在菲律宾黑了博彩老板的钱怎么办 九阴真经3d先遣服更新失败怎么办 公司核名通过不想用了怎么办 公司核名下来了不想注册了怎么办 家人受到小贷公司催款威胁怎么办 商标抽签资料提交上去有问题怎么办 花椒直播助手苹果版下载不了怎么办 在香港酒店住把床单弄上血了怎么办 综英美我能怎么办我也很绝望百度云 护照的名字中间有个空格怎么办 开车不小心压死黄鼠狼了怎么办 三户联保贷款一方不还怎么办 因为隔断中介违约…我该怎么办 上海居住证没下来换住址了怎么办 工商注册后大股东不注资怎么办 公司不给去办理变更股东信息怎么办 滴滴车主注册没有自己的车型怎么办 代办用虚假地址注册的公司怎么办? 写字楼注册公司租户不租了怎么办 租户没把公司迁出我该怎么办 租户不肯把户口迁出了业主怎么办