【问题描述】
编写一程序采用Huffman编码对一个正文文件进行压缩。具体压缩方法如下:
-
对正文文件中字符(换行字符’'除外,不统计)按出现次数(即频率)进行统计
-
依据字符频率生成相应的Huffman树(未出现的字符不生成)
-
依据Huffman树生成相应字符的Huffman编码
-
依据字符Huffman编码压缩文件(即按照Huffman编码依次输出源文件字符)。
说明:
-
只对文件中出现的字符生成Huffman,注意:一定不要处理\n,即不要为其生成Huffman码。
-
采用ASCII码值为0的字符作为压缩文件的结束符(即可将其出现次数设为1来参与编码).
-
在生成Huffman树时,初始在对字符频率权重进行(由小至大)排序时,频率相同的字符ASCII编码值小的在前;新生成的权重节点插入到有序权重序列中时,出现相同权重时,插入到其后(采用稳定排序)。
-
遍历Huffman树生成字符Huffman码时,左边为0右边为1。
-
源文件是文本文件,字符采用ASCII编码,每个字符点8位;而采用Huffman编码后,高频字符编码长度较短(小于8位),因此最后输出时需要使用C语言中的位运算将字符Huffman码依次输出到每个字节中。
【输入形式】
对当前目录下文件input.txt进行压缩。
【输出形式】
将压缩后结果输出到文件output.txt中,同时将压缩结果用十六进制形式(printf("%x",…))输出到屏幕上,以便检查和查看结果。
【样例输入1】
若当前目录下input.txt中内容如下:
aaabbc
【样例输出1】
15f0
同时程序将压缩结果输出到文件output.txt中。
【样例说明】
输入文件中字符的频率为:a为3,b为2,c为1,此外,\0字符将作为压缩文件的结束标志,其出现次数设为1。因此,采用Huffman码生成方法,它们的Huffman编码分别为:
a : 0
b : 10
c : 111
\0 : 110
因此,最终文件压缩结果(按位)为:
0001010111110000
将上述结果按字节按十六进制输出到屏幕上则为15f0(即0001010 111110000的十六进制表示)。
说明:
采用Huffman码输出字符序列长度为:1+1+1+2+2+3+3=13(位),由于C语言中输出的最小单位为字节(8位),因此,最后补了三个位0,压缩后实际输出为2个字节。由于文本文件是按ASCII来解释的,因此,以文本方式打开压缩文件将显示乱码(最好用二进制文件查看器来看)。
【样例输入2】
若当前目录下input.txt中内容如下:
do not spend all that you have.do not sleep as long as you want.
【样例输出2】
ea3169146ce9eee6cff4b2a93fe1a5d462d21d9a87c0eb2f3eb2a9cfe6cae
同时程序将压缩结果输出到文件output.txt中。
【评分标准】
通过所有测试点将得满分。
分析
- 由于本人水平有限,因此方法较为麻烦。
- 首先要将数据从数据文件中读出,保存至字符串数组。
- 进而遍历字符串,统计每个字符出现次数。(为了方便后续排序等操作,本人采取链表形式储存字符)
- 完成统计后,调用函数对该链表进行目标排序。(由于链表排序较为复杂,交换节点需要涉及四个节点,因此本人采取模拟冒泡法的方式对其进行排序,并且仅交换两个节点储存的数据,而不实际地交换两个节点)
- 排完序后,可以依次获取链表两个节点,以这两个节点分别作为左右子孩,创建一个父节点,并将新建的节点插入到后续链表中,便于完成整个哈夫曼树的创建。
- 创建完哈夫曼树以后,可以通过遍历树,为每个叶子节点的字符进行编码。
- 最后对数据字符串遍历,一个一个进行编码并记录,完成后按要求在最后补齐0,再对总编码的每八位做一次输出(按照题目要求必须是每八位做一次十六进制输出,如果每四位进行十六进制转换,会导致结果中某些高位0比系统结果多)。
- 由于我水平问题,对于储存文件,我只能做到将结果作为字符串存入txt文本。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct Hnode* Node;
typedef char EleType; //每个节点储存的数据种类
int number = 1; //数据中字符总数
char chars[1000]; //数据所有字符
char codes[1000][15], code_end[15]; //每个字符对应的哈夫曼编码,单独记录‘\0’的编码
int pos = 0; //动态记录已经读取出的字符数
struct Hnode {
EleType Data; //记录字符
int Times; //记录字符出现的次数
Node Lchild, Rchild; //哈夫曼树的左右子孩
Node Next; //读取字符时按照单项链表储存,便于排序、插入新节点
};
//创建并初始化一个节点
Node init(EleType data)
{
Node N;
N = (Node)malloc(sizeof(struct Hnode));
N->Lchild = N->Rchild = N->Next = NULL;
N->Data = data;
N->Times = 1;
return N;
}
//创建一个链表储存data中所有字符以及出现次数
Node CreatChain(Node N, EleType data[])
{
Node H = N;
int i, l = strlen(data); //遍历字符串,储存每个字符
for (i = 0; i < l; i++) {
if (data[i] != '\n') { //‘\n’不编码
Node p = H;
while (H) { //如果字符在前面读取到则将其出现次数增加1
if (data[i] == H->Data) {
H->Times++;
break;
}
p = H;
H = H->Next;
}
if (!H) { //如果遍历完了链表,说明没有找到该字符,则在最后创建其对应的节点
p->Next = init(data[i]);
number++;
}
}
H = N; //每次查询结束都要将指针回归
}
return N;
}
//交换两个节点的数据
void exchange(Node p, Node q)
{
EleType c = p->Data;
int time = p->Times;
p->Data = q->Data;
p->Times = q->Times;
q->Data = c;
q->Times = time;
}
//对链表进行排序
Node rank(Node N)
{
Node H = N;
int i, j; //冒泡法对链表排序
for (i = 1; i < number; i++) {
for (j = 0; j < number - i; j++) {
Node p = H->Next;
if (p->Times < H->Times) { //频次高的字符在后面
exchange(H, p);
}
if (p->Times == H->Times && p->Data < H->Data) { //频次相同的字符按照ASCII码大小排序
exchange(H, p);
}
H = H->Next;
}
H = N;
}
return N;
}
//向链表中插入一个节点
void insert(Node N, Node r)
{
Node H = N->Next, p = N;
int flag = 0;
while (H) { //将r节点插入第一个比它频次高的节点前面
if (H->Times > r->Times) {
p->Next = r;
r->Next = H;
flag = 1;
break;
}
p = H;
H = H->Next;
}
if (!flag) { //如果r的频次最高,则插入链表最后
p->Next = r;
r->Next = H;
}
}
//利用排好序的链表创建哈夫曼树
Node creatHTree(Node N)
{
Node p = N, q = N->Next;
while (p && q) {
Node r = init(1); //将频次最小的两个节点频次相加创建新节点,并创建哈夫曼树结构
r->Times = p->Times + q->Times;
r->Lchild = p; //频次小的作为左树
r->Rchild = q; //频次大的作为又树
insert(q, r); //将新节点插入链表
p = q->Next; //遍历剩余节点
q = p->Next;
}
return p;
}
//获取每个字符的哈夫曼编码
void getCode(Node N, char code[])
{
char c[15];
if (N->Lchild) { //左孩的编码为0
strcpy(c, code);
strcat(c, "0\0");
getCode(N->Lchild, c);
}
if (N->Rchild) { //右孩的编码为1
strcpy(c, code);
strcat(c, "1\0");
getCode(N->Rchild, c);
}
if (!N->Lchild && !N->Rchild) { //叶子节点,记录下其对应的字符及哈夫曼编码
chars[pos] = N->Data;
strcpy(codes[pos++], code);
if (N->Data == '\0') //单独记录‘\0’的编码
strcpy(code_end, code);
}
}
//每八位进行一次十六进制输出(可避免结果高位出现0)
void output(char str[])
{
int i, j, l = strlen(str);
for (i = 0; i < l; i += 8) { //每八位做一次输出
int a = 0;
for (j = 0; j < 8; j++) //将八位二进制转为十进制,以十六进制格式输出
a = a * 2 + (str[i + j] - '0');
printf("%x", a);
}
printf("\n");
}
//主函数
int main()
{
char str[1000], code[10] = { '\0' }, coding[10000] = { '\0' }, ch;
Node N = init('\0');
int i = 0, l;
FILE* fp = fopen("input.txt", "r");
while (!feof(fp)) { // feof 读到文件末尾的函数
ch = fgetc(fp); // 获取下一个字符
str[i++] = ch; // 输出文件中的字符
}
str[i-1] = '\0';
N = CreatChain(N, str); //创建链表
N = rank(N); //对链表排序
N = creatHTree(N); //创建哈夫曼树
strcpy(code, "");
getCode(N, code); //获取每个字符的哈夫曼编码
for (i = 0; i < strlen(str); i++) //编码,储存到coding
for (pos = 0; pos < number; pos++)
if (str[i] == chars[pos]) {
strcat(coding, codes[pos]);
break;
}
strcat(coding, code_end); //最后添加‘\0’的编码
l = strlen(coding);
for (i = 0; l % 8 && i < 8 - l % 8; i++) {
strcat(coding, "0");
}
output(coding);
fp = fopen("output.txt", "a");
fwrite(coding, strlen(coding), 1, fp);
fclose(fp);
return 0;
}
测试数据
aaaaaaabbbbwwwwwwwwwwwihhh
I will give you some advice about life. Eat more roughage; Do more than others expect you to do and do it pains; Remember what life tells you; Do not take to heart every thing you hear. Do not spend all that you have. Do not sleep as long as you want;
c programming language
qwertyuiopasdfghjklzxcvbnm
aaaaaaaaaaaaaaaaaaaaaaaaaaaa