基于哈夫曼树的数据压缩算法

一、实验目的

1.掌握哈夫曼树的构造算法。

2.掌握哈夫曼编码的构造算法。

二、实验内容

问题描述

输入一串字符串,根据给定的字符串中字符出现的频率建立相应的哈夫曼树, 构造哈夫曼编码表,在此基础上可以对压缩文件进行压缩(即编码),同时可以对 压缩后的二进制编码文件进行解压(即译码)。 输入要求 多组数据,每组数据 1 行,为一个字符串(只考虑 26 个小写字母即可 )。当 输入字符串为“0”时,输入结束 输出要求 每组数据输出 2n+3 行(n 为输入串中字符类别的个数)。第 1 行为统计出来 的字符出现频率(只输出存在的字符,格式为:字符:频度),每两组字符之间用一 个空格分隔,字符按照 ASCI 码从小到大的顺序排列。第 2 行至第 2n 行为哈夫 曼树的存储结构的终态( 如主教材 139 页表 5.2( b),一行当中的数据用空格分 隔)。第 2n+1 行为每个字符的哈夫曼编码(只输出存在的字符。格式为:字符:编码 ), 每两组字符之间用一个空格分隔,字符按照 ASCI 码从小到大的顺序排列。第 2n+2 行为编码后的字符串,第 2n+3 行为解码后的字符串(与输入的字符串相同)

输入样例

aaaaaaabbbbbccdddd

 输出样例

a:7 b:5 c:2 d:4

1 7 7 0 0

2 5 6 0 0

3 2 5 0 0

4 4 5 0 0

5 6 6 3 4

6 11 7 2 5

7 18 0 1 6

a:0 b:10 c:110 d:111

00000001010101010110110111111111111

aaaaaabbbbbccdddd

假设读者已了解Huffman算法,下面给出具体实现代码
1.哈夫曼树的存储表示

typedef struct {
	int weight; //结点权值
	int parent, lchild, rchild; //结点的双亲、左孩子、右孩子
} HTNode, * HuffmanTree; //动态分配数组存储哈夫曼树

typedef char** HuffmanCode;

2.初始化树 

void initHFTree(HuffmanTree& HT, int* count, int HT_length) {

	if (HT_length < 2) {// 判断空树
		cout << "The HFTree is Empty.";
		return;
	}
	for (int i = 1; i < HT_length + 1; i++) {// 对树结点初始化为0
		HT[i].parent = 0;
		HT[i].lchild = 0;
		HT[i].rchild = 0;
		HT[i].weight = 0;
	}
	for (int i = 0, j = 1; i < 26; i++, j++) {// count下标从0开始,HT的下标从1开始
		if (count[i] != 0) {
			HT[j].weight = count[i];
		}
	}




}

 3.寻找最小和第二小的

int* findMin_firAndsec(HTNode* HT, int HT_length) {

	int* res = (int*)malloc(sizeof(int) * 2);// 返回数组进行动态分配
	int fir_min{};
	int sec_min{};
	int current_min = numeric_limits<int>::max();// 对current_min初始化为最大值
	for (int i = 1; i < HT_length + 1; i++) {// 查找最小值
		if (HT[i].weight < current_min && HT[i].parent == 0) {
			current_min = HT[i].weight;
			fir_min = i;
		}
	}

	current_min = numeric_limits<int>::max();// 重新初始化

	for (int i = 1; i < HT_length + 1; i++) {// 查找第二小
		if (HT[i].weight < current_min && HT[i].parent == 0 && i != fir_min) {
			current_min = HT[i].weight;
			sec_min = i;
		}
	}

	res[0] = fir_min;
	res[1] = sec_min;
	return res;// 返回数组
}

 4.创建哈夫曼树

void createHFTree(HuffmanTree& HT, int* count, int node_Count) {



	int HT_length = 2 * node_Count - 1;//表长度为(2*结点个数-1)
	HT = new HTNode[HT_length + 1];//起始节点从1开始,HTNode[0]不使用
	initHFTree(HT, count, HT_length);//初始化树

	for (int i = node_Count + 1; i < HT_length + 1; i++) {// 开始创建哈夫曼树
		int fir_min = 0;
		int sec_min = 0;
		int* res = findMin_firAndsec(HT, i - 1);
		fir_min = res[0];
		sec_min = res[1];
		HT[i].weight = HT[fir_min].weight + HT[sec_min].weight;
		HT[i].lchild = fir_min;
		HT[i].rchild = sec_min;
		HT[fir_min].parent = i;
		HT[sec_min].parent = i;
	}
}

5.对每个字符进行哈夫曼编码

