【数据结构与算法】Huffman编码/译码(C/C++)

实践要求

1. 问题描述

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


2. 基本要求

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

  1. I 初始化(Initialization)
    从终端读入字符集大小n,及n个字符和n个权值,建立哈夫曼树,并将它存于文件hfmtree中。
  2. C:编码(Coding)
    利用已建好的哈夫曼树(如不在内存,则从文件hfmtree中读 入),对文件tobetrans中的正文进行编码,然后将结果存入codefile中。
  3. D:译码(Decoding)
    利用已建好的哈夫曼树将文件codefile中的代码进行译 码,结果存入文件textfile中。
  4. P:印代码文件(Print)
    将文件codefile以紧凑格式显示在终端上,每行50个代 码。同时将此字符形式的编码文件写入文件codeprint中。
  5. T:印哈夫曼树(Tree print)
    将已在内存中的哈夫曼树以直观的方式 树或凹入表行式)显示在终端上,同时将此字符形式的哈夫曼树写入文件treeprint中。

3. 测试数据

可能出现8种字符,其概率分别为0.05,0.29,0.07,0.80,0.14,0.23,0.03,0.11试设计赫夫曼编码。

3.1 input

请输入字符串集大小

8

请输入字符和权值 //用空格分离

a 5
b 29
c 7
d 8
e 14
f 23
g 3
h 11

3.2 output

若要输出"abc"这个字符串的Huffman编码
(正确的结果应为0001111010)


实践报告

1. 题目分析

说明程序设计的任务,强调的是程序要做什么,此外列出各成员分工

程序设计任务:设计一个Huffman编码译码系统,有I:初始化;C:编码;D:译码;P:印代码文件;T:印Huffman树五个功能模块。


2. 数据结构设计

说明程序用到的数据结构的定义,主程序的流程及各模块之间的层次关系

该程序主要用到的数据结构是Huffman树(最优二叉树)

主程序流程图

在这里插入图片描述


3. 程序设计

实现概要设计中的数据类型,对主程序、模块及主要操作写出伪代码,画出函数的调用关系

各模块伪代码

初始化

在这里插入图片描述

编码

在这里插入图片描述

解码

在这里插入图片描述

打印

在这里插入图片描述

打印并存入Huffman树

在这里插入图片描述

各函数层次关系图

Initialization

在这里插入图片描述

EnCode

在这里插入图片描述

DeCode

在这里插入图片描述

Tree

在这里插入图片描述


4. 调试分析

程序复杂度分析

1. 空间复杂度
o ( n ) o(n) o(n)
空间复杂度主要是由节点的内存空间和最小堆的内存空间所占据的空间所决定的,因此总的空间复杂度为O(n)。
2. 时间复杂度
O ( n l o g n ) O(nlogn) O(nlogn)
由于建立哈夫曼树的过程中需要使用最小堆来实现节点的排序和合并。最小堆的插入和删除操作都需要O(logn)的时间复杂度,而需要进行n-1次操作,因此总的时间复杂度为O(nlogn)

所遇到的问题的解决方法

  • 问题1:内存泄露问题,动态分配的空间未释放。
    解决:在函数解放使用free来释放动态分配的空间
  • 问题2:按格式读取文件信息
    解决:使用buffer和characters两个数组,buffer用于接收所有的txt文件中的内容,将buffer中有用的信息赋值给characters。

5. 测试结果

列出测试结果,包括输入和输出

测试结果一

先初始化将字符以及其权值存入txt文件中

在这里插入图片描述
在这里插入图片描述

再对其进行编码

在这里插入图片描述
在这里插入图片描述

对编码结果进行解码

在这里插入图片描述

打印Huffman树并存入txt中

在这里插入图片描述


6. 用户使用说明

给出主界面及主要功能界面

使用说明

首先初始化Huffman树,然后进行编码,编码后可选择译码也可以选择印代码文件或者印Huffman树。

7. 附录

