整合自:

http://blog.csdn.net/shuangde800/article/details/7341289

http://www.cnblogs.com/Jezze/archive/2011/12/23/2299884.html

http://blog.csdn.net/jdhanhua/article/details/6621026

B树介绍:点击打开链接

tire树:点击打开链接    点击打开链接

树集合:点击打开链接

1.定义:

什么是哈夫曼树?

让我们先举一个例子。

判定树:

        在很多问题的处理过程中,需要进行大量的条件判断,这些判断结构的设计直接影响着程序的执行效率。例如,编制一个程序,将百分制转换成五个等级输出。大家可能认为这个程序很简单,并且很快就可以用下列形式编写出来:
 
[cpp] view plaincopy
  1. if(score<60)
  2. cout<<"Bad"<<endl;
  3. else if(score<70)
  4. cout<<"Pass"<<endl
  5. else if(score<80)
  6. cout<<"General"<<endl;
  7. else if(score<90)
  8. cout<<"Good"<<endl;
  9. else
  10. cout<<"Very good!"<<endl;
若考虑上述程序所耗费的时间,就会发现该程序的缺陷。在实际中,学生成绩在五个等级上的分布是不均匀的。当学生百分制成绩的录入量很大时,上述判定过程需要反复调用,此时程序的执行效率将成为一个严重问题。

但在实际应用中,往往各个分数段的分布并不是均匀的。下面就是在一次考试中某门课程的各分数段的分布情况: 

下面我们就利用哈夫曼树寻找一棵最佳判定树,即总的比较次数最少的判定树。
 
第一种构造方式:
 
第二种构造方式:
这两种方式,显然后者的判定过程的效率要比前者高。在也没有别地判定过程比第二种方式的效率更高。
我们称判定过程最优的二叉树为哈夫曼树,又称最优二叉树

在一般的数据结构的书中,树的那章后面,著者一般都会介绍一下哈夫曼(HUFFMAN)

树和哈夫曼编码。哈夫曼编码是哈夫曼树的一个应用。哈夫曼编码应用广泛,如

JPEG中就应用了哈夫曼编码。 首先介绍什么是哈夫曼树。哈夫曼树又称最优二叉树,

是一种带权路径长度最短的二叉树。所谓树的带权路径长度,就是树中所有的叶结点

的权值乘上其到根结点的 路径长度(若根结点为0层,叶结点到根结点的路径长度

为叶结点的层数)。树的带权路径长度记为WPL= (W1*L1+W2*L2+W3*L3+...+Wn*Ln)