void createHFCode(HuffmanTree HT, HuffmanCode& HC, int n) {
	// 分配存储Huffman编码的二维数组
	HC = new char* [n + 1];

	// 临时数组,用于存储编码路径
	char* cd = new char[n];
	cd[n - 1] = '\0';

	// 遍历每个叶子节点,生成对应的Huffman编码
	for (int i = 1; i <= n; ++i) {
		int start = n - 1; // 初始化编码路径的起始位置
		int c = i; // 当前叶子节点索引
		int f = HT[i].parent; // 当前节点的父节点索引

		// 从叶子节点向上遍历到根节点,记录编码路径
		while (f != 0) {
			--start;
			if (HT[f].lchild == c)
				cd[start] = '0'; // 左孩子为0
			else
				cd[start] = '1'; // 右孩子为1

			// 更新当前节点和父节点
			c = f;
			f = HT[f].parent;
		}

		// 分配存储编码的数组并将编码路径复制到其中
		HC[i] = new char[n - start];
		strcpy(HC[i], &cd[start]);
	}

	// 释放临时数组的内存
	delete[] cd;
}

 6.哈夫曼编码

void HFEncode(char str[], HuffmanCode HC, char encodedStr[]) {
	int len = strlen(str); // 获取输入字符串的长度
	int k = 0; // 初始化输出字符串索引

	// 遍历输入字符串,查找每个字符的Huffman编码
	for (int i = 0; i < len; ++i) {
		int index = str[i] - 'a' + 1; // 获取当前字符在Huffman编码数组中的索引
		strcat(encodedStr, HC[index]); // 将当前字符的Huffman编码追加到输出字符串中
	}
}

7.哈夫曼解码

void HuffmanDecode(HuffmanTree HT, char encodedStr[], char decodedStr[], int n) {
	int i = 0;
	int res = 2 * n - 1;

	// 遍历编码字符串,执行解码
	while (encodedStr[i] != '\0') {
		// 根据编码的0或1选择左孩子或右孩子
		if (encodedStr[i] == '0')
			res = HT[res].lchild;
		else
			res = HT[res].rchild;
		// 如果当前节点是叶子节点
		if (res < n + 1) {
			// 将叶子节点对应的字符添加到解码后的字符串
			decodedStr[strlen(decodedStr)] = 'a' + res - 1;
			// 重置为根节点,准备解码下一个字符
			res = 2 * n - 1;
		}
		i++;
	}
}

 完整实现代码如下
 

#include <iostream>
#include <cstring>
using namespace std;
#pragma warning(disable:4996)
typedef struct {
	int weight; //结点权值
	int parent, lchild, rchild; //结点的双亲、左孩子、右孩子
} HTNode, * HuffmanTree; //动态分配数组存储哈夫曼树

typedef char** HuffmanCode;

// 初始化树
void initHFTree(HuffmanTree& HT, int* count, int HT_length) {

	if (HT_length < 2) {// 判断空树
		cout << "The HFTree is Empty.";
		return;
	}
	for (int i = 1; i < HT_length + 1; i++) {// 对树结点初始化为0
		HT[i].parent = 0;
		HT[i].lchild = 0;
		HT[i].rchild = 0;
		HT[i].weight = 0;
	}
	for (int i = 0, j = 1; i < 26; i++, j++) {// count下标从0开始,HT的下标从1开始
		if (count[i] != 0) {
			HT[j].weight = count[i];
		}
	}




}

// 寻找最小和第二小的
int* findMin_firAndsec(HTNode* HT, int HT_length) {

	int* res = (int*)malloc(sizeof(int) * 2);// 返回数组进行动态分配
	int fir_min{};
	int sec_min{};
	int current_min = numeric_limits<int>::max();// 对current_min初始化为最大值
	for (int i = 1; i < HT_length + 1; i++) {// 查找最小值
		if (HT[i].weight < current_min && HT[i].parent == 0) {
			current_min = HT[i].weight;
			fir_min = i;
		}
	}

	current_min = numeric_limits<int>::max();// 重新初始化

	for (int i = 1; i < HT_length + 1; i++) {// 查找第二小
		if (HT[i].weight < current_min && HT[i].parent == 0 && i != fir_min) {
			current_min = HT[i].weight;
			sec_min = i;
		}
	}

	res[0] = fir_min;
	res[1] = sec_min;
	return res;// 返回数组
}

// 创建哈夫曼树
void createHFTree(HuffmanTree& HT, int* count, int node_Count) {



	int HT_length = 2 * node_Count - 1;//表长度为(2*结点个数-1)
	HT = new HTNode[HT_length + 1];//起始节点从1开始,HTNode[0]不使用
	initHFTree(HT, count, HT_length);//初始化树

	for (int i = node_Count + 1; i < HT_length + 1; i++) {// 开始创建哈夫曼树
		int fir_min = 0;
		int sec_min = 0;
		int* res = findMin_firAndsec(HT, i - 1);
		fir_min = res[0];
		sec_min = res[1];
		HT[i].weight = HT[fir_min].weight + HT[sec_min].weight;
		HT[i].lchild = fir_min;
		HT[i].rchild = sec_min;
		HT[fir_min].parent = i;
		HT[sec_min].parent = i;
	}
}

