资源下载地址:https://download.csdn.net/download/sheziqiong/86763807
资源下载地址:https://download.csdn.net/download/sheziqiong/86763807

问题描述

利用哈夫曼编码进行信息通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本。但是,这要求在发送端通过一个编码系统对待传数据预先编码;在接收端将传来的数据进行译码(复原)。对于双工信道(即可以双向传输信息的信道),每端都需要一个完整的编/译码系统。试为这样的信息收发站写一个哈夫曼码的编译码系统。

基本要求

一个完整的系统应具有以下功能:

(1)I:初始化(Initialization)。从终端读入字符集大小 n 及 n 个字符和 m 个权值,建立哈夫曼树,并将它存于文件 hfmtree 中。

(2)C:编码(Coding)。利用已建好的哈夫曼树(如不在内存,则从文件 hfmtree 中读入),对文件 tobetrans 中的正文进行编码,然后将结果存入文件 codefile 中。

(3)D:解码(Decoding)。利用已建好的哈夫曼树将文件 codefile 中的代码进行译码,结果存入文件 textfile 中。

(4)P:打印代码文件(Print)。将文件 codefile 以紧凑格式显示在终端上,每行 50 个代码。同时,将此字符形式的编码文件写入文件 codeprint 中。

(5)T:打印哈夫曼树(Tree printing)。将已在内存中的哈夫曼树以直观的方式(树或凹入表形式)显示在终端上,同时将此字符形式的哈夫曼树写入文件 treeprint 中。

初期设计

                                               架构

文献分析

第一篇

一种空间更优的快速霍夫曼解码算法:

[1]. Chen, H.C., Y.L. Wang and Y.F. Lan, A memory-efficient and fast Huffman decoding algorithm. INFORMATION PROCESSING LETTERS, 1999. 69(3): p. 119-122.

以这棵霍夫曼树为例:

一些定义

各叶子节点从左至右用 s0,s1…sn1s_0,s_1…s_{n-1}s0,s1…sn1 表示(共有 n 个编码元素)

level 表示节点所在,级数,根节点级数为 0,用 lll 表示

height 表示二叉树高度,即霍夫曼树中 level 的最大值,用 hhh 表示

编码元素 Si 的 weight 用 Wi 表示(注意这个跟题干里的元素权重或者出现频率是不一样的),wi=2hlw_i = 2^{h-l}wi=2hl

定义 count0=w0,counti=counti1+wi ,(其实这个是不必要的)


那么对于一棵霍夫曼树,我们就可以得到它的 si,wi,counti 的对应的值的表,以上面的霍夫曼树为例:

用三个数组存储相应元素

第一种算法

伪代码(算法逻辑)

那么,在对霍夫曼编码进行解码的时候,我们就希望在尽可能短的时间内通过编码获取到对应的元素,算法伪代码如下:

  • Step1:计算 t=(c+1)×2^(hd),其中 d 是二进制数 c 的位数,式中的 c 换算成十进制参与计算
  • Step2:在 count 的数组中搜索 t:
    • 若没有相应的 counti 与 t 相等,则 c 不是一个编码
    • 若存在,假设 countk=t
  • Step3:如果 wk≠2^(hd),则 c 不是一个编码,否则 c 对应的编码元素就是 sk(相当于找到了元素存储的下标 k)

举例

(直接用原文的例子吧,很好理解)

原理

假设有高为 h 的满二叉树(霍夫曼树),则有 2^(h+1)1 个节点(注意这里对 h 的定义其实与课程中讲的并不一样)、2^h 个叶子。不妨设各叶子(编码元素)为 s0,s1…s2^h1,那么 wi=1,counti=i+1。

那么,对于给定的二进制编码 c ,c 对应的十进制就是对应的 Sc 的下标值。

假设高为 h 的霍夫曼树 T 不为满二叉树,则级数为 l 的 si 的 wi=2^(hl),也就是当 si 是对应的高为 h 的满二叉树时,该节点作为内部节点的子树的叶子的数量。

