一、实验目的
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