哈夫曼编码:
假设要给一偏文章进行编码,文章由英文组成,对于全文来说,我们可以统计得到这篇文章各个字母出现的个数,个数大的字母意味着它在文章中出现的频率更高,对所有的字母都用相同的空间大小编码的话会产生一定的空间浪费。哈夫曼编码使得高频率出现的字母用更短码值表示,低频率出现的字母用更长的码值表示,可以缩小整篇文章的空间占用,达到压缩的目的。把字母看做编码用的各个编码数值同理。
哈夫曼树定义:
给定n个自带权值的节点构造一个二叉树,若带权路径长度最小,称这样的树为哈夫曼树或者最优二叉树。哈弗曼树是权值最小的二叉树,权值越大的节点离根节点越近。
基本概念:
- 路径和路径长度:在一棵树中,从一个节点往下可以到达的孩子或者孙子节点之间的通路,成为路径。通路中分支路径的个数成为路径长度。
- 节点的权与带权路径长度:若将树中节点赋予一个有意义的数值,则这个数值成为这个节点的权。节点的带权路径长度为节点当前权值乘上路径长度。
- 树的带权路径长度: 树的带权路径长度为所有叶节点的带权路径长度之和,几位WPL。
哈夫曼树的构造:
- 把n个节点看做n棵二叉树,此时每棵树只有一个节点
- 在森林中选择两个权值最小的树进行合并,形成新的二叉树,该树的根节点的权值为合并的左右树的权值之和
- 从森林中删去合并的两棵树,并加入合并后的那棵新树
- 重复2、3步直到森林中只剩一棵树结束,哈夫曼树构造完成。
哈弗曼编码:
先统计带压缩文件的各个节点的权值,生成一个哈夫曼树;根据单词的各个编码字符,从节点开始,找到这个节点的parent节点在比较原节点是父节点的左孩子还是右孩子,一般规定是左孩子为0,右孩子为1,比较到根节点为止,得到的编码是最终编码的倒序,进而得到每个节点的哈夫曼编码。之后结点的个数,符号,以及编码写在哈夫曼压缩文件头部,供文件解压的时候使用;之后开始读入压缩文件,将已有的字符对应的哈夫曼编码写入待输出二进制文件完成压缩。哈夫曼解码的时候根据文件头的信息生成哈夫曼树,因为哈夫曼编码为前缀编码,之后从根节点开始将编码转化成一个个字符(哈夫曼树的叶子节点),以此类推,解压完成。
源程序代码:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define maxsize 20001
typedef struct BTnode{
int weight;//权重
int parent;//父节点
int lchild;//子节点
int rchild;
unsigned char a;
}BTnode;
typedef struct CharNode{
char bit[maxsize];
unsigned char a;
int weight;
}CharNode;//字符节点(存储权重与编码)
BTnode BT[maxsize];
CharNode Dictionary[maxsize];
int root;
int node_num;
long File_Point;
void get_weight(char* input)//读取未压缩文件进行的权重生成
{
printf("字符权重计算中...\n") ;
unsigned char temp, pre;
FILE *fp;
fp = fopen(input, "rb");
if(fp == NULL)
{
printf("Input Error\n");
exit(0);
}
while(!feof(fp))
{
fread(&temp, sizeof(unsigned char), 1, fp);
if(temp == '\0')
continue;
BT[temp].weight++;
BT[temp].a = temp;
temp = '\0';
}
fclose(fp);
printf("计算权重已完成\n");
}
void HuffmanTree()//压缩文件过程中源文件生成的霍夫曼树
{
printf("正在构建哈夫曼树...\n");
BTnode t;
int minweight;
int lchild, rchild;
int k=0;
for(int i = 0; i<maxsize ;i++)//冒泡排序将叶子节点按照从大到小顺序排列
{
for(int j = 0; j<maxsize; j++)
{
if(BT[j].weight<BT[j+1].weight)
{
t = BT[j+1];
BT[j+1] = BT[j];
BT[j] = t;
}
}
}
while(BT[k].weight!=0)
k++;
node_num=k;//统计字符个数
root = 2*k-2;//k个叶子节点k-1个内部节点
for(int i=k; i<2*k-1; i++)//接下去两个for循环取到两个权值最小节点
{
minweight = maxsize;
lchild = -1;
rchild = -1;
for(int j=0; j<i; j++)
{
if(BT[j].weight<minweight && BT[j].parent==-1)
{
minweight = BT[j].weight;
lchild = j;
}
}
minweight = maxsize;//将该变量重置为max值以便于后面比较取值
BT[lchild].parent = i;
for(int h=0; h<i; h++)
{
if(BT[h].weight<minweight &&BT[h].parent==-1)
{
minweight = BT[h].weight;
rchild = h;
}
}
BT[rchild].parent = i;
BT[i].weight = BT[lchild].weight + BT[rchild].weight;
BT[i].lchild = lchild;
BT[i].rchild = rchild;
}
BT[root].parent = maxsize;
printf("哈夫曼树完成构建\n");
}
void coding(){//哈弗曼编码
printf("正在进行哈弗曼编码...\n");
int ndoe = 0;
int findp, itself, point;
int end;
char temp;
for(int node = 0; node<=root; node++)
{
if(BT[node].a != NULL)
{
Dictionary[BT[node].a].a = BT[node].a;
Dictionary[BT[node].a].weight = BT[node].weight;
point=0;
findp = BT[node].parent;
itself = node;
while(findp <= root)
{
if(BT[findp].lchild == itself)//如果该节点是叶子节点
{
Dictionary[BT[node].a].bit[point] = '0';
point ++;
}
else if(BT[findp].rchild == itself)
{
Dictionary[BT[node].a].bit[point] = '1';
point ++;
}
findp = BT[findp].parent;
itself = BT[itself].parent;
}
}
}
for(int i=0; i<maxsize; i++)
{
if(strlen(Dictionary[i].bit)!=0)
{
for(int j=0; j<strlen(Dictionary[i].bit); j++)
{
for(int k=0; k<strlen(Dictionary[i].bit)-j-1; k++)
{
temp = Dictionary[i].bit[k];
Dictionary[i].bit[k] = Dictionary[i].bit[k+1];
Dictionary[i].bit[k+1] = temp;
}
}
}
}
printf("哈弗曼编码已完成\n");
for(int i=0; i<maxsize; i++)
{
if(Dictionary[i].weight!=0)
{
printf("符号:%c\t出现频率:%d\t哈夫曼编码%s\n", Dictionary[i].a, Dictionary[i].weight, Dictionary[i].bit);
}
}
}
void Output(char* input)//压缩后的二进制编码生成二进制文件
{
unsigned char temp;
unsigned char output;
char out_dat[maxsize];
int num = 0;
int all = 0;
FILE *fp, *out;
printf("生成文件名:");
scanf("%s", out_dat);
strcat(out_dat, ".dat");
printf("生成压缩文件%s\n", out_dat);
temp = '\0';
fp = fopen(input, "rb");
out = fopen(out_dat, "wb");
if(fp == NULL || out == NULL)
{
printf("输入文件名错误或建立新文件失败\n");
exit(0);
}
fwrite(&node_num, sizeof(int), 1, out);//将节点个数写入哈弗曼压缩文件
for(int i=0; i<node_num; i++)//压缩文件中写入各节点的字符和权重
{
fwrite(&BT[i].a, sizeof(unsigned char), 1, out);
fwrite(&BT[i].weight, sizeof(int), 1, out);
}
while(!feof(fp))
{
fread(&temp, sizeof(unsigned char), 1, fp);
// printf("%d:", temp);//555555555555555555555555555555555555555555555555
// printf("%s\n", Dictionary[temp].bit);
if(temp == '\0')
continue;
all++;
for(int i=0; i<strlen(Dictionary[temp].bit); i++)
{
output <<= 1 ;
if(Dictionary[temp].bit[i] == '1')
{
output +=1;
// printf("1");////////////////////////////////
}
// else printf("0");/////////////////////////////////
num++;
if(num==8)
{
fwrite(&output, sizeof(unsigned char), 1, out);
// printf("\n");//////////////////////////////////
num = 0;
}
}
temp = '\0';
}
if(num!=0){
for(int i=8; i>num; i--)
{
output <<=1;
// printf("0");//////////////////////////////////
}
fwrite(&output, sizeof(unsigned char), 1, out);
}
fwrite(&all, sizeof(int), 1, out);
printf("\n压缩文件成功\n");
}
void Form_Huffman(char *input)
{
printf("生成霍夫曼树...\n");
FILE *fp;
BTnode t;
int num, minweight, all;
int rchild, lchild;
unsigned char a;
int weight;
fp = fopen(input, "rb");
fseek(fp, -4, 2);
fread(&all, sizeof(int), 1, fp);
// printf("%d\n", all);
fseek(fp, 0, 0);
fread(&num, sizeof(int), 1, fp);
for(int i=0; i<num; i++)
{
fread(&a, sizeof(unsigned char), 1, fp);
fread(&weight, sizeof(int), 1, fp);
BT[i].a = a;
BT[i].weight = weight;
}
root = num;
for(int i = 0; i<num ;i++)
{
for(int j = 0; j<num-1; j++)
{
if(BT[j].weight<BT[j+1].weight)
{
t = BT[j+1];
BT[j+1] = BT[j];
BT[j] = t;
}
}
}
for(int i=num; i<2*num-1; i++)
{
minweight = maxsize;
lchild = -1;
rchild = -1;
for(int j=0; j<i; j++)
{
if(BT[j].weight<minweight && BT[j].parent==-1)
{
minweight = BT[j].weight;
lchild = j;
}
}
minweight = maxsize;
BT[lchild].parent = i;
for(int h=0; h<i; h++)
{
if(BT[h].weight<minweight &&BT[h].parent==-1)
{
minweight = BT[h].weight;
rchild = h;
}
}
BT[rchild].parent = i;
BT[i].weight = BT[lchild].weight + BT[rchild].weight;
BT[i].lchild = lchild;
BT[i].rchild = rchild;
root = i;
}
printf("成功重构霍夫曼树\n");
// for(int i=0; i<num; i++)
// {
// printf("%d\n", BT[i].a);
// }
printf("还原源文件中...\n");
int root_head;
char Origin_Code[maxsize];
unsigned char copy, judge;
FILE *out;
printf("输入您想输出的文件名称:");
scanf("%s", Origin_Code);
out = fopen(Origin_Code, "w");
if(out == NULL)
{
printf("Input Error\n");
exit(0);
}
root_head = root;
while(!feof(fp))
{
fread(©, sizeof(unsigned char), 1, fp);
for(int i=1; i<=8; i++)
{
judge = copy&10000000;
copy<<=1;
if(judge>0)
{
root_head = BT[root_head].rchild;
// printf("1");///////////////
}
else if(judge==0)
{
root_head = BT[root_head].lchild;
// printf("0");/////////////////
}
if(BT[root_head].a !=NULL)
{
fwrite(&BT[root_head].a, sizeof(unsigned char), 1, out);
all--;
root_head = root;
if(all==0)
break;
}
// if(i==8)
// {
// printf("\n");
// }
}
if(all==0)
break;
}
printf("还原源文件成功\n");
}
int main()
{
int num=0;
char File_Name[maxsize];
int choose=0;
printf("=================================================================\n");
printf(" 请输入您想要完成的功能:1、压缩文件;2、解压文件;3、退出程序\n");
printf("\n 注意:(1)压缩文件名带后缀;\n\t(2)压缩文件名默认.dat格式;\n\t(3)带压缩文件名带后缀;\n");
printf("=================================================================\n");
printf("输入:");
scanf("%d", &choose);
memset(File_Name, 0, sizeof(File_Name));
root = 0;
for(int i = 0; i<maxsize; i++)//初始化节点数组
{
BT[i].a = NULL;
BT[i].lchild=-1;
BT[i].parent=-1;
BT[i].rchild =-1;
BT[i].weight = 0;
}
for(int i=0; i<maxsize; i++)
{
Dictionary[i].a = NULL;
Dictionary[i].weight=0;
Dictionary[i].bit[0] = '\0';
}
switch(choose)
{
case 1://压缩文件
printf("请输入待压缩文件名:");
scanf("%s", File_Name);
get_weight(File_Name);//获取各个字符的权重
HuffmanTree();
coding();
Output(File_Name);
// for(int i=0; i<maxsize; i++)
// {
// if(Dictionary[i].a!=NULL)
// {
// printf("%d:", Dictionary[i].a);
// printf("%s", Dictionary[i].bit);
// printf("\n");
// }
// }
break;
case 2://解压缩文件
printf("输出待解压文件名:");
scanf("%s", File_Name);
Form_Huffman(File_Name);
break;
case 3:
return 0;
default :
choose = 0;
printf("输入错误请重新输出数据\n");
break;
}
return 0;
}