,N个权值Wi(i=1,2,...n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径

长度为Li(i=1,2,...n)。可以证明哈夫曼树的WPL是最小的。

2.哈夫曼编码步骤:

一、对给定的n个权值{W1,W2,W3,...,Wi,...,Wn}构成n棵二叉树的初始集合F= {T1,T2,T3,...,Ti,...,Tn},其中每棵二叉树Ti中只有一个权值为Wi的根结点,它的左右子树均为空。(为方便在计算机上实现算 法,一般还要求以Ti的权值Wi的升序排列。)
二、在F中选取两棵根结点权值最小的树作为新构造的二叉树的左右子树,新二叉树的根结点的权值为其左右子树的根结点的权值之和。
三、从F中删除这两棵树,并把这棵新的二叉树同样以升序排列加入到集合F中。
四、重复二和三两步,直到集合F中只有一棵二叉树为止。

简易的理解就是,假如我有A,B,C,D,E五个字符,出现的频率(即权值)分别为5,4,3,2,1,那么我们第一步先取两个最小权值作为左右子树构造一个新树,即取1,2构成新树,其结点为1+2=3,如图:

虚线为新生成的结点,第二步再把新生成的权值为3的结点放到剩下的集合中,所以集合变成{5,4,3,3},再根据第二步,取最小的两个权值构成新树,如图:

再依次建立哈夫曼树,如下图:

其中各个权值替换对应的字符即为下图:

所以各字符对应的编码为:A->11,B->10,C->00,D->011,E->010

例2:

例题:
假设一个文本文件TFile中只包含7个字符{A,B,C,D,E,F,G},这7个字符在文本中出现的次数为{5,24,7,17,34,5,13}
利用哈夫曼树可以为文件TFile构造出符合前缀编码要求的不等长编码
 
具体做法:
1. 将TFile中7个字符都作为叶子结点,每个字符出现次数作为该叶子结点的权值
2.规定哈夫曼树中所有左分支表示字符0,所有右分支表示字符1,将依次从根结点到每个叶子结点所经过的分支的二进制位的序列作为该结点对应的字符编码
3. 由于从根结点到任何一个叶子结点都不可能经过其他叶子,这种编码一定是前缀编码,哈夫曼树的带权路径长度正好是文件TFile编码的总长度
通过哈夫曼树来构造的编码称为哈弗曼编码(huffman code)
 

例3:

下面演示了用Huffman算法构造一棵Huffman树的过程:

霍夫曼编码是一种无前缀编码。解码时不会混淆。其主要应用在数据压缩,加密解密等场合。

3.代码实现:

<span style="font-family: Arial, Helvetica, sans-serif;">//首先:定义哈夫曼树的节点类,为了方便使用集合类的排序功能,实现了Comparable接口(可以不是实现该接口,此时需要实现排序功能)</span>

public class Node<T> implements Comparable<Node<T>> {  private T data;  private double weight;  private Node<T> left;  private Node<T> right;  public Node(T data, double weight){  this.data = data;  this.weight = weight;  }  public T getData() {  return data;  }  public void setData(T data) {  this.data = data;  }  public double getWeight() {  return weight;  }  public void setWeight(double weight) {  this.weight = weight;  }  public Node<T> getLeft() {  return left;  }  public void setLeft(Node<T> left) {  this.left = left;  }  public Node<T> getRight() {  return right;  }  public void setRight(Node<T> right) {  this.right = right;  }  @Override  public String toString(){  return "data:"+this.data+";weight:"+this.weight;  }  @Override  public int compareTo(Node<T> other) {  if(other.getWeight() > this.getWeight()){  return 1;  }  if(other.getWeight() < this.getWeight()){  return -1;  }  return 0;  }
}  //然后:实现哈夫曼树的主题类,其中包括两个静态的泛型方法,为创建哈夫曼树和广度优先遍历哈夫曼树package my.huffmanTree;  import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Queue;  public class HuffmanTree<T> {  public static <T> Node<T> createTree(List<Node<T>> nodes){  while(nodes.size() > 1){  Collections.sort(nodes);  Node<T> left = nodes.get(nodes.size()-1);  Node<T> right = nodes.get(nodes.size()-2);  Node<T> parent = new Node<T>(null, left.getWeight()+right.getWeight());  parent.setLeft(left);  parent.setRight(right);  nodes.remove(left);  nodes.remove(right);  nodes.add(parent);  }  return nodes.get(0);  }  public static <T> List<Node<T>> breadth(Node<T> root){  List<Node<T>> list = new ArrayList<Node<T>>();  Queue<Node<T>> queue = new ArrayDeque<Node<T>>();  if(root != null){  queue.offer(root);  }  while(!queue.isEmpty()){  list.add(queue.peek());  Node<T> node = queue.poll();  if(node.getLeft() != null){  queue.offer(node.getLeft());  }  if(node.getRight() != null){  queue.offer(node.getRight());  }  }  return list;  }
}
最后:编写一共测试端[java] view plain copy
package my.huffmanTree;  import java.util.ArrayList;
import java.util.List;  public class Test {  public static void main(String[] args) {  // TODO Auto-generated method stub  List<Node<String>> list = new ArrayList<Node<String>>();  list.add(new Node<String>("a",7));  list.add(new Node<String>("b",5));  list.add(new Node<String>("c",4));  list.add(new Node<String>("d",2));  Node<String> root = HuffmanTree.createTree(list);  System.out.println(HuffmanTree.breadth(root));
//      System.out.println(list);  }
}  其中添加四个节点,其权重为{7,5,4,2},最终按照广度优先遍历,应为七个节点,为:18,7,11,5,6,2,4;
控制台输出为:[data:null;weight:18.0, data:a;weight:7.0, data:null;weight:11.0, data:b;weight:5.0, data:null;weight:6.0, data:d;weight:2.0, data:c;weight:4.0]与实际想符合。

----------------------------------------

B树总结

AVL树:最早的平衡二叉树之一。应用相对其他数据结构比较少。windows对进程地址空间的管理用到了AVL树

红黑树:平衡二叉树,广泛用在C++的STL中。map和set都是用红黑树实现的。

B/B+树用在磁盘文件组织 数据索引和数据库索引

Trie树 字典树,用在统计和排序大量字符串(场景自己yy = =)


AVL RBtree
B B+
Trie
AVL早期有应用在linux内核上,后来被RBtree代替了,具体是用在哪个模块上,sorry,我忘了,求知欲那么强的你,google一下就有答案了,两者都保持log(n)的插入与查询,是平衡的BST,不会出现(n2)的糟糕情况,那为什么linux内核要用RBtree替代AVL呢,我没具体了解过,但从原理上看,个人猜想是AVL需要大量的旋转来保持平衡,而RBtree的旋转调节可能会少些,这是个人的臆断,真心希望有深入理解的同仁指正,用力的拍,另外我们熟悉的STL的map容器底层是RBtree,当然指的不是unordered_map,后者是hash。

而 B B+则运用在file system database这类持续存储结构,同样能保持lon(n)的插入与查询,也需要额外的平衡调节。像mysql的数据库定义是可以指定B+ 索引还是hash索引。

trie树大都用在word的匹配,但单纯的trie内存消耗很大,建trie树也需要些时间,通常用在带词典的机械分词,jieba分词就是建立在trie上匹配的,trie有其他变体可以压缩空间,像double array trie这类比较老且经典的压缩方法,也有其他比较新的压缩方式,看论文时有看过,没自己实现过所以不断言了,其实面对多模匹配trie没有其变体aho-corasick来得理想,另外aho-corasick也是可以用巧妙的方法来进行压缩空间,这里不再展开,毕竟手机码字,同时想基数树与其也类似,在nginx上有应用,说到aho-corasick其实早期的入侵检测工具snort也有应用实现,但如今改成wu-menber了,具体记不清了,其实trie还是挺有用的,Tengine也用trie实现了了匹配模块。但要是用在大量单词的匹配上确实吃内存。

AVL是一种高度平衡的二叉树,所以通常的结果是,维护这种高度平衡所付出的代价比从中获得的效率收益还大,故而实际的应用不多,更多的地方是用追求局部而不是非常严格整体平衡的红黑树。当然,如果场景中对插入删除不频繁,只是对查找特别有要求,AVL还是优于红黑的。

红黑树的应用就很多了,除了上面同学提到的STL,还有

  • 著名的linux进程调度Completely Fair Scheduler,用红黑树管理进程控制块
  • epoll在内核中的实现,用红黑树管理事件块
  • nginx中,用红黑树管理timer等
  • Java的TreeMap实现

B和B+主要用在文件系统以及数据库中做索引等,比如Mysql:B-Tree Index in MySql

trie 树的一个典型应用是前缀匹配,比如下面这个很常见的场景,在我们输入时,搜索引擎会给予提示


还有比如IP选路,也是前缀匹配,一定程度会用到trie

B树是为磁盘或其他直接存取辅助存储设置而设计的一种平衡查找树。其能够有效降低磁盘I/O操作次数。许多数据库系统使用B树或B树的变形来储存信息。参考《算法导论》第二版第十八章的思想使用java语言实现了一颗简单的B树,在此跟大家分享下:

package com.discover;import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Random;import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;/*** 一颗B树的简单实现。* <p/>* 其实现原理参考《算法导论》第二版第十八章。* <p/>* 如果大家想读懂这些源代码,不妨先看看上述章节。* * @author WangPing** @param <K> - 键类型* @param <V> - 值类型*/
public class BTree<K, V>
{private static Log logger = LogFactory.getLog(BTree.class);/*** 在B树节点中搜索给定键值的返回结果。* <p/> * 该结果有两部分组成。第一部分表示此次查找是否成功,* 如果查找成功,第二部分表示给定键值在B树节点中的位置,* 如果查找失败,第二部分表示给定键值应该插入的位置。*/private static class SearchResult{private boolean result;private int index;public SearchResult(boolean result, int index){this.result = result;this.index = index;}public boolean getResult(){return result;}public int getIndex(){return index;}}/*** 为了简单起见,暂时只支持整型的key,* 等到工作完成后,支持泛型。 * <p/>* * TODO 需要考虑并发情况下的存取。*/private static class BTreeNode{/** 节点的关键字,以非降序存放 */private List<Integer> keys;/** 内节点的子节点 */private List<BTreeNode> children;/** 是否为叶子节点 */private boolean leaf;public BTreeNode(){keys = new ArrayList<Integer>();children = new ArrayList<BTreeNode>();leaf = false;}public boolean isLeaf(){return leaf;}public void setLeaf(boolean leaf){this.leaf = leaf;}/*** 返回关键字的个数。如果是非叶子节点,该节点的* 子节点个数为({@link #size()} + 1)。* * @return 关键字的个数*/public int size(){return keys.size();}/*** 在节点中查找给定的<code>key</code>,如果节点中存在给定的* <code>key</code>,则返回一个<code>SearchResult</code>,* 标识此次查找成功,给定<code>key</code>在节点中的索引和给定* <code>key</code>对应的值。如果不存在,则返回<code>SearchResult</code>* 标识此次查找失败,给定<code>key</code>应该插入的位置,该<code>key</code>* 对应的值为null。* <p/>* 如果查找失败,返回结果中的索引域为[0, {@link #size()}];* 如果查找成功,返回结果中的索引域为[0, {@link #size()} - 1]* <p/>* 这是一个二分查找算法,可以保证时间复杂度为O(log(t))。* * @param key - 给定的键值* @return - 查找结果*/public SearchResult searchKey(Integer key){int l = 0;int h = keys.size() - 1;int mid = 0;while(l <= h){mid = (l + h) / 2; // 先这么写吧,BTree实现中,l+h不可能溢出if(keys.get(mid) == key)break;else if(keys.get(mid) > key)h = mid - 1;else // if(keys.get(mid) < key)l = mid + 1;}boolean result = false;int index = 0;if(l <= h) // 说明查找成功{result = true;index = mid; // index表示元素所在的位置}else{result = false;index = l; // index表示元素应该插入的位置}return new SearchResult(result, index);}/*** 将给定的<code>key</code>追加到节点的末尾,* 一定要确保调用该方法之后,节点中的关键字还是* 以非降序存放。* * @param key - 给定的键值*/public void addKey(Integer key){keys.add(key);}/*** 删除给定索引的键值。* <p/>* 你需要自己保证给定的索引是合法的。* * @param index - 给定的索引*/public void removeKey(int index){keys.remove(index);}/*** 得到节点中给定索引的键值。* <p/>* 你需要自己保证给定的索引是合法的。* * @param index - 给定的索引* @return 节点中给定索引的键值*/public Integer keyAt(int index){return keys.get(index);}/*** 在该节点中插入给定的<code>key</code>,* 该方法保证插入之后,其键值还是以非降序存放。* <p/>* 不过该方法的时间复杂度为O(t)。* <p/>* TODO 需要考虑键值是否可以重复。* * @param key - 给定的键值*/public void insertKey(Integer key){SearchResult result = searchKey(key);insertKey(key, result.getIndex());}/*** 在该节点中给定索引的位置插入给定的<code>key</code>,* 你需要自己保证<code>key</code>插入了正确的位置。* * @param key - 给定的键值* @param index - 给定的索引*/public void insertKey(Integer key, int index){/* TODO* 通过新建一个ArrayList来实现插入真的很恶心,先这样吧* 要是有类似C中的reallocate就好了。*/List<Integer> newKeys = new ArrayList<Integer>();int i = 0;// index = 0或者index = keys.size()都没有问题for(; i < index; ++ i)newKeys.add(keys.get(i));newKeys.add(key);for(; i < keys.size(); ++ i)newKeys.add(keys.get(i));keys = newKeys;}/*** 返回节点中给定索引的子节点。* <p/>* 你需要自己保证给定的索引是合法的。* * @param index - 给定的索引* @return 给定索引对应的子节点*/public BTreeNode childAt(int index){if(isLeaf())throw new UnsupportedOperationException("Leaf node doesn't have children.");return children.get(index);}/*** 将给定的子节点追加到该节点的末尾。* * @param child - 给定的子节点*/public void addChild(BTreeNode child){children.add(child);}/*** 删除该节点中给定索引位置的子节点。* </p>* 你需要自己保证给定的索引是合法的。* * @param index - 给定的索引*/public void removeChild(int index){children.remove(index);}/*** 将给定的子节点插入到该节点中给定索引* 的位置。* * @param child - 给定的子节点* @param index - 子节点带插入的位置*/public void insertChild(BTreeNode child, int index){List<BTreeNode> newChildren = new ArrayList<BTreeNode>();int i = 0;for(; i < index; ++ i)newChildren.add(children.get(i));newChildren.add(child);for(; i < children.size(); ++ i)newChildren.add(children.get(i));children = newChildren;}}private static final int DEFAULT_T = 2;/** B树的根节点 */private BTreeNode root;/** 根据B树的定义,B树的每个非根节点的关键字数n满足(t - 1) <= n <= (2t - 1) */private int t = DEFAULT_T;/** 非根节点中最小的键值数 */private int minKeySize = t - 1;/** 非根节点中最大的键值数 */private int maxKeySize = 2*t - 1;public BTree(){root = new BTreeNode();root.setLeaf(true);}public BTree(int t){this();this.t = t;minKeySize = t - 1;maxKeySize = 2*t - 1;}/*** 搜索给定的<code>key</code>。* <p/>* TODO 需要重新定义返回结果,应该返回* <code>key</code>对应的值。* * @param key - 给定的键值* @return TODO 得返回值类型*/public int search(Integer key){return search(root, key);}/*** 在以给定节点为根的子树中,递归搜索* 给定的<code>key</code>* * @param node - 子树的根节点* @param key - 给定的键值* @return TODO*/private static int search(BTreeNode node, Integer key){SearchResult result = node.searchKey(key);if(result.getResult())return result.getIndex();else{if(node.isLeaf())return -1;else search(node.childAt(result.getIndex()), key);}return -1;}/*** 分裂一个满子节点<code>childNode</code>。* <p/>* 你需要自己保证给定的子节点是满节点。* * @param parentNode - 父节点* @param childNode - 满子节点* @param index - 满子节点在父节点中的索引*/private void splitNode(BTreeNode parentNode, BTreeNode childNode, int index){assert childNode.size() == maxKeySize;BTreeNode siblingNode = new BTreeNode();siblingNode.setLeaf(childNode.isLeaf());// 将满子节点中索引为[t, 2t - 2]的(t - 1)个关键字插入新的节点中for(int i = 0; i < minKeySize; ++ i)siblingNode.addKey(childNode.keyAt(t + i));// 提取满子节点中的中间关键字,其索引为(t - 1)Integer key = childNode.keyAt(t - 1);// 删除满子节点中索引为[t - 1, 2t - 2]的t个关键字for(int i = maxKeySize - 1; i >= t - 1; -- i)childNode.removeKey(i);if(!childNode.isLeaf()) // 如果满子节点不是叶节点,则还需要处理其子节点{// 将满子节点中索引为[t, 2t - 1]的t个子节点插入新的节点中for(int i = 0; i < minKeySize + 1; ++ i)siblingNode.addChild(childNode.childAt(t + i));// 删除满子节点中索引为[t, 2t - 1]的t个子节点for(int i = maxKeySize; i >= t; -- i)childNode.removeChild(i);}// 将key插入父节点parentNode.insertKey(key, index);// 将新节点插入父节点parentNode.insertChild(siblingNode, index + 1);}/*** 在一个非满节点中插入给定的<code>key</code>。* * @param node - 非满节点* @param key - 给定的键值*/private void insertNotFull(BTreeNode node, Integer key){assert node.size() < maxKeySize;if(node.isLeaf()) // 如果是叶子节点,直接插入node.insertKey(key);else{/* 找到key在给定节点应该插入的位置,那么key应该插入* 该位置对应的子树中*/SearchResult result = node.searchKey(key);BTreeNode childNode = node.childAt(result.getIndex());if(childNode.size() == 2*t - 1) // 如果子节点是满节点{// 则先分裂splitNode(node, childNode, result.getIndex());/* 如果给定的key大于分裂之后新生成的键值,则需要插入该新键值的右边,* 否则左边。*/if(key > node.keyAt(result.getIndex()))childNode = node.childAt(result.getIndex() + 1);}insertNotFull(childNode, key);}}/*** 在B树中插入给定的<code>key</code>。* * @param key - 给定的键值*/public void insert(Integer key){if(root.size() == maxKeySize) // 如果根节点满了,则B树长高{BTreeNode newRoot = new BTreeNode();newRoot.setLeaf(false);newRoot.addChild(root);splitNode(newRoot, root, 0);root = newRoot;}insertNotFull(root, key);}/*** 从B树中删除一个给定的<code>key</code>。* * @param key - 给定的键值*/public void delete(Integer key){// root的情况还需要做一些特殊处理delete(root, key);}/*** 从以给定<code>node</code>为根的子树中删除指定的<code>key</code>。* <p/>* 删除的实现思想请参考《算法导论》第二版的第18章。* <p/>* TODO 需要重构,代码太长了* * @param node - 给定的节点* @param key - 给定的键值*/public void delete(BTreeNode node, Integer key){// 该过程需要保证,对非根节点执行删除操作时,其关键字个数至少为t。assert node.size() >= t || node == root;SearchResult result = node.searchKey(key);/** 因为这是查找成功的情况,0 <= result.getIndex() <= (node.size() - 1),* 因此(result.getIndex() + 1)不会溢出。*/if(result.getResult()){// 1.如果关键字在节点node中,并且是叶节点,则直接删除。if(node.isLeaf())node.removeKey(result.getIndex());else{// 2.a 如果节点node中前于key的子节点包含至少t个关键字BTreeNode leftChildNode = node.childAt(result.getIndex());if(leftChildNode.size() >= t){// 使用leftChildNode中的最后一个键值代替node中的keynode.removeKey(result.getIndex());node.insertKey(leftChildNode.keyAt(leftChildNode.size() - 1), result.getIndex());delete(leftChildNode, leftChildNode.keyAt(leftChildNode.size() - 1));// node.}else{// 2.b 如果节点node中后于key的子节点包含至少t个关键字BTreeNode rightChildNode = node.childAt(result.getIndex() + 1);if(rightChildNode.size() >= t){// 使用rightChildNode中的第一个键值代替node中的keynode.removeKey(result.getIndex());node.insertKey(rightChildNode.keyAt(0), result.getIndex());delete(rightChildNode, rightChildNode.keyAt(0));}else // 2.c 前于key和后于key的子节点都只包含t-1个关键字{node.removeKey(result.getIndex());node.removeChild(result.getIndex() + 1);// 将key和rightChildNode中的键值合并进leftChildNodeleftChildNode.addKey(key);for(int i = 0; i < rightChildNode.size(); ++ i)leftChildNode.addKey(rightChildNode.keyAt(i));// 将rightChildNode中的子节点合并进leftChildNode,如果有的话if(!rightChildNode.isLeaf()){for(int i = 0; i <= rightChildNode.size(); ++ i)leftChildNode.addChild(rightChildNode.childAt(i));}delete(leftChildNode, key);}}}}else{/** 因为这是查找失败的情况,0 <= result.getIndex() <= node.size(),* 因此(result.getIndex() + 1)会溢出。*/if(node.isLeaf()) // 如果关键字不在节点node中,并且是叶节点,则什么都不做,因为该关键字不在该B树中{logger.info("The key: " + key + " isn't in this BTree.");return;}BTreeNode childNode = node.childAt(result.getIndex());if(childNode.size() >= t)delete(childNode, key); // 递归删除else // 3{// 先查找右边的兄弟节点BTreeNode siblingNode = null;int siblingIndex = -1;if(result.getIndex() < node.size()) // 存在右兄弟节点{if(node.childAt(result.getIndex() + 1).size() >= t){siblingNode = node.childAt(result.getIndex() + 1);siblingIndex = result.getIndex() + 1;}}// 如果右边的兄弟节点不符合条件,则试试左边的兄弟节点if(siblingNode == null){if(result.getIndex() > 0) // 存在左兄弟节点{if(node.childAt(result.getIndex() - 1).size() >= t){siblingNode = node.childAt(result.getIndex() - 1);siblingIndex = result.getIndex() - 1;}}}// 3.a 有一个相邻兄弟节点至少包含t个关键字if(siblingNode != null){if(siblingIndex < result.getIndex()) // 左兄弟节点满足条件{childNode.insertKey(node.keyAt(siblingIndex), 0);node.removeKey(siblingIndex);node.insertKey(siblingNode.keyAt(siblingNode.size() - 1), siblingIndex);siblingNode.removeKey(siblingNode.size() - 1);// 将左兄弟节点的最后一个孩子移到childNodeif(!siblingNode.isLeaf()){childNode.insertChild(siblingNode.childAt(siblingNode.size()), 0);siblingNode.removeChild(siblingNode.size());}}else // 右兄弟节点满足条件{childNode.insertKey(node.keyAt(result.getIndex()), childNode.size() - 1);node.removeKey(result.getIndex());node.insertKey(siblingNode.keyAt(0), result.getIndex());siblingNode.removeKey(0);// 将右兄弟节点的第一个孩子移到childNode// childNode.insertChild(siblingNode.childAt(0), childNode.size() + 1);if(!siblingNode.isLeaf()){childNode.addChild(siblingNode.childAt(0));siblingNode.removeChild(0);}}delete(childNode, key);}else // 3.b 如果其相邻左右节点都包含t-1个关键字{if(result.getIndex() < node.size()) // 存在右兄弟{BTreeNode rightSiblingNode = node.childAt(result.getIndex() + 1);childNode.addKey(node.keyAt(result.getIndex()));node.removeKey(result.getIndex());node.removeChild(result.getIndex() + 1);for(int i = 0; i < rightSiblingNode.size(); ++ i)childNode.addKey(rightSiblingNode.keyAt(i));if(!rightSiblingNode.isLeaf()){for(int i = 0; i <= rightSiblingNode.size(); ++ i)childNode.addChild(rightSiblingNode.childAt(i));}}else // 存在左节点{BTreeNode leftSiblingNode = node.childAt(result.getIndex() - 1);childNode.addKey(node.keyAt(result.getIndex() - 1));node.removeKey(result.getIndex() - 1);node.removeChild(result.getIndex() - 1);for(int i = leftSiblingNode.size() - 1; i >= 0; -- i)childNode.insertKey(leftSiblingNode.keyAt(i), 0);if(!leftSiblingNode.isLeaf()){for(int i = leftSiblingNode.size(); i >= 0; -- i)childNode.insertChild(leftSiblingNode.childAt(i), 0);}}// 如果node是root并且node不包含任何关键字了if(node == root && node.size() == 0)root = childNode;delete(childNode, key);}}}}/*** 一个简单的层次遍历B树实现,用于输出B树。* <p/>* TODO 待改进,使显示更加形象化。*/public void output(){Queue<BTreeNode> queue = new LinkedList<BTreeNode>();queue.offer(root);while(!queue.isEmpty()){BTreeNode node = queue.poll();for(int i = 0; i < node.size(); ++ i)System.out.print(node.keyAt(i) + " ");System.out.println();if(!node.isLeaf()){for(int i = 0; i <= node.size(); ++ i)queue.offer(node.childAt(i));}}}public static void main(String[] args){Random random = new Random();BTree<Integer, Byte[]> btree = new BTree<Integer, Byte[]>();for(int i = 0; i < 10; ++ i){int r = random.nextInt(100);System.out.println(r);btree.insert(r);}System.out.println("----------------------");btree.output();}
}

转自:http://blog.csdn.net/wangpingfang/article/details/7426943

B+树非叶子结点只存储键值,单个节点占空间小,索引块能够存储更多的节点,从磁盘读索引时所需的索引块更少,所以索引查找时I/O次数较B-Tree索引少,效率更高。而且B+Tree在叶子节点存放的记录以链表的形式链接,范围查找或遍历效率更高。Mysql InnoDB用的就是B+Tree索引。

其他java实现balance tree

package debuggees;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.NoSuchElementException;/*** jBixbe debuggee: test insert and delete operation of a balanced tree data* structure. Using integer values read from keyboard as tree elements.* * @author ds-emedia*/
public class BTree<T extends Comparable<T>> {private static BTree<Integer> tree = new BTree<Integer>();private static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));public static void main(String args[]) throws IOException {System.out.println("test balanced tree operations");System.out.println("*****************************");String input;Integer value;do {input = stringInput("please select: [i]nsert, [d]elete, [e]xit");switch (input.charAt(0)) {case 'i':value = Integer.parseInt(stringInput("insert: "), 10);if (tree.isMember(value)) {System.out.println("value " + value + " already in tree");} else {tree.insert(value);}break;case 'd':value = Integer.parseInt(stringInput("delete: "), 10);if (tree.isMember(value)) {tree.delete(value);} else {System.out.println(value + " not found in tree");}break;}} while ((input.charAt(0) != 'e'));}private static String stringInput(String inputRequest) throws IOException {System.out.println(inputRequest);return reader.readLine();}/* +++++++++++ instance declarations +++++++++++ */private Node root;/*** Creates an empty balanced tree.*/public BTree() {root = null;}/*** Creates a balances tree using the given node as tree root.*/public BTree(Node root) {this.root = root;}/*** Inserts an element into the tree.*/public void insert(T info) {insert(info, root, null, false);}/*** Checks whether the given element is already in the tree.*/public boolean isMember(T info) {return isMember(info, root);}/*** Removes an elememt from the tree.*/public void delete(T info) {delete(info, root);}/*** Returns a text representation of the tree.*/public String toString() {return inOrder();}/*** Returns all elements of the tree in in-order traversing.*/public String inOrder() {return inOrder(root);}/*** Returns all elements of the tree in pre-order traversing.*/public String preOrder() {return preOrder(root);}/*** Returns all elements of the tree in post-order traversing.*/public String postOrder() {return postOrder(root);}/*** Returns the height of the tree.*/public int getHeight() {return getHeight(root);}private void insert(T info, Node node, Node parent, boolean right) {if (node == null) {if (parent == null) {root = node = new Node(info, parent);} else if (right) {parent.right = node = new Node(info, parent);} else {parent.left = node = new Node(info, parent);}restructInsert(node, false);} else if (info.compareTo(node.information) == 0) {node.information = info;} else if (info.compareTo(node.information) > 0) {insert(info, node.right, node, true);} else {insert(info, node.left, node, false);}}private boolean isMember(T info, Node node) {boolean member = false;if (node == null) {member = false;} else if (info.compareTo(node.information) == 0) {member = true;} else if (info.compareTo(node.information) > 0) {member = isMember(info, node.right);} else {member = isMember(info, node.left);}return member;}private void delete(T info, Node node) throws NoSuchElementException {if (node == null) {throw new NoSuchElementException();} else if (info.compareTo(node.information) == 0) {deleteNode(node);} else if (info.compareTo(node.information) > 0) {delete(info, node.right);} else {delete(info, node.left);}}private void deleteNode(Node node) {Node eNode, minMaxNode, delNode = null;boolean rightNode = false;if (node.isLeaf()) {if (node.parent == null) {root = null;} else if (node.isRightNode()) {node.parent.right = null;rightNode = true;} else if (node.isLeftNode()) {node.parent.left = null;}delNode = node;} else if (node.hasLeftNode()) {minMaxNode = node.left;for (eNode = node.left; eNode != null; eNode = eNode.right) {minMaxNode = eNode;}delNode = minMaxNode;node.information = minMaxNode.information;if (node.left.right != null) {minMaxNode.parent.right = minMaxNode.left;rightNode = true;} else {minMaxNode.parent.left = minMaxNode.left;}if (minMaxNode.left != null) {minMaxNode.left.parent = minMaxNode.parent;}} else if (node.hasRightNode()) {minMaxNode = node.right;delNode = minMaxNode;rightNode = true;node.information = minMaxNode.information;node.right = minMaxNode.right;if (node.right != null) {node.right.parent = node;}node.left = minMaxNode.left;if (node.left != null) {node.left.parent = node;}}restructDelete(delNode.parent, rightNode);}private int getHeight(Node node) {int height = 0;if (node == null) {height = -1;} else {height = 1 + Math.max(getHeight(node.left), getHeight(node.right));}return height;}private String inOrder(Node node) {String result = "";if (node != null) {result = result + inOrder(node.left) + " ";result = result + node.information.toString();result = result + inOrder(node.right);}return result;}private String preOrder(Node node) {String result = "";if (node != null) {result = result + node.information.toString() + " ";result = result + preOrder(node.left);result = result + preOrder(node.right);}return result;}private String postOrder(Node node) {String result = "";if (node != null) {result = result + postOrder(node.left);result = result + postOrder(node.right);result = result + node.information.toString() + " ";}return result;}private void restructInsert(Node node, boolean wasRight) {if (node != root) {if (node.parent.balance == '_') {if (node.isLeftNode()) {node.parent.balance = '/';restructInsert(node.parent, false);} else {node.parent.balance = '\\';restructInsert(node.parent, true);}} else if (node.parent.balance == '/') {if (node.isRightNode()) {node.parent.balance = '_';} else {if (!wasRight) {rotateRight(node.parent);} else {doubleRotateRight(node.parent);}}} else if (node.parent.balance == '\\') {if (node.isLeftNode()) {node.parent.balance = '_';} else {if (wasRight) {rotateLeft(node.parent);} else {doubleRotateLeft(node.parent);}}}}}private void restructDelete(Node z, boolean wasRight) {Node parent;boolean isRight = false;boolean climb = false;boolean canClimb;if (z == null) {return;}parent = z.parent;canClimb = (parent != null);if (canClimb) {isRight = z.isRightNode();}if (z.balance == '_') {if (wasRight) {z.balance = '/';} else {z.balance = '\\';}} else if (z.balance == '/') {if (wasRight) {if (z.left.balance == '\\') {doubleRotateRight(z);climb = true;} else {rotateRight(z);if (z.balance == '_') {climb = true;}}} else {z.balance = '_';climb = true;}} else {if (wasRight) {z.balance = '_';climb = true;} else {if (z.right.balance == '/') {doubleRotateLeft(z);climb = true;} else {rotateLeft(z);if (z.balance == '_') {climb = true;}}}}if (canClimb && climb) {restructDelete(parent, isRight);}}private void rotateLeft(Node a) {Node b = a.right;if (a.parent == null) {root = b;} else {if (a.isLeftNode()) {a.parent.left = b;} else {a.parent.right = b;}}a.right = b.left;if (a.right != null) {a.right.parent = a;}b.parent = a.parent;a.parent = b;b.left = a;if (b.balance == '_') {a.balance = '\\';b.balance = '/';} else {a.balance = '_';b.balance = '_';}}private void rotateRight(Node a) {Node b = a.left;if (a.parent == null) {root = b;} else {if (a.isLeftNode()) {a.parent.left = b;} else {a.parent.right = b;}}a.left = b.right;if (a.left != null) {a.left.parent = a;}b.parent = a.parent;a.parent = b;b.right = a;if (b.balance == '_') {a.balance = '/';b.balance = '\\';} else {a.balance = '_';b.balance = '_';}}private void doubleRotateLeft(Node a) {Node b = a.right;Node c = b.left;if (a.parent == null) {root = c;} else {if (a.isLeftNode()) {a.parent.left = c;} else {a.parent.right = c;}}c.parent = a.parent;a.right = c.left;if (a.right != null) {a.right.parent = a;}b.left = c.right;if (b.left != null) {b.left.parent = b;}c.left = a;c.right = b;a.parent = c;b.parent = c;if (c.balance == '/') {a.balance = '_';b.balance = '\\';} else if (c.balance == '\\') {a.balance = '/';b.balance = '_';} else {a.balance = '_';b.balance = '_';}c.balance = '_';}private void doubleRotateRight(Node a) {Node b = a.left;Node c = b.right;if (a.parent == null) {root = c;} else {if (a.isLeftNode()) {a.parent.left = c;} else {a.parent.right = c;}}c.parent = a.parent;a.left = c.right;if (a.left != null) {a.left.parent = a;}b.right = c.left;if (b.right != null) {b.right.parent = b;}c.right = a;c.left = b;a.parent = c;b.parent = c;if (c.balance == '/') {b.balance = '_';a.balance = '\\';} else if (c.balance == '\\') {b.balance = '/';a.balance = '_';} else {b.balance = '_';a.balance = '_';}c.balance = '_';}class Node {T information;Node parent;Node left;Node right;char balance;public Node(T information, Node parent) {this.information = information;this.parent = parent;this.left = null;this.right = null;this.balance = '_';}boolean isLeaf() {return ((left == null) && (right == null));}boolean isNode() {return !isLeaf();}boolean hasLeftNode() {return (null != left);}boolean hasRightNode() {return (right != null);}boolean isLeftNode() {return (parent.left == this);}boolean isRightNode() {return (parent.right == this);}}
}

B树和B+树
这两种处理索引的数据结构的不同之处:
1.B树中同一键值不会出现多次,并且它有可能出现在叶结点,也有可能出现在非叶结点中.而B+树的键一定会出现在叶结点中,并且有可能在非叶结点中也有可能重复出现,以维持B+树的平衡.
2.因为B树键位置不定,且在整个树结构中只出现一次,虽然可以节省存储空间,但使得在插入、删除操作复杂度明显增加.B+树相比来说是一种较好的折中.
3.B树的查询效率与键在树中的位置有关,最大时间复杂度与B+树相同(在叶结点的时候),最小时间复杂度为1(在根结点的时候).而B+树的时候复杂度对某建成的树是固定的.

B+树的定义:

1.任意非叶子结点最多有M个子节点;且M>2;

2.除根结点以外的非叶子结点至少有 M/2个子节点;

3.根结点至少有2个子节点;

4.除根节点外每个结点存放至少M/2和至多M个关键字;(至少2个关键字)

5.非叶子结点的子树指针与关键字个数相同;

6.所有结点的关键字:K[1], K[2], …, K[M];且K[i] < K[i+1];

7.非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树;

8.所有叶子结点位于同一层;

5.为所有叶子结点增加一个链指针;

6.所有关键字都在叶子结点出现;

可以参考 http://blog.csdn.net/manesking/archive/2007/02/09/1505979.aspx
Java实现:
接口:
Java代码  
  1. package com.meidusa.test;
  2. public interface B {
  3. public Object get(Comparable key);   //查询
  4. public void remove(Comparable key);    //移除
  5. public void insertOrUpdate(Comparable key, Object obj); //插入或者更新,如果已经存在,就更新,否则插入
  6. }
[java] view plaincopy
  1. package com.meidusa.test;
  2. public interface B {
  3. public Object get(Comparable key);   //查询
  4. public void remove(Comparable key);    //移除
  5. public void insertOrUpdate(Comparable key, Object obj); //插入或者更新,如果已经存在,就更新,否则插入
  6. }
B+树:
Java代码  
  1. package com.meidusa.test;
  2. import java.util.Random;
  3. public class BplusTree implements B {
  4. /** 根节点 */
  5. protected Node root;
  6. /** 阶数,M值 */
  7. protected int order;
  8. /** 叶子节点的链表头*/
  9. protected Node head;
  10. public Node getHead() {
  11. return head;
  12. }
  13. public void setHead(Node head) {
  14. this.head = head;
  15. }
  16. public Node getRoot() {
  17. return root;
  18. }
  19. public void setRoot(Node root) {
  20. this.root = root;
  21. }
  22. public int getOrder() {
  23. return order;
  24. }
  25. public void setOrder(int order) {
  26. this.order = order;
  27. }
  28. @Override
  29. public Object get(Comparable key) {
  30. return root.get(key);
  31. }
  32. @Override
  33. public void remove(Comparable key) {
  34. root.remove(key, this);
  35. }
  36. @Override
  37. public void insertOrUpdate(Comparable key, Object obj) {
  38. root.insertOrUpdate(key, obj, this);
  39. }
  40. public BplusTree(int order){
  41. if (order < 3) {
  42. System.out.print("order must be greater than 2");
  43. System.exit(0);
  44. }
  45. this.order = order;
  46. root = new Node(true, true);
  47. head = root;
  48. }
  49. //测试
  50. public static void main(String[] args) {
  51. BplusTree tree = new BplusTree(6);
  52. Random random = new Random();
  53. long current = System.currentTimeMillis();
  54. for (int j = 0; j < 100000; j++) {
  55. for (int i = 0; i < 100; i++) {
  56. int randomNumber = random.nextInt(1000);
  57. tree.insertOrUpdate(randomNumber, randomNumber);
  58. }
  59. for (int i = 0; i < 100; i++) {
  60. int randomNumber = random.nextInt(1000);
  61. tree.remove(randomNumber);
  62. }
  63. }
  64. long duration = System.currentTimeMillis() - current;
  65. System.out.println("time elpsed for duration: " + duration);
  66. int search = 80;
  67. System.out.print(tree.get(search));
  68. }
  69. }
[java] view plaincopy
  1. package com.meidusa.test;
  2. import java.util.Random;
  3. public class BplusTree implements B {
  4. /** 根节点 */
  5. protected Node root;
  6. /** 阶数,M值 */
  7. protected int order;
  8. /** 叶子节点的链表头*/
  9. protected Node head;
  10. public Node getHead() {
  11. return head;
  12. }
  13. public void setHead(Node head) {
  14. this.head = head;
  15. }
  16. public Node getRoot() {
  17. return root;
  18. }
  19. public void setRoot(Node root) {
  20. this.root = root;
  21. }
  22. public int getOrder() {
  23. return order;
  24. }
  25. public void setOrder(int order) {
  26. this.order = order;
  27. }
  28. @Override
  29. public Object get(Comparable key) {
  30. return root.get(key);
  31. }
  32. @Override
  33. public void remove(Comparable key) {
  34. root.remove(key, this);
  35. }
  36. @Override
  37. public void insertOrUpdate(Comparable key, Object obj) {
  38. root.insertOrUpdate(key, obj, this);
  39. }
  40. public BplusTree(int order){
  41. if (order < 3) {
  42. System.out.print("order must be greater than 2");
  43. System.exit(0);
  44. }
  45. this.order = order;
  46. root = new Node(true, true);
  47. head = root;
  48. }
  49. //测试
  50. public static void main(String[] args) {
  51. BplusTree tree = new BplusTree(6);
  52. Random random = new Random();
  53. long current = System.currentTimeMillis();
  54. for (int j = 0; j < 100000; j++) {
  55. for (int i = 0; i < 100; i++) {
  56. int randomNumber = random.nextInt(1000);
  57. tree.insertOrUpdate(randomNumber, randomNumber);
  58. }
  59. for (int i = 0; i < 100; i++) {
  60. int randomNumber = random.nextInt(1000);
  61. tree.remove(randomNumber);
  62. }
  63. }
  64. long duration = System.currentTimeMillis() - current;
  65. System.out.println("time elpsed for duration: " + duration);
  66. int search = 80;
  67. System.out.print(tree.get(search));
  68. }
  69. }
节点:
Java代码  
  1. package com.meidusa.test;
  2. import java.util.AbstractMap.SimpleEntry;
  3. import java.util.ArrayList;
  4. import java.util.List;
  5. import java.util.Map.Entry;
  6. public class Node {
  7. /** 是否为叶子节点 */
  8. protected boolean isLeaf;
  9. /** 是否为根节点*/
  10. protected boolean isRoot;
  11. /** 父节点 */
  12. protected Node parent;
  13. /** 叶节点的前节点*/
  14. protected Node previous;
  15. /** 叶节点的后节点*/
  16. protected Node next;
  17. /** 节点的关键字 */
  18. protected List<Entry<Comparable, Object>> entries;
  19. /** 子节点 */
  20. protected List<Node> children;
  21. public Node(boolean isLeaf) {
  22. this.isLeaf = isLeaf;
  23. entries = new ArrayList<Entry<Comparable, Object>>();
  24. if (!isLeaf) {
  25. children = new ArrayList<Node>();
  26. }
  27. }
  28. public Node(boolean isLeaf, boolean isRoot) {
  29. this(isLeaf);
  30. this.isRoot = isRoot;
  31. }
  32. public Object get(Comparable key) {
  33. //如果是叶子节点
  34. if (isLeaf) {
  35. for (Entry<Comparable, Object> entry : entries) {
  36. if (entry.getKey().compareTo(key) == 0) {
  37. //返回找到的对象
  38. return entry.getValue();
  39. }
  40. }
  41. //未找到所要查询的对象
  42. return null;
  43. //如果不是叶子节点
  44. }else {
  45. //如果key小于等于节点最左边的key,沿第一个子节点继续搜索
  46. if (key.compareTo(entries.get(0).getKey()) <= 0) {
  47. return children.get(0).get(key);
  48. //如果key大于节点最右边的key,沿最后一个子节点继续搜索
  49. }else if (key.compareTo(entries.get(entries.size()-1).getKey()) >= 0) {
  50. return children.get(children.size()-1).get(key);
  51. //否则沿比key大的前一个子节点继续搜索
  52. }else {
  53. for (int i = 0; i < entries.size(); i++) {
  54. if (entries.get(i).getKey().compareTo(key) <= 0 && entries.get(i+1).getKey().compareTo(key) > 0) {
  55. return children.get(i).get(key);
  56. }
  57. }
  58. }
  59. }
  60. return null;
  61. }
  62. public void insertOrUpdate(Comparable key, Object obj, BplusTree tree){
  63. //如果是叶子节点
  64. if (isLeaf){
  65. //不需要分裂,直接插入或更新
  66. if (contains(key) || entries.size() < tree.getOrder()){
  67. insertOrUpdate(key, obj);
  68. if (parent != null) {
  69. //更新父节点
  70. parent.updateInsert(tree);
  71. }
  72. //需要分裂
  73. }else {
  74. //分裂成左右两个节点
  75. Node left = new Node(true);
  76. Node right = new Node(true);
  77. //设置链接
  78. if (previous != null){
  79. previous.setNext(left);
  80. left.setPrevious(previous);
  81. }
  82. if (next != null) {
  83. next.setPrevious(right);
  84. right.setNext(next);
  85. }
  86. if (previous == null){
  87. tree.setHead(left);
  88. }
  89. left.setNext(right);
  90. right.setPrevious(left);
  91. previous = null;
  92. next = null;
  93. //左右两个节点关键字长度
  94. int leftSize = (tree.getOrder() + 1) / 2 + (tree.getOrder() + 1) % 2;
  95. int rightSize = (tree.getOrder() + 1) / 2;
  96. //复制原节点关键字到分裂出来的新节点
  97. insertOrUpdate(key, obj);
  98. for (int i = 0; i < leftSize; i++){
  99. left.getEntries().add(entries.get(i));
  100. }
  101. for (int i = 0; i < rightSize; i++){
  102. right.getEntries().add(entries.get(leftSize + i));
  103. }
  104. //如果不是根节点
  105. if (parent != null) {
  106. //调整父子节点关系
  107. int index = parent.getChildren().indexOf(this);
  108. parent.getChildren().remove(this);
  109. left.setParent(parent);
  110. right.setParent(parent);
  111. parent.getChildren().add(index,left);
  112. parent.getChildren().add(index + 1, right);
  113. setEntries(null);
  114. setChildren(null);
  115. //父节点插入或更新关键字
  116. parent.updateInsert(tree);
  117. setParent(null);
  118. //如果是根节点
  119. }else {
  120. isRoot = false;
  121. Node parent = new Node(false, true);
  122. tree.setRoot(parent);
  123. left.setParent(parent);
  124. right.setParent(parent);
  125. parent.getChildren().add(left);
  126. parent.getChildren().add(right);
  127. setEntries(null);
  128. setChildren(null);
  129. //更新根节点
  130. parent.updateInsert(tree);
  131. }
  132. }
  133. //如果不是叶子节点
  134. }else {
  135. //如果key小于等于节点最左边的key,沿第一个子节点继续搜索
  136. if (key.compareTo(entries.get(0).getKey()) <= 0) {
  137. children.get(0).insertOrUpdate(key, obj, tree);
  138. //如果key大于节点最右边的key,沿最后一个子节点继续搜索
  139. }else if (key.compareTo(entries.get(entries.size()-1).getKey()) >= 0) {
  140. children.get(children.size()-1).insertOrUpdate(key, obj, tree);
  141. //否则沿比key大的前一个子节点继续搜索
  142. }else {
  143. for (int i = 0; i < entries.size(); i++) {
  144. if (entries.get(i).getKey().compareTo(key) <= 0 && entries.get(i+1).getKey().compareTo(key) > 0) {
  145. children.get(i).insertOrUpdate(key, obj, tree);
  146. break;
  147. }
  148. }
  149. }
  150. }
  151. }
  152. /** 插入节点后中间节点的更新 */
  153. protected void updateInsert(BplusTree tree){
  154. validate(this, tree);
  155. //如果子节点数超出阶数,则需要分裂该节点
  156. if (children.size() > tree.getOrder()) {
  157. //分裂成左右两个节点
  158. Node left = new Node(false);
  159. Node right = new Node(false);
  160. //左右两个节点关键字长度
  161. int leftSize = (tree.getOrder() + 1) / 2 + (tree.getOrder() + 1) % 2;
  162. int rightSize = (tree.getOrder() + 1) / 2;
  163. //复制子节点到分裂出来的新节点,并更新关键字
  164. for (int i = 0; i < leftSize; i++){
  165. left.getChildren().add(children.get(i));
  166. left.getEntries().add(newSimpleEntry(children.get(i).getEntries().get(0).getKey(), null));
  167. children.get(i).setParent(left);
  168. }
  169. for (int i = 0; i < rightSize; i++){
  170. right.getChildren().add(children.get(leftSize + i));
  171. right.getEntries().add(new SimpleEntry(children.get(leftSize + i).getEntries().get(0).getKey(), null));
  172. children.get(leftSize + i).setParent(right);
  173. }
  174. //如果不是根节点
  175. if (parent != null) {
  176. //调整父子节点关系
  177. int index = parent.getChildren().indexOf(this);
  178. parent.getChildren().remove(this);
  179. left.setParent(parent);
  180. right.setParent(parent);
  181. parent.getChildren().add(index,left);
  182. parent.getChildren().add(index + 1, right);
  183. setEntries(null);
  184. setChildren(null);
  185. //父节点更新关键字
  186. parent.updateInsert(tree);
  187. setParent(null);
  188. //如果是根节点
  189. }else {
  190. isRoot = false;
  191. Node parent = new Node(false, true);
  192. tree.setRoot(parent);
  193. left.setParent(parent);
  194. right.setParent(parent);
  195. parent.getChildren().add(left);
  196. parent.getChildren().add(right);
  197. setEntries(null);
  198. setChildren(null);
  199. //更新根节点
  200. parent.updateInsert(tree);
  201. }
  202. }
  203. }
  204. /** 调整节点关键字*/
  205. protected static void validate(Node node, BplusTree tree) {
  206. // 如果关键字个数与子节点个数相同
  207. if (node.getEntries().size() == node.getChildren().size()) {
  208. for (int i = 0; i < node.getEntries().size(); i++) {
  209. Comparable key = node.getChildren().get(i).getEntries().get(0).getKey();
  210. if (node.getEntries().get(i).getKey().compareTo(key) != 0) {
  211. node.getEntries().remove(i);
  212. node.getEntries().add(i, new SimpleEntry(key, null));
  213. if(!node.isRoot()){
  214. validate(node.getParent(), tree);
  215. }
  216. }
  217. }
  218. // 如果子节点数不等于关键字个数但仍大于M / 2并且小于M,并且大于2
  219. } else if (node.isRoot() && node.getChildren().size() >= 2
  220. ||node.getChildren().size() >= tree.getOrder() / 2
  221. && node.getChildren().size() <= tree.getOrder()
  222. && node.getChildren().size() >= 2) {
  223. node.getEntries().clear();
  224. for (int i = 0; i < node.getChildren().size(); i++) {
  225. Comparable key = node.getChildren().get(i).getEntries().get(0).getKey();
  226. node.getEntries().add(new SimpleEntry(key, null));
  227. if (!node.isRoot()) {
  228. validate(node.getParent(), tree);
  229. }
  230. }
  231. }
  232. }
  233. /** 删除节点后中间节点的更新*/
  234. protected void updateRemove(BplusTree tree) {
  235. validate(this, tree);
  236. // 如果子节点数小于M / 2或者小于2,则需要合并节点
  237. if (children.size() < tree.getOrder() / 2 || children.size() < 2) {
  238. if (isRoot) {
  239. // 如果是根节点并且子节点数大于等于2,OK
  240. if (children.size() >= 2) {
  241. return;
  242. // 否则与子节点合并
  243. } else {
  244. Node root = children.get(0);
  245. tree.setRoot(root);
  246. root.setParent(null);
  247. root.setRoot(true);
  248. setEntries(null);
  249. setChildren(null);
  250. }
  251. } else {
  252. //计算前后节点
  253. int currIdx = parent.getChildren().indexOf(this);
  254. int prevIdx = currIdx - 1;
  255. int nextIdx = currIdx + 1;
  256. Node previous = null, next = null;
  257. if (prevIdx >= 0) {
  258. previous = parent.getChildren().get(prevIdx);
  259. }
  260. if (nextIdx < parent.getChildren().size()) {
  261. next = parent.getChildren().get(nextIdx);
  262. }
  263. // 如果前节点子节点数大于M / 2并且大于2,则从其处借补
  264. if (previous != null
  265. && previous.getChildren().size() > tree.getOrder() / 2
  266. && previous.getChildren().size() > 2) {
  267. //前叶子节点末尾节点添加到首位
  268. int idx = previous.getChildren().size() - 1;
  269. Node borrow = previous.getChildren().get(idx);
  270. previous.getChildren().remove(idx);
  271. borrow.setParent(this);
  272. children.add(0, borrow);
  273. validate(previous, tree);
  274. validate(this, tree);
  275. parent.updateRemove(tree);
  276. // 如果后节点子节点数大于M / 2并且大于2,则从其处借补
  277. } else if (next != null
  278. && next.getChildren().size() > tree.getOrder() / 2
  279. && next.getChildren().size() > 2) {
  280. //后叶子节点首位添加到末尾
  281. Node borrow = next.getChildren().get(0);
  282. next.getChildren().remove(0);
  283. borrow.setParent(this);
  284. children.add(borrow);
  285. validate(next, tree);
  286. validate(this, tree);
  287. parent.updateRemove(tree);
  288. // 否则需要合并节点
  289. } else {
  290. // 同前面节点合并
  291. if (previous != null
  292. && (previous.getChildren().size() <= tree.getOrder() / 2 || previous.getChildren().size() <= 2)) {
  293. for (int i = previous.getChildren().size() - 1; i >= 0; i--) {
  294. Node child = previous.getChildren().get(i);
  295. children.add(0, child);
  296. child.setParent(this);
  297. }
  298. previous.setChildren(null);
  299. previous.setEntries(null);
  300. previous.setParent(null);
  301. parent.getChildren().remove(previous);
  302. validate(this, tree);
  303. parent.updateRemove(tree);
  304. // 同后面节点合并
  305. } else if (next != null
  306. && (next.getChildren().size() <= tree.getOrder() / 2 || next.getChildren().size() <= 2)) {
  307. for (int i = 0; i < next.getChildren().size(); i++) {
  308. Node child = next.getChildren().get(i);
  309. children.add(child);
  310. child.setParent(this);
  311. }
  312. next.setChildren(null);
  313. next.setEntries(null);
  314. next.setParent(null);
  315. parent.getChildren().remove(next);
  316. validate(this, tree);
  317. parent.updateRemove(tree);
  318. }
  319. }
  320. }
  321. }
  322. }
  323. public void remove(Comparable key, BplusTree tree){
  324. //如果是叶子节点
  325. if (isLeaf){
  326. //如果不包含该关键字,则直接返回
  327. if (!contains(key)){
  328. return;
  329. }
  330. //如果既是叶子节点又是跟节点,直接删除
  331. if (isRoot) {
  332. remove(key);
  333. }else {
  334. //如果关键字数大于M / 2,直接删除
  335. if (entries.size() > tree.getOrder() / 2 && entries.size() > 2) {
  336. remove(key);
  337. }else {
  338. //如果自身关键字数小于M / 2,并且前节点关键字数大于M / 2,则从其处借补
  339. if (previous != null
  340. && previous.getEntries().size() > tree.getOrder() / 2
  341. && previous.getEntries().size() > 2
  342. && previous.getParent() == parent) {
  343. int size = previous.getEntries().size();
  344. Entry<Comparable, Object> entry = previous.getEntries().get(size - 1);
  345. previous.getEntries().remove(entry);
  346. //添加到首位
  347. entries.add(0, entry);
  348. remove(key);
  349. //如果自身关键字数小于M / 2,并且后节点关键字数大于M / 2,则从其处借补
  350. }else if (next != null
  351. && next.getEntries().size() > tree.getOrder() / 2
  352. && next.getEntries().size() > 2
  353. && next.getParent() == parent) {
  354. Entry<Comparable, Object> entry = next.getEntries().get(0);
  355. next.getEntries().remove(entry);
  356. //添加到末尾
  357. entries.add(entry);
  358. remove(key);
  359. //否则需要合并叶子节点
  360. }else {
  361. //同前面节点合并
  362. if (previous != null
  363. && (previous.getEntries().size() <= tree.getOrder() / 2 || previous.getEntries().size() <= 2)
  364. && previous.getParent() == parent) {
  365. for (int i = previous.getEntries().size() - 1; i >=0; i--) {
  366. //从末尾开始添加到首位
  367. entries.add(0, previous.getEntries().get(i));
  368. }
  369. remove(key);
  370. previous.setParent(null);
  371. previous.setEntries(null);
  372. parent.getChildren().remove(previous);
  373. //更新链表
  374. if (previous.getPrevious() != null) {
  375. Node temp = previous;
  376. temp.getPrevious().setNext(this);
  377. previous = temp.getPrevious();
  378. temp.setPrevious(null);
  379. temp.setNext(null);
  380. }else {
  381. tree.setHead(this);
  382. previous.setNext(null);
  383. previous = null;
  384. }
  385. //同后面节点合并
  386. }else if(next != null
  387. && (next.getEntries().size() <= tree.getOrder() / 2 || next.getEntries().size() <= 2)
  388. && next.getParent() == parent){
  389. for (int i = 0; i < next.getEntries().size(); i++) {
  390. //从首位开始添加到末尾
  391. entries.add(next.getEntries().get(i));
  392. }
  393. remove(key);
  394. next.setParent(null);
  395. next.setEntries(null);
  396. parent.getChildren().remove(next);
  397. //更新链表
  398. if (next.getNext() != null) {
  399. Node temp = next;
  400. temp.getNext().setPrevious(this);
  401. next = temp.getNext();
  402. temp.setPrevious(null);
  403. temp.setNext(null);
  404. }else {
  405. next.setPrevious(null);
  406. next = null;
  407. }
  408. }
  409. }
  410. }
  411. parent.updateRemove(tree);
  412. }
  413. //如果不是叶子节点
  414. }else {
  415. //如果key小于等于节点最左边的key,沿第一个子节点继续搜索
  416. if (key.compareTo(entries.get(0).getKey()) <= 0) {
  417. children.get(0).remove(key, tree);
  418. //如果key大于节点最右边的key,沿最后一个子节点继续搜索
  419. }else if (key.compareTo(entries.get(entries.size()-1).getKey()) >= 0) {
  420. children.get(children.size()-1).remove(key, tree);
  421. //否则沿比key大的前一个子节点继续搜索
  422. }else {
  423. for (int i = 0; i < entries.size(); i++) {
  424. if (entries.get(i).getKey().compareTo(key) <= 0 && entries.get(i+1).getKey().compareTo(key) > 0) {
  425. children.get(i).remove(key, tree);
  426. break;
  427. }
  428. }
  429. }
  430. }
  431. }
  432. /** 判断当前节点是否包含该关键字*/
  433. protected boolean contains(Comparable key) {
  434. for (Entry<Comparable, Object> entry : entries) {
  435. if (entry.getKey().compareTo(key) == 0) {
  436. return true;
  437. }
  438. }
  439. return false;
  440. }
  441. /** 插入到当前节点的关键字中*/
  442. protected void insertOrUpdate(Comparable key, Object obj){
  443. Entry<Comparable, Object> entry = new SimpleEntry<Comparable, Object>(key, obj);
  444. //如果关键字列表长度为0,则直接插入
  445. if (entries.size() == 0) {
  446. entries.add(entry);
  447. return;
  448. }
  449. //否则遍历列表
  450. for (int i = 0; i < entries.size(); i++) {
  451. //如果该关键字键值已存在,则更新
  452. if (entries.get(i).getKey().compareTo(key) == 0) {
  453. entries.get(i).setValue(obj);
  454. return;
  455. //否则插入
  456. }else if (entries.get(i).getKey().compareTo(key) > 0){
  457. //插入到链首
  458. if (i == 0) {
  459. entries.add(0, entry);
  460. return;
  461. //插入到中间
  462. }else {
  463. entries.add(i, entry);
  464. return;
  465. }
  466. }
  467. }
  468. //插入到末尾
  469. entries.add(entries.size(), entry);
  470. }
  471. /** 删除节点*/
  472. protected void remove(Comparable key){
  473. int index = -1;
  474. for (int i = 0; i < entries.size(); i++) {
  475. if (entries.get(i).getKey().compareTo(key) == 0) {
  476. index = i;
  477. break;
  478. }
  479. }
  480. if (index != -1) {
  481. entries.remove(index);
  482. }
  483. }
  484. public Node getPrevious() {
  485. return previous;
  486. }
  487. public void setPrevious(Node previous) {
  488. this.previous = previous;
  489. }
  490. public Node getNext() {
  491. return next;
  492. }
  493. public void setNext(Node next) {
  494. this.next = next;
  495. }
  496. public boolean isLeaf() {
  497. return isLeaf;
  498. }
  499. public void setLeaf(boolean isLeaf) {
  500. this.isLeaf = isLeaf;
  501. }
  502. public Node getParent() {
  503. return parent;
  504. }
  505. public void setParent(Node parent) {
  506. this.parent = parent;
  507. }
  508. public List<Entry<Comparable, Object>> getEntries() {
  509. return entries;
  510. }
  511. public void setEntries(List<Entry<Comparable, Object>> entries) {
  512. this.entries = entries;
  513. }
  514. public List<Node> getChildren() {
  515. return children;
  516. }
  517. public void setChildren(List<Node> children) {
  518. this.children = children;
  519. }
  520. public boolean isRoot() {
  521. return isRoot;
  522. }
  523. public void setRoot(boolean isRoot) {
  524. this.isRoot = isRoot;
  525. }
  526. public String toString(){
  527. StringBuilder sb = new StringBuilder();
  528. sb.append("isRoot: ");
  529. sb.append(isRoot);
  530. sb.append(", ");
  531. sb.append("isLeaf: ");
  532. sb.append(isLeaf);
  533. sb.append(", ");
  534. sb.append("keys: ");
  535. for (Entry entry : entries){
  536. sb.append(entry.getKey());
  537. sb.append(", ");
  538. }
  539. sb.append(", ");
  540. return sb.toString();
  541. }

哈夫曼编码(基于哈夫曼树-最优二叉树,不唯一)、B树(b-树)、B+树相关推荐

  1. 哈夫曼编码、哈夫曼树

    已知一个文件中出现的各字符及其对应的率如下表所示.若采用定长编码,则该文件中字符的码长应为( ).若采用Huffman编码,则字符序列face的编码应为( ) 字符 a b c d e f 频率 45 ...

  2. 霍夫曼树和霍夫曼编码以及霍夫曼编码的应用

    文章目录 霍夫曼树介绍 1.1霍夫曼树的定义 1.2霍夫曼树的几个概念 1.3构建霍夫曼树的过程 1.4代码实现霍夫曼树 霍夫曼编码介绍 什么是霍夫曼编码 通信领域的应用 字符串压缩 1.构造霍夫曼树 ...

  3. 【Android 内存优化】Android 原生 API 图片压缩原理 ( 哈夫曼编码开关 | 哈夫曼编码原理 | libjpeg-turbo 函数库 )

    文章目录 一. 哈夫曼编码开关 二. 哈夫曼编码原理 三. libjpeg-turbo 函数库 四. libjpeg-turbo 函数库下载 [Android 内存优化]图片文件压缩 ( Androi ...

  4. 算法导论——贪心算法:哈夫曼编码(霍夫曼编码)

    2019独角兽企业重金招聘Python工程师标准>>> package org.loda.greedy;import org.junit.Test; import org.loda. ...

  5. labview霍夫曼编码_哈夫曼编解码压缩解压文件—C++实现

    前言 哈夫曼编码是一种贪心算法和二叉树结合的字符编码方式,具有广泛的应用背景,最直观的是文件压缩.本文主要讲述如何用哈夫曼编解码实现文件的压缩和解压,并给出代码实现. 哈夫曼编码的概念 哈夫曼树又称作 ...

  6. 数据结构与算法之Huffman tree(赫夫曼树 / 霍夫曼树 / 哈夫曼树 / 最优二叉树)

    目录 赫夫曼树概述 定义 构造赫夫曼树步骤 代码实现 赫夫曼树概述 HuffmanTree因为翻译不同所以有其他的名字:赫夫曼树.霍夫曼树.哈夫曼树 赫夫曼树又称最优二叉树,是一种带权路径长度最短的二 ...

  7. 哈夫曼树-最优二叉树

    # 定义 1. 路径:结点中一个结点到另一个结点的通路(线)叫做路径 2. 路径长度:结点到另一个结点,线的累加和 3. 权:结点带有的数值 4. 带权路径长度:权×路径长度 最优二叉树指的是带权路径 ...

  8. java 实现最优二叉树_哈夫曼树(最优二叉树) - Java实现

    简介 哈夫曼树(Huffman)树又称最优二叉树,是指对于一组带有确定权值的叶子结点所构造的具有带权路径长度最短的二叉树.从树中一个结点到另一个结点之间的分支构成了两结点之间的路径,路径上的分支个数称 ...

  9. 哈夫曼树 (最优二叉树)

最新文章

  1. Java 判断 list 为空
  2. TPO 按主题刷题记录
  3. 老张 .NetCore与Vue 框架学习
  4. Java中的HashSet
  5. JavaSE——数组基础(创建格式、下标、获取长度、常见问题、多维数组)
  6. Java Stream flatMap()
  7. 2018网易校招前端笔试考后总结
  8. ios 纯代码 图标排列
  9. python小学生教程-python 小学生教程|怎么让一个小学生学会Python?
  10. U盘 量产记录(俩盘符合并为一个盘符)
  11. h264 java_Java解码H264格式视频流中的图片
  12. 【ARM学习笔记】问题Processor ARM920T raised an exception.Cause:Undefined instruction的解决方法
  13. matlab钢琴音教程,手把手教你钢琴基本指法(有图有真相,通俗易懂,钢琴入门必备)...
  14. 商之翼小京东+ucenter1.6.0+discuz3.3整合经历
  15. (DDS)正弦波形发生器——幅值、频率、相位可调(一)
  16. 51单片机RC522无线射频IC卡驱动教程
  17. Win10玩dnf输入法图标消失怎么办?
  18. 编译原理——词法分析(有穷自动机)
  19. 23-移动端布局方式-VM
  20. java计算时针和分针的夹角_【Java算法】一天24小时中,时针和分针一共重合多少次?...

热门文章

  1. KT1025A蓝牙音频芯片_立讯KC认证FCC测试现场整改记录
  2. Oracle 11g 局域网多端口连接配置 同一Wifi下
  3. 方舟服务器傻瓜开服教程(含教程以及所有开服常见问题解决办法)
  4. 支持4K30帧的海鸟4K潜水套装上手体验
  5. LaTeX表格自定义行高+自定义列宽+大表格自适应页面宽度
  6. 求方程ax^2+bx+c=0的根(C语言)
  7. javase之java基础语法0基础版
  8. JavaScript:实现ExtendedEuclidean扩展欧几里德GCD算法(附完整源码)
  9. round在python是什么意思_python – round()和numpy.round()之间的底层差异是什么?
  10. STC15单片机利用PCA功能测量脉宽应用示例