当前位置:   article > 正文

哈夫曼编码与解码课程设计

哈夫曼编码与解码课程设计

目录

源码:哈夫曼编码与解码源码

一、问题描述

1.1 课程设计目的:

1.2 课程设计任务:

二、设计过程 

2.1  设计思想(数据结构):

2.2  设计表示(函数说明):

2.3  详细设计(主要算法):

2.4  用户手册(使用说明):

三、测试报告

3.1  测试用例:

3.2  测试结果:

四、分析与探讨

五、源码

5.1 createHT:

5.2 createHCode:


源码:哈夫曼编码与解码源码

本学期期末数据结构的课程设计,作者抽到的题目是哈夫曼编码与解码,为了方便有需要的友友做出更好的设计,这里写下我的做法,供大家参考。

效果演示:

一、问题描述

1.1 课程设计目的:

程序设计课程的主要目的包括以下几个方面:1. 培养编程思维让学生学会用逻辑和结构化的方式思考问题,能够将复杂的任务分解成可管理的步骤。2. 掌握编程技能比如能够用特定编程语言实现数据处理、算法实现等。3. 提高问题解决能力面对各种实际问题,能够运用编程知识和思维来找到解决方案。4. 培养创新能力鼓励学生通过编程来创造新的工具、应用或解决方案。5. 增强计算机素养6.为后续学习和职业发展奠定基础无论是在计算机相关领域进一步深造,还是从事软件开发等职业,都提供了必要的基础。

1.2 课程设计任务:

哈夫曼编码是一种可变字长编码方式,由大卫·哈夫曼于 1952 年提出。它的主要目的是根据字符出现的概率来构造平均长度最短的码字,从而实现数据的无损压缩。例如,对于一个包含大量重复字符的文本,哈夫曼编码可以使这些高频字符的编码很短,从而节省大量存储空间。

基本要求:测试数据为任意一段完整的英文,也可以是一段完整的中文;采用哈夫曼算法进行编码; 可输出对应字符编码的解码。

二、设计过程 

2.1  设计思想(数据结构):

实现哈夫曼编码的过程中,运用到了哈希表和哈夫曼树的数据结构。下面是对应其对应数据结构的相关理论。

• 哈希表(Hash table)是一种数据结构,它通过哈希函数将键映射到表中的位置来访问记录。它通过把关键码值映射到表中一个位置来访问记录,以加快查找速度。

• 哈夫曼树(Huffman Tree)是一种带权路径长度最短的二叉树,通常用于数据压缩中。它通过频率来构建树结构,频率越高的字符越靠近根部,以实现更高效的编码和解码过程。

2.2  设计表示(函数说明):

使用函数对代码的部分功能进行封装。

• createHT:来实现构建哈夫曼树。将每个节点都构成一颗只有一个根节点的二叉树,从当前的二叉树集合中选择权值最小的两颗二叉树,将它们合并成一颗新的二叉树,合并的方式是将这两颗二叉树作为新二叉树的左右子树,新二叉树的根节点的权值为这两颗子树的根节点权值之和。最后将上述操作进行重复合并。

• CreateHCode:配合哈希表存储的编码规则来完成哈夫曼编码。

• tranFor:进行哈夫曼编码,配合字符串算法,进行字母串的匹配和替换。哈夫曼编码保证编码出来的字符串长度较优。

• unTranFor:进行解码,将二进制数翻译成对应的字母。哈夫曼解码的关键是正确构建哈夫曼树和解码规则。在编码过程中,每个字符都被分配了一个唯一的编码,而在解码过程中,需要根据这些编码来还原原始字符。需要注意的是,哈夫曼解码需要知道编码时使用的哈夫曼树和编码规则,否则无法正确解码。因此,在实际应用中,通常需要将哈夫曼树和编码规则与编码数据一起存储或传输,以便在解码时使用。

• main:主函数,将各个模块总结起来,实现哈夫曼编码的各种逻辑,拼接。

• Menu:菜单函数:用来提示用户输入输出,及该程序的使用手册。

2.3  详细设计(主要算法):

首先读入相应的数据,采用哈希表来存储,字母及其出现的频率,方便后续编码。接着使用 createHT 构建哈夫曼树算法来构建哈夫曼树。同时利用哈夫曼树来生成哈夫曼编码,哈夫曼编码的规则采用另一个哈希表单独存储,最后再遍历一遍读入的字符,运用哈夫曼编码的对应规则来进行字符串编码。最后根据哈夫曼编码是一种无前缀编码,所以在解码时不会混淆,既能保证长度够小,又能保证解码时不会出错,可见哈夫曼编码是一种非常优秀的编码。下面是哈夫曼编码与解码的模块图。哈夫曼流程图(下面这张图是贴在任务书里面的,为了方便大家所以把这张图一起贴过来了)。

