贪心算法 - 哈夫曼编码 Huffman

贪心算法 - 哈夫曼编码 Huffman

哈夫曼编码:
一种字符编码方式,常用于数据文件压缩。压缩率通常在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.java

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);
    }
}

MinHeap.java

/**
 *             0
 *            / \
 *           1   2
 *          / \
 *         3  4
 *
 */
public class MinHeap<T extends Comparable> {
    private Object[] data;

    private int size;
    public int size()
    {
        return size;
    }

    public MinHeap(int capacity) {
        data = new Object[capacity];
        size = 0;

    }

    public boolean add(T val) {
        if (size >= data.length)
            return false;
        int i = size, p;
        data[size] = val;
        size++;

        while (i > 0) {
            p = parentIndex(i);
            if (get(i).compareTo(get(p)) < 0) {
                swap(data, i, p);
            } else {
                break;
            }
            i = p;
        }

        return true;
    }

    public T remove(int index) {
        if (index >= size)
            return null;
        int i = index, left, right, p;
        T val = (T) data[index];
        data[index] = data[size - 1];
        size--;
        while (!isLeaf(i)) {
            left = leftIndex(i);
            right = rightIndex(i);
            p = i;
            i = right >= size || get(left).compareTo(get(right)) < 0 ? left
                    : right;
            if (get(i).compareTo(get(p)) < 0) {
                swap(data, i, p);
            }
        }

        return val;
    }

    public T removeMin() {
        return remove(0);
    }

    public T get(int index) {
        if (index >= size)
            throw new IllegalArgumentException("index is greater than size : "
                    + index);
        return (T) data[index];
    }

    private static int leftIndex(int index) {
        return 2 * index + 1;
    }

    private static int rightIndex(int index) {
        return 2 * index + 2;
    }

    private static int parentIndex(int i) {
        return i % 2 == 0 ? i / 2 - 1 : i / 2;
    }

    private boolean isLeaf(int index) {
        return leftIndex(index) >= size;
    }

    private void swap(Object[] data, int i1, int i2) {
        Object temp = data[i1];
        data[i1] = data[i2];
        data[i2] = temp;
    }
}

运行结果

f+e : 5.0+9.0 = 14.0
c+b : 12.0+13.0 = 25.0
+d : 14.0+16.0 = 30.0
+ : 25.0+30.0 = 55.0
a+ : 45.0+55.0 = 100.0
a : 0
b : 101
c : 100
d : 111
e : 1101
f : 1100
z+q : 0.074+0.095 = 0.16899999999999998
x+j : 0.15+0.153 = 0.303
+ : 0.16899999999999998+0.303 = 0.472
+k : 0.472+0.772 = 1.244
v+ : 0.978+1.244 = 2.222
b+p : 1.492+1.929 = 3.4210000000000003
y+g : 1.974+2.015 = 3.989
+f : 2.222+2.228 = 4.45
w+m : 2.36+2.406 = 4.766
u+c : 2.758+2.782 = 5.54
+ : 3.4210000000000003+3.989 = 7.41
l+d : 4.025+4.253 = 8.278
+ : 4.45+4.766 = 9.216000000000001
+r : 5.54+5.987 = 11.527000000000001
h+s : 6.094+6.327 = 12.421
n+i : 6.749+6.966 = 13.715
+o : 7.41+7.507 = 14.917
a+ : 8.167+8.278 = 16.445
t+ : 9.056+9.216000000000001 = 18.272
+ : 11.527000000000001+12.421 = 23.948
e+ : 12.702+13.715 = 26.417
+ : 14.917+16.445 = 31.362000000000002
+ : 18.272+23.948 = 42.22
+ : 26.417+31.362000000000002 = 57.779
+ : 42.22+57.779 = 99.999
a : 1110
b : 110000
c : 01001
d : 11111
e : 100
f : 00101
g : 110011
h : 0110
i : 1011
j : 001001011
k : 0010011
l : 11110
m : 00111
n : 1010
o : 1101
p : 110001
q : 001001001
r : 0101
s : 0111
t : 000
u : 01000
v : 001000
w : 00110
x : 001001010
y : 110010
z : 001001000
Process finished with exit code 0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值