算法导论 第18章 B 树

来源:互联网 发布:im域名国内可以备案么 编辑:程序博客网 时间:2024/05/22 15:23

前言

   B树和红黑树一样,也是一种平衡树。但是前者主要用在数据库系统或者文件系统中,有助于降低磁盘I/O次数;此外,B树可以有很多的子女,几十或者上千个不等,“分支因子”较大。


B树定义

   B树是递归定义的,每个节点,都可以看做是一棵以该节点为根的子B树。


B树高度

  


B树的操作

   本篇博客实现的B树提供以下操作:

   1、判空操作empty:返回一个bool值,表明树是否为空;

   2、插入操作insert:接受一个值,插入B树,无返回值;

   3、删除操作erase:接受一个值,在B树搜寻删除此值,返回一个bool值,表明是否删除成功;

   4、查找操作search:接受一个值,在B树中查找是否存在此值,返回一个bool值;

   5、输出操作sequentialPrint:按顺序输出B树中的所有关键字;

   6、清空操作clear:释放B树节点占用的资源,使其回到最初状态。


B树节点类型

   首先给出实现的B树的节点类型,C++实现

template < typename T, int degree = 3, typename Compare = less<T> >struct node{//B树节点类型,degree为度,默认为3,Compare比较器类型,默认小于static const int min_num = degree - 1;//每个节点的最小关键字数static const int max_num = 2 * degree - 1;//节点最大关键字数static Compare compare;//比较器int num = 0;//该节点关键字数目bool leaf = true;//是否为叶子节点T key[max_num];//关键字数组node *child[max_num + 1];//孩子节点数组static void setCompare(const Compare &c){ compare = c; }//设置比较器,由B树构造时设置node(){//默认构造函数for (int i = 0; i != max_num; ++i){key[i] = T();child[i] = nullptr;}child[max_num] = nullptr;}int search(const T&)const;void insert(const T&);bool erase(int);};

设计理由:

   1、节点内的关键字均是有序的,那么为了加快查找速度,节点内采用二分查找,返回第一个不小于k的关键字索引;

   2、由于采用二分查找,那么关键字的存储最好使用连续的内存空间,因而选择数组;

   3、比较器类型是为了我们可以设置自定义数据类型的比较规则;由B树构造时触发设置。


B树实现代码,C++实现,注释详细,因此就不再画蛇添足叙述算法了

//B树#include<iostream>#include<vector>using namespace std;template < typename T, int degree = 3, typename Compare = less<T> >struct node{//B树节点类型,degree为度,默认为3static const int min_num = degree - 1;//每个节点的最小关键字数static const int max_num = 2 * degree - 1;//节点最大关键字数static Compare compare;int num = 0;//该节点关键字数目bool leaf = true;//是否为叶子节点T key[max_num];//关键字数组node *child[max_num + 1];//孩子节点数组static void setCompare(const Compare &c){ compare = c; }node(){//默认构造函数for (int i = 0; i != max_num; ++i){key[i] = T();child[i] = nullptr;}child[max_num] = nullptr;}int search(const T&)const;void insert(const T&);bool erase(int);};//定义静态变量template < typename T, int degree, typename Compare> const int node<T, degree, Compare>::max_num;template < typename T, int degree, typename Compare> const int node<T, degree, Compare>::min_num;template < typename T, int degree, typename Compare> Compare node<T, degree, Compare>::compare;template < typename T, int degree = 3, typename Compare = less<T> >int node<T, degree,Compare>::search(const T &k)const{//节点内关键字查找int low = 0, high = num - 1;while (low < high){//二分查找int mid = (low + high) / 2;if (!compare(k,key[mid]) && !compare(key[mid],k)) return mid;else if (compare(k,key[mid])) high = mid - 1;else low = mid + 1;}if (compare(key[low], k) && low < num - 1) ++low;return low;//返回第一个不小于k的关键字的索引}template < typename T, int degree = 3, typename Compare = less<T> >void node<T, degree, Compare>::insert(const T &k){//节点内插入int i = num - 1;while (i >= 0 && compare(k,key[i])){//找寻插入位置key[i + 1] = key[i];--i;}key[++i] = k;//插入++num;}template <typename T,int degree = 3,class Compare>bool node<T, degree, Compare>::erase(int index){//节点内删除for (int i = index + 1; i != num; ++i)key[i - 1] = key[i];--num;return true;}template < typename T, int degree = 3, typename Compare = less<T> >class Btree{//B树public:typedef node<T, degree, Compare>node;typedef Comparecomp;private:node *root;Compare compare;void destroy(node*);//销毁树void split(node*, int);//分割节点void insert_aux(node*, const T&);//插入辅助bool erase_aux(node*, const T&);//删除辅助void merge(node*, int);//合并节点T erasePredecessor(node*);//删除前驱T eraseSuccessor(node*);//删除后继void borrowFromRightSibling(node*, int);//向右兄弟借关键字void borrowFromLeftSibling(node*, int);//向左兄弟借关键字void print(node*)const;//按顺序打印public:Btree() :root(nullptr), compare(Compare()){ node::setCompare(compare); }Btree(const Compare &c) :root(nullptr), compare(c){ node::setCompare(c); }bool empty()const { return root == nullptr; }void insert(const T&);bool search(const T&)const;void sequentialPrint()const { print(root); }void clear(){destroy(root); root = nullptr;}bool erase(const T &k) { return erase_aux(root, k); }~Btree(){ destroy(root); }};template <typename T,int degree,class Compare>bool Btree<T,degree, Compare>::search(const T &k)const{//在B树中查找knode *curr = root;while (curr != nullptr){int index = curr->search(k);//在当前节点查找if (!compare(k, curr->key[index]) && !compare(curr->key[index], k)) return true;//若存在else if (compare(k,curr->key[index]))//若k小于index处的关键,则在其左边查找curr = curr->child[index];else curr = curr->child[index + 1];//否则在右边查找}return false;}template <typename T, int degree, class Compare>void Btree<T, degree, Compare>::split(node *curr, int index){//将curr所指节点的index处孩子节点分割成两个节点,两边各degree - 1个关键字,第degree个关键字上升到curr中node *new_child = new node,*old_child = curr->child[index];T k = old_child->key[degree - 1];for (int first = 0; first != degree - 1; ++first)//将原节点的后一半关键字复制到新节点new_child->key[first] = old_child->key[first + degree];if (!old_child->leaf)//如果不是叶子for (int first = 0; first != degree; ++first)//则还要复制一半的孩子节点指针new_child->child[first] = old_child->child[first + degree];new_child->leaf = old_child->leaf;new_child->num = degree - 1;//新节点关键字数old_child->num -= degree;//原节点关键字数减半for (int last = curr->num - 1; last >= index; --last)//将curr中index(包括)之后的关键字全部后移一个位置curr->key[last + 1] = curr->key[last];for (int last = curr->num; last > index; --last)//将curr中index(不包括)之后的孩子指针全部后移一个位置curr->child[last + 1] = curr->child[last];curr->key[index] = k;//在curr中的index处填上升上来的关键字curr->child[index + 1] = new_child;//index后的孩子指针指向新节点++curr->num;}template <typename T,int degree,class Compare>void Btree<T, degree, Compare>::insert_aux(node *curr, const T &k){//插入辅助函数if (curr->leaf) curr->insert(k);//若当前节点是叶子,则直接插入else{//否则int index = curr->search(k);//找到第一个不小于k的关键字索引if (compare(curr->key[index], k)) ++index;//极端情况,该节点所有关键字均小于kif ((curr->child[index])->num == node::max_num){//若该所引处的孩子节点关键字数已满split(curr, index);//则将其从该处分割if (compare(curr->key[index], k))//分割后上升上来的关键字若小于k++index;//则将要到新生成的节点中继续插入}insert_aux(curr->child[index], k);//递归插入}}template <typename T, int degree, class Compare>void Btree<T, degree, Compare>::insert(const T &k){//插入关键字if (root == nullptr){//如果在空树中插入第一个关键字root = new node;root->insert(k);return;}else if (root->num == node::max_num){//否则如果根节点满node *p = root;root = new node;//树将会长高root->child[0] = p;root->leaf = false;split(root, 0);//树根分裂}insert_aux(root, k);}template <typename T,int degree,class Compare>void Btree<T, degree, Compare>::merge(node *curr, int index){//合并curr中index处的孩子节点和其右兄弟,此时两者均恰好只有degree - 1个关键字node *left = curr->child[index], *right = curr->child[index + 1];left->key[degree - 1] = curr->key[index];//curr中index处关键字先下降for (int i = 0; i != right->num; ++i)//复制right所有关键字过来left->key[degree + i] = right->key[i];for (int i = index + 1; i != curr->num; ++i)//将curr中index之后的关键字前移1curr->key[i - 1] = curr->key[i];if (!left->leaf)//如果不是叶子for (int i = 0; i <= right->num; ++i)//则还要移动right的孩子指针left->child[degree + i] = right->child[i];for (int i = index + 2; i <= curr->num; ++i)//移动curr中index+2(包括)指针前移1,index处不必被覆盖curr->child[i - 1] = curr->child[i];--curr->num; ++left->num;left->num += right->num;delete right;}template <typename T,int degree,class Compare>T Btree<T, degree, Compare>::erasePredecessor(node *curr){//删除前驱,在情况2.a中被调用if (curr->leaf){//若是叶子,则说明已经可以删除最后一个元素,即前驱了T tmp = curr->key[curr->num - 1];--curr->num;return tmp;//返回前驱}//否则是内节点,继续递归向下?else if (curr->child[curr->num]->num == node::min_num){//若最后一个孩子关键字数目已达最小值if (curr->child[curr->num - 1]->num > node::min_num)//若左兄弟有多余关键字borrowFromLeftSibling(curr, curr->num);//那么借一个else merge(curr, curr->num - 1);//否则只有合并了}return erasePredecessor(curr->child[curr->num]);//继续向下递归}template <typename T,int degree,class Compare>T Btree<T, degree, Compare>::eraseSuccessor(node *curr){//删除后继,在情况2.b中被调用if (curr->leaf){//若是叶子节点,则直接删除最前面元素,即后继T tmp = curr->key[0];curr->erase(0);return tmp;}//否则,是内节点,继续向下?else if (curr->child[0]->num == node::min_num){//若第一个孩子关键字数目已达最小值if (curr->child[1]->num > node::min_num)//若右兄弟有足够关键字borrowFromRightSibling(curr, 0);//则借一个else merge(curr, 0);//否则只有合并了}return eraseSuccessor(curr->child[0]);//继续向下递归}template <typename T,int degree,class Compare>void Btree<T, degree, Compare>::borrowFromRightSibling(node *curr, int index){//curr中index孩子向其右兄弟借一个关键字node *left = curr->child[index], *right = curr->child[index + 1];left->key[left->num] = curr->key[index];//先将curr中index处的关键字添入该子树根,left所指curr->key[index] = right->key[0];//再用右兄弟(right所指)第一个关键字覆盖curr中index槽位for (int i = 1; i != right->num; ++i)//将右兄弟从1开始的所有关键字前移1right->key[i - 1] = right->key[i];if (!left->leaf){//若并非叶子,则还要设置相关孩子指针域left->child[left->num + 1] = right->child[0];//右兄弟第一个孩子成为left最后一个孩子for (int i = 1; i <= right->num; ++i)//前移右兄弟的孩子指针数组right->child[i - 1] = right->child[i];}++left->num; --right->num;}template <typename T,int degree,class Compare>void Btree<T, degree, Compare>::borrowFromLeftSibling(node *curr, int index){--index;//移到左兄弟槽位node *left = curr->child[index], *right = curr->child[index + 1];right->insert(curr->key[index]);curr->key[index] = left->key[left->num - 1];if (!right->leaf){//非叶子,移动指针for (int i = right->num; i >= 0; --i)right->child[i + 1] = right->child[i];right->child[0] = left->child[left->num];}--left->num;}template <typename T,int degree,class Compare>bool Btree<T, degree, Compare>::erase_aux(node *curr, const T &k){//删除辅助函数int index = curr->search(k);//找到第一个不小于k的关键字if (curr->leaf && (!compare(curr->key[index], k) && !compare(k, curr->key[index])))return curr->erase(index);//情况1,关键字在叶子else if (curr->leaf) return false;//不在叶子节点,则删除失败,不存在该关键字if (!curr->leaf && (!compare(curr->key[index], k) && !compare(k, curr->key[index]))){//情况2,关键字在该内节点。则使用该关键字的前驱或者后继代替if (curr->child[index]->num > node::min_num){//情况2.a,其左孩子有足够的关键字,即至少degree - 1个,则使用前驱代替。//删除其前驱,并返回前驱关键字,以覆盖该关键字curr->key[index] = erasePredecessor(curr->child[index]);return true;}else if (curr->child[index + 1]->num > node::min_num){//情况2.b,否则其右孩子有足够关键字,则使用后继代替curr->key[index] = eraseSuccessor(curr->child[index + 1]);//同上return true;}else{//否则,由于该关键字左右孩子的关键字数均处于最少,则不能采用前驱或者后继代替,那么合并左右孩子以及该关键字merge(curr, index);//将curr节点的index处相关关键字和孩子合并return erase_aux(curr->child[index], k);}}else{//情况3,关键字不在该节点,处于该关键字的左子树中if (compare(curr->key[index], k)) ++index;//极端情况,当curr中所有关键字均比k小时出现if (curr->child[index]->num == node::min_num){//若左子树关键字数已到达最小值if (index < curr->num && curr->child[index + 1]->num > node::min_num)//情况3.a,存在右兄弟,且有足够节点,即至少degree - 1个,则向其借一个borrowFromRightSibling(curr, index);else if (index > 0 && curr->child[index - 1]->num > node::min_num)//情况3.b,存在左兄弟且关键字数足够,类似于3.aborrowFromLeftSibling(curr, index);else{//3.c,左/右兄弟均只有degree - 1个关键字,那么合并节点if (index == curr->num) --index;//呼应上述极端情况下的节点合并merge(curr, index);if (curr == root && curr->num == 0){//若当前curr是根,且仅存一个元素root = curr->child[index];delete curr;//那么树高降1return erase_aux(root, k);}}}return erase_aux(curr->child[index], k);}}template <typename T,int degree,class Compare>void Btree<T, degree, Compare>::print(node *curr)const{//打印整棵树if (curr->leaf){//若是叶子节点,则打印全部关键字cout << "[ ";for (int i = 0; i != curr->num; ++i)cout << curr->key[i] << ' ';cout << ']' << endl;}else{//否则cout << '{' << endl;for (int i = 0; i <= curr->num; ++i){//依次打印孩子和关键字print(curr->child[i]);if (i < curr->num)cout << curr->key[i] << endl;}cout << '}' << endl;}}template <typename T,int degree,class Compare>void Btree<T, degree, Compare>::destroy(node *curr){//销毁B树if (curr == nullptr) return;if (curr->leaf) delete curr;//若是叶子,直接销毁else{//否则for (int i = 0; i <= curr->num; ++i)//依次销毁所有孩子后destroy(curr->child[i]);delete curr;//再销毁该节点}}int main(){/*Btree<char> bt;vector<char> cvec = {'P','C','M','T','X','A','B','D','E','F','J','K','L','N','O','Q','R','S','U','V','Y','Z'};for (size_t i = 0; i != cvec.size(); ++i)bt.insert(cvec[i]);cout << boolalpha << bt.search('A') << endl;bt.erase('L');bt.erase('M');bt.erase('K');bt.erase('A');bt.erase('X');bt.erase('Y');bt.erase('J');cout << boolalpha << bt.search('A') << endl;bt.sequentialPrint();*/Btree<int,10> bt;for (int i = 0; i != 10000; ++i)bt.insert(i);for (int i = 0; i != 100; ++i)bt.erase(3 * i);bt.sequentialPrint();getchar();return 0;}

习题 18.1-1

    因为节点中的关键字数目至少为1,那么查找方向至少有两个,因而度至少要为2.


习题18.1-4

   (2t)^(h+1) - 1


习题18.1-5

   2-3-4树


习题 18.2-2

    根满之后分裂,会出现一次冗余读。


习题 18.3-2

    见erase和erase_aux代码,非常简洁。






0 0
原创粉丝点击