// 对每个字符进行哈夫曼编码
void createHFCode(HuffmanTree HT, HuffmanCode& HC, int n) {
	// 分配存储Huffman编码的二维数组
	HC = new char* [n + 1];

	// 临时数组,用于存储编码路径
	char* cd = new char[n];
	cd[n - 1] = '\0';

	// 遍历每个叶子节点,生成对应的Huffman编码
	for (int i = 1; i <= n; ++i) {
		int start = n - 1; // 初始化编码路径的起始位置
		int c = i; // 当前叶子节点索引
		int f = HT[i].parent; // 当前节点的父节点索引

		// 从叶子节点向上遍历到根节点,记录编码路径
		while (f != 0) {
			--start;
			if (HT[f].lchild == c)
				cd[start] = '0'; // 左孩子为0
			else
				cd[start] = '1'; // 右孩子为1

			// 更新当前节点和父节点
			c = f;
			f = HT[f].parent;
		}

		// 分配存储编码的数组并将编码路径复制到其中
		HC[i] = new char[n - start];
		strcpy(HC[i], &cd[start]);
	}

	// 释放临时数组的内存
	delete[] cd;
}

// 哈夫曼编码
void HFEncode(char str[], HuffmanCode HC, char encodedStr[]) {
	int len = strlen(str); // 获取输入字符串的长度
	int k = 0; // 初始化输出字符串索引

	// 遍历输入字符串,查找每个字符的Huffman编码
	for (int i = 0; i < len; ++i) {
		int index = str[i] - 'a' + 1; // 获取当前字符在Huffman编码数组中的索引
		strcat(encodedStr, HC[index]); // 将当前字符的Huffman编码追加到输出字符串中
	}
}

// 哈夫曼解码
void HuffmanDecode(HuffmanTree HT, char encodedStr[], char decodedStr[], int n) {
	int i = 0;
	int res = 2 * n - 1;

	// 遍历编码字符串,执行解码
	while (encodedStr[i] != '\0') {
		// 根据编码的0或1选择左孩子或右孩子
		if (encodedStr[i] == '0')
			res = HT[res].lchild;
		else
			res = HT[res].rchild;
		// 如果当前节点是叶子节点
		if (res < n + 1) {
			// 将叶子节点对应的字符添加到解码后的字符串
			decodedStr[strlen(decodedStr)] = 'a' + res - 1;
			// 重置为根节点,准备解码下一个字符
			res = 2 * n - 1;
		}
		i++;
	}
}

int main() {
	char str[100];
	int node_Count = 0;//统计输入字符类别的个数(结点的个数)

	cin >> str;

	int count[26] = { 0 };//创建存储的表
	for (int i = 0; i < strlen(str); i++) {
		count[str[i] - 'a']++;//统计同一类别字符的数量
	}
	for (int i = 0; i < 26; i++) {
		if (count[i] != 0) {
			printf("%c:%d ", i + 'a', count[i]);
			node_Count++;
		}
	}
	cout << endl;

	HuffmanTree HT;//创建Huffman树
	HuffmanCode HC;//创建Huffman编码存储表
	createHFTree(HT, count, node_Count);
	createHFCode(HT, HC, node_Count);

	for (int i = 1; i < 2 * node_Count; i++) {
		cout << i << " " << HT[i].weight << " " << HT[i].parent << " " << HT[i].lchild << " " << HT[i].rchild << endl;
	}

	for (int i = 1; i < node_Count + 1; i++) {
		printf("%c:%s ", i + 'a' - 1, HC[i]);
	}
	cout << endl;

	char encodedStr[1000] = "";
	HFEncode(str, HC, encodedStr);
	cout << "Encoded String: " << encodedStr << endl;

	char decodedStr[100] = "";
	HuffmanDecode(HT, encodedStr, decodedStr, node_Count);
	cout << "Decoded String: " << decodedStr << endl;

	return 0;
}

测试例输入

aaaaaaabbbbbccdddd

 测试例输出

a:7 b:5 c:2 d:4
1 7 7 0 0
2 5 6 0 0
3 2 5 0 0
4 4 5 0 0
5 6 6 3 4
6 11 7 2 5
7 18 0 1 6
a:0 b:10 c:110 d:111
Encoded String: 00000001010101010110110111111111111
Decoded String: aaaaaaabbbbbccdddd
  • 13
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值