B树是一种平衡搜索树,它可以看做是2-3Tree和2-3-4Tree的一种推广。CLRS上介绍了B树目前主要针对磁盘等直接存取的辅存设备,许多数据库系统也利用B树或B树的变种来存储信息。

本文主要针对代码实现作一些讲解。如果对B树性质或特点不了解的,请对照B树的定义来阅读本文。或先了解B树的定义,对定义了然于胸后,可以更好地把注意力放在逻辑实现上。

本文实现思路来自于CLRS,但书中只给出了search和insert的伪代码,和delete的思路,所以本文的实现细节都是自己想出来的,比较晦涩冗杂。(我自己都不能一下子看懂),所以特别针对自己深有体会的部分加以讲述。

开始。

#include <iostream>
#include <vector>
#include <utility>
using namespace std; class BTree {
private :
struct Node {
//int n; //the number of the keys in this node
vector<int> key; //key.size() return n
vector<Node*> pToChild; //the pointer to the children,p.empty() return isLeaf
//bool isLeaf;
};
using PlaceOfChild = pair<Node*, int>;
private :
Node * root;
int t; //the minimum degree, more than or equal to 2
private :
void SplitChild(Node * x, const int i);
void Insert_notfull(Node * x, const int k);
PlaceOfChild search(Node * p, const int k);
PlaceOfChild predecessor(Node * x, int i);
PlaceOfChild successor(Node * x, int i);
Node * minimum(Node * p);
Node * maximum(Node * p);
void combine(Node * x, Node * y, PlaceOfChild z);
void remove(Node * x, const int k);
void inorderWalk(Node * p);
public :
BTree(int deg) : root(new Node), t(deg >= ? deg : ) {}
~BTree() {delete root;}
void insert(const int k);
void remove(const int k) {remove(root, k);}
bool search(const int k) {return (search(root, k).first ? true : false);}
int minimum() {return minimum(root)->key[];} //can't be empty
int maximum() {return *(maximum(root)->key.end() - );} //can't be empty
void inorderWalk() {inorderWalk(root);}
};

BTree类的定义如上,因为使用了pair记录结点关键值的准确位置,所以需要包含头文件<utility>,Btree的数据成员包含指向根结点的指针root和最小度数t,对B树最小度数不了解的一定要先看看它的定义,Btree的很多性质都与最小度数有关。

结点的数据成员包括两个vector,第一个用来存储关键字,第二个用来存储指向孩子结点的指针。书上给的实现还包括了两个bool值,一个是标识该结点是否为叶子结点,另一个记录结点key值的个数。因为我使用vector保存数据,所以直接可以用vector.size()和vector.empty()来判断是否是叶子和key的个数,但也因此导致代码比较复杂。另外我推荐大家自己实现的时候维护一个指向父结点的指针,后面实现删除的时候有指向父结点的指针就会更简单实现一些。

实现过程中值得注意的点和难点:

1.任何时候使用pToChild,一定要先检查结点是否为叶子结点,即先判断pToChild.empty()。

2.combine和split的时候,该pop的元素一定要记得pop。因为使用的是vector而不是固定数组,所以vector元素个数一定要保证绝对正确。

3.因为没有设置parent指针,所以找前驱和后继的时候使用stack来记录沿途的结点,这也是常用的手法。

4.remove的情况太过复杂,写的时候一定把每种情况都先写下来,再写代码,不然debug的时候就很难受了。

5.只有根结点的合并和分裂才会导致B树高度的变化。

6.删除的时候会保证经过的结点key值个数最小为t(除了root),所以不必担心叶子结点被删除某一个key后,key的个数小于t-1。

7.删除的时候,combine的过程中一定要递归删除而不是直接从内部结点中直接删除(当初我纠结了好久),因为直接从内部删除结点会导致下一层结点,即combine最后留下的结点有两个指针没有关键字分割。

8.删除的时候,如果是使用前驱(后继)替换需要删除的结点的情况,再递归删除的时候也一定要一层一层地递归,而不是直接对前驱(后继)所在的结点递归。因为需要一层一层的递归来保证删除函数的前提(删除函数访问的结点的key值个数最小为t,除了根结点)被满足。

