B-Tree(B树)原理及C++代码实现
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++代码实现的更多相关文章
- [转] Splay Tree(伸展树)
好久没写过了,比赛的时候就调了一个小时,差点悲剧,重新复习一下,觉得这个写的很不错.转自:here Splay Tree(伸展树) 二叉查找树(Binary Search Tree)能够支持多种动态集 ...
- CJOJ 1976 二叉苹果树 / URAL 1018 Binary Apple Tree(树型动态规划)
CJOJ 1976 二叉苹果树 / URAL 1018 Binary Apple Tree(树型动态规划) Description 有一棵苹果树,如果树枝有分叉,一定是分2叉(就是说没有只有1个儿子的 ...
- 【数据结构】B-Tree, B+Tree, B*树介绍 转
[数据结构]B-Tree, B+Tree, B*树介绍 [摘要] 最近在看Mysql的存储引擎中索引的优化,神马是索引,支持啥索引.全是浮云,目前Mysql的MyISAM和InnoDB都支持B-Tre ...
- 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 ...
- POJ1741 Tree(树分治——点分治)题解
题意:给一棵树,问你最多能找到几个组合(u,v),使得两点距离不超过k. 思路:点分治,复杂度O(nlogn*logn).看了半天还是有点模糊. 显然,所有满足要求的组合,连接这两个点,他们必然经过他 ...
- AVL树原理及实现 +B树
1. AVL定义 AVL树是一种改进版的搜索二叉树.对于一般的搜索二叉树而言,如果数据恰好是按照从小到大的顺序或者从大到小的顺序插入的,那么搜索二叉树就对退化成链表,这个时候查找,插入和删除的时间都会 ...
- 1. 决策树(Decision Tree)-决策树原理
1. 决策树(Decision Tree)-决策树原理 2. 决策树(Decision Tree)-ID3.C4.5.CART比较 1. 前言 决策树是一种基本的分类和回归方法.决策树呈树形结构,在分 ...
- poj 1741 Tree(树的点分治)
poj 1741 Tree(树的点分治) 给出一个n个结点的树和一个整数k,问有多少个距离不超过k的点对. 首先对于一个树中的点对,要么经过根结点,要么不经过.所以我们可以把经过根节点的符合点对统计出 ...
- AVL树,红黑树,B-B+树,Trie树原理和应用
前言:本文章来源于我在知乎上回答的一个问题 AVL树,红黑树,B树,B+树,Trie树都分别应用在哪些现实场景中? 看完后您可能会了解到这些数据结构大致的原理及为什么用在这些场景,文章并不涉及具体操作 ...
- 【POJ 2486】 Apple Tree(树型dp)
[POJ 2486] Apple Tree(树型dp) Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 8981 Acce ...
随机推荐
- vue实现CheckBox与数组对象绑定
实现需求: 实现一个简易的购物车,页面的表格展示data数据中的一个数组对象,并提供选中商品和全选商品checkbox复选框,页面实时显示选中商品的总金额: 分析: 1:使用v-for循环渲染arra ...
- [极客大挑战 2019]FinalSQL
0x00 知识点 盲注 0x01 解题 根据题目提示盲注,随便点几下找到注入点 发现我们输入^符号成功跳转页面,证明存在注入 1^(ord(substr((select(group_concat(sc ...
- ES6的一些语法
let, const, class, extends, super, arrow functions, template string, destructuring, default, rest ar ...
- mybatis+mysql 通过sql脚本生成mapper的部分内容
SQL mysql SELECT concat('<if test="', COLUMN_NAME, ' != null"> ', COLUMN_NAME, ',< ...
- python3+Opencv 搭建环境和基本操作
一.必备前提: Python3.5及以上版本.pip.windows环境 二.搭建opencv 该部分可以创建隔绝的Python环境来引入,参照virtualenv的使用 在目标的cmd窗口,依次输入 ...
- Neo4j--常用的查询语句
参考 https://www.w3cschool.cn/neo4j 准备工作 插入一堆朝代节点 插入我大明皇帝节点 创建大明皇帝统治大明王朝的关系 看一下结果 WHERE WHERE 语法 WHERE ...
- C语言中getopt()和getopt_long()函数的用法
一.参考文章 1.C语言中getopt()和getopt_long()函数的用法 2.linux 中解析命令行参数 (getopt_long用法) 二.调试经验
- .NET core ABP 获取远程IP地址
2.asp.net core 2.x上配置 第一步:在控制器中定义变量 private IHttpContextAccessor _accessor; 第二步: 控制器的构造函数进行注入 public ...
- Windows安装使用SonarQube7.4 对java项目进行代码质量扫描
我这里使用7.4因为使用JDK是1.8 其它版本看下依赖版本就好 1.下载7.4版本安装包 https://binaries.sonarsource.com/CommercialDistributio ...
- 4)栈和队列-->受限线性表
栈和队列叫 受限线性表 只不过他们插入和删除的位置 相对于之前的线性表有了限制 所以叫受限线性表 1)栈-->就是先进后出 2)队列-->先进先出 3)循环链表框图: 4)队列