浅谈B-树、B+树
来源:互联网 发布:两小无猜网络剧播出 编辑:程序博客网 时间:2024/06/06 02:45
B树
数据库的索引大多用B+树实现,要了解B+树,我们必须先了解什么是B-树?
首先要清楚的是,B-树不能叫做B减树,否则可就让人笑掉大牙了,所以,后文中我们直接用作B树。
之前我们讲过,二叉搜索树的效率是O(log2^N),那为何数据库中不用二叉搜索树来作为索引呢?此时我们必须考虑到磁盘IO。数据库索引是存储在磁盘上的,当数据量比加大 的时候,索引的大小可能有几个G甚至更多。当我们利用索引查询的时候,嗯呢该吧整个索引都加载到内存中吗?显然是不可能的,能做的只能是逐一加载每一个磁盘页,这里的磁盘页对应着索引树的节点。举个栗子:
要在上面这棵二叉搜索树中查找10这个节点。
第一次IO:
第二次IO:
第三次IO:
第四次IO:
我们可以发现,在最坏的情况下,磁盘IO的次数等于这棵索引树的高度,为了减少磁盘IO的次数,我们需要让这棵树“降高度”,B树就是让这种“瘦高”的搜索树变成“矮胖”,从而减少磁盘IO的次数,提高搜索效率。
B树的性质
B树是一种用于外查找的多路平衡搜索树。
一棵M阶的B树:
1. 根节点至少有两个孩子,【2,M】2. 每个非根节点有【M/2,M】个孩子3. 每个非根节点有【M/2-1,M-1】个关键字,并且以升序排列4. 每个节点孩子的数量比关键字的数量多一个5. 所有的叶子节点都在同一层6. key[i]和key[i+1]之间的孩子节点的值介于key[i]、key[i+1]之间
假如我们要加下面这棵B树中查找5这个节点:
第一次磁盘IO,在内存中定位,和9比较:
第二次磁盘IO,在内存中定位,和2,6比较:
第三次磁盘IO,在内存中定位,和3,5比较:
我们可以看出,B树在查询过程中的比较次数其实不比二叉查找树少,尤其当单一节点中的元素数量很多时。可是,相比于磁盘IO的速度,内存中的比较耗时几乎可以忽略。所以只要树的高度足够低,IO次数足够找,就可以提升性能。相比之下内部元素很多也没有关系,仅仅是多了几次内存交互,只要不超过磁盘页大小即可。
B树的插入:
B树的插入只能在叶子节点,且当节点中的关键字满了,要及逆行分裂操作。用上面的B树举例:
在叶子节点插入:
第一次分裂:
第二次分裂:
B树的删除:
当删除一个导致该树不符合B树的特性时,要进行左旋操作。比如,要删除下面B树的11这个节点,删除后,12只有一个孩子,不符合B树,此时,我们找出12,13,15这三个树的中位数13,取代节点12,经过左旋12成为第一个孩子。
下面给出B树的结构和插入操作:
#include<iostream>using namespace std;template<class K, class V, size_t M>struct BTreeNode{ //多开一个空间,方便分裂 pair<K, V> _kvs[M];//关键字数组 BTreeNode<K, V, M>* _subs[M+1];//孩子节点 BTreeNode<K, V, M>* _parent;//三叉 size_t size; BTreeNode() :_parent(NULL) , size(0) { for (size_t i = 0; i < M+1; ++i) { _subs[i] = NULL; } }};template<class K,class V,size_t M>class BTree{ typedef BTreeNode<K, V, M> Node;public: BTree() :_root(NULL) {} pair<Node*, int> Find(const K& key) { //要返回这个节点和在这个节点中的位置 Node* cur = _root; Node* parent = NULL; while (cur) { size_t i = 0; while (i < cur->size) { //在当前位置的左树 if (cur->_kvs[i].first > key) break; else if (cur->_kvs[i].first < key) { ++i; } else return make_pair(cur, i); } //在左树或是没找到 parent = cur; cur = cur->_subs[i]; } return make_pair(parent, -1); } void InSertKV(Node* cur, const pair<K, V>& kv, Node* sub) { int end = cur->size - 1; while (end >= 0) { if (cur->_kvs[end].first > kv.first) { //左子树的下标是与当前节点下标相同,右子树的下标是当前节点坐标+1 cur->_kvs[end + 1] = cur->_kvs[end]; cur->_subs[end + 2] = cur->_subs[end + 1]; --end; } else { break; } } //end<0或kv.first>cur_kvs[end].first cur->_kvs[end + 1] = kv; cur->_subs[end + 2] = sub; if (sub) sub->_parent = cur; cur->size++; } Node* Divided(Node* cur) { Node* newNode = new Node; int mid = (cur->size) / 2; size_t j = 0; size_t i = mid + 1; for (; i < cur->size; ++i) { newNode->_kvs[j] = cur->_kvs[i]; newNode->_subs[j] = cur->_subs[i]; if (newNode->_subs[j]) newNode->_subs[j]->_parent = newNode; newNode->size++; j++; } //右孩子还没拷 newNode->_subs[j] = cur->_subs[i]; if (newNode->_subs[j]) newNode->_subs[j]->_parent = newNode; return newNode; } bool InSert(const pair<K, V>& kv) { //节点为NULL直接插入 if (_root == NULL) { _root = new Node; _root->_kvs[0] = kv; _root->size = 1; return true; } //找到相同值返回false,没找到返回true,节点的关键字满了就进行分裂 pair<Node*, int> ret = Find(kv.first); if (ret.second >= 0) return false; //没找到,可以插入节点 Node* cur = ret.first; pair<K, V> newKV = kv;//新的关键字 Node* sub = NULL; while (1) { //插入一个人孩子和一个关键字 InSertKV(cur, newKV, sub); if (cur->size < M) return true; else { //需要分裂 Node* newNode = Divided(cur); pair<K, V> midKV = cur->_kvs[(cur->size) / 2]; //根节点分裂 cur->size -= (newNode->size + 1); if (cur == _root) { _root = new Node; _root->_kvs[0] = midKV; _root->size = 1; _root->_subs[0] = cur; _root->_subs[1] = newNode; cur->_parent = _root; newNode->_parent = _root; return true; } else { sub = newNode; newKV = midKV; cur = cur->_parent; } } } } void InOrder() { _InOrder(_root); }protected: void _InOrder(Node* root) { if (root == NULL) return; Node* cur = root; size_t i = 0; for (; i < cur->size; ++i) { _InOrder(root->_subs[i]); cout << cur->_kvs[i].first << " "; } _InOrder(cur->_subs[i]); }private: Node* _root;};void Test(){ int a[] = { 53, 75, 139, 49, 145, 36, 101 }; int sz = sizeof(a) / sizeof(a[0]); BTree<int, int, 3>bt; for (size_t i = 0; i < sz; ++i) { bt.InSert(make_pair(a[i],i)); } bt.InOrder();}
B树主要应用于文件系统以及部分数据库索引,比如著名的非关系型数据库MongoDB,而大部分关系型数据库,比如Mysql,则使用B+树作为索引。
B+树
B+树的大体特征与B树相似,但B+树有自己的特性:
1.有k个子树的中间节点包含有k个元素(B树中是k-1个元素),每个元素不保存数据,只用来索引,所有数据都保存在叶子节点。
2.所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。
3.所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。
在B树中,所有的节点都携带数据,但在B+树中,只有叶子节点中有数据,中间节点仅仅是索引,没有任何数据关联。
由于B+树的中间节点上没有数据,所以,同样大小的磁盘也可以容纳更多的节点元素,这就意味着,数据量相同的情况下,B+树的结构哦比B树更加“矮胖”,因此查询时IO的次数也更少。其次,B+树的查询必须查找到叶子节点,而B树是只要找到匹配元素即可,无论是中间节点还是叶子节点,因此,B树的查找性能并不稳定,最好情况是查找到根节点,最坏情况是查找到叶子节点,而B+树的查询时稳定的,每一次都是查找到叶子节点。B树对节点的遍历只能是繁琐的中序遍历,而B+树的遍历值需要对叶子节点的链表进行遍历即可。
总结一下,B+树相对于B树的优势有三个:
1.单一节点存储更多的元素,使得查询的IO次数更少。
2.所有查询都要查找到叶子节点,查询性能稳定。
3.所有叶子节点形成有序链表,便于范围查询。
- 浅谈B-树、B+树
- 浅谈数据结构中的树(B/B+/B-/B*)
- B,B-,B+,B*树
- B-、B、B+、B*树
- B/B+/B*树
- B- ,B+ , B*树
- B , B+ ,B*树
- B-, B+,B* 树
- B树B-B+树
- B- B+ B*树 小结
- B 、B-、B+树总结
- B-、B+、B*树介绍
- B 树、 B- 树、 B+ 树、 B*
- B树、B-树、B+树、B*
- B树,B-,B+,B*树
- B树、B-、B+、B*树
- B树,B-,B+,B* 各种树
- B,B-,B+ 和B*树
- 探索深入理解java虚拟机之Java内存模型和线程(7)
- 【观察】聚焦本地合作,看InterSystems如何破解新医改难题
- 索引的注意事项和原理分析
- 绘制三角形
- npm安装Angular CLI
- 浅谈B-树、B+树
- R4985是一种便于使用的内部集成了译码器的微步进电机驱动器
- 如何在linux上安装VMware tools
- 【esp8266】Soc方式使用esp8266控制继电器
- 主键索引的创建
- Vmware虚拟机网络模式之NAT模式
- 01 创建第一个Spring工程
- HDU
- 欢迎使用CSDN-markdown编辑器