哈夫曼编码压缩率计算_漫画:“哈夫曼编码” 是什么鬼?

94b109e3-0314-eb11-8da9-e4434bdf6706.gif

98b109e3-0314-eb11-8da9-e4434bdf6706.jpeg 作者 | 小灰 来源 | 程序员小灰(ID:chengxuyuanxiaohui)

在上一期,我们介绍了一种特殊的数据结构 “哈夫曼树”,也被称为最优二叉树。没看过的小伙伴可以点击下方链接:

漫画:什么是 “哈夫曼树” ?

那么,这种数据结构究竟有什么用呢?我们今天就来揭晓答案。

9ab109e3-0314-eb11-8da9-e4434bdf6706.png

9fb109e3-0314-eb11-8da9-e4434bdf6706.png

a4b109e3-0314-eb11-8da9-e4434bdf6706.png

a8b109e3-0314-eb11-8da9-e4434bdf6706.png

计算机系统是如何存储信息的呢?

计算机不是人,它不认识中文和英文,更不认识图片和视频,它唯一“认识”的就是0(低电平)和1(高电平)。

因此,我们在计算机上看到的一切文字、图像、音频、视频,底层都是用二进制来存储和传输的。

acb109e3-0314-eb11-8da9-e4434bdf6706.png

从狭义上来讲,把人类能看懂的各种信息,转换成计算机能够识别的二进制形式,被称为编码。

编码的方式可以有很多种,我们大家最熟悉的编码方式就属ASCII码了。

在ASCII码当中,把每一个字符表示成特定的8位二进制数,比如:

aeb109e3-0314-eb11-8da9-e4434bdf6706.png

显然,ASCII码是一种等长编码,也就是任何字符的编码长度都相等。

b2b109e3-0314-eb11-8da9-e4434bdf6706.png

b4b109e3-0314-eb11-8da9-e4434bdf6706.png

b6b109e3-0314-eb11-8da9-e4434bdf6706.png

b9b109e3-0314-eb11-8da9-e4434bdf6706.png

为什么这么说呢?让我们来看一个例子:

假如一段信息当中,只有A,B,C,D,E,F这6个字符,如果使用等长编码,我们可以把每一个字符都设计成长度为3的二进制编码:

bdb109e3-0314-eb11-8da9-e4434bdf6706.png

如此一来,给定一段信息 “ABEFCDAED”,就可以编码成二进制的 “000 001 100 101 010 011 000 100 011”,编码总长度是27。

c5b109e3-0314-eb11-8da9-e4434bdf6706.png

但是,这样的编码方式是最优的设计吗?如果我们让不同的字符对应不同长度的编码,结果会怎样呢?比如:

c7b109e3-0314-eb11-8da9-e4434bdf6706.png

如此一来,给定的信息 “ABEFCDAED”,就可以编码成二进制的 “0 00 10 11 01 1 0 10 1”,编码的总长度只有14。

c8b109e3-0314-eb11-8da9-e4434bdf6706.png

cdb109e3-0314-eb11-8da9-e4434bdf6706.png

d0b109e3-0314-eb11-8da9-e4434bdf6706.png

d2b109e3-0314-eb11-8da9-e4434bdf6706.png

哈夫曼编码(Huffman Coding),同样是由麻省理工学院的哈夫曼博所发明,这种编码方式实现了两个重要目标:

1.任何一个字符编码,都不是其他字符编码的前缀。

2.信息编码的总长度最小。

dfb109e3-0314-eb11-8da9-e4434bdf6706.png

e3b109e3-0314-eb11-8da9-e4434bdf6706.png

e8b109e3-0314-eb11-8da9-e4434bdf6706.png

哈夫曼编码的生成过程是什么样子呢?让我们看看下面的例子:

假如一段信息里只有A,B,C,D,E,F这6个字符,他们出现的次数依次是2次,3次,7次,9次,18次,25次,如何设计对应的编码呢?

我们不妨把这6个字符当做6个叶子结点,把字符出现次数当做结点的权重,以此来生成一颗哈夫曼树:

eab109e3-0314-eb11-8da9-e4434bdf6706.png

这样做的意义是什么呢?

哈夫曼树的每一个结点包括左、右两个分支,二进制的每一位有0、1两种状态,我们可以把这两者对应起来,结点的左分支当做0,结点的右分支当做1,会产生什么样的结果?

ecb109e3-0314-eb11-8da9-e4434bdf6706.png