2.4  用户手册(使用说明):

该程序的主要作用是:输入对应数据,程序会将数据转化成哈夫曼编码,同时还会询问用户是否需要查看编码规则,如果需要则输出对应的编码规则,最后会再询问用户是否要选择退出,保证可以进行多次输入输出。哈夫曼编码在数据压缩、加密解密等领域都有广泛的应用。例如,在文件压缩中,哈夫曼编码可以将文件中的字符转换为较短的编码,从而减小文件的大小;在通信中,哈夫曼编码可以用于压缩数据,提高传输效率。

三、测试报告

3.1  测试用例:

• 中文示例:

其实生活中本没有那么多烦恼,一切只不过是庸人自扰。只要我们稍微停下脚步,就可以更好地珍惜生命本原的美丽,不为爱欲所炫目,不为污秽所恶心,永远正确看待生命,永远那么透明地看。那时候,你将发现,在人生的幽僻处、细小处,都闪耀着光芒。

• 英文示例:

We all stood there under the awning and just inside the door of the Wal-Mart. We all waited, some patiently, others irritated, because nature messed up their hurried day. I am always mesmerized by rainfall. I get lost in the sound and sight of the heavens washing away the dirt and dust of the world. Memories of running, splashing so carefree as a child come pouring in as a welcome reprieve from the worries of my day.

3.2  测试结果:

中文结果:

英文结果:

四、分析与探讨

哈夫曼编码分析和探讨:

高效压缩:通过为不同频率的字符分配不同长度的编码,能极大地减少表示文本所需的总位数,实现高效压缩。

自适应性:可以根据实际数据中字符的频率分布动态生成编码,适用于各种不同类型的数据。

最优性:在一定条件下,它能生成最优的无前缀编码,保证解码的唯一性和准确性。

例如,对于一个包含大量重复字符的文本,哈夫曼编码可以使这些高频字符的编码很短,从而节省大量存储空间。

总体而言,哈夫曼编码与解码是一种非常有效的数据压缩和恢复技术,在数据存储、传输等方面有着重要的应用价值。

总结:在参与这次团队合作实验的过程中,我获得了许多深刻的感悟和宝贵的经验。团队合作让我明白了集体力量的强大。当我们将每个人的智慧、技能和努力汇聚在一起时,能够解决许多单凭个人难以攻克的难题。不同成员的独特视角和想法相互碰撞、融合,为实验带来了更多的创新思路和解决方案。

有效的沟通在团队合作中至关重要。我们学会了清晰地表达自己的观点和想法,同时也认真倾听他人的意见。通过及时、准确的沟通,避免了误解和重复工作,确保整个团队始终朝着共同的目标前进。

分工与协作是团队高效运作的关键。根据成员的优势和特长进行合理的分工,让每个人都能在自己擅长的领域发挥最大作用。同时,在执行过程中,成员之间紧密协作,相互支持、配合,使得实验的各个环节能够顺利衔接。

面对实验中的困难和挑战,团队的凝聚力得以充分体现。大家共同面对问题,积极寻找解决办法,不推诿责任,这种团结一心的精神让我们能够战胜诸多困难。

这次团队合作实验也让我看到了自己的不足之处,比如在沟通技巧和团队协调方面还有待提升。这促使我在今后更加注重自身能力的培养和完善。

五、源码

源码在文章开头已经给出,这里就解释一下里面一些方法(createHT,createHCode),帮助理解一下。

5.1 createHT:

此方法的作用为构建哈夫曼树,有了哈夫曼树后才能进行哈夫曼编码。为了方便大家理解这里我用案例字符串:ABBBCCCCCDDDDDDD。来给大家手动模拟一下。首先统计出对应字母出现的次数。字母后面对应的数字就是对应的出现次数。

接着每次都从中挑选两个出现次数最小的进行合并。并合并后的节点权重为原来两个节点之和。

后续都一样,我就直接给出结果。注意:我们下面写的代码是左孩子是最小的,右孩子是次小的

 HTNode 的代码表示的是哈夫曼树的节点,参数有字符,权重,父亲,左右孩子节点参数。

  1. public class HTNode {
  2. char data;//字符
  3. int weight;//权重
  4. int parent;//父亲节点
  5. int lchild;//左孩子
  6. int rchild;//右孩子
  7. //对应的构造方法,初始化对应参数。
  8. public HTNode(char data,int weight){
  9. this.data = data;
  10. this.weight = weight;
  11. this.lchild = -1;//没有都初始化成 - 1
  12. this.rchild = -1;
  13. this.parent = -1;
  14. }
  15. //第二种构造方法,用来应对两种方式
  16. public HTNode(){
  17. this.lchild = -1;
  18. this.rchild = -1;
  19. this.parent = -1;
  20. }
  21. }

