哈夫曼编码压缩文件

#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>

using namespace std;
const long INF = 0x7FFFFFFF;

struct HuffNode {
	unsigned char b;
	long count;
	long parent;
	long lch, rch;
	char bits[256];
};
HuffNode HuffTree[512];

/*
函数描述:初始化HuffTree
函数名:init
返回值:void
参数描述:无
注意事项:无
*/
void init() {
	for (int i = 0; i < 512; i++) {    //初始化
		HuffTree[i].b = 0;
		HuffTree[i].count = 0;
		HuffTree[i].parent = -1;
		HuffTree[i].lch = -1;
		HuffTree[i].rch = -1;
		HuffTree[i].bits[0] = 0;
	}
}

/*
函数描述:自定义cmp函数,按权值从大到小排序
返回值:bool
参数描述:无
注意事项:无
*/

bool cmp(HuffNode A, HuffNode B) {	//按权值从大到小排序
	return A.count > B.count;
}

void compress() {	//压缩
	init();	//初始化
	char filename[255],outputfile[255],buf[255];
	unsigned char c;
	long f, p, l;
	long n, m, i, j;
	long min1, pt1;
	long flength=0,length1;
	FILE* ifp, * ofp;
	printf("\t请您输入需要压缩的文件名:");
	//getchar();
	//gets_s(filename);
	scanf("%s", &filename);		//文件路径
	ifp = fopen(filename, "rb");	//以二进制流形式打开文件
	if (ifp == NULL) {	//判断文件是否存在
		printf("\n\t文件打开失败!\n\n");
		return;
	}
	printf("\t将在当前目录下压缩,请您输入压缩后文件名包括扩展名:");
	//gets_s(outputfile);
	scanf("%s", outputfile);
	ofp = fopen(outputfile, "wb");
	if (ofp == NULL) {
		printf("\n\t解压缩文件失败!\n");
		return;
	}
	while (!feof(ifp)) {
		fread(&c, 1, 1, ifp);	//从文件读取一个字节到c
		HuffTree[c].count++;
		flength++;	//原文件长度
	}
	flength--;
	length1 = flength;
	HuffTree[c].count--;
	for (int i = 0; i < 512; i++) {
		if (HuffTree[i].count != 0) {
			HuffTree[i].b = (unsigned char)i;
		}
		else {
			HuffTree[i].b = 0;
		}
		HuffTree[i].parent = -1;
		HuffTree[i].lch = HuffTree[i].rch = -1;
	}
	sort(HuffTree,HuffTree+256,cmp);	//按照权值大小排序
	for (i = 0; i < 256; i++)
		if (HuffTree[i].count == 0) break;
	n = i;	//叶子节点个数
	m = 2 * n - 1;	//哈夫曼总结点数
	for (i = n; i < m; i++) {
		min1 = INF;
		for (j = 0; j < i; j++) {	//取出权值最小的节点
			if (HuffTree[j].parent != -1)	//该节点已在哈夫曼树中
				continue;
			if (min1 > HuffTree[j].count) {
				pt1 = j;
				min1 = HuffTree[j].count;
				continue;
			}
		}
		//将取出的节点作为哈夫曼树的左节点
		HuffTree[i].count = HuffTree[pt1].count;
		HuffTree[pt1].parent = i;
		HuffTree[i].lch = pt1;
		min1 = INF;
		for (j = 0; j < i; j++) {	//重复取出权值最小的节点的操作
			if (HuffTree[j].parent != -1)
				continue;
			if (min1 > HuffTree[j].count) {
				pt1 = j;
				min1 = HuffTree[j].count;
				continue;
			}
		}
		//将取出的节点作为哈夫曼树的右节点
		HuffTree[i].count += HuffTree[pt1].count;
		HuffTree[i].rch = pt1;
		HuffTree[pt1].parent = i;
	}
	for (i = 0; i < n; i++) {	//哈夫曼编码
		f = i;
		HuffTree[i].bits[0] = 0;
		while (HuffTree[f].parent != -1) {
			j = f;
			f = HuffTree[f].parent;
			if (HuffTree[f].lch == j) {
				j = strlen(HuffTree[i].bits);
				memmove(HuffTree[i].bits + 1, HuffTree[i].bits, j + 1);
				HuffTree[i].bits[0] = '0';
			}
			else {
				j = strlen(HuffTree[i].bits);
				memmove(HuffTree[i].bits + 1, HuffTree[i].bits, j + 1);
				HuffTree[i].bits[0] = '1';
			}
		}
	}
	/*
	for (int i = 0; i < n; i++) {
		printf("%d %c %d %d %d %d %s\n", i, HuffTree[i].b, HuffTree[i].count, HuffTree[i].parent, HuffTree[i].lch, HuffTree[i].rch, HuffTree[i].bits);
	}
	*/
	
	fseek(ifp, 0, SEEK_SET);
	fseek(ofp, 8, SEEK_SET);
	buf[0] = 0;
	pt1 = 8;
	f = 0;
	while (!feof(ifp)) {	//扫描原文件
		c = fgetc(ifp);
		//cout << c;
		f++;
		for (i = 0; i < n; i++) {
			if (c == HuffTree[i].b)
				break;
		}
		//cout << HuffTree[i].bits<<" ";
		strcat(buf, HuffTree[i].bits);
		j = strlen(buf);
		c = 0;
		while (j >= 8) {	//如果长度大于8进行拆分写入
			for (i = 0; i < 8; i++) {
				if (buf[i] == '1')c = (c << 1) | 1;
				else c = c << 1;
			}
			fwrite(&c, 1, 1, ofp);	//把凑好的一个字节编码写入文件
			pt1++;
			strcpy(buf, buf + 8);
			j = strlen(buf);
		}
		if (f == flength)
			break;
	}
	if (j > 0) {	//若buf中还有内容且不足一个字节,需要补0
		strcat(buf, "00000000");
		for (i = 0; i < 8; i++) {
			if (buf[i] == '1')c = (c << 1) | 1;
			else c <<= 1;
		}
		fwrite(&c, 1, 1, ofp);
		pt1++;
	}
	fseek(ofp, 0, SEEK_SET);
	fwrite(&flength, sizeof(long), 1, ofp);
	fwrite(&pt1, sizeof(long), 1, ofp);
	fseek(ofp, pt1, SEEK_SET);
	fwrite(&n, sizeof(long), 1, ofp);
	//cout << pt1 << " " << n << endl;
	for (i = 0; i < n; i++) {
		fwrite(&(HuffTree[i].b), 1, 1, ofp);	//写入每个字节的代表字符
		c = strlen(HuffTree[i].bits);
		fwrite(&c, 1, 1, ofp);	//写入每个字符哈夫曼编码长度
		j = strlen(HuffTree[i].bits);
		if (j % 8 != 0) {	//如果不是8的倍数,需要补0
			for (f = j % 8; f < 8; f++)
				strcat(HuffTree[i].bits, "0");
		}
		while (HuffTree[i].bits[0] != 0) {
			c = 0;
			for (j = 0; j < 8; j++) {
				if (HuffTree[i].bits[j] == '1')
					c = (c << 1) | 1;
				else
					c = c << 1;
			}
			strcpy(HuffTree[i].bits, HuffTree[i].bits + 8);
			fwrite(&c, 1, 1, ofp);
		}
	}
	long length2 = pt1--;
	double div = ((double)length1 - (double)length2) / (double)length1;
	fclose(ifp);
	fclose(ofp);
	printf("\n\t压缩文件成功!\n");
	printf("\t压缩率为 %f%%\n\n", div * 100);
}

