1. 程式人生 > >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 <stdio.h>
#include <stdlib.h>

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;
}