在搜索 c 对应的编码元素时,若 c 的位数小于 h,则追加足够的 1 来获得长度为 h 的二进制串 c′(其实就相当于补成满二叉树的情况),那么,定义 c 对应的 weight 为 X,则 X=c+1 或 X=c′+1。

显然,若 X 不在 count 的数列中,则 c 一定不是一个元素的二进制编码。若在,但由于我们追加了足够的 1 去获取长度为 h 的二进制串,则可能存在多个二进制串对应值为 X 的 weight,所以需要第三步的检验。

复杂度

显然时间复杂度是 O(log2n),相较于使用优先级队列或直接使用队列的 O(nlog2n)有较大提升。

占用的空间则为 3n+1,用于存储三个数列及树的高度 h 。但是其实至少可以优化到 2n1 ,因为 wi=counticounti1 ,所以存储 w 的数列是不必要的。

第二种算法(改进的算法)

现在的空间占用已经压缩到了 2n+1 ,我们希望进一步压缩 count 的占用空间。

令 Wi=w2i+w2i+1,i=1,2,…,n1/2。若 n 为奇数,W(n1)/2=wn1

令 COUNT0=W0,且 COUNTi=COUNTi1+Wi,i=1,2,…,n1/2。

此外,定义一个新变量 bi,若 w2i≤w2i+1,i=1,2,…,n1/2,则令 bi=0,否则,即 w2i>w2i+1 时, bi=1。

由于我们能够通过 COUNTi 数列获取(计算出)Wi,所以不必存储 Wi。

伪代码

输入:s,b,COUNT 的数列、树的高度 h 以及二进制编码 c 。

输出:c 对应的编码元素 sk

过程

  • Step1:计算 t=(c+1)×2^(hd),其中 d 是二进制数 c 的位数,式中的 c 换算成十进制参与计算

  • Step2:找到 COUNTk,使得 COUNTk1<t≤COUNTk。

  • Step3:计算 x=COUNTkCOUNTk1 。

  • Step4:分解 xxx 为 x1,x2,使得

    x=x1+x2,xi=2^ei,i=1,2 对于非负整数 ei ,不失整体性地假设

    e1≤e2

    • 该分解可以如下进行:判断 2log2x 是否等于 x,若不相等,那么 x2=2log2x,x1=xx2。否则,x1=x2=x/2
  • Step5:用

    bk,x1,x2 计算出 s2k,s2k+1 相应的 weight wa,wb

    • **其实应当注意到,x=Wk,x1,x2 对应 w2i,w2i+1,a=2k,b=2k+1。
    • 对应地易解 bk 以及 xi=2^ei(也就是 wi=2^ei 的形式)
  • Step6:若 t=COUNTk 且 wb=2^(hd) ,那么 c 对应的编码元素就是 s2k+1 。令 k=2k+1,并终止算法。

  • Step7:若 t=COUNTkwb 且 wa=2^(hd),令 k=2k+1,并终止算法。都不满足,则 c 不是一个编码。

举例

还是以上面的霍夫曼树为例

  • c=0111

    • 第一步,t=(7+1)×2^(54)=16 。
    • 第二步,COUNT1<t≤COUNT2,k=2。
    • 第三步,x=3。
    • 第四步,x1=1,x2=2。
    • 第五步,a=4,b=5,wa=1,wb=2
    • 第六步,t=COUNT2=16,wb=2=2^(54),则 c 对应的编码元素是 s5

原理其实是跟上面的算法一样的,只不过这里没有存储 w 并且将 w 进行了两两归并。

复杂度

时间复杂度仍为 O(log2n) 。

空间复杂度,数列 s,COUNT,b 以及树的高度 h 的空间复杂度分别为 n,n/2,n/2log2n,1,总共的空间复杂度就是这四项的和。

即:3n/2+n/2log2n+1

第二篇

[1]胡丽莹,林鹭.一种基于多级查找表的高效 Huffman 编码算法[J].数学杂志,2012,32(04):753-760.DOI:10.13548/j.sxzz.2012.04.028.