void uncompress() {	//解压
	init();
	char filename[255], outputfile[255], buf[255], bx[255];
	unsigned char c;
	long i, j, m, n, f, p, l;
	long flength;
	FILE* ifp, * ofp;
	printf("\t请您输入需要解压缩的文件名:");
	//getchar();
	scanf("%s", &filename);
	//gets_s(filename);
	ifp = fopen(filename, "rb");
	if (ifp == NULL) {
		printf("\n\t文件打开失败!\n");
		return;
	}
	printf("\t将在当前目录下解压,请您输入解压后文件名包括扩展名:");
	scanf("%s", &outputfile);
	//gets_s(outputfile);
	ofp = fopen(outputfile, "wb");
	if (ofp == NULL) {
		printf("\n\t解压缩文件失败!\n");
		return;
	}

	fseek(ifp, 0, SEEK_SET);

	for (int i = 0; i < 9; i++) {
		fread(&c, 1, 1, ifp);
	}

	fseek(ifp, 0, SEEK_SET);
	fread(&flength, sizeof(long), 1, ifp);
	fread(&f, sizeof(long), 1, ifp);
	fseek(ifp, f, SEEK_SET);
	fread(&n, sizeof(long), 1, ifp);
	//cout << flength<<" "<<f << " " << n << endl;
	for (i = 0; i < n; i++) {	//构造Huffman树的n个叶子结点
		fread(&HuffTree[i].b, 1, 1, ifp);
		fread(&c, 1, 1, ifp);
		p = (long)c;
		HuffTree[i].count = p;
		HuffTree[i].bits[0] = 0;

		if (p % 8 > 0)
			m = p / 8 + 1;
		else
			m = p / 8;
		for (j = 0; j < m; j++) {
			fread(&c, 1, 1, ifp);
			f = c;	//十六进制转十进制
			itoa(f, buf, 2);
			f = strlen(buf);
			for (l = 8; l > f; l--) {
				strcat(HuffTree[i].bits, "0");
			}
			strcat(HuffTree[i].bits, buf);
		}
		HuffTree[i].bits[p] = 0;
	}
	HuffNode tmp;
	for (i = 0; i < n; i++) {	//根据哈夫曼编码长度排序
		for (j = i+1; j < n; j++) {
			if (strlen(HuffTree[i].bits) > strlen(HuffTree[j].bits)) {
				tmp = HuffTree[i];
				HuffTree[i] = HuffTree[j];
				HuffTree[j] = tmp;
			}
		}
	}
	p = strlen(HuffTree[n - 1].bits);
	fseek(ifp, 8, SEEK_SET);
	m = 0;
	bx[0] = 0;
	while (1) {	//解码
		while (strlen(bx) < (unsigned int)p) {
			fread(&c, 1, 1, ifp);
			f = c;
			itoa(f, buf, 2);
			f = strlen(buf);
			for (l = 8; l > f; l--) {
				strcat(bx, "0");
			}
			strcat(bx, buf);
		}
		for (i = 0; i < n; i++) {
			if (memcmp(HuffTree[i].bits, bx, HuffTree[i].count) == 0)
				break;
		}
		strcpy(bx, bx + HuffTree[i].count);
		c = HuffTree[i].b;
		fwrite(&c, 1, 1, ofp);
		m++;
		if (m == flength) break;
	}
	fclose(ifp);
	fclose(ofp);
	printf("\n\t解压缩文件成功!\n");
	if (m == flength)
		printf("\t解压缩文件与原文件相同!\n\n");
	else
		printf("\t解压缩文件与原文件不同!\n\n");
	return;
}

int main() {
	int c;
	while (1) {
		printf("\t_________________________________________\n");
		printf("\n");
		printf("\t          * 压缩、解压缩 小工具 *        \n");
		printf("\t_________________________________________\n");
		printf("\t_________________________________________\n");
		printf("\t|                                       |\n");
		printf("\t|1.压缩                                 |\n");
		printf("\t|2.解压缩                               |\n");
		printf("\t|0.退出                                 |\n");
		printf("\n");
		printf("\t                说明:(1)采用哈夫曼编码\n");
		printf("\t                      (2)适用于字符型文本文件\n");
		printf("\n");
		do {
			printf("\n\t*请选择相应功能(0-2):");
			c = getchar();
			//printf("%c\n", c);
			if (c != '0' && c != '1' && c != '2') {
				printf("\t@_@请检查您输入的数字在0~2之间!\n");
				printf("\t请再输入一边!\n");
			}
		} while (c != '0' && c != '1' && c != '2');
		if (c == '1') {
			compress();
		}
		else if (c == '2') {
			uncompress();
		}
		else {
			printf("\t欢迎您再次使用该工具^_^\n");
			exit(0);
		}
		system("pause");
		system("cls");
	}


	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值