Huffman 编码与解码系统

前言:个人实现的一个 Huffman 编码与解码系统,该系统支持键盘、csv文件(即Excel文件)以及txt文本文件的输入。另外两个文件会放在后文中,请自己创建相应的文件并放在同一路径下。接下来就开始对我的代码进行讲解,个人建议有一定程序设计基础的读者先自行学习 Huffman 编码相关的知识再来看,这里就不做赘述了。

存储结构

Huffman二叉树结点结构体

采用静态三叉链表存储 Huffman 二叉树,这样更有助于建立结点之间的信息。其结点的结构体如下,因为该结构体同时存储了结点的双亲和左右孩子结点的下标信息,而且采用的是值存储而非指针指向的方式,所以名为静态三叉链表。data 域本来不想初始化的,但是 Visual Studio 2019 一直警告我初始化,所以还是随便初始化了一下…

注意:在结构体内初始化仅支持 C++11 以上的版本,若出现报错请将编译器设置成支持 C++11的版本。

struct HuffmanNode // Huffman结点结构
{
	char data = '\0'; // 存储的字符
	int weigth = 0; // 权值
	int parent = -1; // 双亲结点的下标
	int lchild = -1; // 左孩子的下标
	int rchild = -1; // 右孩子的下标
};

字符编码结构体

Huffman 树创建完成后,每个字符都应该有其自己的编码,所以要另用结构体存储,其结构体如下。

struct HuffmanCodeTable // Huffman 编码表结构
{
	char data; // 存储的字符
	char code[MAXLEN]; // 该字符的 Huffman 编码
};

全局变量

int InputCharCnt = 0; // 统计字符集中的字符个数
int HuffmanTreeNodeCnt; // 统计 Huffman 树的结点数
HuffmanNode HN[MAXLEN]; // 结点数组
HuffmanCodeTable HCT[MAXLEN]; // Huffman 编码表

由于在创建 Huffman 二叉树后,所有原字符集中的字符都会变为叶子结点,所以要用一个变量记录原字符集中的字符个数。同时,创建完的 Huffman 二叉树的结点个数也需要用一个新变量来存储。然后定义两个结构体数组分别存放所有字符的信息和编码。

程序框架

整个系统的运行过程:

  1. 选择导入给定的字符集数据的方式(三种):
  • 通过“字符”“权值”的方式从键盘输入;
  • 通过 csv 文件导入每一个字符和其权值;
  • 通过 txt 文本文件中的内容读取,然后统计字符及其权值;
  • 将字符及其权值的信息存储在 HuffmanNode 结点结构体中;
  1. 构建 Huffman 树:
  • 在字符集中搜寻权值最小且尚未分配双亲结点的两个结点;
  • 新建一个结点,其权值为第一步中两个结点权值之和,其左右孩子结点设置为第一步中的两个结点。其中下标小的为左孩子,这样更方便输出编码表。
  • 将新建立的结点放入字符集中,注意此时该结点尚未分配双亲结点,所以也处于第一步的搜寻队列中;
  • 重复前三个步骤直到只剩下一个尚未分配双亲的结点,其就为 Huffman 二叉树的根结点,令其双亲结点为-1,Huffman 二叉树构建完毕。
  1. 创建 Huffman 编码表
  • 遍历叶结点,检查其是否有双亲结点;
  • 若存在双亲结点,查看该结点是左孩子还是右孩子。若是左孩子则在叶结点编码字符串中加入0,若是右孩子加入1;
  • 往上定位其双亲结点,重复前两个步骤,直到双亲结点为-1即不存在双亲结点为止,也说明此时到达了根结点;
  • 由于是由下至上进行的编码,所以在要将得到编码字符串倒置才算是字符的编码,该叶结点编码完毕。
  • 遍历下一个叶结点;
  1. Huffman 编码表输出

  2. 进行 Huffman 解码

  • 输入相应待解码的二进制码。
  • 设置追踪标记,标记根结点。
  • 设置指针读取二进制码字符串的字符。读到0,追踪标记追踪左孩子,读到1追踪右孩子。指针后移。
  • 重复第三步直到左右孩子均为-1,即叶结点为止。此时输出该叶结点的所代表的字符,即解码出第一个字符。然后追踪标记回到根结点,指针后移,开始解码下一个字符。
  • 重复第三第四步直到二进制码字符串读取完,解码完毕。

数据文件

csv文件数据

在这里插入图片描述

txt文本

这是一整行的文本内容,分行的话,在代码中要做另外的处理。

A common thread is emerging from the impeachment bombshells, court fights and multiple scandals all coming to head this week inside the one-year mark to the next general election. It’s a picture of a President and his men who subscribe to a staggeringly broad interpretation of executive power and have no reservations about using it often for domestic political ends.The trend, which threatens to recast the conception of the presidency shared by America’s founders, shone through the first witness testimony released from the impeachment inquiry Monday.One former ambassador, Marie Yovanovitch, who apparently had been in the way of Trump’s plans to get dirt from Ukraine on former Vice President Joe Biden, was shocked when the President told his counterpart in Kiev on a phone call that the official US diplomatic representative to his country was “bad news.”