文中扩展霍夫曼树和分级存储的思想对我的霍夫曼树的存储方式有较大启发。

该文中通过对树进行分级并定义偏移量来提高传统查找表的数据存储效率,但实际上原文中构建多重查找表的过程中使用的仍然是一个时间复杂度 O(n2n)的算法(更严格的说是近似 O(h2^h) 的算法, h 为树的高度。这就导致虽然在空间复杂度上实现了优化,但也付出了一定的时间复杂度的代价,是一种以时间换空间的方法。

但其实在霍夫曼树中,真正有效的数据其实就是叶子元素(原始字符与其编码)及他们的位置。所以完全可以通过将树补满后记录各个叶子节点的位置和内容,在恢复霍夫曼树时通过叶子节点自底向上完成构建。

基于这种思想我完成了对叶子节点的数据保存策略,在综合开发-逻辑层-初始化-叶子节点的定位策略部分给出了分析。

第三篇

[1]文国知.基于 C 语言的自适应 Huffman 编码算法分析及实现研究.武汉工业学院学报,2011,30(2):53-57+62.
通过 C 语言程序,动态统计信源符号概率,逐步构造 Huffman 编码树,实现了自适应 Huffman 编码,解决了静态编码树不能根据信源符号的局部变化做出相应变化的主要问题。结果表明,自适应 Huffman 编码算法压缩率很大,能进一步提高数据传输的效率。

这篇文献关注的是课程设计的初始化和编码两个功能。

Huffman 编码算法进行编码时,必须进行两次扫描,第一次扫描统计字符出现的概率(权重),并据此进行构造 Huffman 树;第二次扫描是按 Huffman 树的字符进行编码。

虽然题目中给出的条件允许输入各节点的权值,但在实际应用过程中是不现实的。

而自定义哈夫曼编码,预先不知道各种符号的出现频率,编码树的初始状态只包含一个叶节点,即 NYT(Not Yet Transmitted),NYT 是一个溢出码,不同于任何一个将要传送的符号,当一个尚未包含在编码树中的符号需要被编码时,首先输出 NYT 的编码,然后跟着符号的原始表达。当解码器解出一个 NYT 之后,它就知道下面的内容暂时不再是 Huffman 编码,而是一个从未在编码数据流中出现过的原始符号。当插入一个符号 q 时,会出现两种情况:

  1. q 是第一次出现的字符结点。构造一个新的子树,子树包含 NYT 符号和新符号两个叶节点,如下图所示。然后

    判断该子树的父节点是否是是当前权重下编号最大的结点,如果是,直接更新权重即可;否则,将父节点与相同权重的编号最高的结点交换,再更新权重值。

                                      插入新的字符节点
    
  2. q 不是第一次出现的字符结点。如果 q 所在节点,是当前节点权重下编号最大的结点,则直接使其当前节点权重及父节点权重加 1 即可。否则,将当前节点与相同权重的编号最高的结点交换,再更新权重值。

通过这种方式可以在进行编码的同时构建霍夫曼树并获取到相应编码,提升程序运行效率。

但是存在的最主要的问题是其编码与采用普通霍夫曼编码所得的编码序列并不一致,所以无法采用这种方法直接获取到可解析的霍夫曼编码(其实如果需要解析还是需要依据这棵编码树的动态调整,效率相对较低)

(其实这种方法最大的问题在于获取到的编码与普通霍夫曼编码所得的编码序列并不一致,所以还要重新编码)

初始化:

通过自适应霍夫曼编码对获取到的字符序列进行编码,直接完成初始化,不必读入权值。编码后对霍夫曼树进行压缩发送(将所有非叶子节点全部剪掉而仅保留有效信息),使视图层能通过接收到的霍夫曼树的信息进行重建霍夫曼树。

获取霍夫曼编码的策略:

使用栈,由叶子节点循环向父节点移动,若当前节点是父节点的左孩子则向栈中加入 0,否则加入 1,结束后将栈内容依次 pop 即可。(相当于完成了一次字符串反向输出,时间复杂度 O(log2n)

查询策略:

动态编码时常常要查找某字符是否已经编码过,若使用线性结构存储已经编码的元素则总体编码的时间会达到 O(nm)(n 为字符个数,m 为总的字符长度)。

为此,使用 unordered_map 替换线性表,可将这一部分时间杂度降低到 O(m)。

但由于更新一个节点权重的时间复杂度在 O(nlog2n)到 O(n(log2n)^2)之间(如果不用调换子树与节点位置则复杂度为 O(nlog2n),但可能存在调换子树与某一节点位置并更新编码的情况使得复杂度大于这个值但小于 O(n(log2n)^2)),所以总体的时间复杂度仍介于 O(nlog2n)到 O(mn(log2n)^2)之间。(也可以表示为 O(un(log2n)^2),u 为调换的次数,通常来说 u 小于 m)。

节点的定位策略:

对于任意霍夫曼树 H,定义其对应的满二叉树为 B 。

定义树的级数为 0,1,2…h,h 为树的高度。(避免“级数是从 1 开始算”的误解)。

定义两节点之间的落差 ddrop)为两节点的级数 l(level)之差的绝对值,也即 d=∣Δl∣

用 n 代表某一节点在 B 中所在级数的位置(从左到右为 0 ~ 2^level-1)

对于任意的一个编码,编码的长度其实就是该叶子节点在树中的级数,编码从头到尾的过程也就是从霍夫曼树根节点到编码元素叶子节点的过程。

那么如何计算 n:相对偏移量累加

定义两节点间的相对偏移量 =2^(d1),d=1,2,3 ,依据编码,指针从根节点开始,若下一节点是当前节点的右子树,则累加当前节点与叶子节点间的相对偏移量,并将指针移向下一个节点。重复这个步骤,直至当前指针指到叶子节点。

如:

对于节点(2),编码 101,level=3,n=231+2(11)=5,也就是说,该叶子节点是树中第四级第六个节点(如果认为根节点算第一级)。

那么,该节点的定位 locate=2^l+n1

不足:

  • 每次更新编码都要在 unordered_map 里更新一遍对应编码,造成一定的时间浪费。

  • 在增加节点权值是出现调换子树与某一节点位置后要更新节点的霍夫曼编码,增加权值与更新节点的霍夫曼编码的复杂度都为

    O(log2n),遍历无序哈希表为所有已编码元素更新编码的时间复杂度为 O(n),使得整个过程复杂度为

    O(n(log2n)^2),但其实并不是所有的编码都需要更新,所以仍然可以优化。

    • (其实这里被迫遍历是因为编码采用了无序哈希表存储,但若采用有序表存储,在更新时仍要涉及元素的查找,也至少是 O(lon2n)的时间复杂度,所以可优化的空间并不是很大)

编码:

编码过程可以直接使用初始化时形成的无序哈希表,使得整体的时间复杂度为 O(m)。但由于编码的存储形式都是字符(串),所以还需要将字符串转化成数字。

方案一:

Unsigned long long 的数值范围是 0~18446744073709551615 (2^64 - 1),可以将编码后的数字转化成 Unsigned long long 存储,每个数字占用 8 字节空间,不过存在开头是 0 而被省去的情况,但只要严格按照每 20 位存为一个数,只要在被省去的位置补 0 就可以了。

方案二:

可以对编码进行二次编码。即每四位转换成一个 16 进制数存入数组,但可能存在末尾不足四位的情况,用数组的第一个数字(0,1,2,3)直接记录最后末尾的位数,然后用 0 补足空位进行转换。在解码时再转换回二进制数即可。由于一个十六进制数只占半个字节,所以也可以节省很多空间。

综合来看方案二更优:每 20 位,方案一占用 8 字节,方案二占用 2.5 字节。(相较之下,如果不转换成数字再进行压缩,那么每个 char 就要占用 1 字节)

测试、总结

测试

时间统计:QueryPerformanceCounter()(逻辑层时间)

压缩率基本维持在 60% 到 80%

文件 大小 时间 CPU 占用(共 16G)
1 195 字节 0.0130425s
2 7.44K 1.79969s 19% ±\pm± 1% (3.04G ±\pm± 0.16G)
3 28K 7.3096s 27% ±\pm± 1% (4.32G ±\pm± 0.16G)
4 397K 139.738s 24% ±\pm± 3% (3.84 ±\pm± 0.48G)

总结

经验与不足

ifstreamofstream 在读取中文文件时并不能做到很好的适配,同时其 ASCI 的编码格式导致在开发过程中需要考虑一定的编码问题(一般常用的编码都是 utf8)。最要命的是不支持中文路径

getline()substr() 对于中文编码适配也不好,与中文的编码格式有关。

总而言之,IO 较佳的读写方案是使用 FILE 类进行操作,能避免很多问题。

最大的不足在于初始化霍夫曼树时使用了过多指针导致性能过低

应该采用哈希表与模拟指针解决这一问题,能够极大地提高程序运行效率

其次,文件读写更推荐使用二进制文件进行读写,使用文本文件读写在读出时会遭遇一系列问题,唯一的优点是可以直接可视化。

如无特殊需求,建议使用二进制文件。

其他:

  • 涉及 IO 仍然较多,导致一定的性能瓶颈
  • 局限性较大:只能压缩文本
  • 初始化霍夫曼树的复杂度较高导致了程序耗时较长(初始化霍夫曼树的时间占了总时间的 95% 以上)
  • 视图层与逻辑层之间的交互效率比较低导致在使用 UI 界面时体验不佳(与中间层的调度有一定关系)
  • 耦合度过高导致的维护困难,导致难以将指针重写成哈希表。

更进一步:

以上的所有内容算是完成了课设内容,但我对自己完成的程序的压缩效率觉得特别不满意,在查阅一些资料后针对现有的问题重写了一部分代码。

解决的问题:

  • ifstreamofstream 在中文内容与中文路径上不适配的问题,使用 FILE 代替
  • 过多指针导致的效率低下,使用哈希表和模拟指针代替
  • 局限于文本文件的编码导致实际上只完成了编码而没有完成压缩,不能压缩常见的二进制文件(如 png),使用读写二进制文件代替。

时间紧张没有做的部分:

  • 霍夫曼树的可视化和完整的 UI 界面,所以也没有再像上面那样从满二叉树的视角去看待霍夫曼树

一些不足:

  • 霍夫曼编码部分仍然使用了传统的编码思路,即:遍历获取权重-> 建立霍夫曼树-> 编码,导致一个文件要读取三遍。
  • 省略了很多霍夫曼树的细节如树高和节点位置的标记,使得整棵树难以做高效的可视化。
  • 二进制文件中其实有很多冗余信息(不必要的占位),使得在压缩小文件时效率并不高

二进制文件格式:

测试

文件 大小 时间
1 195 字节 0.0005313s
2 7.44K 0.0119407s
3 28K 0.0070038s
4 397K 0.104739s

资源下载地址:https://download.csdn.net/download/sheziqiong/86763807
资源下载地址:https://download.csdn.net/download/sheziqiong/86763807

哈夫曼码的编译码系统相关推荐

  1. 最详细的C++实现哈夫曼树中英文编解码

    目录 1.程序设计思路 1.框架构想 2.数据结构的选择 2.相应功能的函数实现及程序变量解释 1.宏定义及全局变量的解释 2.根据指定文本构建哈夫曼树 3.根据哈夫曼树构建哈夫曼编码表 4.根据哈夫 ...

  2. LDPC码的编译码原理简述

    关于fpga调用ldpc IP core的相关参数问题可以看我的另一篇文章 LDPC码由Gallager在1962年提出,全称为 Low Density Parity-check Codes 低密度奇 ...

  3. c语言赫夫曼树的编码与译码,哈夫曼树与编码译码实现

    一.哈弗曼树的基本概念. 哈夫曼树,又称最优树,是一类带权路径长度最短的树.下面有几个概念: (1)路径. 树中一个结点到另一个结点之间的分支构成这两个结点之间的路径. (2)路径长度. 路径上的分枝 ...

  4. 哈夫曼树编码和译码c语言,C++哈夫曼树编码和译码的实现

    78 /*-----------创建工作---------------------------*/ 79     int s1,s2; 80     for (int i = n + 1; i < ...

  5. 对文件进行哈夫曼编码压缩与译码的C++实现 以及压缩率计算 ——HIT杨朔

    哈夫曼编码压缩原理:由于每个字符在内存中都是以ASCII码进行存储,所以每个字符都占用了八个01位,利用哈夫曼树对每个字符进行01编码,根据字符在文章中出现的频率调整01串长度,出现频率高的字符在哈夫 ...

  6. java哈夫曼编码与译码_哈夫曼树与编码译码实现

    标签: 一.哈弗曼树的基本概念. 哈夫曼树,又称最优树,是一类带权路径长度最短的树.下面有几个概念: (1)路径. 树中一个结点到另一个结点之间的分支构成这两个结点之间的路径. (2)路径长度. 路径 ...

  7. 哈夫曼树编码与译码(完整C/C++实现代码)

    哈夫曼编码的设计与应用 问题需求分析 用哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种.Huffman于1952年提出一种编码方法 ...

  8. 利用哈夫曼树编码与译码

    #include<iostream> #include<string.h> #include<stdlib.h> using namespace std;typed ...

  9. ◆5.2③哈夫曼编/译码器

    ** ◆5.2③哈夫曼编/译码器 ** [问题描述] 利用哈夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成 本.但是,这要求在发送端通过一个编码系统对待传数据预先编码,在接收端将 ...

  10. 数据结构实验——哈夫曼编码

    目录 问题描述 基本要求 问题分析 实验代码 运行结果 实验总结 问题描述 利用哈夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本.但是,这要求在发送端通过一个编码系统对待传数据 ...

最新文章

  1. python20191031_20191031:Python取反运算详解
  2. java开发 中台
  3. python不能处理excel文件-别以为Python的pandas不能处理非规范Excel数据
  4. Content Aware ABR技术
  5. 最大似然估计和最大后验概率估计的理解与求解
  6. 封装axios的接口请求数据方法
  7. java中塑形_Java学习5——接口和多态
  8. vue watch的监听
  9. python split()方法_聊聊 Python 的单元测试框架(一):unittest
  10. varnish的了解与常用配置使用
  11. spark发行版笔记13
  12. 搜狗拼音输入法软件相关问题
  13. 微信公众号软件安装管家所有软件插件打包
  14. C语言求若干个数的均值和方差
  15. 单播、多播(组播)和广播解释
  16. Flutter对话框(AlertDialog,SimpleDialog,showModalBottomSheet,showToast)以及定时器
  17. 红米k30支持html,红米K30S至尊纪念版发布:骁龙865+支持144Hz高刷
  18. (转)被讨厌的勇气--目录
  19. Unity 进阶 之 简单模仿鼠标交互(场景:手机屏幕当做触摸板Touch Pad,移动鼠标,鼠标确定等操作)
  20. 统计正数和负数的个数然后计算这些数的平均值。

热门文章

  1. 微信怎么找群聊?找回微信群聊只需要这样…
  2. GPRS PDP APN
  3. 使用IPV6技术访问网站
  4. 商场会员管理系统c语言,商场收银系统(C语言).doc
  5. 网络安全技术第四章——身份认证技术(身份认证及方式、身份认证三要素、身份认证协议、KERBEROS协议、SSL协议)
  6. 什么是浏览器指纹,如何完整修改浏览器指纹?
  7. 冲浪涨停预警,让你快速跟上涨停板,主力主升浪趋势,通达信选涨停股选股公式
  8. 关于USB3.0的U盘正确用法
  9. 核磁谱图分析步骤_核磁共振氢谱 解析图谱的步骤
  10. matlab中升余弦滚降滤波器_升余弦滤波器原理