B-树
前面介绍的查找算法都在内存中进行的,它们适合用于较小的文件,而对于较大的、存放在外存的文件就不合适,对于此类较大规模的文件,即使是采用了平衡二叉树,在查找效率上仍然较低。例如若将存放在外存的10亿条记录组织为平衡二叉树,则每次访问记录需要进行约30次外存访问;而若采用256阶的B-树数据结构,则每次访问记录需要进行的外存访问次数为4到5次。
B-树的定义
B-树是一种平衡的多路查找树,在文件系统中,B-树已经成为索引文件的有效结构,并得到了广泛的应用研究。
一颗m阶的(m≥3)的B-树,或为空树,或满足以下特性:
树中的每个结点至多有m棵子树
若根结点不是叶子结点,则至少有两棵子树
所有非终端结点中包含下列信息:(n, P0, K1, P1, K2……Kn, Pn)
其中,Ki(1≤i≤n)为关键字,且K i < K i+1;Pj所指子树中所有结点的关键字值均小于K j+1,大于K j。n([m/2] – 1 ≤ n ≤ m-1)为关键字个数,n+1为子树个数除根结点外的所有非终端结点至少有[m/2]棵子树,也即至少应有[m/2]-1个关键字
所有的叶子结点出现在同一层次上,并且不带信息(可以看作是外部结点或查找失败的结点,实际上这些结点不存在,指向这些结点指针为空)
如下图为8个非终端结点、14个叶子结点和13个关键字组成的4阶的B-树示意图:
在一棵4阶的B-树中,每个结点关键字个数最少为[m/2] – 1”=1,最多为m-1=3;每个结点的子树数目最少为[m/2]=2,最多为m=4
基于B-树的查找算法
B-树又称为多路查找树,在B-树中查找一个关键字值=给定值key的具体过程是
首先在根结点的关键字序列(key1,key2,key3……keyn)中查找,由于这个关键字序列是有序的,因此既可采用顺查找,也可采用二分查找;若无匹配,(假设key i < key < key i+1),此时应沿着pi指针所指的结点继续在相应的子树中查找。
需要说明的是,B-树经常用于外部文件的查找,某些子树未常驻内存,因为查找过程需要从外存中读入内存,读盘次数与待查找的结点在树中的层次有关,但最多不会超过树的深度,而在内存查找所需的时间与结点中关键字的个数密切相关。
因为在外存上读取结点比在内存中进行关键字查找耗时多,所以在外存上读取结点的次数,即B-树的层次树是决定B-树查找效率的首要因素。
㏒m (n+1) ≤ h ≤ ㏒[m/2] (n+1)/2 + 1,若当n=10000,m=10时,B-树的深度在5-6之间。
[BTree_1
package Search;
class Node<T> {
private int keyNum; // 关键字个数域
private boolean isLeaf; // 是否为树叶
private T[] key; // 关键字数组
private Node[] child; // 子树指针数组
private Node parent; // 双亲结点指针
Node(int m){
keyNum = 0;
isLeaf = true;
key = (T[])(new Object[2 * m - 1]);
'2509child = new Node[2 * m];
parent = null;
}
public int getKeyNum() {
return keyNum;
}
public void setKeyNum(int keyNum) {
this.keyNum = keyNum;
}
public boolean isLeaf() {
return isLeaf;
}
public void setLeaf(boolean isLeaf) {
this.isLeaf = isLeaf;
}
public T[] getKey() {
return key;
}
public void setKey(T[] key) {
this.key = key;
}
public Node[] getChild() {
return child;
}
public void setChild(Node'5B] child) {
this.child = child;
}
public Node getParent() {
return parent;
}
public void setParent(Node parent) {
this.parent = parent;
}
}
class Result{
private Node resultNode; //B-树查找结果类型
private int i; //指向找到的结点
private boolean found; //true找到 false未找到
public Node getResultNode() {
return resultNode;
}
public void setResultNode(Node resultNode) {
this.resultNode = resultNode;
}
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
public boolean isFound() {
return found;
}
public void setFound(boolean found) {
this.found = found;
}
}
public class BTree<T> {
private Node<T> root = null;
private int"degree; public BTree(int t){ degree = t; } /** * @description B-树查找算法 * @param root * @param key * @return * @time 2016年1月13日 下午11:54:57 */ public Result searchBTree(Node<T> root, T key){ int"i = 0;
Node<T> p = root, q = null; //p指向待查找的结点,q指向p的双亲结点
boolean found = false;
Result rs = new Result(); //存放查找结果
Comparable<T> k = (Comparable<T>) key;
while(p != null && !found){
i = 0;
while(i < p.getKeyNum() && k.compareTo(p.getKey()[i])>0)
i++;
//找到
if(i < p.getKeyNum() && k.compareTo(p.getKey()[i]) == 0)
found = true;
else{
q = p; //保存双亲结点
p = p.ggtChild()[i]; //在子树中查找
}
}
if(found == false)
p = q;
rs.setResultNode(p);
rs.setI(i);
rs.setFound(found);
return rs;
}
}
基于B-树的插入算法
首先在树中查找key,若找到则直接返回,否则,查找操作必定失败在某个叶子结点上,则插入。若此结点原来是满的,插入后违反了规则,则以key[m/2]为划分点,分成两个结点,然后把key[m/2]升到双亲结点中。于是双亲结点指向被插入结点的指针p就改成p1和p2两部分。
基于B-树的删除算法
若删除结点Ki是最下层的非终端结点(即叶子结点的上一层),则应删除Ki及它右边的指针;删除后若结点中关键字个数不少于[m/2]-1,则删除完成;否则要进行合并结点操作。
被删关键字所在结点的关键字个数不小于[m/2],则只需从该结点删除关键字Ki和相应的指针Pi,树的其他部分保持不变。
被删除关键字所在结点的关键字个数等于[m/2]-1,而与该结点相邻的有兄弟(或左兄弟)结点的关键字>[m/2]-1,则需将其右兄弟(或左兄弟)的最小关键字上移到双亲结点中,而将其双亲结点中<(或>)该上移关键字的关键字下移到被删关键字所在的结点中。
被删关键字所在的结点的关键字个数和其相邻的兄弟结点中的关键字个数均等于[m/2]-1,此时需将被删除关键字的所有结点与其左或右兄弟合并。
2.若删除结点是最下层的非终端结点以上某个层次的结点,根据B-树的特性可知,可用Ki右边指针Pi所指子树中最小关键字Y代替Ki,然后在相应的结点中删除Y。
B+树
B+树和B-树的区别
B-树中,每一个结点含有n个关键字和n+1棵子树;而在B+树中,每一个结点含有n个关键字和n棵子树,即每一个关键字对应一棵子树
在B-树中,每个结点(除根结点)中关键字取值范围为[m/2]-1 ≤ n ≤ m-1;而在B+树中是[m/2] ≤ n ≤ m
B+树中所有叶子结点包含了全部关键字及指向对应记录的指针,且所有叶子结点按关键字从小到大的顺序依次连接。
4.B+树中所有叶子结点仅起到索引作用,即结点中的每一个索引项只含有对应子树的最大关键字和指向该子树的指针,不含有该关键字对应记录的存储地址。
红黑树
红黑树又称“对称二叉B树“,是一种自平衡的二叉查找树。除了具有二叉排序树的性质外,还具有以下三点性质:
根结点和所有外部结点的颜色都是黑色的。
从根结点到外部结点的所有路径上没有两个连续的红色结点。
从根结点到外部结点的所有路径上都包含相同数目的黑色结点。
红黑树的查找
同二叉排序树的查找算法。
红黑树的插入
首先使用二叉排序树的插入算法将一个结点插入到红黑树中,该结点作为新的叶子结点插入到红黑树中某一外部结点位置。在插入过程中需要为新结点设置颜色。
新插入的结点肯定为红色,此时与性质2发生冲突,红黑树不平衡。通过检查新结点u、父结点pu、祖父结点gu,可对不平衡的总类进行分类(8种):
LLr型:pu是gu左孩子,u是pu左孩子,gu另一孩子为红色;
LRr型:pu是gu左孩子,u是pu右孩子,gu另一孩子为红色;
RRr型:pu是gu右孩子,u是pu右孩子,gu另一孩子为红色;
RLr型:pu是gu右孩子,u是pu左孩子,gu另一孩子为红色;
LLb型:pu是gu左孩子,u是pu左孩子,gu另一孩子为黑色;
LRb型:pu是gu左孩子,u是pu右孩子,gu另一孩子为黑色;
RRb型:pu是gu右孩子,u是pu右孩子,gu另一孩子为黑色;
RLr型:pu是gu右孩子,u是pu左孩子,gu另一孩子为黑色;
对于以上1-4种可以通过改变颜色来进行,5-8种需要进行一次旋转处理。具体算法有待研究,这里只作初步了解。