这样一来,从哈夫曼树的根结点到每一个叶子结点的路径,都可以等价为一段二进制编码:

f0b109e3-0314-eb11-8da9-e4434bdf6706.png

上述过程借助哈夫曼树所生成的二进制编码,就是哈夫曼编码。

现在,我们面临两个关键的问题:

首先,这样生成的编码有没有前缀问题带来的歧义呢?答案是没有歧义。

因为每一个字符对应的都是哈夫曼树的叶子结点,从根结点到这些叶子结点的路径并没有包含关系,最终得到的二进制编码自然也不会是彼此的前缀。

其次,这样生成的编码能保证总长度最小吗?答案是可以保证。

哈夫曼树的重要特性,就是所有叶子结点的(权重 X 路径长度)之和最小。

放在信息编码的场景下,叶子结点的权重对应字符出现的频次,结点的路径长度对应字符的编码长度。

所有字符的(频次 X 编码长度)之和最小,自然就说明总的编码长度最小。

f2b109e3-0314-eb11-8da9-e4434bdf6706.png

f4b109e3-0314-eb11-8da9-e4434bdf6706.png

f6b109e3-0314-eb11-8da9-e4434bdf6706.png

f9b109e3-0314-eb11-8da9-e4434bdf6706.png

fcb109e3-0314-eb11-8da9-e4434bdf6706.png

feb109e3-0314-eb11-8da9-e4434bdf6706.png

ffb109e3-0314-eb11-8da9-e4434bdf6706.png

03b209e3-0314-eb11-8da9-e4434bdf6706.png

private Node root;private Node[] nodes;//构建哈夫曼树public void createHuffmanTree(int[] weights) {//优先队列,用于辅助构建哈夫曼树
Queue nodeQueue = new PriorityQueue<>();
    nodes = new Node[weights.length];//构建森林,初始化nodes数组for(int i=0; i
        nodes[i] = new Node(weights[i]);
        nodeQueue.add(nodes[i]);
}//主循环,当结点队列只剩一个结点时结束while (nodeQueue.size() > 1) {//从结点队列选择权值最小的两个结点
Node left = nodeQueue.poll();
Node right = nodeQueue.poll();//创建新结点作为两结点的父节点
Node parent = new Node(left.weight + right.weight, left, right);
        nodeQueue.add(parent);
}
    root = nodeQueue.poll();
}//输入字符下表,输出对应的哈夫曼编码public String convertHuffmanCode(int index) {return nodes[index].code;
}//用递归的方式,填充各个结点的二进制编码public void encode(Node node, String code){if(node == null){return;
}
    node.code = code;
    encode(node.lChild, node.code+"0");
    encode(node.rChild, node.code+"1");
}public static class Node implements Comparable<Node>{int weight;//结点对应的二进制编码
String code;
Node lChild;
Node rChild;public Node(int weight) {this.weight = weight;
}public Node(int weight, Node lChild, Node rChild) {this.weight = weight;this.lChild = lChild;this.rChild = rChild;
}@Overridepublic int compareTo(Node o) {return new Integer(this.weight).compareTo(new Integer(o.weight));
}
}public static void main(String[] args) {char[] chars = {'A','B','C','D','E','F'};int[] weights = {2,3,7,9,18,25};
HuffmanCode huffmanCode = new HuffmanCode();
    huffmanCode.createHuffmanTree(weights);
    huffmanCode.encode(huffmanCode.root, "");for(int i=0; i
System.out.println(chars[i] +":" + huffmanCode.convertHuffmanCode(i));
}
}
这段代码中,Node类增加了一个新字段code,用于记录结点所对应的二进制编码。

当哈夫曼树构建之后,就可以通过递归的方式,从根结点向下,填充每一个结点的code值。

04b209e3-0314-eb11-8da9-e4434bdf6706.png

【END】 06b209e3-0314-eb11-8da9-e4434bdf6706.png

更多精彩推荐

☞那个分分钟处理10亿节点图计算的Plato,现在怎么样了?

☞每一节网课背后,硬核黑科技大曝光

☞数据库激荡40年,深入解析PostgreSQL、NewSQL演进历程

☞黑客用上机器学习你慌不慌?这7种窃取数据的新手段快来认识一下!

☞超详细!一文告诉你SparkStreaming如何整合Kafka!附代码可实践

☞Libra的Move语言初探,10行代码实现你第一个智能合约

08b209e3-0314-eb11-8da9-e4434bdf6706.png 你点的每个“在看”,我都认真当成了喜欢
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值