下面的代码就是模拟上面的这个过程,代码如下:

  1. //创建哈夫曼树
  2. //调试完毕
  3. //参数说明:ht 表示传入的叶子节点,n 表示叶子节点数
  4. //作用:构建哈夫曼树,存储在 ht 中。
  5. public static void createHT(HTNode ht[], int n) {
  6. //初始化
  7. int k = 0;//用来后续查找最小的两个叶子节点的权值下标
  8. int total = 2 * n - 1;//表示全部节点数
  9. double minl, minr;//存储区间内最小权值的两个节点
  10. int lnode, rnode;//存储最小权值的两个节点在数组中的下标
  11. for (int i = 0; i < total; i++) ht[i].parent = ht[i].lchild = ht[i].rchild = -1;//都初始化为 -1 ,表示叶子节点
  12. //开始创建
  13. for (int i = n; i < total; i++) {
  14. //要找最小,所以刚开始要初始化为 最大
  15. minl = 0x3f3f3f3f;
  16. minr = 0x3f3f3f3f;//minl 是最小的,minr 是次小的权值
  17. lnode = rnode = -1;//存储最小权值对应的下标
  18. for (k = 0; k <= i - 1; k++) {
  19. if (ht[k].parent == -1) {
  20. if (ht[k].weight < minl) //如果比 minl 还小的话,两个存储值都要改变
  21. {
  22. minr = minl;
  23. rnode = lnode;
  24. minl = ht[k].weight;
  25. lnode = k;
  26. } else if (ht[k].weight < minr) { //走到这里说明 minl 小于 ht[k].weight,所以只需要修改 minr 即可
  27. minr = ht[k].weight;
  28. rnode = k;
  29. }
  30. }
  31. }
  32. // i 表示 合并新节点的下标
  33. // lchild 表示被合并的左节点
  34. // rchild 表示被合并的右节点
  35. ht[i].weight = ht[lnode].weight + ht[rnode].weight;
  36. ht[i].lchild = lnode;
  37. ht[i].rchild = rnode;
  38. ht[lnode].parent = i;
  39. ht[rnode].parent = i;
  40. }
  41. }

5.2 createHCode:

案例字符串:ABBBCCCCCDDDDDDD。同样的我就直接利用上面构成的哈夫曼树来进行编码。

这个就更加简单,只需要记住左孩子是 0 ,右孩子是 1。

这里就直接给出编码的最终结果。

这是编码规则的节点设置,我们使用 StringBuilder 来存储最终的编码结果。(注意:看上图最后的叶子节点都是我们最初的节点。)使用 StringBuilder 的好处有不用频繁的创建对象,且有自带的字符串逆置方法(最后拿到的是逆序的,要将其转化一下)。 

  1. public class HCode {
  2. StringBuilder cd;//哈夫曼编码
  3. char data;//存储字符,Java 的 char 是两个字节,所以能存储汉字
  4. /**
  5. * 构造方法,初始化 cd ,防止报错
  6. */
  7. public HCode(){
  8. cd = new StringBuilder();
  9. }
  10. }

对应的代码如下:

  1. /**
  2. * 方法作用为:创建哈夫曼编码规则
  3. * @param ht
  4. * @param n
  5. * @return
  6. */
  7. public static HCode[] createHCode(HTNode ht[], int n) {
  8. HCode[] hcds = new HCode[n];
  9. int parent; int cur;
  10. for (int i = 0; i < n; i++)
  11. {
  12. HCode tmp = new HCode();
  13. cur = i;
  14. //预备工作,初始化cur 和 parent
  15. parent = ht[cur].parent;
  16. tmp.data = ht[cur].data;
  17. while (parent != -1)
  18. {
  19. if (cur == ht[parent].lchild)
  20. {
  21. //是左孩子
  22. tmp.cd.append('0') ;
  23. }
  24. else
  25. {
  26. //右孩子
  27. tmp.cd.append('1') ;
  28. }
  29. //向上查找
  30. cur = parent;
  31. parent = ht[cur].parent;
  32. }
  33. //进行翻转
  34. tmp.cd = tmp.cd.reverse();
  35. hcds[i] = tmp;
  36. }
  37. return hcds;
  38. }

 到这里大部分代码逻辑就讲述完毕,剩下的一些零碎,在源码里面都有解释,请放心食用 本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】

推荐阅读
相关标签