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