【问题描述】
编写一程序采用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;
}