实验四 基于哈夫曼树的数据压缩算法
一、实验目的
1.
掌握哈夫曼树的构造算法。
2.
掌握哈夫曼编码的构造算法。
二、实验内容
问题描述
输入一串字符串,根据给定的字符串中字符出现的频率建立相应的哈夫曼树,
构造哈夫曼编码表,在此基础上可以对压缩文件进行压缩
(
即编码
)
,同时可以对
压缩后的二进制编码文件进行解压
(
即译码
)
。
输入要求
多组数据,每组数据
1
行,为一个字符串
(
只考虑
26
个小写字母即可
)
。当
输入字符串为
“0”
时,输入结束
输出要求
每组数据输出
2n+3
行
(n
为输入串中字符类别的个数
)
。第
1
行为统计出来
的字符出现频率
(
只输出存在的字符,格式为
:
字符
:
频度
)
,每两组字符之间用一
个空格分隔,字符按照
ASCI
码从小到大的顺序排列。第
2
行至第
2n
行为哈夫
曼树的存储结构的终态
(
如主教材
139
页表
5.2( b),
一行当中的数据用空格分
隔
)
。第
2n+1
行为每个字符的哈夫曼编码
(
只输出存在的字符。格式为
:
字符
:
编码
)
,
每两组字符之间用一个空格分隔,字符按照
ASCI
码从小到大的顺序排列。第
2n+2
行为编码后的字符串,第
2n+3
行为解码后的字符串
(
与输入的字符串相同
)
。
输入样例
aaaaaaabbbbbccdddd
aabccc
0
输出样例
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
a:2 b:1 c:3
1 2 4 0 0
2 1 4 0 0
3 3 5 0 0
4 3 5 2 1
5 6 0 3 4
a:11 b:10 c:0
111110000
aabccc
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include <cstring>
#include <cmath>
using namespace std;
int num;
int alpha[26];
int num2 = 0;
int k = 0;
char list[26];
typedef struct {
int weight; //结点的权值
int parent, lchild, rchild;
}HTNode, * HuffmanTree; //哈夫曼编码表
typedef char** HuffmanCode;
void Select(HuffmanTree HT, int sum, int& s1, int& s2)
{
int i, min1 = 100, min2 = 100;
for (i = 1; i <= sum; i++)
{
if (HT[i].parent == 0 && HT[i].weight < min1)
{
min1 = HT[i].weight;
s1 = i;
}
}
int temp = HT[s1].weight; //将原值存放起来,然后先赋予最大值,防止s1被重复选择
HT[s1].weight = 100;
for (i = 1; i <= sum; i++)
{
if (HT[i].parent == 0 && HT[i].weight < min2)
{
min2 = HT[i].weight;
s2 = i;
}
}
HT[s1].weight = temp;
}
void CreateHuffmanTree(HuffmanTree& HT, int n)
{
int i;
if (n <= 1) return;
int m = 2 * n - 1;
HT = new HTNode[m + 1];
for (i = 1; i <= m; i++)
{
HT[i].parent = 0; HT[i].lchild = 0; HT[i].rchild = 0;
}
for (i = 1; i <= n; i++) //输入叶子结点的权值
HT[i].weight = alpha[i - 1];
int s1, s2;
for (i = n + 1; i <= m; i++)
{
Select(HT, i - 1, s1, s2);
HT[s1].parent = i;
HT[s2].parent = i;
HT[i].lchild = s1;
HT[i].rchild = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight;
}
}
void CreatHuffmanCode(HuffmanTree HT, HuffmanCode& HC, int n)
{ //从叶子到根逆向求每个字符的赫夫曼编码,存储在编码表HC中
int i, start, c, f;
HC = new char* [n + 1]; //分配n个字符编码的编码表空间
char* cd = new char[n]; //分配临时存放编码的动态数组空间
cd[n - 1] = '\0'; //编码结束符
for (i = 1; i <= n; ++i) //逐个字符求赫夫曼编码
{
start = n - 1; //start开始时指向最后,即编码结束符位置
c = i;
f = HT[i].parent; //f指向结点c的双亲结点
while (f != 0)
{ //从叶子结点开始向上回溯,直到根结点
--start; //回溯一次start向前指一个位置
if (HT[f].lchild == c)
cd[start] = '0'; //结点c是f的左孩子,则生成代码0
else
cd[start] = '1'; //结点c是f的右孩子,则生成代码1
c = f;
f = HT[f].parent; //继续向上回溯
} //求出第i个字符的编码
HC[i] = new char[n - start]; // 为第i 个字符编码分配空间
strcpy(HC[i], &cd[start]); //将求得的编码从临时空间cd复制到HC的当前行中
}
delete cd; //释放临时空间
} // CreatHuffanCode
void Output(HuffmanTree HT, HuffmanCode HC)
{
for (int i = 1; i <= 2 * k - 1; i++)
cout << i << " " << HT[i].weight << " " << HT[i].parent << " " << HT[i].lchild << " " << HT[i].rchild << endl;
for (int i = 1; i <= k; i++)
{
cout << list[i - 1] << ":" << HC[i];
if (i != k)cout << " ";//多输出了一个
}
cout << endl;
}
void Initalpha(int a[], string s)
{
int i = 0;
while (s[i] != '\0')
{
int e = (int)s[i];
if (e >= 97) a[e - 97]++;
i++;
}
for (i = 0; i < 26; i++)
if (a[i]) {
num++;
}
for (i = 0; i < 26; i++)
if (a[i]) {
char e = (char)(i + 97);
cout << e << ":" << a[i];
if (k != num - 1)cout << " ";//多输出了一个
alpha[k] = a[i];
list[k] = e;
k++;
}
cout << endl;
}
void InitData(int fre[])
{
for (int i = 0; i < 26; i++)
fre[i] = 0;
for (int i = 0; i < 26; i++)
list[i] = 0;
k = 0; num2 = 0, num = 0;
return;
}
void Translate(HuffmanCode HC, string s)
{
int i = 0;
while (s[i]) {
int j = 0;
while (j < 26)
{
if (s[i] == list[j])//如果该字符与namelist中某个字符相等
cout << HC[j + 1];//输出其对应编码
j++;
}
i++;
}
cout << endl;
i = 0;
while (s[i]) {
char e;
e = s[i];
cout << e;
i++;
}
cout << endl;
}
int main()
{
string s;
while (cin >> s && s != "0")
{
InitData(alpha);
Initalpha(alpha, s);
HuffmanTree HT;
HuffmanCode HC;
CreateHuffmanTree(HT, k);
CreatHuffmanCode(HT, HC, k);
Output(HT, HC);
Translate(HC, s);
}
return 0;
}