Huffman编码的设计与实现

(一)设计描述

1.题目描述
设计一个利用哈夫曼算法的编码和译码系统,重复地显示并处理以下项目,直到选择退出为止。

1) 输入一串字符,然后统计其中各个字符的个数,即每个字符的权值。
2)  进行初始化操作,并建立哈夫曼树;
3)  编码:利用建好的哈夫曼树生成哈夫曼编码,并输出编码;
4)  译码,即输入每个字符对应的二进制的编码(0或1),然后输出对应的字符。
5) 结束操作。

2.设计目的与要求

1)目的:

通过布置具有一定难度的实际程序设计项目,进一步理解和掌握课堂上所学各种基本抽象数据类型的逻辑结构、存储结构和操作实现算法

2)要求:

1.要求利用C\C++语言来完成系统的设计; 
2.突出C语言的函数特征(以多个函数实现每一个子功能)或者C++语言面向对象的编程思想;
3.画出功能模块图;
4.进行简单界面设计,能够实现友好的交互;
5.具有清晰的程序流程图和数据结构的详细定义;
6.熟练掌握C语言或者C++语言的各种操作。

(二)需求分析

第一步定义Treecount(哈夫曼树节点的个数),随后定义两个结构体,HTnode和HTcode,结构体HTnode储存数据信息,即(输入的字符(data),权值(weight),父结点(parent),左孩子(lchild),右孩子(rchild))。 结构体HTnode储存结果信息,即(输入的字符(data),BT数组(储存哈夫曼编码结果),start(哈夫曼编码的起始位置),num统计各个字符的出现的个数,即权值,input 输入的字符)
第二步定义字符指针P,数组letter[]和数组string[],数组letter用来调用数据 input,data,num;string[]用来输入字符,同时让指针P指向string[];由于数组是单个存储,判断字符是否相同,用account,num分别统计字母个数以及每个字符的对应次数,即求权值。
第三步首先定义两个数组huffnode,array[]分别储存结点与字符,然后进行哈夫曼树的初始化,然后找到权值最小的两个结点,并记录当前下标。由于是逆序输出,左右子树的结点位置为2n-1;得知左右子树的权值后,让左右子树权值进行相加,把值赋给一个新的结点。
第四步结构体HTcode实例化一个对象begin,并定义两个数组huffnode,huffcode,调用哈夫曼树,并记录编码的位置为n-1;倒序编码,并记录下标。然后回溯头结点,找到左孩子,赋值为0;找到右孩子赋值为1。然后进行start–,倒着计算编码,沿着父母结点往上走到结点,保存并求出每个叶子节点的哈夫曼编码与编码的起始位。
第五步定义数组code数组,字符指针q,输入0和1的数字组合,此时哈夫曼树对应的叶子结点下标为2n-2;然后进行译码,如果读到0,从根结点的左孩子继续读入,直到读到1,然后继续读入,即读到了一个叶子(字符),即翻译一个字符成功,然后进行遍历输出结果。
第六步用户选择结束系统

(三)详细设计

1.实现思想

1.首先要构造一棵哈夫曼树,哈夫曼树的结点结构包括权值,双亲,左右孩子;假如由n个字符来构造一棵哈夫曼树,则共有结点2n-1个;在构造前,先初始化,初始化操作是把双亲,左右孩子的下标值都赋为0;然后依次输入每个结点的权值

2.第二步是通过n-1次循环,每次先找输入的权值中最小的两个结点,把这两个结点的权值相加赋给一个新结点,,并且这个新结点的左孩子是权值最小的结点,右孩子是权值第二小的结点;鉴于上述找到的结点都是双亲为0的结点,为了下次能正确寻找到剩下结点中权值 最小的两个结点,每次循环要把找的权值最小的两个结点的双亲赋值不为0(i).就这样通过n-1循环下、操作,创建了一棵哈夫曼树,其中,前n个结点是叶子(输入的字符结点)后n-1个是度为2的结点

3.编码的思想是逆序编码,从叶子结点出发,向上回溯,如果该结点是回溯到上一个结点的左孩子,则在记录编码的数组里存“0”,否则存“1”,注意是倒着存;直到遇到根结点(结点双亲为0),每一次循环编码到根结点,把编码存在编码表中,然后开始编码下一个字符(叶子)

4.译码的思想是循环读入一串哈夫曼序列,读到“0”从根结点的左孩子继续读,读到“1”从右孩子继续,如果读到一个结点的左孩子和右孩子是否都为0,如果是说明已经读到了一个叶子(字符),翻译一个字符成功,把该叶子结点代表的字符存在一个存储翻译字符的数组中,然后继续从根结点开始读,直到读完这串哈夫曼序列,遇到结束符便退出翻译循环

