文章目录
- (一)设计描述
- (二)需求分析
- (三)详细设计
- (四)代码实现与测试
- (五)个人总结
(一)设计描述
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;elsebegin.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走过了,加一恢复到开始位,编码个数小于nprintf("%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走过了,加一恢复到开始位,编码个数小于nprintf("%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;
}
(五)个人总结
通过这次课程设计,我收获了很多,认清了课程设计的需求,更认清了自己。谈起缺点,自己代码实现的能力也是破绽百出,对相对较难的题目不能冷静解决,经常犯一些低级的错误。然无独有偶,自己也有优点,自己思想上能理解难题,并尝试解决难题,考虑比较周到。这次的课程设计,难度处于中等地位,但实现起来并不没有想象中的那么容易。它需要考虑多种因素,必须充分理解题目要求,化大难题为多个小难题,把具体实现步骤细分,认真完成每一步步骤,然后综合实现题目对应的问题。 时间如白驹过隙,无声滑过指尖。
学期即将完结,即将大二,不免多生感想。回想一年时光,自己虽说付出努力,然并不能收益众多。深知自己与他人之差距,然必须成为优秀者,方可日后有一席之地。
我没有科学家的那样的天赋异禀,而我却又有一颗积极向上的心,以“努力不一定成功,但不努力绝不会成功”为座右铭,付诸行动与汗水,不忘初心,厚积薄发。正如诗人王之涣所说:“欲穷千里目,更上一层楼。”要有“燕雀安知鸿鹄之志”的眼光,实现能力之飞跃,思想之迅捷,然自己必须十分努力,才能看起来毫不费力。
风雨过后才会见到彩虹。你尽管去努力,剩下的交给天命。定当不忘学习,砥砺前行。肺腑之言,溢于言表。