贪心算法 - 哈夫曼编码 Huffman

转载地址  :   http://blog.csdn.net/xuefeng0707/article/details/7844834

哈夫曼编码:

一种字符编码方式,常用于数据文件压缩。压缩率通常在20%~90%。

主要思想:

采取可变长编码方式,对文件中出现次数多的字符采取比较短的编码,对于出现次数少的字符采取比较长的编码,可以有效地减小总的编码长度。

例如,在英文中,e的出现频率最高,z的出现频率最低,所以可以用最短的编码来表示e,用最长的编码表示z。

例子:

一个文件包含100 000个字符,且仅含有a,b,c,d,e,f六个字符,那么可以用3位bit来编码这六个字符,称之为定长码。

那么采取定长码共需要的位数为 3* 100 000 = 600 000位。


这六个字符出现的频率如图所示,a最多,f最少。因此如果采取图中所示的变长码,所需要的位数为:

(45*1 + 13*3 + 12 * 3 + 16*3 + 9*4 + 5*4) * 1000 = 224 000。

可以看出压缩了25%,a的编码长度最短,为一位,f的编码长度最长,为4位。

前缀码:

当读取文件时,对于定长码文件,已经知道了每个字符的编码长度,所以只需按部就班的一个一个的读取字符即可;

对于变长码文件,各个字符的编码长度不一,因此需要小心设计各个字符的编码,一面混淆。

比如,如果a的编码为0,b的编码为01,c的编码为1,那么解码的时候如果遇到‘001’,则既可以解码成‘aac’,也可以解码成‘ab’。

因为b的编码中包含了a的编码,也就是a的编码是b的编码的前缀。

所以,变长码编码的设计,每个字符的编码都不能是其他字符编码的前缀,这种方式称之为前缀码

可以用二叉树来表示每个字符的编码,以左为0,以右为1,这样每个叶子节点的路径均不同,也都不会称为其他叶子节点的前缀。

这样每个叶子节点的路径,就是每个字符的编码。


图中形成的编码与表格中的有些差异,但是各个字符编码的长度都相同,这是左子树和右子树的区别,但是对编码长度没有影响。


贪心算法代码实现:

/**
 * Huffman code
 * @author xuefeng
 *
 */
public class Huffman {

	/**
	 * ignore exception
	 * @param cs : characters
	 * @param freqs : frequency of each character
	 */
	public static void huffman(char[] cs, double[] freqs) {
		int n = cs.length;
		MinHeap<Code> heap = new MinHeap<Code>(cs.length);
		Code[] codes = new Code[n];
		for (int i = 0; i < n; i++) {
			Code c = new Code(cs[i], freqs[i]);
			heap.add(c); // 以最小堆来每次取出频率最小的两个
			codes[i] = c; // 保存所有的叶子节点
		}

		Code c, c1, c2;
		while (heap.size() > 1) {
			c1 = heap.removeMin();
			c2 = heap.removeMin();// 取出两个最小的

			c = new Code('\0', c1.freq + c2.freq);
			c.left = c1;
			c.right = c2;
			c1.parent = c;
			c2.parent = c;
			heap.add(c); // 组合成一个新的节点,并放入堆中,继续比较

			System.out.println(c1.val + "+" + c2.val + " : " + c1.freq + "+" + c2.freq + " = " + c.freq);
		}
		c = heap.removeMin(); // 二叉树根节点

		StringBuffer sb;
		for (int i = 0; i < n; i++) {
			c = codes[i]; // 从每个叶子节点,向上追溯,直到根节点,确定每个字符的编码
			sb = new StringBuffer();
			while (c != null) {
				if (c.parent != null) {
					if (c == c.parent.left) {
						sb.insert(0, 0); // 如果是左边的,编码取1
					} else {
						sb.insert(0, 1); // 如果是右边的,编码取1
					}
				}
				c = c.parent;
			}
			System.out.println(codes[i].val + " : " + sb.toString());
		}
	}

	public static void main(String[] args) {
		char[] cs = { 'a', 'b', 'c', 'd', 'e', 'f' };
		double[] freqs = { 45, 13, 12, 16, 9, 5 };// %

		huffman(cs, freqs);

		// http://zh.wikipedia.org/wiki/%E5%AD%97%E6%AF%8D%E9%A2%91%E7%8E%87
		char[] cs2 = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
				'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
				'x', 'y', 'z' };
		double[] freqs2 = { 8.167, 1.492, 2.782, 4.253, 12.702, 2.228, 2.015,
				6.094, 6.966, 0.153, 0.772, 4.025, 2.406, 6.749, 7.507, 1.929,
				0.095, 5.987, 6.327, 9.056, 2.758, 0.978, 2.360, 0.150, 1.974,
				0.074 };

		huffman(cs2, freqs2);
	}
}

class Code implements Comparable<Code> {

	public char val;

	public double freq;

	public Code left, right, parent;

	public Code(char val, double freq) {
		this.val = val;
		this.freq = freq;
	}

	@Override
	public int compareTo(Code c) {
		double d = freq - c.freq;
		return d > 0 ? 1 : (d == 0 ? 0 : -1);
	}
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值