完整代码

上面没太看懂的地方不要紧,注释写的很详细,仔细看看代码理解一下就好了。另外有心的读者可以关注一下 csv 读入后的处理,我一直觉得我的处理办法并不是一个很好的方法,因为我也是百度了一下才知道可以读取 csv 的。所以有经验的同学,欢迎在评论区给出详细的更好用代码,大家互相学习才能进步。

#include <iostream>
#include <string>
#include <iomanip>
#include <fstream>
#include <cstdlib>
using namespace std;

#define MAXLEN 1024

struct HuffmanNode // Huffman 结点结构
{
	char data = '\0'; // 存储的字符
	int weigth = 0; // 权值
	int parent = -1; // 双亲结点的下标
	int lchild = -1; // 左孩子的下标
	int rchild = -1; // 右孩子的下标
};

struct HuffmanCodeTable // Huffman 编码表结构
{
	char data; // 存储的字符
	char code[MAXLEN]; // 该字符的 Huffman 编码
};

int InputCharCnt = 0; // 统计字符集中的字符个数
int HuffmanTreeNodeCnt; // 统计 Huffman 树的结点数
HuffmanNode HN[MAXLEN]; // 结点数组
HuffmanCodeTable HCT[MAXLEN]; // Huffman 编码表

void InputByKeyoard() // 从键盘读取数据
{
	char data; // 输入的字符
	int weight; // 输入的权值
	cout << "请在一行中按照“字符”,“权值”的格式输入数据,并以回车作为输入结束:" << endl;

	cin.get(); // 过滤回车
	while (cin.peek() != '\n') // 读到文件结尾结束循环
	{
		cin >> data >> weight;
		HN[InputCharCnt].data = data;
		HN[InputCharCnt].weigth = weight;
		++InputCharCnt;
	} // i 1 l 2 o 3 v 4 e 5 u 6
}

void LoadByCSV() // 从 csv 文件中读取数据
{
	char line[MAXLEN];
	ifstream file;
	file.open("weight.csv", ios::in);

	while (file.getline(line, MAXLEN))
	{
		HN[InputCharCnt].data = line[0]; // csv 读取到的也是字符串,且第一个为字符,第二个为空格
		for (int i = 2; line[i] != '\0'; ++i) // 从 line[2] 开始才是权值
			HN[InputCharCnt].weigth = HN[InputCharCnt].weigth * 10 + line[i] - '0';
		// cout << HN[InputCharCnt].data << ' ' << HN[InputCharCnt].weigth << endl;
		++InputCharCnt;
	}

	file.close();
}

void LoadByTXT() // 从 txt 文件读取数据
{
	char line[MAXLEN];
	int isRecord = 0;

	ifstream file;
	file.open("huffman.txt", ios::in);
	file.getline(line, MAXLEN);

	for (int i = 0; line[i] != '\0'; ++i) // 遍历读取到的字符串
	{
		isRecord = 0; // isRecord = 1 表明记录过
		for (int j = 0; j < InputCharCnt; ++j) // 遍历 Huffman 结点数组
		{
			if (HN[j].data == line[i]) // 已在结点数组中记录该字符
			{
				++HN[j].weigth; // 权值+1
				isRecord = 1;
				break;
			}
		}
		if (!isRecord)// 未在结点数组中记录该字符
		{
			HN[InputCharCnt].data = line[i]; // 记录该字符
			++HN[InputCharCnt].weigth; // 权值+1
			++InputCharCnt; // 字符个数+1
		}
	}

	file.close();
}

void SwitchLoad(int choice) // 选择加载数据的方式
{
	switch (choice)
	{
		case 1:
			InputByKeyoard();
			break;
		case 2:
			LoadByCSV();
			break;
		case 3:
			LoadByTXT();
			break;
		default:
			break;
	}
}

int SearchMinWeight(int newNode) // 寻找权值最小且尚未分配双亲结点的结点
{
	int Min = MAXLEN;
	int minNum = MAXLEN - 1;
	for (int i = 0; i < HuffmanTreeNodeCnt; ++i)
	{
		if (HN[i].weigth < Min && HN[i].parent == -1) // 权值更小且尚未分配双亲结点
		{
			Min = HN[i].weigth;
			minNum = i;
		}
	}
	HN[minNum].parent = newNode; // 标记该结点双亲结点

	return minNum;
}

void CreatHuffmanTree() // 构建 Huffman 树
{
	int lchild, rchild; // 左孩子,右孩子
	HuffmanTreeNodeCnt = InputCharCnt; // InputCharCnt 是字符集中的字符个数,前者是 Huffman 树结点个数
	for (int i = InputCharCnt; i < 2 * InputCharCnt - 1; ++i) // 循环建立 n - 1 个新的 Huffman 结点
	{
		lchild = SearchMinWeight(i); // 寻找权重最小的两个结点
		rchild = SearchMinWeight(i);
		if (lchild > rchild) // 下标小的做左孩子
		{
			int temp = rchild;
			rchild = lchild;
			lchild = temp;
		}
		HN[i].lchild = lchild; // 标记左孩子
		HN[i].rchild = rchild; // 标记右孩子
		HN[i].weigth = HN[lchild].weigth + HN[rchild].weigth; // 双亲结点的权重为孩子权重之和
		++HuffmanTreeNodeCnt;
	}
}

