基于Huffman编码的文本文件压缩程序(一)
从8.5号以来,我一直在写一个基于Huffman编码的文本文件压缩程序,但是我发现困难比想像中的要大得多,Huffman编码是那么简洁明了的一种思想,那么直观的实现算法,对于大多数的学过数据结构的人,你给他一些字符及其对应的频率值,他几下就画出一棵树,然后写出编码来了,但是,真正的要把那个过程用代码表达出来,却不是那么容易,我想应该是因为我的编程经验还不丰富,设计能力还不够,尽管我已经知道并实践了许多树程序生成,遍历的实现,但这次还是没能很快完成程序,我还得到一个教训就是有难题不能拖,一拖下来,就不想去解决了。
序的功能就是对给定的一个文本文件(暂时只能处理ASCII码,因为我不知道UNICODE怎么个具体对应法,再说C语言本身并不提供对UNICODE的支持,首先没有一个直接的数据存储类型......),做出该文件的压缩副本。
程序大概的流程为:
(1):读该文件,统计该文件中出现了128个字符中的哪些字符,然后计算出每个字符的出现次数,以此代替Huffman编码时的那个频率值;这一步中用了一个128大小的数组记录对应的字符的出现次数,这种索引方法很不错。
(2):根据该次数值构造Huffman树;这一步中首先要准备遍历上一步生成的那个数组,每读到一个出现次数大于0的字符,就把它的值以及对应的次数做为一组数据存放在一个结构中:
typedef struct htnode //huffman tree node
{
int val;
int times;
htnode * next;
htnode * left;
htnode * right;
}htnode;
为了后面生成树方便,所以在这步中我是把每次读到的字符按从小到大的顺序构成了一个linkedlist,其中next指针就起这个作用。遍历完数组以后,所有需要编码的字符就在这个linkedlist中了,然后我取该linkedlist中的前两个元素,用一个新根结点把它们约束起来,新根结点的val即次数值等于其两个子结点的val值的和,然后我删除处理过的两个字符结点,把新生成的结点按序再次插入原linkedlist中去,这样重复做直到linkedlist中只剩下一个结点为止。此时该结点下面其实就生成了一棵完整的Huffman树。
(3):遍历上一步构成的树,得到每个字符对应的Huffman编码;
这个遍历,用人脑做简直简单的要命,可是实现的时候,我想了很多方法都不太好,因为我要在遍历的时候要标记每找到的一个叶结点(即字符而非内部结点)的路径,这样才能得到编码。递归的非递归的都试了,还是没成功,最后从另一个程序中看到了一种方法,总算实现了。那些弯路就不在这记述了。
(4):根据得到的编码重新生成一个文件,原来的每个字符要用8位存储,而现在按可变长编码存储,应该会减小文件大小,生成这个文件,我们必须把得到的编码按2进制写入文件,可是二进制写文件又不能按bit写,因为数据存取的最小单位是字节,所以我必须用位操作把每8个编码一组的所有编码做成unsigned int 型,然后写入文件。(呵呵,这一步暂未实现)
在写代码的过程中我碰到的问题有:
(1):调试时如果单步执行遇到系统库函数调用,则要用F10跳过该调用,如果此时仍用F11的话,就会进入该函数的源代码处,而该函数是已经编译好了形成的库,没有源文件提供的,当你点击取消就进入了汇编代码库文件,那样就没办法跟踪了,再说那是那个库函数的实现,你看了也多半没用,所以碰到它们时用F10选择不进入该函数就可以了。(因为这个问题,想了半天,又问了人才知道,几乎耽搁了一(shit,就在此时我的第二块键盘打不进字了,真几巴操......又得买键盘))
(2):写二进制文件没有直接按位写的方法。
(3):有点滥用链表的嫌疑!
下面是一份样本编码以及程序的源代码 :
呵呵,对齐出了点问题。
#include<stdio.h>
#include<stdlib.h>
#define INTERNAL -1 //flag for intenal node
int chars[128]; //only deal with ASCII 128 chars
char code_tmp[100]; //stores codes for traverse
int q=0; //trace the different length of codes when traversing
typedef struct htnode //huffman tree node
{
int val;
int times;
htnode * next;
htnode * parent;
htnode * left;
htnode * right;
}htnode;
htnode * htroot; //root ,head node of the tree
htnode * currnode; //active available
htnode * tmp;
void linkinsert(htnode * newnode); //insert a node which is new or is combined by two existed nodes to the linkedlist
typedef struct codenode //to store the code ,one byte store one bit,but i don't know how to change this waste
{
char code;
codenode * next;
}codenode;
codenode * codehead;
codenode * codecurrent;
void codeinsert(char code);
void freecodelist(codenode * cnode);
typedef struct charnode //all this type nodes made of the char and code list
{
char val;
charnode * nextchar;
codenode * nextnode;
}charnode;
charnode * charhead;
charnode * charcurrent;
void charinsert(char value);
void init();
int getweight(char * argv1);
void buildhftree();
void huffmancoding(htnode * subroot);
void delehftree();
int generafile();
int main(int argc,char * argv[])
{
if(argc!=2) //checking
{
printf("Arguments error!/n");
return -1;
}
init(); //init global variables
if((getweight(argv[1]))==-1) //open the file and get weights of each char
{
return -1;
}
buildhftree();
huffmancoding(htroot->next);
delehftree();
generafile(); //generate compressed file
return 0;
}
void init()
{
htroot=(htnode *)malloc(sizeof(htnode));
htroot->val=INTERNAL;
htroot->times=0;
htroot->next=NULL;
htroot->parent=NULL;
htroot->left=NULL;
htroot->right=NULL;
codehead=(codenode *)malloc(sizeof(codenode));
codehead->code=0;
codehead->next=NULL;
codecurrent=codehead;
charhead=(charnode *)malloc(sizeof(charnode));
charhead->nextchar=NULL;
charhead->nextnode=NULL;
charhead->val=0;
charcurrent=charhead;
}
int getweight(char * argv1)
{
FILE * fp;
if((fp=fopen(argv1,"r"))==NULL)//open file
{
printf("Open file failed!");
return -1;
}
int current=0;
while((current=getc(fp))!=EOF) //read and analyse the file
{
if(current>=0 && current <128)
{
chars[current]++;
}
else
{
printf("Read file error!");
return -1;
}
}
fclose(fp); //close file
return 0;
}
void buildhftree()
{
//build base linked list of all nodes which appears at least one times, from small to lardge
for(int i=0;i<128;i++)
{
if(chars[i]!=0)
{
tmp=(htnode *)malloc(sizeof(htnode));
tmp->times=chars[i];
tmp->val=i;
tmp->next=NULL;
tmp->parent=NULL;
tmp->left=NULL;
tmp->right=NULL;
linkinsert(tmp);
tmp=NULL;
}
}
//combine first two nodes circularly to build Huffman tree
while(htroot->next->next!=NULL) //here suppose the file has more than 1 char at least
{
htnode * combined=(htnode *)malloc(sizeof(htnode));
combined->val=INTERNAL;
combined->times=(htroot->next->times)+(htroot->next->next->times);
combined->left=htroot->next;
combined->right=htroot->next->next;
combined->next=NULL;
combined->parent=NULL;
htroot->next->parent=combined;
htroot->next->next->parent=combined;
htroot->next=htroot->next->next->next;
linkinsert(combined); //then insert the new node
}
}
void linkinsert(htnode * newnode)
{
htnode * curr;
curr=htroot;
while(curr->next!=NULL)
{
if((newnode->times)<=(curr->next->times))
{
newnode->next=curr->next;
newnode->parent=curr;
curr->next=newnode;
return; //only a flag for after judgement
}
else
{
curr=curr->next;
}
}
curr->next=newnode;
newnode->parent=curr;
}
void huffmancoding(htnode * subroot)
{
if(subroot->left==NULL && subroot->right==NULL)
{
charinsert(subroot->val);
return;
}
code_tmp[q++]='0';
huffmancoding(subroot->left);
q--;
code_tmp[q++]='1';
huffmancoding(subroot->right);
q--;
}
void codeinsert(char code)
{
codenode * codetmp=(codenode *)malloc(sizeof(codenode));
codetmp->code=code;
codetmp->next=NULL;
codecurrent->next=codetmp;
codecurrent=codecurrent->next;
}
void freecodelist(codenode * cnode)
{
if(cnode==NULL) return;
freecodelist(cnode->next);
free(cnode);
return;
}
void charinsert(char value)
{
for(int i=0;i<q;i++) //generate code list from the chars array
{
if(code_tmp[i]=='0')
{
codeinsert('0');
}
else
{
codeinsert('1');
}
}
charnode * chartmp=(charnode *)malloc(sizeof(charnode));
chartmp->nextnode=codehead->next;
chartmp->nextchar=NULL;
chartmp->val=value;
charcurrent->nextchar=chartmp;
charcurrent=charcurrent->nextchar;
codehead->next=NULL; //clear the codehaed and codecurrent for next time of generate
codecurrent=codehead;
}
void delehftree()
{
}
int generafile()
{
int i=0;
FILE * fp;
if((fp=fopen("4.ar","w"))==NULL)
{
printf("Create compressed file failed!");
return -1;
}
charnode * char_tmp=charhead->nextchar;
while(char_tmp!=NULL)
{
printf("%c/t",char_tmp->val);
codenode * code_tmp=char_tmp->nextnode;
while(code_tmp!=NULL)
{
printf("%c",code_tmp->code);
fputc(code_tmp->code,fp);
code_tmp=code_tmp->next;
}
if(i%2==0)
{
printf("/n");
}
else
{
printf("/t/t/t/t");
}
i++;
char_tmp=char_tmp->nextchar;
}
fclose(fp);
return 0;
}