实验题目描述
1.基本要求
利用哈夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本。但是,这要求在发送端通过一个编码系统对待传数据预先编码,在接收端将传来的数据进行译码(复原)。对于双工信道(即可以双向传输信息的信道〉,每端都需要一个完整的编/译码系统。试为这样的信息收发站写一个哈夫曼码的编/译码系统。
2.参考
利用教科书《数据结构》(紫书)例6-2 中的数据调试程序。
3.实验测试用例
用下表给出的字符集和频度的实际统计数据建立哈夫曼树,并实现以下报文的编码和译码:“THIS PROGRAM IS MY FAVORITE ”。
实验过程
1.开发环境描述
1.开发环境:Visual Studio 2019
2.操作系统:win10
2.题目分析及解题思路
- 问题抽象:
由于字符集权值已经给出,故可以直接由字符集权值建立哈夫曼树,再由已经建成字符集权值生成每个字符的编码; - 问题解决思路:
①从文件中导入字符集权值,存入哈夫曼树的叶子结点中;
②遍历每个叶子结点。在所有结点中寻找最小的两个结点,两个结点生成一个根节点,然后不断重复这个操作,直到最后生成一个完整的哈夫曼树;
③设定哈夫曼树的左孩子是“0”,右孩子为“1”,从根节点开始遍历,遍历到叶子结点,从中可以得出每一个叶子结点的哈夫曼编码;
④由用户输入的字符串得到编码,或是从用户输入的编码得到字符串,实现一个双向的编码、译码;
⑤本人补充了基于mqtt协议与单片机的哈夫曼编码译码系统设计,实现了两台电脑通过mqtt这座“桥梁”实现双向编码译码 - 确定输入输出:
Input:
①用户输入的对应功能的选择编号(1打印字符集的权值以及字符集的编码;2. 将字符串翻译为编码;3. 将编码翻译为字符串)
②用户输入模式(手动输入或由文件导入)
③用户输入的待编码的字符串 or 用户输入的待翻译代码
Output:
①打印字符集的权值以及字符集的编码
②编码 or 译码
3.实验代码
①处理交互的Interaction.h文件
#ifndef INTERACTION_H
#define INTERACTION_H
void Initialize(); //初始化
int Intercommand(); //输入选择模式
int Get_choose(); //得到y or n
void Is_refresh(); //检查页面是否更新
int Get_int(); //我编写的判断 输入是否为整形的函数,
char* Get_Capital_letter(); //得到大写字母
char* Get_01(); //得到01码
#endif // !INTERACTION_H
处理交互的Interaction.cpp文件
#include"Interaction.h"
#include<iostream>
#include<stdio.h>
#include<conio.h>
#include<math.h>
#include<string>
using namespace std;
#define backColor 7
#define textColor 1
enum Color
{
black, blue, green, lakeBlue, red, purple, yellow, white, gray
};
void Initialize()
{
system("cls");
//设置背景颜色
char command[9] = "color 07"; //默认颜色
command[6] = '0' + backColor; //将backColor变量改为字符型(背景颜色设置为7,为白色)
command[7] = '0' + textColor; //将textColor变量改为字符型 (背景颜色设置为1,为蓝色)
system(command); //调用系统函数
//设置菜单
printf(" **************哈夫曼树编码器与译码器***********\n");
printf(" | \t0. 退出 |\n");
printf(" | \t1. 打印字符集的权值以及字符集的编码 |\n");
printf(" | \t2. 将字符串翻译为编码 |\n");
printf(" | \t3. 将编码翻译为字符串 |\n");
printf(" ***********************************************\n");
}
int Intercommand()
{
cout << "请输入您的选择:";
int input;
while (1)
{
int n = Get_int();
if (n >= 0 && n < 4)
{
input = n;
break;
}
cout<<"输入错误,请重输:";
}
return input;
}
int Get_choose()
{
char ch[21] = { 0 };
int flag;
int i = 0;
while (1)
{
fflush(stdin);
scanf_s("%s", ch, 20);
if (ch[i] == 'y' || ch[i] == 'Y')
{
flag = 1;
break;
}
else if (ch[i] == 'n' || ch[i] == 'N')
{
flag = 0;
break;
}
else {
cout << "输入错误,请重新输入" << endl;
}
}
return flag;
}
char* Get_Capital_letter()
{
char s[100] ;
int i ;
cin.get();
label2:
i = 0;
fflush(stdin);
gets_s(s);
while (s[i])
{
if (s[i] >= 65 && s[i] <= 90)
i++;
else if (s[i] == 32)
{
s[i++] = 32;
}
else if(s[i]==13){
break;
}
else {
cout << "您输入非大写字母的字符,无法进行翻译,请重新输入:" << endl;
goto label2;
}
}
s[i] = '\0';
return s;
}
char* Get_01()
{
char s[300] = { 0 };
int i;
label3:
i = 0;
fflush(stdin);
scanf_s("%s", s, 300);
while (s[i])
{
if (s[i] =='0'||s[i]=='1')
i++;
else if (s[i] == 13) {
break;
}
else {
cout << "您输入非01的字符,无法进行翻译,请重新输入:" << endl;
goto label3;
}
}
s[i] = '\0';
return s;
}
void Is_refresh()
{
printf("\n");
cout << "----------------------------------------------------"<<endl;
cout << "输入任意字符完成查看,回到菜单页面:" << endl;
_getch();
}
int Get_int()
{
int num = 0,i=0; //int
char ch[11] = { 0 };
label1:
num = 0;
scanf_s("%s", ch,10);
i = 0;
if (ch[0] == 45 || ch[0] == 43) //"+""-"
{
i++;
}
while (ch[i])
{
if (ch[i] < '0' || ch[i]>'9')
{
cout << "输入错误,请输入数字:" << endl;
goto label1;
}
else {
num = num * 10 + (int)ch[i] - 48;
}
i++;
}
if (ch[0] == 45)
{
return -num;
}
else
{
return num;
}
}
② 处理文件读取的file.h
#ifndef FILE_H
#define FILE_H
#include<fstream>
#include<string>
#include"Huffman.h"
using namespace std;
void load_weightdata_file(HTNode hnode[]);//加载每个字符的权值
char* load_code(); //载入待翻译的代码
char* load_text(); //载入待编码的文本
#endif // !FILE_H
处理文件读取的file.cpp
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include <fstream>
#include <string>
#include <vector>
#include <windows.h>
#include"file.h"
#include"Huffman.h"
using namespace std;
#define N 50
int g_flag = 0;
//用该函数读取每个字符对应的权值
void load_weightdata_file(HTNode hnode[])
{
FILE* fp;
int i = 0;
fp = fopen("weightdata.txt", "r");
if (fp == NULL)
{
cout<<"打开文件失败";
}
while (!feof(fp))
{
fscanf(fp, "%c", &(hnode[i].data));
fscanf(fp, "%d", &(hnode[i].weight));
fseek(fp, 2, SEEK_CUR);
i++;
if (i == N - 2)
break;
}
g_flag = 1;
fclose(fp);
}
char* load_code() {
ifstream in("code.txt");
char buffer[256];
if (!in.is_open()) {
cout << "加载文件错误" << endl;
return NULL;
}
cout << "载入编码文件" << endl;
in.getline(buffer, 200, ' ');
return buffer;
}
char* load_text()
{
ifstream in("text.txt");
char buffer[256];
if (!in.is_open()) {
cout << "加载文件错误" << endl;
return NULL;
}
cout << "载入待编码字段" << endl;
in.getline(buffer, 100);
return buffer;
}
//用该函数输出各个字符的编码 字符 对应编码
int Print_HuffMan_file()
{
FILE* fp;
char data[50], c;
int d;
fp = fopen("Huffman.txt", "r");
if (fp == NULL )
{
printf("打开文件哈夫曼编码错误。\n");
return -1;
}
while (1)
{
fscanf(fp, "%c%d%s", &c, &d, data);
if (feof(fp))
break;
printf("%c\t%d\t%s\n", c, d, data);
fseek(fp, 2, SEEK_CUR);
}
fclose(fp);
}
③处理哈夫曼编码与译码的Huffman.h
#ifndef HUFFMAN_H
#define HUFFMAN_H
#include<string>
#define N 50 /*叶子结点数*/
#define M 2*N-1 /*树中结点总数*/
using namespace std;
/*实现思路:
一.先由存入文件中的各字符的频率计算出各字符的哈夫曼树编码,由此得到哈夫曼结点
哈弗曼结点包含了下列信息:1.该结点对应的编码(存入到HCode中的cd中) 2.权重(就是频率)
3.父母结点,左子结点,右子结点
二.输入字符串,由于已经计算了每个字符对应的编码,故直接把每个字符翻译出来就好了*/
typedef struct {
char data; //定义结点值,此处为ABCD.....
unsigned int weight; //定义每个节点的权重
unsigned int parent, lchild, rchild;
}HTNode,*HuffmanTree;
typedef struct {
char cd[N];
int start;
}HCode; //定义
void Print_weightdata(HTNode hnode[],int n=27); //输出权值
void CreateHT(HTNode ht[], int n); //创建哈夫曼树,将树结点数组转换为树
void CreateHCode(HTNode ht[], HCode hcd[], int n); //创建哈夫曼树编码
void DispHCode(HTNode ht[], HCode hcd[], int n); //打印字符集编码
void Encode(char* s, HTNode ht[], HCode hcd[], int n);//实现编码并打印
void DeCode(string s, int n, HTNode h[]);//实现译码并打印
#endif // !HUFFMAN_H
处理哈夫曼编码与译码的Huffman.cpp
#include"Huffman.h"
#include<iostream>
#include<stdlib.h>
using namespace std;
void CreateHT(HTNode ht[], int n)
{
int i, j, k, lnode, rnode;
double min1, min2;
for (int i = 0; i < 2 * n - 1; i++)
ht[i].parent = ht[i].lchild = ht[i].rchild = -1; //将整根树进行一个初始化
for (i = n; i < 2 * n - 1; i++) {
min1 = min2 = 10000;
lnode = rnode = -1;
for (k = 0; k < i; k++)
{
//循环,找到最小的结点
if (ht[k].parent == -1) {
if (ht[k].weight < min1) {
min2 = min1; rnode = lnode;
min1 = ht[k].weight; lnode = k;
}
else if (ht[k].weight < min2) {
min2 = ht[k].weight; rnode = k;
}
}
}
ht[i].weight = ht[lnode].weight + ht[rnode].weight;
ht[i].lchild = lnode; ht[i].rchild = rnode;
ht[lnode].parent = i; ht[rnode].parent = i;
} //完成树的创建
}
void CreateHCode(HTNode ht[], HCode hcd[], int n) { /*创建哈夫曼编码*/
int i, f, c;
HCode hc;
for (i = 0; i < n; i++) {
hc.start = n; c = i;
f = ht[i].parent;
while (f != -1) { //如果不是根节点,从当前结点循环,直到编码完成
if (ht[f].lchild == c) //如果为该节点的左孩子
hc.cd[hc.start--] = '0';
else
hc.cd[hc.start--] = '1';
c = f; f = ht[f].parent;
}
hc.start++;
hcd[i] = hc;
}
}
void DispHCode(HTNode ht[], HCode hcd[], int n) { /*显示各个字符的哈夫曼编码*/
int i, k;
int sum = 0, m = 0;
cout << "该字符集的哈夫曼编码如下: " << endl;
for (i = 0; i < n; i++) {
cout << ht[i].data << " ";
for (k = hcd[i].start; k <= n; k++) {
cout << hcd[i].cd[k];
}
printf("\n");
}
}
/*将字符串进行编码*/
void Encode(char* s, HTNode ht[], HCode hcd[], int n) { /*显示字符串s的哈夫曼编码*/
int i, j, k;
for (i = 0; s[i] != '\0'; i++)
cout << s[i];
cout << "的哈夫曼编码是: " << endl;
i = j = 0;
while (s[j] != '\0') {
if (s[j] == ht[i].data) {
for (k = hcd[i].start; k <= n; k++)
cout << hcd[i].cd[k];
j++;
i = 0;
}
else
i++;
}
cout << endl;
}
/*将编码进行翻译,翻译为字符串*/
void DeCode(string s, int n, HTNode h[])
{
int i = 0, j = 0, lchild = 2 * n - 2, rchild = 2 * n - 2;
while (s[i] != '\0') {
if (s[i] == '0') {
lchild = h[lchild].lchild;
rchild = j = lchild;
}
if (s[i] == '1') {
rchild = h[rchild].rchild;
lchild = j = rchild;
}
if (h[lchild].lchild == -1 && h[rchild].rchild == -1) {
cout << h[j].data;
lchild = rchild = 2 * n - 2;
j = 0;
}
i++;
}
}
void Print_weightdata(HTNode hnode[],int n)
{
int i;
cout << "该字符集的权值如下: " << endl;
for (i = 0; i < n; i++) {
cout << hnode[i].data << " ";
cout << hnode[i].weight << endl;
}
}
④实现逻辑功能的源.cpp
#include<iostream>
#include<string.h>
#include<stdio.h>
#include<conio.h>
#include"Huffman.h"
#include"Interaction.h"
#include"file.h"
using namespace std;
#define N 50 /*叶子结点数*/
#define M 2*N-1 /*树中结点总数*/
int n = 27;
int main() {
int choose ; //选择变量
HTNode ht[M];
HCode hcd[N];//定义哈夫曼树结点和哈夫曼编码
load_weightdata_file(ht);
CreateHT(ht, n);
CreateHCode(ht, hcd, n);
char s[100] = { 0 };
char input_code[300] = { 0 };
int y_or_n = 0;
while (1)
{
Initialize();
choose = Intercommand();
switch (choose)
{
case 0:
exit(0);
case 1:
/*进行权重输出*/
Print_weightdata(ht);
//进行编码输出
DispHCode(ht, hcd, n);
//检测用户是否查看完毕并进行页面的刷新
Is_refresh();
break;
case 2:
/*文件导入或者手动输入字符串*/
cout << "请问您要手动输入字符串还是通过文件导入需要编码的字符串,文件输入请按y,手动输入请按n:";
fflush(stdin);
getchar();
y_or_n = Get_choose();
if (y_or_n == 0)
{
cout << "请输入字符串:" << endl;
fflush(stdin);
strcpy_s(s, Get_Capital_letter());
//gets_s(s);
Encode(s, ht, hcd, n);
}
else if (y_or_n == 1)
{
strcpy_s(s, load_text());
Encode(s, ht, hcd, n);
}
/*检测用户是否查看完毕并进行页面的刷新*/
Is_refresh();
break;
case 3:
/*文件导入或手动输入编码*/
cout << "请问您要手动输入编码还是通过文件导入需要译码的编码,文件输入请按y,手动输入请按n:";
fflush(stdin);
getchar();
y_or_n = Get_choose();
if (y_or_n == 0)
{
cout << "请输入编码:" << endl;
fflush(stdin);
strcpy_s(input_code, Get_01());
DeCode(input_code, n, ht);
}
else if (y_or_n == 1)
{
strcpy_s(input_code, load_code());
DeCode(input_code, n,ht);
}
/*检测用户是否查看完毕并进行页面的刷新*/
Is_refresh();
break;
}
}
return 0;
}
4.文件
- Huffman.txt内容
111
A 1010
B 100000
C 00000
D 10110
E 010
F 110011
G 100001
H 0001
I 0110
J 1100001000
K 11000011
I 10111
M 110010
N 0111
O 1001
P 100010
Q 1100001001
R 0010
S 0011
T 1101
U 00001
V 1100000
W 110001
X 1100001010
Y 100011
Z 1100001011 - weightdata.txt内容
186
A 64
B 13
C 22
D 32
E 103
F 21
G 15
H 47
I 57
J 1
K 5
L 32
M 20
N 57
O 63
P 15
Q 1
R 48
S 51
T 80
U 23
V 8
W 18
X 1
Y 16
Z 1 - text.txt内容
THIS IS MY FAVORITE PROGRAM
联系我
如果对代码有改进想法的或者需要源代码的,可联1274669808@qq.com