
1. 哈夫曼树的介绍
Huffman Tree,中文名是哈夫曼树或霍夫曼树,它是最优二叉树。
定义:给定n个权值作为n个叶子结点,构造一棵二叉树,若树的带权路径长度达到最小,则这棵树被称为哈夫曼树。 这个定义里面涉及到了几个陌生的概念,下面就是一颗哈夫曼树,我们来看图解答。
1.1 路径和路径长度
定义:在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1。
例子:100和80的路径长度是1,50和30的路径长度是2,20和10的路径长度是3。
1.2 结点的权及带权路径长度
定义:若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积。
例子:节点20的路径长度是3,它的带权路径长度= 路径长度 权 = 3 20 = 60。
1.3 树的带权路径长度
定义:树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL。
例子:示例中,树的WPL= 1100 + 280 + 320 + 310 = 100 + 160 + 60 + 30 = 350。
比较下面两棵树
上面的两棵树都是以{10, 20, 50, 100}为叶子节点的树。
左边的树WPL=210 + 220 + 250 + 2100 = 360
右边的树WPL=350
左边的树WPL > 右边的树的WPL。你也可以计算除上面两种示例之外的情况,但实际上右边的树就是{10,20,50,100}对应的哈夫曼树。至此,应该堆哈夫曼树的概念有了一定的了解了,下面看看如何去构造一棵哈夫曼树。
2. 哈夫曼树的图文解析
假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,哈夫曼树的构造规则为:
1. 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);
2. 在森林中选出根结点的权值最小的两棵树进行合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;
3. 从森林中删除选取的两棵树,并将新树加入森林;
4. 重复(02)、(03)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。
以{5,6,7,8,15}为例,来构造一棵哈夫曼树。
第1步:创建森林,森林包括5棵树,这5棵树的权值分别是5,6,7,8,15。
第2步:在森林中,选择根节点权值最小的两棵树(5和6)来进行合并,将它们作为一颗新树的左右孩子(谁左谁右无关紧要,这里,我们选择较小的作为左孩子),并且新树的权值是左右孩子的权值之和。即,新树的权值是11。 然后,将”树5”和”树6”从森林中删除,并将新的树(树11)添加到森林中。
第3步:在森林中,选择根节点权值最小的两棵树(7和8)来进行合并。得到的新树的权值是15。 然后,将”树7”和”树8”从森林中删除,并将新的树(树15)添加到森林中。
第4步:在森林中,选择根节点权值最小的两棵树(11和15)来进行合并。得到的新树的权值是26。 然后,将”树11”和”树15”从森林中删除,并将新的树(树26)添加到森林中。
第5步:在森林中,选择根节点权值最小的两棵树(15和26)来进行合并。得到的新树的权值是41。 然后,将”树15”和”树26”从森林中删除,并将新的树(树41)添加到森林中。
此时,森林中只有一棵树(树41)。这棵树就是我们需要的哈夫曼树!
3. 哈夫曼树的基本操作
哈夫曼树的重点是如何构造哈夫曼树。本文构造哈夫曼时,用到了以前介绍过的”(二叉堆)最小堆”。下面对哈夫曼树进行讲解。
基本定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
public class implements Comparable, Cloneable { protected int key; protected HuffmanNode left;
protected HuffmanNode right;
protected HuffmanNode parent;
protected (int key, HuffmanNode left, HuffmanNode right, HuffmanNode parent) { this.key = key; this.left = left; this.right = right; this.parent = parent; } public Object clone() { Object obj=null; try { obj = (HuffmanNode)super.clone();
} catch(CloneNotSupportedException e) { System.out.println(e.toString()); } return obj; } public int compareTo(Object obj) { return this.key - ((HuffmanNode)obj).key; } }
|
HuffmanNode是哈夫曼树的节点类。
1 2 3 4 5 6
|
public class Huffman { private HuffmanNode mRoot;
... }
|
Huffman是哈夫曼树对应的类,它包含了哈夫曼树的根节点和哈夫曼树的相关操作。
4. 构造哈夫曼树
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
* 创建Huffman树 * * @param 权值数组 */
public Huffman(int a[]) { HuffmanNode parent = null; MinHeap heap;
heap = new MinHeap(a); for(int i=0; i<a.length-1; i++) { HuffmanNode left = heap.dumpFromMinimum();
HuffmanNode right = heap.dumpFromMinimum();
parent = new HuffmanNode(left.key+right.key, left, right, null); left.parent = parent; right.parent = parent;
heap.insert(parent); } mRoot = parent;
heap.destroy(); }
|
首先创建最小堆,然后进入for循环。每次循环时:
- 首先,将最小堆中的最小节点拷贝一份并赋值给left,然后重塑最小堆(将最小节点和后面的节点交换位置,接着将”交换位置后的最小节点”之前的全部元素重新构造成最小堆);
- 接着,再将最小堆中的最小节点拷贝一份并将其赋值right,然后再次重塑最小堆;
- 然后,新建节点parent,并将它作为left和right的父节点;
- 接着,将parent的数据复制给最小堆中的指定节点。
在二叉堆中已经介绍过堆,这里就不再对堆的代码进行说明了。若有疑问,直接参考后文的源码。其它的相关代码,也Please RTFSC(Read The Fucking Source Code)!
5. 哈夫曼树的完整源码
哈夫曼树的节点类(HuffmanNode.java)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
* Huffman节点类(Huffman.java的辅助类) * * @author skywang * @date 2014/03/27 */
public class implements Comparable, Cloneable { protected int key; protected HuffmanNode left;
protected HuffmanNode right;
protected HuffmanNode parent;
protected (int key, HuffmanNode left, HuffmanNode right, HuffmanNode parent) { this.key = key; this.left = left; this.right = right; this.parent = parent; } public Object clone() { Object obj=null; try { obj = (HuffmanNode)super.clone();
} catch(CloneNotSupportedException e) { System.out.println(e.toString()); } return obj; } public int compareTo(Object obj) { return this.key - ((HuffmanNode)obj).key; } }
|
哈夫曼树的实现文件(Huffman.java)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
|
* Huffman树 * * @author skywang * @date 2014/03/27 */
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
public class Huffman { private HuffmanNode mRoot;
* 创建Huffman树 * * @param 权值数组 */ public Huffman(int a[]) { HuffmanNode parent = null; MinHeap heap;
heap = new MinHeap(a); for(int i=0; i<a.length-1; i++) { HuffmanNode left = heap.dumpFromMinimum();
HuffmanNode right = heap.dumpFromMinimum();
parent = new HuffmanNode(left.key+right.key, left, right, null); left.parent = parent; right.parent = parent;
heap.insert(parent); } mRoot = parent;
heap.destroy(); }
* 前序遍历"Huffman树" */ private void preOrder(HuffmanNode tree) { if(tree != null) { System.out.print(tree.key+" "); preOrder(tree.left); preOrder(tree.right); } } public void preOrder() { preOrder(mRoot); }
* 中序遍历"Huffman树" */ private void inOrder(HuffmanNode tree) { if(tree != null) { inOrder(tree.left); System.out.print(tree.key+" "); inOrder(tree.right); } } public void inOrder() { inOrder(mRoot); }
* 后序遍历"Huffman树" */ private void postOrder(HuffmanNode tree) { if(tree != null) { postOrder(tree.left); postOrder(tree.right); System.out.print(tree.key+" "); } } public void postOrder() { postOrder(mRoot); }
* 销毁Huffman树 */ private void destroy(HuffmanNode tree) { if (tree==null) return ; if (tree.left != null) destroy(tree.left); if (tree.right != null) destroy(tree.right); tree=null; } public void destroy() { destroy(mRoot); mRoot = null; }
* 打印"Huffman树" * * key -- 节点的键值 * direction -- 0,表示该节点是根节点; * -1,表示该节点是它的父结点的左孩子; * 1,表示该节点是它的父结点的右孩子。 */ private void print(HuffmanNode tree, int key, int direction) { if(tree != null) { if(direction==0)
System.out.printf("%2d is rootn", tree.key); else
System.out.printf("%2d is %2d's %6s childn", tree.key, key, direction==1?"right" : "left"); print(tree.left, tree.key, -1); print(tree.right,tree.key, 1); } } public void print() { if (mRoot != null) print(mRoot, mRoot.key, 0); } }
|
哈夫曼树对应的最小堆(MinHeap.java)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
|
* 最小堆(Huffman.java的辅助类) * * @author skywang * @date 2014/03/27 */
import java.util.ArrayList;
import java.util.List;
public class MinHeap { private List<HuffmanNode> mHeap;
* 创建最小堆 * * 参数说明: * a -- 数据所在的数组 */ protected MinHeap(int a[]) { mHeap = new ArrayList<HuffmanNode>();
for(int i=0; i<a.length; i++) { HuffmanNode node = new HuffmanNode(a[i], null, null, null); mHeap.add(node); }
for (int i = a.length / 2 - 1; i >= 0; i--) filterdown(i, a.length-1); }
* 最小堆的向下调整算法 * * 注:数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。 * * 参数说明: * start -- 被下调节点的起始位置(一般为0,表示从第1个开始) * end -- 截至范围(一般为数组中最后一个元素的索引) */ protected void filterdown(int start, int end) { int c = start;
int l = 2*c + 1;
HuffmanNode tmp = mHeap.get(c);
while(l <= end) {
if(l < end && (mHeap.get(l).compareTo(mHeap.get(l+1))>0)) l++;
int cmp = tmp.compareTo(mHeap.get(l)); if(cmp <= 0) break;
else { mHeap.set(c, mHeap.get(l)); c = l; l = 2*l + 1; } } mHeap.set(c, tmp); }
* 最小堆的向上调整算法(从start开始向上直到0,调整堆) * * 注:数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。 * * 参数说明: * start -- 被上调节点的起始位置(一般为数组中最后一个元素的索引) */ protected void filterup(int start) { int c = start;
int p = (c-1)/2;
HuffmanNode tmp = mHeap.get(c);
while(c > 0) { int cmp = mHeap.get(p).compareTo(tmp); if(cmp <= 0) break; else { mHeap.set(c, mHeap.get(p)); c = p; p = (p-1)/2; } } mHeap.set(c, tmp); }
* 将node插入到二叉堆中 */ protected void insert(HuffmanNode node) { int size = mHeap.size(); mHeap.add(node);
filterup(size);
}
* 交换两个HuffmanNode节点的全部数据 */ private void swapNode(int i, int j) { HuffmanNode tmp = mHeap.get(i); mHeap.set(i, mHeap.get(j)); mHeap.set(j, tmp); }
* 新建一个节点,并将最小堆中最小节点的数据复制给该节点。 * 然后除最小节点之外的数据重新构造成最小堆。 * * 返回值: * 失败返回null。 */ protected HuffmanNode dumpFromMinimum() { int size = mHeap.size();
if(size == 0) return null;
HuffmanNode node = (HuffmanNode)mHeap.get(0).clone();
mHeap.set(0, mHeap.get(size-1));
mHeap.remove(size-1); if (mHeap.size() > 1) filterdown(0, mHeap.size()-1); return node; }
protected void destroy() { mHeap.clear(); mHeap = null; } }
|
哈夫曼树的测试程序(HuffmanTest.java)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
|
* Huffman树的测试程序 * * @author skywang * @date 2014/03/27 */
public class HuffmanTest { private static final int a[]= {5,6,8,7,15}; public static void main(String[] args) { int i; Huffman tree; System.out.print("== 添加数组: "); for(i=0; i<a.length; i++) System.out.print(a[i]+" ");
tree = new Huffman(a); System.out.print("n== 前序遍历: "); tree.preOrder(); System.out.print("n== 中序遍历: "); tree.inOrder(); System.out.print("n== 后序遍历: "); tree.postOrder(); System.out.println(); System.out.println("== 树的详细信息: "); tree.print();
tree.destroy(); } }
|
原文:大专栏 哈夫曼树