赫夫曼编码

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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值