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 ...
随机推荐
- redis(三)----连接池配置
1. 目录结构: 2. 测试源码 package com.redis; import redis.clients.jedis.Jedis; import redis.clients.jedis.Jed ...
- chan-vese模型
Python--level set (水平集)和 chan-vese模型 2018年08月28日 10:51:54 GlassySky0816 阅读数:1604 版权声明:本文为博主原创文章,未 ...
- lambda的题
def num(): return [lambda x: i*x for i in range(4)] print([m(2) for m in num()]) 这个式子,lambda相当于闭包函数, ...
- 18 11 26 用多进程 多线程 携程 实现 http 服务器的创建
下面是一个 多进程 服务器的创建 import socket import re import multiprocessing def service_client(new_socket): &qu ...
- Cookie API和记录上次来访时间
1.什么是Cookie? Cookie是一种会话技术,用千将会话过程中的数据保存到用户的浏览器中,从而使浏览器和服务器可以更好地进行数据交互. 在现实生活中,当顾客在购物时,商城经常会赠送顾客一张会员 ...
- textField 基本属性
_textField.frame = CGRectMake(0, 0, 200, 50); _textField.delegate = self; _textField.text = str; [_t ...
- 删除xcode项目中不再使用的图片资源
1. 利用工具 下载地址 http://jeffhodnett.github.io/Unused/ 运行效果如下 2. 通过终端 执行 shell 命令 a. 第一步建立.sh 文件 如 ...
- scrapy 在pycharm中调试 不用到命令行中启动爬虫方法
(目录结构如上图) 在主目录中加入main.py,在其中加入代码,运行此文件就可以运行整个爬虫: # -*- coding: utf-8 -*- __author__='pasaulis' #在程序中 ...
- Linux--shell 脚本免密码输入
参考:https://www.cnblogs.com/lixigang/articles/4849527.html #!/bin/bash ssb=" password=mysql data ...
- 基于springboot实现Ueditor并生成.html的示例
一.项目架构 二.项目代码 1.HtmlProductController.java package com.controller; import java.io.File; import java. ...