1. 问题描述
利用赫夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本。这要求在发送端通过一个编码系统对待传输数据预先编码,在接收端将传来的数据进行译码(复原)。对于双工信道(即可以双向传输信息的信道),每端都需要一个完整的编/译码系统。试为这样的信息收发站编写一个赫夫曼码的编/译码系统。
2. 基本要求
一个完整的系统应具有以下功能:
(1)I:初始化(Initialization)。从指定的英文文件中Sourcefile.txt读取数据,根据文件内容统计的字符的频度,建立哈夫曼树。
(2)E:编码(Encoding)。利用已经建好的哈夫曼树进行编码,并将每个字符的编码写入文件HuffCode.txt中保存。
(3)C:压缩(Compress)。根据HuffCode.txt中编码对文件Sourcefile.txt进行重新编码,并将重新编码后的内容写入文件CodeFile.txt中。
(4)D:译码(Decoding)。利用已经建好的哈夫曼树将文件CodeFile.txt中的代码进行译码,结果存入文件TextFile中。
(5)P:打印代码文件(Print)。将文件CodeFile.txt的内容显示在终端上,每行50个代码。
(6)T:显示哈夫曼树(Treeprinting)。将已经在内存中的哈夫曼树以直观的方式(树或凹入表形式)显示在终端上。
3 测试数据1:
Sourcefile.txt中的内容如下:
I love data Structure, I love Computer。I will try my best to study data Structure.
测试数据2:自定义
实现提示:
(1)用户界面可以设计为“菜单”方式:显示上述功能符号,再加上“Q”选项,表示退出(Quit)。用户可以键入一个选择功能符。此功能执行完毕后再显示此菜单,直至某次用户选择了“Q”为止。
(2)根据Sourcefile.txt中每个字符出现的次数统计频度,对没有出现的字符一律不用编码。
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
// 数据结构:
// 字符频率统计:使用哈希表,int hash[128] 统计频度
// 叶子结点练表:存储树的根节点,使用链表,按频度升序
// 哈夫曼树:二叉树,父结点到左孩子为0,到右孩子为1
// 字频数据元素,保存字符及其频度
typedef struct{
int cnt;
char c;
} Data;
// 哈夫曼树--二叉树
typedef struct _TNode{
Data data;
struct _TNode* left,*right;
} Tree,*Huff;
// 链表 -- 带头结点的单链表
typedef struct _Node{
Huff huff;
struct _Node* next;
} Node,*List;
// 哈希表,字符频度统计
Data Hash[128];
// 编码集
char EncodingSet[128][50];
// 链表操作集
// 创建空链表
List creatList( void ) {
List list = (List)malloc(sizeof(Node));
list->next = NULL;
return list;
}
// 头插
void push_front( List list, Huff huff ) {
Node* node = (Node*)malloc( sizeof(Node) );
node->huff = huff;
node->next = list->next;
list->next = node;
}
// 按频度升序插入元素--
void push( List list, Huff huff ) {
Node* start = list;
Node* node = (Node*)malloc(sizeof(Node));
int cnt = huff->data.cnt;
// 比当前元素小,往后走,否则插入当前位置,若没有元素则直接插入当前结点后
while( start->next ){ // 即找到第一个大于等于自己的元素
start = start->next;
if( start->huff->data.cnt >= cnt ){
node->huff = start->huff;
node->next = start->next;
start->huff = huff;
start->next = node;
return ;// 插入后直接退出
}
}
// 当前元素最大,直接插入末尾
node->huff = huff;
node->next = NULL;
start->next = node;
}
// 删除头部元素并返回 ,当前算法默认链表不会出错,不删空链表
Huff pop( List list ){
Huff h = list->next->huff;
Node* n = list->next;
list->next = n->next;
free( n );
return h;
}
// 尾插--实现队列
void push_back( List list, Huff huff ){
Node* node = (Node*)malloc(sizeof(Node));
node->next = NULL;
node->huff = huff;
while( list->next ){
list = list->next;
}
list->next = node;
}
// 遍历链表
void printList( List list ){
Node* start = list->next;
printf("字符频度:\n");
while( start ){
printf("字符:%c 频度:%d\n",start->huff->data.c,start->huff->data.cnt);
start = start->next;
}
}
// 二叉树操作集
// 将结点作为左右孩子加入到新的父结点并返回
Huff addChildren( Huff l, Huff r ) {
Huff f = (Huff)malloc( sizeof(Tree) );
f->data.cnt = l->data.cnt + r->data.cnt;
f->data.c = '\0';
f->left = l;
f->right = r;
return f;
}
// 创建一个无孩子的根节点,
Huff creatTree( Data data ){
Huff h = (Huff)malloc(sizeof(Tree));
h->left = h->right = NULL;
h->data = data;
return h;
}
// 获取树的深度
int getDepth( Huff huff ) {
if( huff == NULL ){
return 0;
} else {
int left = 1 + getDepth( huff->left );
int right = 1 + getDepth( huff->right );;
if( left > right ){
return left;
} else {
return right;
}
}
}
// 层序遍历
void levelOrder( Huff huff ) {
assert( huff != NULL );
List list = creatList();
push_back( list, huff );
Huff t;
int times = 1;
while( list->next ){
t = pop( list );
if( t->left ){
push_back(list, t->left);
}
if( t->right ){
push_back(list, t->right);
}
if( t->data.c == '\0' ){
printf("() %d",t->data.cnt);
} else {
printf("(%c) %d",t->data.c,t->data.cnt);
}
}
printf("\n");
}
// 哈希表操作集
// 将哈希表设置为设置字符,初始化频度s
void initHash( Data* hash ) {
int i;
for( i = 0; i < 128; i++ ){
hash[i].cnt = 0;
hash[i].c = i;
}
}
// 按频度升序
int cmp( const void* a, const void* b ) {
return (*(Data*)a).cnt-(*(Data*)b).cnt;
}
// 调用C语言的快排
void quickSort( Data* hash ){
qsort( hash,128,sizeof(Data),cmp );
}
// 算法设计:I:初始化(Initialization)。从指定的英文文件中Sourcefile.txt读取数据
// ,根据文件内容统计的字符的频度,建立哈夫曼树。
// I:初始化
// 1.读入文件并统计字符频度 2.将字符即频度按升序插入到链表中
// 3.根据链表,每次提取前两个叶子节点,将频度相加后构成新的叶子结点插入到链表中
// 同时两个叶子结点按小到大的次序作为新结点的左右孩子
// 1.读入文件并统计字符频度
void read( char* filePath ){
FILE* fp = fopen( filePath,"r" );
char c = fgetc( fp );
int i = 0;
while( c != EOF ){
++Hash[c].cnt;
c = fgetc(fp);
}
fclose( fp );
fp = NULL;
}
// 读取文件,返回该文件所建立的哈夫曼树
Huff init( char* filePath ){
// 初始化哈希表,统计字频
initHash( Hash );
read( filePath );
// 按字频升序加入链表
quickSort( Hash );// 升序排序
int i,j,k;
for( i = 0; i < 128; i++ ) {
if( Hash[i].cnt ){// 找到出现字符的区间
break;
}
}
if( i == 128 ){
printf("哈夫曼树建立失败!\n");
return NULL;
}
List list = creatList();
Huff tree;
for( j=127; j != i; --j ){// 其实可以直接用头插,从大到小遍历
tree = creatTree( Hash[j] );
push_front( list, tree );
}
// for( i = 0; i < 128; i++ ){
// printf("%c %d\n",Hash[i].c,Hash[i].cnt);
// }
Huff left,right;
printList( list );
while( list->next->next ){// 当链表只有一个元素时说明哈夫曼树构建成功 ,且根节点即为链表第一个元素
left = pop(list);
right = pop(list);
Huff t = addChildren( left,right );
push( list, t );
// printf("%c %d\n",t->data.c,t->data.cnt);
}
// printList(list);
Huff huff = (list->next->huff);
// levelOrder( huff );
return huff;
}
// E:编码(Encoding)。利用已经建好的哈夫曼树进行编码,并将每个字符的编码写入文件HuffCode.txt中保存。
// 传入哈夫曼编码,生成一个编码集,用存储字符串的数组保存
void getEncodingSet( Huff huff,char* code,int start ){
if( huff->left == NULL && huff->right == NULL ) {// 叶子结点
code[start] = '\0';
strcpy( EncodingSet[huff->data.c], code );
} else {
if( huff->right ){
char code1[50];
strcpy( code1,code );
code1[start] = '1';
getEncodingSet( huff->right, code1, start+1 );
}
if( huff->left ){
code[start] = '0';
getEncodingSet( huff->left, code, start+1 );
}
}
}
void encoding( Huff huff, char* filePath ) {
FILE* fp = fopen( filePath, "w" );
char code[50];
getEncodingSet( huff, code, 0 );
int i;
for( i = 0; i < 128; i++ ){// 字符从小到大 显出编码
if( EncodingSet[i][0] != '\0' ){
fprintf( fp,"%c %s\n",i,EncodingSet[i] );
}
}
fclose( fp );
fp = NULL;
}
// C:压缩(Compress)。根据HuffCode.txt中编码对文件Sourcefile.txt进行重新编码,并将重新编码后的内容写入文件CodeFile.txt中
// 读取Sourcefile.txt 文件到 EncodingSet 编码集中
void compress( char* huffFile, char* srcFile, char* codeFile ){
FILE* huff = fopen( huffFile, "r" );
FILE* src = fopen( srcFile, "r" );
FILE* code = fopen( codeFile, "w" );
// 先加载编码集
char coding[50];
char c;
if( fscanf( huff, " %s", coding ) != EOF ){// 第一行是空格
strcpy( EncodingSet[' '], coding );
}
while( fscanf( huff, " %c %s", &c, coding ) != EOF ){
// printf( "%c %s\n", c, coding );
strcpy( EncodingSet[c], coding );
}
// 压缩
c = fgetc( src );
while( c != EOF ){
fprintf( code, "%s", EncodingSet[c] );
c = fgetc( src );
}
fclose( huff );
fclose( code );
fclose( src );
huff = NULL;
src = NULL;
code = NULL;
}
// D:译码(Decoding)。利用已经建好的哈夫曼树将文件CodeFile.txt中的代码进行译码,结果存入文件TextFile中。
void decoding( Huff huff, char* codeFile, char* textFile ) {
FILE* code = fopen( codeFile, "r" );
FILE* text = fopen( textFile, "w" );
if( code == NULL ) {
printf("^^^^^^^^^^^^^^^^^^\n");
}
char c;
// printf("****%c*******\n",c);
Huff t = huff;
while( 1 ){
c = fgetc( code );
if( c == EOF ){
break;
}
if( c == '0' ) {
t = t->left;
} else {
t = t->right;
}
if( t->left == NULL && t->right == NULL ){
fprintf( text, "%c", t->data.c);
t = huff;
}
}
fclose( code );
fclose( text );
code = NULL;
text = NULL;
}
// P:打印代码文件(Print)。将文件CodeFile.txt的内容显示在终端上,每行50个代码。
void print( char* codeFile ) {
printf("当前文件名:%s\n",codeFile);
FILE* fp = fopen( codeFile, "r" );
char c = getc(fp);
int cnt = 0;
while( c != EOF ){
printf("%c",c);
if( ++cnt == 50 ){
cnt = 0;
printf("\n");
}
c = fgetc(fp);
}
printf("\n");
fclose(fp);
fp = NULL;
}
Huff trees[10000][10000];
void setTrees( Huff huff, int start, int end,int row ){
// printf("1\n");
if( start == end ){
trees[row][start] = huff;
} else {
int mid = (start+end)/2;
trees[row][mid] = huff;
if( huff->left ){
setTrees( huff->left, start, mid, row+1 );
}
if( huff->right ){
setTrees( huff->right, mid+1, end, row+1 );
}
}
}
//T:显示哈夫曼树(Treeprinting)。将已经在内存中的哈夫曼树以直观的方式(树或凹入表形式)显示在终端上。
void treePrint( Huff huff ){
int depth = getDepth( huff );
int i,j;
int num = 1 << depth;
--num ;
printf("num=%d\n",num);
for( i = 0; i < num; i++ ){// 初始化
for( j = 0; i < num; i++ ){
trees[i][j] = NULL;
}
}
setTrees( huff, 0, num-1, 0 );// 将树加入到二维表
int is = 0;
for( i = 0; i < depth; i++ ){
for( j = 0; j < num; j++ ){
if( trees[i][j] ){
if( trees[i][j]->data.c == '\0' ){
printf("( )%d",trees[i][j]->data.cnt);
} else {
printf("(%c)%d",trees[i][j]->data.c,trees[i][j]->data.cnt);
}
// if( trees[i][j]->data.c == '\0' ){
// printf("( )%d",trees[i][j]->data.cnt);
// } else {
// printf("(%c)%d",trees[i][j]->data.c,trees[i][j]->data.cnt);
// }
} else {
// if( is++ % 2 == 0 ){
// printf(" ");
// }
printf(" ");
// printf(" ",trees[i][j]->data.c,trees[i][j]->data.cnt);
}
}
printf("\n");
}
}
int view( void ){
printf("\n\t\t\t\t\t*******赫夫曼编码系统********\n");
printf("\t\t\t\t\t请选择以下功能:\n");
printf("\t\t\t\t\t1.初始化(读取文件)\t2.编码(写入编码集)\t3.压缩(文件编码)\n");
printf("\t\t\t\t\t4.译码(文件解码)\t5.打印代码文件\t6.显示哈夫曼树\n");
printf("\t\t\t\t\t7.退出\n");
int select;
scanf("%d",&select);
return select;
}
char* files[10] = { "Sourcefile.txt", "HuffCode.txt", "CodeFile.txt", "TextFile.txt" };
int main() {
int select;
char str[50];
char str1[50];
char str2[50];
Huff huff;
while( 1 ){
select = view();
switch( select ){
case 1:
printf("请输入用于构建赫夫曼树的指定文件(输入1默认为Sourcefile.txt):\n");
scanf("%s",str);
if( strcmp( str, "1" ) == 0 ){
huff = init( files[0] );
} else {
huff = init( str );
}
printf("赫夫曼树已建立成功!\n");
break;
case 2:
printf("请输入用于写入编码的指定文件(输入1默认为HuffCode.txt):\n");
scanf("%s",str);
if( strcmp( str, "1" ) == 0 ){
strcpy( str, files[1] );
}
encoding( huff, str );
printf("编码集已写入!\n");
break;
case 3:
printf("请输入编码集的文件(输入1默认为HuffCode.txt):\n");
scanf("%s",str);
if( strcmp( str, "1" ) == 0 ){
strcpy( str, files[1] );
}
printf("请分别输入目标文件和目的文件:(输入1默认分别为 Sourcefile.txt CodeFile.txt):\n");
scanf("%s",str1);
if( strcmp( str1,"1" ) == 0 ){
strcpy( str1, files[0] );
strcpy( str2, files[2] );
}
compress( str, str1, str2 );
printf("压缩成功!\n");
break;
case 4:
// char* files[10] = { "Sourcefile.txt", "HuffCode.txt", "CodeFile.txt", "TextFile" };
// compress( "HuffCode.txt", "Sourcefile.txt", "CodeFile.txt" );
printf("请输入需要译码的源文件(输入1默认为CodeFile.txt):\n");
scanf("%s",str);
if( strcmp( str, "1" ) == 0 ){
strcpy( str, files[2] );
}
printf("请输入结果保存的文件名(输入1默认为TextFile.txt):\n");
scanf("%s",str1);
if( strcmp( str1, "1" ) == 0 ){
strcpy( str1, files[3] );
}
decoding( huff, str, str1 );
printf("译码成功!\n");
break;
case 5:
printf("请输入需要打印的文件(输入1默认为CodeFile.txt):\n");
scanf("%s",str);
if( strcmp( str, "1" ) == 0 ){
strcpy( str, files[2] );
}
print( str );
break;
case 6:
treePrint( huff );
break;
case 7:
printf("系统已退出~~~~\n");
return 0;
}
}
encoding( huff, "HuffCode.txt" );
compress( "HuffCode.txt", "Sourcefile.txt", "CodeFile.txt" );
decoding( huff, "CodeFile.txt", "TextFile.txt" );
print( "CodeFile.txt" );
levelOrder( huff );
printf("树的深度为:%d\n",getDepth(huff));
treePrint( huff );
return 0;
}