文章参考自:程序员小灰:漫画:“哈夫曼编码” 是什么鬼?

哈夫曼编码是一种高效的编码方式,在信息存储和传输过程中,用于对信息进行压缩。

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

计算机不是人,它不认识中文和英文,更不认识图片和视频,它唯一“认识”的就是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。

但是,上面这样的设计会带来歧义,A的编码是0,B的编码是00,那么000即是AB也是AAA。

所以不定长编码是不能随意设计的,如果一个字符串的编码恰好是另一个字符串编码的前缀,就会产生上面的歧义。

这个时候就要用到哈夫曼编码哈夫曼编码是一种不定长的编码方式。

哈夫曼编码(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 编码长度)之和最小,自然就说明总的编码长度最小。

总结起来,哈夫曼树就是利用特殊的二叉树来生成二进制编码。

在这个例子中,哈夫曼编码的总长度是141,比定长编码短了百分之二十多。

哈夫曼编码(HaffmanCoding)代码实现:

using System;
using System.Collections.Generic;
using UnityEngine;namespace Algorithm
{public class TreeNode : IComparable{public int weight;      // 权重public string code;     // 节点对应的二进制编码public TreeNode lChild;public TreeNode rChild;public TreeNode(int weight){this.weight = weight;}public TreeNode(int weight, TreeNode lChild, TreeNode rChild){this.weight = weight;this.lChild = lChild;this.rChild = rChild;}// 实现比较接口,用于排序public int CompareTo(object obj){int result = 0;TreeNode tmp = obj as TreeNode;if (tmp.weight > this.weight){result = 1;}else if (tmp.weight < this.weight){result = -1;}return result;}}// 哈夫曼树public class HuffmanCodingTree{public TreeNode root;private TreeNode[] nodes;/// <summary>/// 构建哈夫曼树/// </summary>/// <param name="weights">权重数组</param>public void CreateHuffmanCodingTree(int[] weights){// 优先队列,用于辅助构建哈夫曼树Queue<TreeNode> nodeQueue = new Queue<TreeNode>();nodes = new TreeNode[weights.Length];// 构建森林,初始化nodes数组for (int i = 0; i < weights.Length; i++){nodes[i] = new TreeNode(weights[i]);nodeQueue.Enqueue(nodes[i]);}// 主循环,当节点队列只剩下一个节点结束while (nodeQueue.Count > 1){// 从节点队列选择权值最小的两个节点TreeNode left = nodeQueue.Dequeue();TreeNode right = nodeQueue.Dequeue();// 创建新节点作为两节点的父节点TreeNode parent = new TreeNode(left.weight + right.weight, left, right);nodeQueue.Enqueue(parent);}root = nodeQueue.Dequeue();}// 输入下表,输出对应的哈夫曼编码public string ConvertHuffmanCode(int index){return nodes[index].code;}// 用递归的方式,填充各个节点public void Encode(TreeNode node, string code){if (node == null){return;}node.code = code;Encode(node.lChild, node.code + "0");Encode(node.rChild, node.code + "1");}public void Output(TreeNode head){if (head == null){return;}Debug.Log(head.weight);Output(head.lChild);Output(head.rChild);}}
}

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

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

代码仓库:https://gitee.com/hankangwen/MyUnityTool

18.C#写算法之“哈夫曼编码” 是什么鬼?相关推荐

  1. 霍夫曼算法_霍夫曼编码算法

    霍夫曼算法 In this tutorial, we'll be discussing and implementing the Huffman Coding Algorithm in Java. 在 ...

  2. 贪心算法-03哈夫曼编码问题

    哈夫曼编码 简介 哈夫曼编码是一种字符编码方式,可以对指定的字符集进行数据压缩,压缩率在20%到90%. 问题描述 现在有一个包含5个字符{A,B,C,D,E},各个字符的出现频率依次为{0.35, ...

  3. 哈夫曼算法证明+哈夫曼编码译码程序实现

    哈夫曼算法证明 哈夫曼算法是一种贪心算法,我们考虑证明其最优子结构和贪心选择性质: 最优子结构:假设一个树是哈夫曼树,则以其任意节点为根节点的最大子树也是哈夫曼树. 证明:子树的根节点的值是其所有叶子 ...

  4. 算法导论——贪心算法:哈夫曼编码(霍夫曼编码)

    2019独角兽企业重金招聘Python工程师标准>>> package org.loda.greedy;import org.junit.Test; import org.loda. ...

  5. 数据结构与算法之霍夫曼编码解码实现

    目标:将字符串"can you can a can as a can canner can a can."编码再解码 流程: 将字符串转成bytes (byte[]格式)(eg.[ ...

  6. 贪心算法2——哈夫曼编码

    [构造哈夫曼树] 假设有n个叶子结点,对应的权值分别是w1.w2.....,wn则哈夫曼树的构造如下: (1)将w1,w2,....wn看成是有n课树的森林(每棵树仅有一个结点). (2)在森林中选出 ...

  7. 手机号段对应地区编码_漫画:“哈夫曼编码” 是什么鬼?

    在上一期,我们介绍了一种特殊的数据结构 "哈夫曼树",也被称为最优二叉树.没看过的小伙伴可以点击下方链接: 漫画:什么是 "哈夫曼树" ? 那么,这种数据结构究 ...

  8. 南邮哈夫曼编码c语言代码_漫画:“哈夫曼编码” 是什么鬼?

    ​在上一期,我们介绍了一种特殊的数据结构 "哈夫曼树",也被称为最优二叉树.没看过的小伙伴可以点击下方链接: 漫画:什么是 "哈夫曼树" ? 那么,这种数据结构 ...

  9. 漫画:“哈夫曼编码” 是什么鬼?

    在上一期,我们介绍了一种特殊的数据结构 "哈夫曼树",也被称为最优二叉树.没看过的小伙伴可以点击下方链接: 漫画:什么是 "哈夫曼树" ? 那么,这种数据结构究 ...

最新文章

  1. 学习Mybatis与mysql数据库的示例笔记
  2. SDWebImage开源库阅读分析(全)
  3. 无界阻塞队列 LinkedBlockingQueue 原理探究
  4. 【自动驾驶】8. MDC通信架构 + DDS + SOME/IP
  5. Vue 用户管理后台思维导图
  6. 2017-9-15-Linux移植:WinSCP软件 SSH Server开启
  7. 《Sibelius 脚本程序设计》连载(十四) - 2.1 注释、语句、语句块
  8. asp activex 读取服务器上的文件,webshell中上传asp文件调用服务器ActiveX控件溢出获取shell...
  9. 更加全面的ASP.NET AJAX(Atlas)学习、参考资源(英文)
  10. matlab 窗函数频谱,功率谱、频率分辨率、频谱泄漏与窗函数
  11. 金蝶系统无法建立数据可连接服务器,金蝶K3打开,提示无法建立数据链接
  12. python pySerial模块介绍
  13. html5设置谷歌浏览器兼容性,google浏览器
  14. Python分支+简单循环
  15. ltp install
  16. mac更新go到最新版本
  17. Golang 错误处理机制详解
  18. 21 个好用的持续集成工具,总有一款适合你
  19. 汉语编程能给我们带来什么?
  20. 分享134个ASP源码,总有一款适合您

热门文章

  1. 【接口测试基础】第八篇 | PostMan常用断言及断言的工作原理
  2. lazarus python_lazarus中文版
  3. 一级计算机综合训练第五卷,2014年计算机等级考试一级MSOFFICE综合训练(5).pdf
  4. 王者荣耀cpu测试软件,王者荣耀90帧模式实测:骁龙888对比骁龙865,谁最强?
  5. android系统稳定性测试,Android的稳定性
  6. 山石网科Hillstone防火墙基础上网配置_CLI命令行(最新版)
  7. AE 「酷酷的藤」特效字幕制作方法
  8. opencv形态学处理
  9. 一个产品经理的自我总结:我常犯的一些毛病(from http://forum.eet-cn.com)
  10. HTML5实例教程——简易涂鸦板-何韬-专题视频课程