内查找:搜索二叉树
外查找:红黑树
B树配合二分查找
1.数据库索引
2.文件系统
决定数据库性能:B+树,缓存系统(热数据:常访问)
建索引:另建B树索引
两个字段也可建索引,封装成结构体。
B树特点:
根节点至少有两个孩子
每个非根节点有[M/2,M]个孩子
每个非根节点有[M/2-1,M-1]个关键字,并且以升序排列
key[i]和key[i+1]之间的孩子节点的值介于key[i]、key[i+1]之间
所有的叶子节点都在同一层
B+树特点:
B+树的定义
B+树是应文件系统所需而出的一种B-树的变型树。一棵m阶的B+树和m阶的B-树的差异在于:
1.有n棵子树的结点中含有n个关键字,每个关键字不保存数据,只用来索引,所有数据都保存在叶子节点。
2.所有的叶子结点中包含了全部关键字的信息,及指向含这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。
3.所有的非终端结点可以看成是索引部分,结点中仅含其子树(根结点)中的最大(或最小)关键字。
通常在B+树上有两个头指针,一个指向根结点,一个指向关键字最小的叶子结点。
一棵m阶的B+树和m阶的B树的异同点在于:
所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接。(而B 树的叶子节点并没有包括全部需要查找的信息)
所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。(而B 树的非终节点也包含需要查找的有效信息)
#pragma once #include<iostream> using namespace std; template<class K,int M> struct BPTreeNoneLeafNode { K _keys[M]; //BPTreeNoneLeafNode<K, M>* _subs[M]; void* _subs[M]; size_t _size; BPTreeNoneLeafNode<K, M>* _parent; }; template<class K,class V,int M> struct BPTreeLeafNode { K _keys[M]; BPTreeLeafNode<K, V, M>* _subs[M]; size_t _size; BPTreeNoneLeafNode<K, M>* _parent; }; //另一种定义方式 template<class K,class V,int M> struct BPTreeNode { K _keys[M]; void* _subs[M]; BPTreeNode<K, V, M>* _parent; size_t size; bool _IsLeaf; }; template<class K,class V,int M> class BPTree { public: bool Insert(K& key, V& val); bool Remove(K& key); BPTreeLeafNode<K, V, M>* Find(K& key); private: BPTreeNoneLeafNode<K, M>* _root; BPTreeLeafNode<K, V, M>* _data;//链表 };
注:
为什么说B+树比B 树更适合实际应用中操作系统的文件索引和数据库索引?
B+树的磁盘读写代价更低
B+树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。
B+树的查询效率更加稳定
由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
B*树
是B+树的变体,在B+树的非根和非叶子结点再增加指向兄弟的指针;B*树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3(代替B+树的1/2);
B+树的分裂:当一个结点满时,分配一个新的结点,并将原结点中1/2的数据复制到新结点,最后在父结点中增加新结点的指针;B+树的分裂只影响原结点和父结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针;
B*树的分裂:当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了);如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针;
所以,B*树分配新结点的概率比B+树要低,空间使用率更高;