9.自己动手模拟了100个结点的insert和remove过程,而且还模拟了好几遍。(因为不得不debug...)到最后对各种情况基本上可以胸有成竹了。建议有耐心的小伙伴也试试自己模拟构建B树,一定会有更深地领悟。

代码如下:(仅供参考)

 //if child is full and the parent is not full, split the child.
void BTree::SplitChild(Node * x, const int i) { //O(t)
Node * y = x->pToChild[i];
Node * z = new Node; for (int j = ; j < t - ; ++j) //right half side of key
z->key.push_back(y->key[j+t]); if (!y->pToChild.empty()) {//y is not a leaf
for (int j = ; j < t; ++j) //right half side of pointer
z->pToChild.push_back(y->pToChild[j+t]);
for (int j = ; j < t; ++j)
y->pToChild.pop_back();
} x->key.insert(x->key.begin() + i, y->key[t-]);
x->pToChild.insert(x->pToChild.begin() + i + , z);
for (int j = ; j < t; ++j)
y->key.pop_back();
} void BTree::Insert_notfull(Node * x, const int k) {
int i = x->key.size() - ;
while (i >= && k < x->key[i]) //find insertion place
--i;
if (x->pToChild.empty()) {
x->key.insert(x->key.begin() + i + , k);
}
else {
++i;
if (x->pToChild[i]->key.size() == * t - ) {
SplitChild(x, i);
if (k >= x->key[i])
++i;
}
Insert_notfull(x->pToChild[i], k);
}
} void BTree::insert(const int k) { //O(t*(logn to t))
Node * r = root;
if (r->key.size() == * t - ) { //root is full
Node * s = new Node;
root = s;
s->pToChild.push_back(r);
SplitChild(s, );
Insert_notfull(s, k);
}
else
Insert_notfull(r, k);
} BTree::PlaceOfChild BTree::search(Node * p, const int k) {
int i = ;
while (i < p->key.size() && k > p->key[i])
++i;
if (i < p->key.size() && k == p->key[i])
return make_pair(p, i);
else if (p->pToChild.empty())
return make_pair(nullptr, );
else
return search(p->pToChild[i], k);
} BTree::Node * BTree::minimum(Node * p) {
while (!p->pToChild.empty())
p = p->pToChild[];
return p;
} BTree::Node * BTree::maximum(Node * p) {
while (!p->pToChild.empty())
p = p->pToChild[p->pToChild.size()-];
return p;
} BTree::PlaceOfChild BTree::predecessor(Node * x, int i) {
if (!x->pToChild.empty()) {
x = maximum(x->pToChild[i]);
return make_pair(x, x->key.size() - );
}
else if (i != ) {
return make_pair(x, i - );
}
int key = x->key[i];
Node * y = root;
vector<PlaceOfChild> stk;
while () {
if (y->key[] == key)
break;
for (i = ; i < y->key.size() && key > y->key[i]; ++i)
;
stk.push_back(make_pair(y, i));
y = y->pToChild[i];
}
PlaceOfChild p;
while (!stk.empty()) {
p = stk.back();
stk.pop_back();
if (p.second != )
return p;
}
return make_pair(nullptr, );
} BTree::PlaceOfChild BTree::successor(Node * x, int i) {
if (!x->pToChild.empty()) {
x = minimum(x->pToChild[i+]);
return make_pair(x, );
}
else if (i != x->key.size() - ) {
return make_pair(x, i + );
}
int key = x->key[i];
Node * y = root;
vector<PlaceOfChild> stk;
while () {
if (y->key.back() == key)
break;
for (i = ; i < y->key.size() && key > y->key[i]; ++i)
;
stk.push_back(make_pair(y, i));
y = y->pToChild[i];
}
PlaceOfChild p;
while (!stk.empty()) {
p = stk.back();
stk.pop_back();
if (p.second != p.first->key.size())
return p;
}
return make_pair(nullptr, );
} void BTree::combine(Node * x, Node * y, PlaceOfChild z) {
x->key.push_back(z.first->key[z.second]);
for (int i = ; i < t - ; ++i)
x->key.push_back(y->key[i]);
if (!x->pToChild.empty())
for (int i = ; i < t; ++i) {
x->pToChild.push_back(y->pToChild[i]);
}
delete y; z.first->key.erase(z.first->key.begin() + z.second);
z.first->pToChild.erase(z.first->pToChild.begin() + z.second + );
if (z.first->key.empty()) {
root = z.first->pToChild[z.second];
delete z.first;
}
} void BTree::remove(Node * x, const int k) { //This function guarantees x->key.size() >= t,except root
int i = ;
while (i < x->key.size() && x->key[i] < k)
++i;
if (i < x->key.size() && x->key[i] == k) {
if (x->pToChild.empty())
x->key.erase(x->key.begin() + i);
else {
if (x->pToChild[i]->key.size() >= t) {
PlaceOfChild preOfk = predecessor(x, i);
x->key[i] = preOfk.first->key[preOfk.second];
remove(x->pToChild[i], x->key[i]); //recursive in the child ,not the successor
}
else if (x->pToChild[i+]->key.size() >= t) {
PlaceOfChild sucOfk = successor(x, i);
x->key[i] = sucOfk.first->key[sucOfk.second];
remove(x->pToChild[i+], x->key[i]); //recursive in the child ,not the successor
}
else {
combine(x->pToChild[i], x->pToChild[i+], make_pair(x, i));
remove(x->pToChild[i], k);
}
}
}
else {
if (x->pToChild.empty())
return ;
else if (x->pToChild[i]->key.size() != t - )
remove(x->pToChild[i], k);
else {
Node *y, *z;
if (i > && x->pToChild[i-]->key.size() != t - ) {
y = x->pToChild[i-];
z = x->pToChild[i];
z->key.insert(z->key.begin(), x->key[i-]);
if (!y->pToChild.empty()) {
z->pToChild.insert(z->pToChild.begin(), y->pToChild.back());
y->pToChild.pop_back();
}
x->key[i-] = y->key.back();
y->key.pop_back();
remove(z, k);
}
else if (i < x->pToChild.size() - && x->pToChild[i+]->key.size() != t - ){
y = x->pToChild[i+];
z = x->pToChild[i];
z->key.push_back(x->key[i]);
if (!y->pToChild.empty()) {
z->pToChild.push_back(y->pToChild[]);
y->pToChild.erase(y->pToChild.begin());
}
x->key[i] = y->key[];
y->key.erase(y->key.begin());
remove(z, k);
}
else if (i > ) {
y = x->pToChild[i-];
z = x->pToChild[i];
combine(y, z, make_pair(x, i-));
remove(y, k);
}
else if (i < x->pToChild.size() - ) {
y = x->pToChild[i];
z = x->pToChild[i+];
combine(y, z, make_pair(x, i));
remove(y, k);
}
}
}
} void BTree::inorderWalk(Node * p) {
int i;
if (!p->pToChild.empty()) {
for (i = ; i < p->key.size(); ++i) {
inorderWalk(p->pToChild[i]);
cout << p->key[i] << ' ';
}
inorderWalk(p->pToChild[i]);
}
else {
for (i = ; i < p->key.size(); ++i)
cout << p->key[i] << ' ';
}
}