3.2 创建哈夫曼树图示
已知叶子节点为{10,20,30,40},以这4个权值构建树b的过程为:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
3.实现流程图
在这里插入图片描述

(四)代码实现与测试

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<windows.h>

#define Treecount 50            //最大叶结点个数
#define MAXSIZE 500
typedef struct node {
	char data;
	int weight;
	int parent;
	int lchild;
	int rchild;
} HTnode;
typedef struct code {
	char  data;
	int BT[MAXSIZE];      //数组,存放字符的哈夫曼编码
	int start;           //该编码在数组中的开始位置
	char input;
	int num;
} HTcode;
void Createhufftree(HTnode HuffNode[],int n,HTcode array[]) {   // array[]: 用来存储字符及其权值
	int i,j;
	int minfirst,minsecond;
	int Lnode,Rnode;
	for(i=0; i<2*n-1; i++) {
		HuffNode[i].data=0;
		HuffNode[i].weight=0;
		HuffNode[i].parent=-1;
		HuffNode[i].lchild=-1;
		HuffNode[i].rchild=-1;
	}

	for(i=0; i<n-1; i++) {
		minfirst=minsecond=32767;  //给最小和次小的树赋最大值
		Lnode=Rnode=-1;
		for(j=0; j<n+i; j++) {
			if(HuffNode[j].parent==-1 && HuffNode[j].weight<minfirst) {  	//每次找出剩下的最小权值,并将最小权值赋给次小
				minfirst=minsecond;
				Rnode=Lnode;
				minfirst=HuffNode[j].weight;
				Lnode=j;   //记录下标
			} else if(HuffNode[j].parent==-1 && HuffNode[j].weight<minsecond) {
				minsecond=HuffNode[j].weight;
				Rnode=j;
			}
		}
		HuffNode[Lnode].parent = n+i;
		HuffNode[Rnode].parent = n+i;
		HuffNode[n+i].weight = HuffNode[Lnode].weight+HuffNode[Rnode].weight;
		HuffNode[n+i].lchild = Lnode;
		HuffNode[n+i].rchild = Rnode;
	}
	for(i=0; i<n; i++) {
		HuffNode[i].weight=array[i].num;    //将字符个数赋给权值
		HuffNode[i].data=array[i].input;
	}
}
void QQ() {
	printf("请输入二进制译码启动密码,密码错误默认结束(即为默认退出):");
	long sercret;
	scanf("%d",&sercret);
	if(sercret==11011) {
		Sleep(1000);
	} else {
		Sleep(1000);
		printf("非法操作,结束系统");
		exit(-1);
	}
}
void  HuffmanCoding(int  n, HTcode array[]) {// array[]: 用来存储字符及其权值
	HTnode  HuffNode[1000];
	HTcode  HuffCode[Treecount];
	HTcode  begin;
	FILE *fp;
	fp=fopen("D:\\a.txt","w");

	Createhufftree(HuffNode,n,array);
	int  i, j ;
	int  loc,p;
	char code[30],*q;
	for(i=0; i<n; i++) {
		begin.start=n-1;                  //编码的开始位置
		loc=i;
		p=HuffNode[loc].parent;
		while(p!=-1) {
			if(HuffNode[p].lchild==loc)
				begin.BT[begin.start]=0;
			else
				begin.BT[begin.start]=1;
			begin.start--;                       //倒着计算编码
			loc=p;                               //沿着父母结点往上走到顶点
			p=HuffNode[loc].parent;
		}
		for(j=begin.start+1; j<n; j++)
			HuffCode[i].BT[j]=begin.BT[j];
		HuffCode[i].start=begin.start;                      //   保存求出的每个叶节点的哈夫曼编码和编码的起始位
		printf("\n");
	}
	printf("各个字符对应的二进制编码(1//0//1//0)如下:\n");
	for(i=0; i<n; ++i) {
		printf("字符%c的密文:",HuffNode[i].data);
		for(j=HuffCode[i].start + 1; j<n; j++)      //start走过了,加一恢复到开始位,编码个数小于n
			printf("%d", HuffCode[i].BT[j]);
		printf("\n");
	}
	printf("\n");
	printf("编码成功,1s后打印哈夫曼编码:\n");
	Sleep(1000);
	for(i=0; i<n; ++i) {
		for(j=HuffCode[i].start+1; j<n; j++) {
			fprintf(fp,"%d",HuffCode[i].BT[j]);
		}
		for(j=HuffCode[i].start + 1; j<n; j++) {
			printf("%d", HuffCode[i].BT[j]);
		}
	}
	printf("\n\n");
	if(i>0) {
		QQ();
	}
	//   译码:
	system("cls");
	printf("            ----- ∮哈夫曼译码系统已经就绪 ∮-------        \n");
	printf("各字符的对应的编码如下:\n"); 
	
	for(i=0; i<n; ++i) {
		printf("字符%c的密文:",HuffNode[i].data);
		for(j=HuffCode[i].start + 1; j<n; j++)      //start走过了,加一恢复到开始位,编码个数小于n
		printf("%d", HuffCode[i].BT[j]);
		printf("\n");
	}
	printf("                  --> 请输入对应的二进制数字:              \n");
	scanf("%s",&code);
	q = code;
	loc = 2*n -2;                                  //    哈夫曼树的根节点下标
	printf("译码成功,正在打印译码结果:\n");
	Sleep(2000);
	while( *q != NULL) {
		if( *q== '0') {
			loc = i =HuffNode[loc].lchild;
			if(HuffNode[loc].lchild == -1 && HuffNode[loc].rchild == -1) {
				printf("%c",HuffNode[i].data);
				fprintf(fp,"%c",HuffNode[i].data);
				loc = 2*n - 2;
			}
		} else if(*q == '1') {
			loc = i = HuffNode[loc].rchild;
			if(HuffNode[loc].lchild == -1 && HuffNode[loc].rchild == -1) {
				fprintf(fp,"%c",HuffNode[i].data);
				printf("%c",HuffNode[i].data);
				loc = 2*n-2;                       // 重新从根遍历
			}
		} else {
			printf("非法输入!");
		}
		q++;
	}
	printf("\n");
	fclose(fp);
}
void menu() {
	system("color F");
	printf("                                                      ");
	printf("                                                     \n");
    printf("   ____〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓____    \n");
	printf("               ||  哈夫曼编码  ||                    \n");
	printf("        _________☆界面选择如下☆_________           \n");
	printf("                                                     \n");
	printf("             -> 1 ☆.编码与译码                      \n");
	printf("             -> 2 ☆.结束系统                        \n");
	printf("------------ ☆             ☆-----------------      \n");
   	printf("              ∮请手动输入编号:              "); 
}
void Treeaction() {
	HTcode letter[50];
	char string[1000], *p;
	int i, account=0;
	printf("          ----- ∮哈夫曼编码系统已经就绪 ∮-------        \n");
	printf("            》》  请输入你要编码的字符串:");
	scanf("%s",&string);
	for(i=0; i<50; i++) {
		letter[i].input=0;
		letter[i].num=0;
	}
	p=string;
	while(*p!=NULL) {
		for(i=0; i<=account+1; i++) {
			if(letter[i].input==NULL) {      //letter[i].input存储所有的字符,用letter[i].num存储相同的字符的数量
				letter[i].input=*p;
				letter[i].num++;
				account++;                       // 统计不同字符的个数
				break;
			} else if(letter[i].input==*p) {
				letter[i].num++;
				break;
			}
		}
		p++;
	}
	printf("\n");
	printf("不同的明文(字符)个数:%d\n",account);
	printf("\n");
	printf("输出输入的字符的权值\n");
	for(i=0; i<account; i++) {       //输出不同字符及其所对应的权值
		printf("当前字符%c的权值为%d",letter[i].input,letter[i].num);
		printf("\n");
	}
	printf("\n");
	HuffmanCoding(account,letter);

}
int main() {
	int  n,flaglist=1;
	char choice;
	while(flaglist) {
		menu();
		scanf("%d",&n);
		switch(n) {
			case 1:
				system("cls");
				Treeaction();
				printf("\n");
				printf("2秒后系统进行返回!\n") ;
				Sleep(2000);
				printf("\n");
				system("cls");
				printf("已经返回!\n"); 
				printf("请根据个人意愿进行操作!\n");
				printf("\n");
				break;
			case 2:
				exit(0);
		}
	}
	return 0;
}

(五)个人总结

通过这次课程设计,我收获了很多,认清了课程设计的需求,更认清了自己。谈起缺点,自己代码实现的能力也是破绽百出,对相对较难的题目不能冷静解决,经常犯一些低级的错误。然无独有偶,自己也有优点,自己思想上能理解难题,并尝试解决难题,考虑比较周到。
这次的课程设计,难度处于中等地位,但实现起来并不没有想象中的那么容易。它需要考虑多种因素,必须充分理解题目要求,化大难题为多个小难题,把具体实现步骤细分,认真完成每一步步骤,然后综合实现题目对应的问题。 时间如白驹过隙,无声滑过指尖。
学期即将完结,即将大二,不免多生感想。回想一年时光,自己虽说付出努力,然并不能收益众多。深知自己与他人之差距,然必须成为优秀者,方可日后有一席之地。
  • 10
    点赞
  • 69
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值