Huffman编码文件压缩

【问题描述】

编写一程序采用Huffman编码对一个正文文件进行压缩。具体压缩方法如下:

  1. 对正文文件中字符(换行字符’'除外,不统计)按出现次数(即频率)进行统计

  2. 依据字符频率生成相应的Huffman树(未出现的字符不生成)

  3. 依据Huffman树生成相应字符的Huffman编码

  4. 依据字符Huffman编码压缩文件(即按照Huffman编码依次输出源文件字符)。

说明:

  1. 只对文件中出现的字符生成Huffman,注意:一定不要处理\n,即不要为其生成Huffman码。

  2. 采用ASCII码值为0的字符作为压缩文件的结束符(即可将其出现次数设为1来参与编码).

  3. 在生成Huffman树时,初始在对字符频率权重进行(由小至大)排序时,频率相同的字符ASCII编码值小的在前;新生成的权重节点插入到有序权重序列中时,出现相同权重时,插入到其后(采用稳定排序)。

  4. 遍历Huffman树生成字符Huffman码时,左边为0右边为1。

  5. 源文件是文本文件,字符采用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
  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谛凌

本人水平有限,感谢您支持与指正

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值