Huffman编码文件压缩 - 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中。


产生Huffman树的主要思路:

把所有结点按权重(出现次数)先用链表从小到大串起来(ASCII码一共不过128个,链表O(n)的查找效率其实没多大影响)。

从而每次用头2个结点生成一个新结点,新结点的权为这两个结点之和,它的两个子结点就是那头2个结点。

这个新结点插入链表并按题目要求使链表保持有序,从此可以不再考虑头2个结点,相当于移除。

从第3个结点开始继续,如此反复直至链表只剩一个结点,那么那个结点就是Huffman树的根节点。


编码的主要思路:(每个结点的成员code是一个无符号整数,利用前16位存储编码长度,后16位存储编码)

从根节点开始,(如果有孩子的话)每次把左孩子的code中处于目前递归深度depth的那一位染成0,右孩子的则染成1,然后再在左孩子和右孩子往下递归。如果左右孩子都没有,说明这个结点是叶结点,存储的是ASCII码,那么还要把depth存储到它的code的前16个bit里。 这个时候,depth体现了huffman编码的长度,用于读取编码时确定应当读多少位。


读码写码的主要思路:

C文件流不支持1个1个位写入,因此只有用fputchar一次8个位写入,为此设立一个char型变量writein,由原文件开始读字符

(*1) 每读到一个字符,提取相应叶结点中存储编码的成员,拿出depth(前16位)和Code(后16位)

(*2) 将Code由高位到低位一位位写入writein:

a) 若Code写完(写入的位个数达到depth),writein没满8位,回到(*1)

b)若writein满8位,写入一次,writein重新设为0,初始化高位,回到(*2)

以上,为满足题目要求,每写入一次时顺便将其打印一下。需要注意,计算机打印char变量时是把它当int打印的,如果Most Significant Bit 是1的话计算机会自动把前面全部补为1,因为它认为这是一个负数的补码... 比如输出0xf1显示的是0xfffffff1, 哪怕规定只输出2字节也没有用...

上网查了一下,按"%hhx"输出可以解决这个问题。

#include 
#include 

struct charnode
{
	int count;
	unsigned int code;  // the first 16 bits is to store the depth, the latter 16 bits is the huffmancode.
	struct charnode *lchild, *rchild, *next;
};
struct charnode charnodes[128];

void insert_huffmannode(struct charnode *newnode)
{
	struct charnode *p = charnodes;
	while (p->next->count <= newnode->count) 
	{
		if(p->next->next!=NULL)
			p = p->next;
		else
		{
			p->next->next = newnode;
			p = NULL;
			break;
		}
	}
	if (p != NULL)
	{
		newnode->next = p->next;
		p->next = newnode;
	}
}

struct charnode *build_huffmantree(struct charnode *node1)
{// merge node1 and node2 to form a new node, and insert the node into the linked list
	struct charnode *node2 = node1->next;
	if (node2 != NULL)
	{
		struct charnode *newnode = (struct charnode *)malloc(sizeof(struct charnode));
		newnode->count = node1->count + node2->count;
		newnode->code = 0;
		newnode->lchild = node1;
		newnode->rchild = node2;
		newnode->next = NULL;

		insert_huffmannode(newnode);
		return build_huffmantree(node2->next);
	}
	else
	{
		return node1;
	}
}

void linkup(struct charnode *root)
{// link up the existing nodes according to the weight (increasing order)
	int i = 0;
	struct charnode *p = NULL;
	int min = 0x3f3f3f3f;
	for (; i < 128; i++)
	{
		if (charnodes[i].count != 0 && charnodes[i].next == NULL && (charnodes + i) != root)
		{// the next node should should appear at least once and not be linked already
			if (charnodes[i].count < min)
			{
				p = &charnodes[i];
				min = charnodes[i].count;
			}
			else if (charnodes[i].count == min)
			{// the next node, if there is several nodes with the same count, should be with the least sym
				if ( &charnodes[i] <= p)
				{
					p = &charnodes[i];
				}
			}
		}
	}
	root->next = p;
	if (p != NULL)
		linkup(p);
}

void writecode(struct charnode *root, int depth)
{
	if (root != NULL)
	{
		int f_left = 1, f_right = 1;
		if (root->lchild != NULL)
		{
			root->lchild->code |= (root->code) << 1;
			f_left = 0;
		}
		if (root->rchild != NULL)
		{
			root->rchild->code |= ((root->code) << 1)|1;
			f_right = 0;
		}
		if (f_left&&f_right)
		{// leaf node, leave a info about depth
			root->code &= (1 << 16) - 1;
			root->code |= depth << 16;
			// the depth is stored in the first 16 bits
		}
		writecode(root->lchild, depth+1);
		writecode(root->rchild, depth+1);
	}
}

int main()
{
	FILE *fin, *fout;
	fin = fopen("input.txt", "r");
	if (fin == NULL) exit(1);
	fout = fopen("output.txt", "w");
	if (fout == NULL) exit(1);
	
	char probe;
	struct charnode *HFT;
	int i = 0;
	for (i = 0; i < 128; i++)
	{
		charnodes[i].count = 0;
		charnodes[i].code = 0;
		charnodes[i].next = NULL;
	}
	while ((probe = fgetc(fin)) != EOF)
	{
		if (probe != '\n')
		{
			charnodes[probe].count++;
		}
	}
	charnodes[0].count = 1;
	linkup(charnodes);
	HFT = build_huffmantree(charnodes);
	writecode(HFT, 0);

	rewind(fin);
	char writein = 0;
	int bits = 0, depth = 0;
	for (;;)
	{
		probe = fgetc(fin);
		if (probe != EOF && probe != '\n')
		{
			depth = (charnodes[probe].code) >> 16; // code is unsigned, thus the shifting is logical
			for (i = depth - 1; i >= 0; i--)
			{// depth depicts how many bits are in the code
				char code = ((charnodes[probe].code) & (1 << i)) >> i;
				writein |= code << (7 - bits);
				bits++;
				if (bits == 8)
				{
					fputc(writein, fout);
					printf("%hhx", writein);
					bits = 0;
					writein = 0;
				}
			}
		}
		else if (probe == EOF)
		{
			depth = (charnodes[0].code) >> 16; // code is unsigned, thus the shifting is logical
			for (i = depth - 1; i >= 0; i--)
			{// depth depicts how many bits are in the code
				char code = ((charnodes[0].code) & (1 << i)) >> i;
				writein |= code << (7 - bits);
				bits++;
				if (bits == 8)
				{
					fputc(writein, fout);
					printf("%hhx", writein);
					bits = 0;
					writein = 0;
				}
			}
			if (bits != 0)
			{
				printf("%hhx", writein);
				fputc(writein, fout);
			}
			break;
		}
	}

	fclose(fin);
	fclose(fout);
	return 0;
}


你可能感兴趣的:(C/C++,课程学习报告)