数据结构实验三:哈夫曼编/译码器

实验题目描述

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值