当前位置:首页 > > 21ic电子网
[导读]哈夫曼树(Huffman)又称为最优二叉树,是指对于一组带有确定权值的叶子结点所构造的具有带权路径长度最短的二叉树。 那么,这种数据结构究竟有什么用呢?我们今天就来揭晓答案。 计算机系统是如何存储信息的呢? 计算机不是人,它不认识中文和英文,更不认识





哈夫曼树(Huffman)又称为最优二叉树,是指对于一组带有确定权值的叶子结点所构造的具有带权路径长度最短的二叉树。


那么,这种数据结构究竟有什么用呢?我们今天就来揭晓答案。一组漫画告诉你,“哈夫曼编码”是什么鬼?

一组漫画告诉你,“哈夫曼编码”是什么鬼?

一组漫画告诉你,“哈夫曼编码”是什么鬼?

一组漫画告诉你,“哈夫曼编码”是什么鬼?



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


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


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


一组漫画告诉你,“哈夫曼编码”是什么鬼?


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


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


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

一组漫画告诉你,“哈夫曼编码”是什么鬼?


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

一组漫画告诉你,“哈夫曼编码”是什么鬼?

一组漫画告诉你,“哈夫曼编码”是什么鬼?

一组漫画告诉你,“哈夫曼编码”是什么鬼?


一组漫画告诉你,“哈夫曼编码”是什么鬼?



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


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


一组漫画告诉你,“哈夫曼编码”是什么鬼?


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


一组漫画告诉你,“哈夫曼编码”是什么鬼?


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


一组漫画告诉你,“哈夫曼编码”是什么鬼?


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


一组漫画告诉你,“哈夫曼编码”是什么鬼?


一组漫画告诉你,“哈夫曼编码”是什么鬼?


一组漫画告诉你,“哈夫曼编码”是什么鬼?

一组漫画告诉你,“哈夫曼编码”是什么鬼?



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


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

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

一组漫画告诉你,“哈夫曼编码”是什么鬼?

一组漫画告诉你,“哈夫曼编码”是什么鬼?

一组漫画告诉你,“哈夫曼编码”是什么鬼?



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


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


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


一组漫画告诉你,“哈夫曼编码”是什么鬼?


这样做的意义是什么呢?


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


一组漫画告诉你,“哈夫曼编码”是什么鬼?


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


一组漫画告诉你,“哈夫曼编码”是什么鬼?


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


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


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


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


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


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


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


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


一组漫画告诉你,“哈夫曼编码”是什么鬼?


一组漫画告诉你,“哈夫曼编码”是什么鬼?

一组漫画告诉你,“哈夫曼编码”是什么鬼?

一组漫画告诉你,“哈夫曼编码”是什么鬼?



一组漫画告诉你,“哈夫曼编码”是什么鬼?

一组漫画告诉你,“哈夫曼编码”是什么鬼?

一组漫画告诉你,“哈夫曼编码”是什么鬼?

一组漫画告诉你,“哈夫曼编码”是什么鬼?


       
  1. privateNode root;

  2. privateNode[] nodes;


  3. //构建哈夫曼树

  4. publicvoid createHuffmanTree(int[] weights) {

  5. //优先队列,用于辅助构建哈夫曼树

  6. Queue<Node> nodeQueue = newPriorityQueue<>();

  7. nodes = newNode[weights.length];


  8. //构建森林,初始化nodes数组

  9. for(int i=0; i<weights.length; i++){

  10. nodes[i] = newNode(weights[i]);

  11. nodeQueue.add(nodes[i]);

  12. }


  13. //主循环,当结点队列只剩一个结点时结束

  14. while(nodeQueue.size() > 1) {

  15. //从结点队列选择权值最小的两个结点

  16. Node left = nodeQueue.poll();

  17. Node right = nodeQueue.poll();

  18. //创建新结点作为两结点的父节点

  19. Node parent = newNode(left.weight + right.weight, left, right);

  20. nodeQueue.add(parent);

  21. }

  22. root = nodeQueue.poll();

  23. }


  24. //输入字符下表,输出对应的哈夫曼编码

  25. publicString convertHuffmanCode(int index) {

  26. return nodes[index].code;

  27. }


  28. //用递归的方式,填充各个结点的二进制编码

  29. publicvoid encode(Node node, String code){

  30. if(node == null){

  31. return;

  32. }

  33. node.code = code;

  34. encode(node.lChild, node.code+"0");

  35. encode(node.rChild, node.code+"1");

  36. }


  37. publicstaticclassNodeimplementsComparable<Node>{

  38. int weight;

  39. //结点对应的二进制编码

  40. String code;

  41. Node lChild;

  42. Node rChild;


  43. publicNode(int weight) {

  44. this.weight = weight;

  45. }


  46. publicNode(int weight, Node lChild, Node rChild) {

  47. this.weight = weight;

  48. this.lChild = lChild;

  49. this.rChild = rChild;

  50. }


  51. @Override

  52. publicint compareTo(Node o) {

  53. returnnewInteger(this.weight).compareTo(newInteger(o.weight));

  54. }

  55. }


  56. publicstaticvoid main(String[] args) {

  57. char[] chars = {'A','B','C','D','E','F'};

  58. int[] weights = {2,3,7,9,18,25};

  59. HuffmanCode huffmanCode = newHuffmanCode();

  60. huffmanCode.createHuffmanTree(weights);

  61. huffmanCode.encode(huffmanCode.root, "");

  62. for(int i=0; i<chars.length; i++){

  63. System.out.println(chars[i] +":"+ huffmanCode.convertHuffmanCode(i));

  64. }

  65. }



这段代码中,Node类增加了一个新字段code,用于记录结点所对应的二进制编码


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

一组漫画告诉你,“哈夫曼编码”是什么鬼?



—————END—————




作者:小灰

来源:程序员小灰


推荐阅读

一组漫画告诉你,“哈夫曼编码”是什么鬼?

你和大牛工程师之间到底差了啥?
加入技术交流群,与高手面对面 
添加管理员微信
一组漫画告诉你,“哈夫曼编码”是什么鬼?
加入“中国电子网微信群”交流

一组漫画告诉你,“哈夫曼编码”是什么鬼?
具体加群详情请戳

免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

21ic电子网

扫描二维码,关注更多精彩内容

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