B-Tree(B树)原理及C++代码实现的更多相关文章

  1. [转] Splay Tree(伸展树)

    好久没写过了,比赛的时候就调了一个小时,差点悲剧,重新复习一下,觉得这个写的很不错.转自:here Splay Tree(伸展树) 二叉查找树(Binary Search Tree)能够支持多种动态集 ...

  2. CJOJ 1976 二叉苹果树 / URAL 1018 Binary Apple Tree(树型动态规划)

    CJOJ 1976 二叉苹果树 / URAL 1018 Binary Apple Tree(树型动态规划) Description 有一棵苹果树,如果树枝有分叉,一定是分2叉(就是说没有只有1个儿子的 ...

  3. 【数据结构】B-Tree, B+Tree, B*树介绍 转

    [数据结构]B-Tree, B+Tree, B*树介绍 [摘要] 最近在看Mysql的存储引擎中索引的优化,神马是索引,支持啥索引.全是浮云,目前Mysql的MyISAM和InnoDB都支持B-Tre ...

  4. 04-树5 Root of AVL Tree + AVL树操作集

    平衡二叉树-课程视频 An AVL tree is a self-balancing binary search tree. In an AVL tree, the heights of the tw ...

  5. POJ1741 Tree(树分治——点分治)题解

    题意:给一棵树,问你最多能找到几个组合(u,v),使得两点距离不超过k. 思路:点分治,复杂度O(nlogn*logn).看了半天还是有点模糊. 显然,所有满足要求的组合,连接这两个点,他们必然经过他 ...

  6. AVL树原理及实现 +B树

    1. AVL定义 AVL树是一种改进版的搜索二叉树.对于一般的搜索二叉树而言,如果数据恰好是按照从小到大的顺序或者从大到小的顺序插入的,那么搜索二叉树就对退化成链表,这个时候查找,插入和删除的时间都会 ...

  7. 1. 决策树(Decision Tree)-决策树原理

    1. 决策树(Decision Tree)-决策树原理 2. 决策树(Decision Tree)-ID3.C4.5.CART比较 1. 前言 决策树是一种基本的分类和回归方法.决策树呈树形结构,在分 ...

  8. poj 1741 Tree(树的点分治)

    poj 1741 Tree(树的点分治) 给出一个n个结点的树和一个整数k,问有多少个距离不超过k的点对. 首先对于一个树中的点对,要么经过根结点,要么不经过.所以我们可以把经过根节点的符合点对统计出 ...

  9. AVL树,红黑树,B-B+树,Trie树原理和应用

    前言:本文章来源于我在知乎上回答的一个问题 AVL树,红黑树,B树,B+树,Trie树都分别应用在哪些现实场景中? 看完后您可能会了解到这些数据结构大致的原理及为什么用在这些场景,文章并不涉及具体操作 ...

  10. 【POJ 2486】 Apple Tree(树型dp)

    [POJ 2486] Apple Tree(树型dp) Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 8981   Acce ...

随机推荐

  1. 在mysql中计算百分比

    通过查找资料,得到了如下解决方法: 用到了concat()和left() 两个函数 1.CONCAT(str1,str2,...) 返回来自于参数连结的字符串.如果任何参数是NULL, 返回NULL. ...

  2. socket 错误之:OSError: [WinError 10057] 由于套接字没有连接并且(当使用一个 sendto 调用发送数据报套接字时)没有提供地址,发送或接收数据的请求没有被接受。

    出错的代码 #server端 import socket import struct sk=socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen( ...

  3. Android自定义View——简单实现边缘凹凸电子票效果

        View继承LinearLayout,在View的上下边缘画出白色的圆形即可,这里只要计算出圆的个数和圆的循环规律即可,下面请看分析 我们取卡片的前2个凹凸来看,将其分为四部分,并且两部分为循 ...

  4. React之生命周期函数(16.3以后新版本)

    学习链接: https://www.jianshu.com/p/514fe21b9914 学习链接:https://zhuanlan.zhihu.com/p/38030418 学习链接:https:/ ...

  5. Django学习笔记〇四——数据库ORM的使用(有待修改)

    Django框架基本上都是要和数据库结合使用的,我在以前讲过SQLAlchemy框架的使用,Django支持的不是SQLAlchemy,但是也内嵌了ORM框架,可以不需要直接面对数据库编程,而可以通过 ...

  6. soupui--替换整个case的url

    添加新的URL 随便进入一个case的[REST]step,添加新的url 更换URL 添加完之后双击想要更换url的case,在弹出的窗口中点击URL按钮 在弹出的set endpoint窗口中选择 ...

  7. nginx反向代理和负载均衡的实现

    反向代理和负载均衡的关系可以理解为,一个ip的负载均衡就是反向代理. 反向代理使用的是:proxy_pass指令   负载均衡使用的是:proxy_pass指令+upstream指令 负载均衡的3中方 ...

  8. c++ 广度优先搜索

    #include <iostream> using namespace std; ; ; // >=9皆可 struct node//声明图形顶点结构 { int vertex; s ...

  9. 干货 | MySQL数据库安全之审计

    干货 | MySQL数据库安全之审计 原创: 李勇 京东云开发者社区  今天 每家公司都希望业务高速增长,最好能出几个爆款产品或者爆款业务,从而带动公司营收高速攀升.但站在数据库管理员的角度,这却是实 ...

  10. 梯度消失、梯度爆炸以及Kaggle房价预测

    梯度消失.梯度爆炸以及Kaggle房价预测 梯度消失和梯度爆炸 考虑到环境因素的其他问题 Kaggle房价预测 梯度消失和梯度爆炸 深度模型有关数值稳定性的典型问题是消失(vanishing)和爆炸( ...