源程序文件清单:
huffman.cpp
treeprint.txt
textfile.txt
hfmtree.txt
codefile.txt
codeprint.txt

8. 全部代码

huffman.cpp

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>using namespace std;// 定义哈夫曼树的节点结构体
typedef struct node
{int weight;         // 权值char character;     // 字符struct node *left;  // 左子树指针struct node *right; // 右子树指针
} Node;// 定义哈夫曼树的节点类型
typedef Node *HuffmanTree;// 定义哈夫曼树的节点最小堆
typedef struct heap
{int size;          // 堆的大小int capacity;      // 堆的容量HuffmanTree *data; // 堆数据存储指针
} MinHeap;// 初始化最小堆
MinHeap *initMinHeap(int capacity)
{// 动态分配最小堆MinHeap *heap = (MinHeap *)malloc(sizeof(MinHeap));heap->capacity = capacity;heap->size = 0;heap->data = (HuffmanTree *)malloc(sizeof(HuffmanTree) * capacity);return heap;
}// 最小堆中插入元素
void insert(MinHeap *heap, HuffmanTree node)
{if (heap->size >= heap->capacity){return; // 如果堆已满,直接退出}heap->data[heap->size] = node;  // 将元素插入堆底int current = heap->size++;     // 更新堆大小int parent = (current - 1) / 2; // 父节点的下标// 自下往上调整堆,直到找到合适的位置插入新元素while (parent >= 0 && heap->data[current]->weight < heap->data[parent]->weight){// 如果当前元素比父节点的权值小,则交换两个元素的位置HuffmanTree temp = heap->data[parent];heap->data[parent] = heap->data[current];heap->data[current] = temp;current = parent;parent = (current - 1) / 2;}
}// 从最小堆中取出最小元素
HuffmanTree extractMin(MinHeap *heap)
{if (heap->size == 0) // 如果堆为空,直接返回空指针{return NULL;}HuffmanTree min = heap->data[0];          // 最小元素即为堆顶元素heap->data[0] = heap->data[--heap->size]; // 将堆底元素移到堆顶,并更新堆大小int current = 0;                          // 当前节点的下标int child = current * 2 + 1;              // 当前节点的左孩子的下标// 自上往下调整堆,直到找到合适的位置插入堆底元素while (child < heap->size) // 当前节点还有孩子节点{if (child + 1 < heap->size && heap->data[child + 1]->weight < heap->data[child]->weight){child++; // 找到当前节点的左右孩子中较小的一个}if (heap->data[child]->weight < heap->data[current]->weight){// 将当前节点和较小孩子节点交换位置HuffmanTree temp = heap->data[child];heap->data[child] = heap->data[current];heap->data[current] = temp;current = child;         // 更新当前节点的下标child = current * 2 + 1; // 更新当前节点的左孩子下标}else{break; // 如果已经满足最小堆的性质,则退出循环}}return min; // 返回被取出的最小元素
}// 用最小堆构建哈夫曼树
HuffmanTree buildHuffmanTree(int *weights, char *characters, int n)
{// 初始化最小堆,将每个字符及其权重转换成节点,并插入堆中MinHeap *heap = initMinHeap(n);for (int i = 0; i < n; i++){Node *node = (Node *)malloc(sizeof(Node));node->weight = weights[i];node->character = characters[i];node->left = NULL;node->right = NULL;insert(heap, node); // 将节点插入堆中}// 不断从最小堆中取出权重最小的两个节点,合并成一个新节点,再插入堆中while (heap->size > 1){HuffmanTree left = extractMin(heap);  // 取出堆顶节点,即最小权重节点HuffmanTree right = extractMin(heap); // 再次取出最小权重节点Node *parent = (Node *)malloc(sizeof(Node));parent->weight = left->weight + right->weight; // 新节点的权重为左右节点的权重之和parent->left = left;                           // 将左节点作为新节点的左孩子parent->right = right;                         // 将右节点作为新节点的右孩子insert(heap, parent);                          // 将新节点插入堆中}HuffmanTree root = extractMin(heap); // 最后堆中只剩下根节点,即为哈夫曼树的根节点free(heap->data);                    // 释放堆数组占用的空间free(heap);                          // 释放最小堆结构体占用的空间return root;                         // 返回哈夫曼树的根节点指针
}
// 对单个字符进行编码模块
char *encodeChar(HuffmanTree root, char ch)
{static char code[100]; // 申请存储编码的空间static int index = 0;  // 记录编码位数if (root == NULL){return NULL;}if (root->character == ch){code[index] = '\0'; // 编码结尾index = 0;          // 编码位数归零return code;}code[index++] = '0';char *leftCode = encodeChar(root->left, ch);if (leftCode != NULL){return leftCode;}index--; // 回溯code[index++] = '1';char *rightCode = encodeChar(root->right, ch);if (rightCode != NULL){return rightCode;}index--; // 回溯return NULL;
}// 对文本进行编码模块
char *encode(HuffmanTree root, char *text)
{char *result = (char *)malloc(strlen(text) * 100 * sizeof(char)); // 申请存储编码结果的空间result[0] = '\0';                                                 // 初始化for (int i = 0; i < strlen(text); i++){char *code = encodeChar(root, text[i]); // 对单个字符编码if (code){strcat(result, code); // 将编码拼接到结果中}}return result;
}// 初始化函数
HuffmanTree initialization()
{int n; // 字符集大小printf("请输入字符集大小:\n");scanf("%d", &n);int *weights = (int *)malloc(sizeof(int) * n);       // 动态分配n个权重值char *characters = (char *)malloc(sizeof(char) * n); // 动态分配n个字符printf("请输入字符和权值:\n");for (int i = 0; i < n; i++){scanf(" %c %d", &characters[i], &weights[i]);}HuffmanTree root = buildHuffmanTree(weights, characters, n);FILE *fp = fopen("hfmtree.txt", "w");fprintf(fp, "%d\n", n);for (int i = 0; i < n; i++){fprintf(fp, "%c%s", characters[i], i == n - 1 ? "\n" : " ");}for (int i = 0; i < n; i++){fprintf(fp, "%d%s", weights[i], i == n - 1 ? "\n" : " ");}fclose(fp);return root;
}void EnCodeChar(HuffmanTree root)
{FILE *fp;char *characters;char buffer[100];int n;// 打开文件fp = fopen("hfmtree.txt", "r");if (fp == NULL){printf("文件打开失败\n");exit(1);}// 读取第一行,获取字符集大小fscanf(fp, "%d", &n);// 分配空间characters = (char *)malloc(n * sizeof(char));// 读取第二行数据fgets(buffer, sizeof(characters) * 2, fp);fgets(buffer, sizeof(characters) * 2, fp);int i = 0;int j = 0;while (buffer[i] != NULL){if (buffer[i] != ' '){characters[j] = buffer[i];j++;}i++;}fclose(fp);for (int i = 0; i < n; i++){int index = 0;char *res = encodeChar(root, characters[i]);printf("%c: %s\n", characters[i], res);}
}void EnCode(HuffmanTree root)
{char tobetrans[100];char *result;printf("请输入一个字符串:\n");scanf("%s", tobetrans); // 读取输入的字符串,存储到字符数组中result = encode(root, tobetrans);printf("%s\n", result);FILE *fp = fopen("codefile.txt", "w");for (int i = 0; i < strlen(result); i++){fprintf(fp, "%c", result[i]);}fclose(fp);
}char DeCodeChar(HuffmanTree root, char *code)
{HuffmanTree p = root;while (*code != '\0'){if (*code == '0'){p = p->left;}else if (*code == '1'){p = p->right;}if (p->left == NULL && p->right == NULL){return p->character;}code++;}return '\0';
}void DeCode(HuffmanTree root)
{// 读取译码文件FILE *fp_code = fopen("codefile.txt", "r");char *code = (char *)malloc(1000 * sizeof(char)); // 申请存储代码的空间fscanf(fp_code, "%s", code);                      // 读取代码fclose(fp_code);// 译码char *text = (char *)malloc(1000 * sizeof(char)); // 申请存储译文的空间int i = 0, j = 0;while (code[i] != '\0'){char *tmp = (char *)malloc(100 * sizeof(char)); // 申请临时空间存储单个字符的编码int k = 0;while (DeCodeChar(root, tmp) == '\0'){tmp[k++] = code[i++];}text[j++] = DeCodeChar(root, tmp); // 译码并存储译文free(tmp);                         // 释放临时空间}text[j] = '\0';// 存储译文到文件中FILE *fp_text = fopen("textfile.txt", "w");fprintf(fp_text, "%s", text);fclose(fp_text);// 释放申请的空间free(code);free(text);
}void Print()
{FILE *fp;char buffer[100];fp = fopen("codefile.txt", "r");if (fp == NULL){printf("文件打开失败\n");exit(1);}// 读取数据fgets(buffer, 100, fp);printf("%s", buffer);fclose(fp);fp = fopen("codeprint.txt", "w");for (int i = 0; i < strlen(buffer); i++){fprintf(fp, "%c", buffer[i]);}fclose(fp);
}// 打印哈夫曼树的函数
void PrintHfmTree(FILE *file, HuffmanTree root, int level, char *prefix, int is_last)
{// 如果根节点为空,则返回if (root == NULL){return;}// 打印当前节点的值printf("%s", prefix);printf(is_last ? "└── " : "├── ");printf("(%c:%d)\n", root->character, root->weight);fprintf(file, "%s", prefix);fprintf(file, is_last ? "└── " : "├── ");fprintf(file, "(%c:%d)\n", root->character, root->weight);// 更新前缀char new_prefix[128];sprintf(new_prefix, "%s%s", prefix, is_last ? "    " : "│   ");// 递归打印左右子树PrintHfmTree(file, root->left, level + 1, new_prefix, (root->right == NULL));PrintHfmTree(file, root->right, level + 1, new_prefix, 1);
}void Tree(HuffmanTree root)
{// 打开文件treeprintFILE *file = fopen("treeprint.txt", "w");// 调用打印函数打印哈夫曼树并写入文件PrintHfmTree(file, root, 0, "", 1);// 关闭文件fclose(file);
}HuffmanTree root = nullptr; // 将huffman树根设置为全局变量void Window()
{char choice;char exit_choice;while (1){printf("请选择您的操作:\n");printf("I. 初始化\n");printf("C. 编码\n");printf("D. 译码\n");printf("P. 印代码文件\n");printf("T. 印哈夫曼树\n");printf("E. 退出\n");scanf(" %c", &choice); // 加上空格忽略换行符switch (choice){case 'I':printf("您选择了初始化操作。\n");root = initialization();break;case 'C':if (root == NULL){printf("请先进行初始化操作!\n");break;}printf("您选择了编码操作。\n");EnCode(root);break;case 'D':if (root == NULL){printf("请先进行初始化操作!\n");break;}printf("您选择了译码操作。\n");DeCode(root);break;case 'P':printf("您选择了打印操作。\n");Print();break;case 'T':printf("您选择了打印操作。\n");Tree(root);break;case 'E':printf("您确定要退出吗?按E键确认退出,按其他键返回上级菜单。\n");scanf(" %c", &exit_choice); // 加上空格忽略换行符if (exit_choice == 'E' || exit_choice == 'e'){printf("谢谢使用,再见!\n");return;}break;default:printf("无效的选择,请重新选择。\n");break;}}
}int main()
{Window();return 0;
}

codefile.txt

0001111010

codeprint.txt

0001111010

hfmtree.txt

8
a b c d e f g h
5 29 7 8 14 23 3 11

textfile.txt

abc

treeprint.txt

└── (
:100)├── (
:42)│   ├── (
:19)│   │   ├── (
:8)│   │   │   ├── (g:3)│   │   │   └── (a:5)│   │   └── (h:11)│   └── (f:23)└── (
:58)├── (
:29)│   ├── (e:14)│   └── (
:15)│       ├── (c:7)│       └── (d:8)└── (b:29)

结束语

  因为是算法小菜,所以提供的方法和思路可能不是很好,请多多包涵~如果有疑问欢迎大家留言讨论,你如果觉得这篇文章对你有帮助可以给我一个免费的赞吗?我们之间的交流是我最大的动力!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/9537.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

上亿用户面临风险!小米、WPS等知名安卓应用竟藏有“文件覆盖”漏洞

Google Play商店中的几款热门安卓应用程序容易受到与路径遍历相关的漏洞攻击&#xff0c;该漏洞的代号为“Dirty Stream”攻击&#xff0c;恶意应用程序可能会利用此漏洞覆盖易受攻击的应用程序主目录中的任意文件。 微软威胁情报团队的Dimitrios Valsamaras在周三发布的一份报…

elementui- button按钮自适应大小

<el-button type"primary" class"daochu" click"download">导出</el-button> .daochu {width: calc(100vw * 80 / 1920);height: calc(100vw * 30 / 1920);font-size: calc(100vw * 13 / 1920); } 效果图&#xff1a;

做一个属于自己的软件-pyside6快速上手教程

首先环境需要安装python3和pip&#xff0c;软件使用pycharm&#xff0c;安装也都很简单 首先需要安装pyside6,在终端执行&#xff1a; pip install pyside6 然后进入可视化编辑界面 pyside6-designer 进入后创建即可 可以从左侧点击鼠标拉组件进入到中间的工作区&#xff…

android基础-服务

同样使用intent来传递服务 oncreate是服务第一次启动调用&#xff0c;onStartCommand是服务每次启动的时候调用&#xff0c;也就是说服务只要启动后就不会调用oncreate方法了。可以在myservice中的任何位置调用stopself方法让服务停止下来。 服务生命周期 前台服务类似于通知会…

人人都是开发者?Baidu Comate智能代码助手改变你传统的编程之路

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 引入一、人人都是开发者二、Baidu Comate 智能编码助手2.1 Baidu Comate 是什么&#xff1f;2.2 Baidu Comate 支持那…

FANUC机器人工具坐标偏移的用法

一、工具坐标偏移的使用场景 在机器人位置不改变的情况下&#xff0c;工业机器人使用默认工具坐标系示教的一系列运动点位&#xff0c;要保持原本点位位置不变的情况下&#xff0c;改变机器人工具坐标的参数&#xff0c;就要用到机器人坐标转化的功能。在FANUC机器人上体现为机…

OSPF综合实验(超详细易懂)(HCIP)

1、拓扑信息 2、需求分析 3、IP规划 4、配置 5、测试 1、拓扑信息 2、需求分析 R4为ISP&#xff0c;其上只能配置I地址&#xff1b; R4与其他所有直连设备间均使用公…

【Linux系列】tail查询使用

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

在数字化时代保持企业财务管理的持续技术创新

根据全球市场调查&#xff0c;在现阶段企业如果还不更新自身的商业运作模式&#xff0c;企业的业务可能会一点一点丧失市场竞争力。技术进步是所有行业发展的明显趋势。许多年轻的初创企业具有创新管理思维、精益求精的决策流程和现代化的商业基础设施&#xff0c;这些顺应时代…

转行网络安全的重要建议,助你顺利入门

目录 为什么写这篇文章 为什么我更合适回答这个问题 先问自己3个问题 1.一定要明确自己是否是真喜欢&#xff0c;还是一时好奇。 2.自学的习惯 3.选择网安、攻防这行的目标是什么&#xff1f; 确认无误后&#xff0c;那如何进入这个行业&#xff1f; 1.选择渗透测试集中…

重生奇迹mu烈火剑带什么技能

在重生奇迹mu游戏中&#xff0c;35级是每个职业的分水岭&#xff0c;只要到了35级&#xff0c;三职业都可以学习自己的高级技能&#xff0c;道士可以召唤自己的大狗&#xff0c;法师拥有冰咆哮&#xff0c;战士就是咱们今天要说的烈火剑法&#xff0c;这三种技能都需要玩家自己…

Chromium 调试指南2024 Windows11篇-安装代码编辑器VSCode(三)

1. 前言 在进行Chromium项目的开发和调试之前&#xff0c;安装适合的代码编辑器是至关重要的一步。本文将介绍如何安装Visual Studio Code&#xff0c;这是一个功能强大且广受开发者欢迎的代码编辑器。通过正确安装Visual Studio Code&#xff0c;我们将为后续的Chromium代码编…

张驰咨询六西格玛黑带培训,上海开班,质量精英的摇篮!

一、课程背景与意义 在当今竞争激烈的市场环境中&#xff0c;企业要想立于不败之地&#xff0c;就必须不断提升自身的核心竞争力。而六西格玛作为一种先进的质量管理工具和方法&#xff0c;已经被越来越多的企业所采纳。通过六西格玛黑带培训&#xff0c;学员们可以系统地掌握…

Windows上安装Seata服务

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 Seata 是一款开源的…

IDEA创建SpringBoot项目仅支持JDK17及以上版本的解决方案

背景 IDEA创建SpringBoot项目仅支持JDK17及以上版本的解决方案 原因 由于SpringBoot3.x.x版本的到来&#xff0c;官方称将来17及以上版本将成为主流&#xff0c;但是大多数的项目仍然使用JDK8。 1.解决方案一 将https://start.spring.io/或者http://start.springboot.io/替…

MBR与GPT分区表

文章目录 MBR分区表MBR分区表结构MBR分区表项查看U盘的分区表信息查看系统中所有磁盘的分区类型获取分区表信息 GPT分区表保护性MBRGPT分区表头格式GPT分区表项格式分区类型分区属性分区表项内容 MBR分区表 CHS &#xff1a;磁头&#xff08;Heads&#xff09;、柱面(Cylinder…

羊大师:当代年轻人如何应对压力

羊大师&#xff1a;当代年轻人如何应对压力 当代年轻人面临各种压力&#xff0c;包括工作、学习、人际关系、经济等方面的压力。以下是一些建议&#xff0c;帮助年轻人应对这些压力&#xff1a; 认识并接受压力&#xff1a; 首先要认识到压力是生活中不可避免的一部分。 尝试…

盘点2024年4月Sui生态发展,了解Sui近期成长历程!

2024年4月是Sui的活动月&#xff0c;10–11日聚焦全世界目光的Sui Basecamp会议如约而至&#xff0c;来自65个国家的超过1100人参加了这场在巴黎举办的Sui全球性活动。21日&#xff0c;Sui首届全球黑客松正式开放注册。同时&#xff0c;20日-28日&#xff0c;七天四场大陆地区重…

【数据结构】单链表专题详细分析

与其临渊羡鱼&#xff0c;不如退而结网。&#x1f493;&#x1f493;&#x1f493; 目录 •✨说在前面 &#x1f34b;知识点一&#xff1a;什么是链表&#xff1f; • &#x1f330;1.链表的概念 • &#x1f330;2.链表的结构 • &#x1f330;3.链表的分类 &#x1f34b;…

9.为什么有时候会“烫烫烫”——之函数栈桢

目录 1. 什么是函数栈帧 2. 理解函数栈帧能解决什么问题呢&#xff1f; 3. 函数栈帧的创建和销毁解析 3.1 什么是栈&#xff1f; 3.2 认识相关寄存器和汇编指令 3.3 解析函数栈帧的创建和销毁 小知识&#xff1a;烫烫烫~ Q&A 1. 什么是函数栈帧 我们在写C语言代码…