void ReverseCode(char code[], int length) // 倒转由下至上得到的字符编码
{
	char temp;
	for (int i = 0; i < length / 2; ++i)
	{
		temp = code[i];
		code[i] = code[length - i - 1];
		code[length - i - 1] = temp;
	}
}

void CreatHuffmanCodeTable() // 创建 Huffman 编码表
{
	for (int i = 0; i < InputCharCnt; ++i) // 遍历字符集
	{
		int len = 0;
		int parent, child = i;
		HCT[i].data = HN[i].data;

		while (HN[child].parent != -1) // 不断地跟踪双亲结点直到根结点为止,只有根结点的双亲为-1
		{
			parent = HN[child].parent;
			if (HN[parent].lchild == child) // 该结点是左孩子时编码为0
				HCT[i].code[len++] = '0';
			else if (HN[parent].rchild == child) // 该结点是右孩子时编码为1
				HCT[i].code[len++] = '1';
			child = parent; // 往双亲结点继续编码
		}

		ReverseCode(HCT[i].code, len);
	}
}

void PrintHuffmanCodeTable() // 打印 Huffman 编码表
{
	cout << setiosflags(ios::left) // 打印表格标题
		<< setw(5) << "Num"
		<< setw(10) << "Data"
		<< setw(10) << "Weigth"
		<< setw(10) << "Parent"
		<< setw(10) << "Lchild"
		<< setw(10) << "Rchild"
		<< setw(15) << "Code"
		<< "\n" << endl;

	for (int i = 0; i < InputCharCnt; i++) // 输出所有字符集中的结点信息
		cout << setiosflags(ios::left)
		<< setw(5) << i
		<< setw(10) << HN[i].data
		<< setw(10) << HN[i].weigth
		<< setw(10) << HN[i].parent
		<< setw(10) << HN[i].lchild
		<< setw(10) << HN[i].rchild
		<< setw(15) << HCT[i].code
		<< endl;
	cout << endl;

	cout << setiosflags(ios::left) // 打印表格标题
		<< setw(5) << "Num"
		<< setw(10) << "Data"
		<< setw(10) << "Weigth"
		<< setw(10) << "Parent"
		<< setw(10) << "Lchild"
		<< setw(10) << "Rchild"
		<< setw(15) << "Code"
		<< "\n" << endl;

	for (int i = InputCharCnt; i < HuffmanTreeNodeCnt; i++) // 输出所有空结点的信息
		cout << setiosflags(ios::left)
		<< setw(5) << i
		<< setw(10) << "NULL"
		<< setw(10) << HN[i].weigth
		<< setw(10) << HN[i].parent
		<< setw(10) << HN[i].lchild
		<< setw(10) << HN[i].rchild
		<< setw(15) << "NULL"
		<< endl;
}

void HuffmanDecode() // 利用 Huffman 树进行解码
{
	cout << "请输入要解码的二进制码:";
	char Binary[MAXLEN];
	char* s = Binary;
	cin >> Binary;
	cout << "解码后的数据为:";

	int track = HuffmanTreeNodeCnt - 1;
	while (*s != '\0') // 用 *s 代替数组下标移动
	{
		if (*s == '0')
			track = HN[track].lchild;
		else
			track = HN[track].rchild;
		if (HN[track].lchild == -1) // 只用判断一个孩子,因为只要没左孩子必然就是叶结点也即字符结点
		{
			cout << HN[track].data;
			track = HuffmanTreeNodeCnt - 1;
		}
		++s;
	}
	cout << endl;
}

void SwitchOperation(int choice) // 选择操作
{
	switch (choice)
	{
		case 1:
			PrintHuffmanCodeTable();
			break;
		case 2:
			HuffmanDecode();
			break;
		case 3:
			exit(0);
		default:
			break;
	}
}

int main(void)
{
	int choice;
	cout << "欢迎使用本Huffman编码系统" << endl;
	cout << "请选择您字符集的加载方式:" << endl;
	cout << "1--键盘数据输入\n2--加载CSV文件\n3--加载txt文本文件" << endl;
	cin >> choice;
	SwitchLoad(choice);
	CreatHuffmanTree(); // 创建 Huffman 树
	CreatHuffmanCodeTable(); // 创建 Huffman 编码表
	cout << "加载成功!请输入您接下来的操作:" << endl;

	while (1)
	{
		cout << "1--打印Huffman编码表" << endl;
		cout << "2--进行Huffman解码" << endl;
		cout << "3--不操作了,退出程序" << endl;
		cin >> choice;
		SwitchOperation(choice);
	}
}


希望本篇博客能对你起到一点的帮助作用,也希望能动动小手点个赞,这样我才能知道,我的付出没有白费啦~~。

  • 5
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值