目录
1 需求分析
1.1 任务
根据输入的字符串统计字符串中出现的字符种类以及各个字符出现的次数,并将出现次数记为该字符所具有的权值。而后根据字符及其权值以结构体数组的形式构造Huffman树。在Huffman树构造完成之后依据Huffman树的相关信息构造每个字符的Huffman编码。注意该字符串中所出现的字符即为最终Huffman树中的叶子结点。
1.2 输入形式
(1)长度输入:占一行,为一个int型整数。初步估计输入的字符串长度并将长度输入,原则上可比输入的字符串长度大,但一定不能比输入的字符串长度小。
(2)字符串输入:占一行,输入一个字符串。输出的字符串的总长度需要小于上一步输入的整数。
(3)样例输入: 请输入字符串估计长度:>>30
请输入字符串:>>ASDFSXZCASDSCXZCDSAFCZX
1.3 输出形式
本程序输出形式共三种。
(1)字符及权值输出:在字符串及长度输入完成后,程序会将字符串中所出现的字符及其权值输出到屏幕上。共n行(n为字符串中字符种类数量),每一行第一段为字符,第二段数字为其权值。例如:A 3
(2)Huffman树输出:以二维表格的形式输出。共2*n-1行,每行包括Num、Word、Weight、Parent、Lchild、Rchild五个信息:Num表示当前字符所在节点的序号;Word表示当前节点的字符值,特别注意有叶子结点生成的父节点无字符值为空;Weight表示当前节点的权值;Parent表示当前节点的父节点的Num值,注意最终的整树根节点的Parent值为0;Lchild和Rchild分别表示当前节点的左右孩子所在的Num值,注意叶子结点的该值为0。
(3)Huffman编码的输出:共n行。每一行第一段为提示语,即提示随后出现的编码为哪一个字符,第二段为该字符所对应的Huffman编码。例如:字符A的编码为:011
1.4 程序功能
1.4.1 输入初始信息
“void Enter_and_Analysis(Singal_word *SW)”功能为接收输入的字符串长度及字符串,并调用相关函数对输入的字符串进行分析。
1.4.2 字符串分析
“Status Analysis(Singal_word *SW,char *str)”接收“输入初始信息”函数的调用来分析字符串,生成对应的字符及其权值并将其信息保存到SW数组以备调用。
1.4.3 寻址及判断
“Bool Adjust(Singal_word *SW,char c)”接收“字符串分析”函数的调用,来判断“字符串分析”函数当前所处理的字符是否已经在SW中出现过。若出现过则返回所在位置,若未出现过则返回-1。
1.4.5 Huffman树构造
“void HuffmanTreeing(HuffmanTree *HT,Singal_word *SW)”根据SW中信息生成Huffman树,结果保存到结构体数组HT中。
1.4.6 选取节点
“void Select(HuffmanTree *HT,int m,int *s1,int *s2)”接收“Huffman树构造”函数调用,为“Huffman树构造”函数选取当前可用节点中两个权值最小的节点。
1.4.7 Huffman编码构造
“void HuffmanCoding(HuffmanTree *HT,HuffmanCode *HC,int n)”调用Huffman树从叶子到根逆向求每个字符的Huffman编码,保存到结构体数组HC中。
1.4.8 输出
“void Printf_AResult(Singal_word *SW)”、“void Printf_HuffmanTree(HuffmanTree *HT,int n)”、和“void Printf_HuffmanCode(HuffmanCode *HC,Singal_word *SW)”分别输出“字符及其权值”、“Huffman树”以及“Huffman编码”。
2 概要设计
2.1 抽象数据类型
//定义Huffman树节点信息,包括字符、权值、双亲结点、左右孩子
typedef struct tree{
char word;
int weight,parent,lchild,rchild;
}HuffmanTree;
//定义Huffman编码,为字符串类型
typedef struct code{
char *code;
}HuffmanCode;
//定义Singal_word保存从字符串中分析得到的结果,length保存结果数量,仅在第一个位置使用
typedef struct temp{
char word;
int weight;
int length;
}Singal_word;
2.2 主程序流程图
3 详细设计
3.1 功能函数设计
//输入并调用函数进行分析
void Enter_and_Analysis(Singal_word *SW){
//输入字符串并调用分析
char *str;
int i,t;
printf("请输入字符串估计长度:>>");
scanf("%d",&t);
str=(char *)malloc(t*sizeof(char));
printf("请输入字符串:>>");
scanf("%s",str);
SW[0].length=0;
for(i=0;i<Init_Singal_word;i++){
//初始化存放分析结果的空间
SW[i].weight=0;
SW[i].word=' ';
}
Analysis(SW,str);
}
//字符串分析函数
Status Analysis(Singal_word *SW,char *str){
//统计输入的字符串中字符种类及其对应的权值
int i=0,flag=0;
for(i=0;str[i]!='\0';i++){
flag=Adjust(SW,str[i]);
if(flag==-1){
//判定为未存在的字符时操作——增加节点信息
SW[SW[0].length].word=str[i];
SW[SW[0].length].weight++;
SW[0].length++;
}
else{
//判定为已存在的字符时操作——增加权值
SW[flag].weight++;
}
}
return OK;
}
//寻址及判断函数
Bool Adjust(Singal_word *SW,char c){
//判断参数中字符是否已经在参数数组中出现
int i=0;
if(SW[0].length==0) return -1;
for(i=0;i<=SW[0].length;i++){
if(c==SW[i].word) return i;
}
return -1;
}
//Huffman树构造函数
void HuffmanTreeing(HuffmanTree *HT,Singal_word *SW){
//构造Huffman树
int i,n,m,s1,s2;
HuffmanTree *p;
n=SW[0].length;
if(n<=1) return;
m=2*n-1;
for(p=HT+1,i=1;i<=n; ++ i,++p){
//初始化Huffman树
p->word=SW[i-1].word;
p->weight=SW[i-1].weight;
p->lchild=0;
p->rchild=0;
p->parent=0;
}
for(;i<=m; ++ i, ++ p){
//初始化Huffman树
p->word=' ';
p->weight=0;
p->lchild=0;
p->rchild=0;
p->parent=0;
}
for(i=n+1;i<=m; ++ i){
//构造Huffman树
Select(HT, i-1, &s1, &s2); //选两个最小节点
HT[s1].parent=i;HT[s2].parent=i;
HT[i].lchild=s1;HT[i].rchild=s2;
HT[i].weight=HT[s1].weight+HT[s2].weight;
}
}
//最小节点选择函数:
void Select(HuffmanTree *HT,int m,int *s1,int *s2){
//选择parent=0且weight最小的两个结点
int i,min;
for(i=1;i<=m;i++){
//在(*HT)[1..i-1]中选择parent=0且weight最小的两个结点
if(HT[i].parent==0){
min=i;
i=m+1;
}
}
for(i=1;i<=m;i++){
if(HT[i].parent==0){
if(HT[i].weight<HT[min].weight)
min=i;
}
}
*s1=min; //parent=0且weight最小的两个结点,第一个序号为s1
for(i=1;i<=m;i++){
//在(*HT)[1..i-1]中选择parent=0且weight最小的两个结点
if(HT[i].parent==0&&i!=(*s1)){
min=i;
i=m+1;
}
}
for(i=1;i<=m;i++){
if(HT[i].parent==0&&i!=(*s1)){
if(HT[i].weight<HT[min].weight)
min=i;
}
}
*s2=min; //parent=0且weight最小的两个结点,第二个序号为s2
}
//Huffman编码构造函数:
void HuffmanCoding(HuffmanTree *HT,HuffmanCode *HC,int n){
//从叶子到根逆向求每个字符的Huffman编码
char *cd;
int i,start,f,c,j,k;
//分配n个字符编码的头指针向量 ,0号单元未用
cd=(char *)malloc(n*sizeof(char)); //分配编码的临时工作空间
cd[n-1]='\0'; //编码结束符
for (i=1; i<=n; ++i){ //逐个字符求赫夫曼编码
start=n-1; //编码结束符位置
for(c=i,f =HT[i].parent; f!=0; c=f,f =HT[f].parent){
if(HT[f].lchild==c) cd[--start]='0';
else cd[--start]='1';
}
HC[i].code=(char*)malloc((n-start)*sizeof(char));
//为第i个字符编码分配空间
for(j=start,k=0;cd[j]!='\0';){
HC[i].code[k++]=cd[j++];
}
HC[i].code[k]='\0';
}
free(cd); //释放工作空间
}
3.2 主函数设计
int main(){
HuffmanTree *HT;
HuffmanCode *HC;
Singal_word *SW;
printf("+-----------------------HuffmanTree&HuffmanCode-----------------------+\n");
printf("+样例输入: +\n");
printf("+请输入字符串估计长度:>>20 +\n");
printf("+请输入字符串:>>ASDFGHSSAADFGSDFGASD +\n");
printf("+-------------------------------------------------------------------------------------+\n");
SW=(Singal_word *)malloc(Init_Singal_word*sizeof(Singal_word));
Enter_and_Analysis(SW);
Printf_AResult(SW);
if(SW[0].length==1){
printf("\n本次输入只有一个节点,不符合Huffman树规则,程序退出!谢谢使用!\n");
return 0;
}
HT=(HuffmanTree *)malloc((2*SW[0].length)*sizeof(HuffmanTree));
HuffmanTreeing(HT,SW);
Printf_HuffmanTree(HT,2*SW[0].length-1);
HC=(HuffmanCode *)malloc((SW[0].length+1)*sizeof(HuffmanCode));
HuffmanCoding(HT,HC,SW[0].length);
Printf_HuffmanCode(HC,SW);
printf("\n程序已结束!谢谢使用!\n");
return 0;
}
4 调试分析
4.1 调试过程的问题
(1)问题1.在字符串的分析统计模块,Analysis和Adjust函数均无法正常运行;
解决:在Adjust中增加语句“if(SW[0].length==0) return -1;”来解决第一次进入函数时出现的终止返回问题,此外将Adjust的返回值有简单的“0或1”改为“-1和位置i”。
(2)问题2.函数调用时实参在函数运行结束后内容未完成更改;
解决:分析得原因在于实参在传递前未实例化分配空间导致该错误。因此在主函数中调用函数之前提前为相关实参分配空间再传值,同时检查函数调用时的语句改正传参错误。
4.2 时空分析
函数 | 时间复杂度 | 空间复杂度 |
Bool Adjust(Singal_word *SW,char c) | O(n) | O(1) |
Status Analysis(Singal_word *SW,char *str) | O(n) | O(1) |
void Select(HuffmanTree *HT,int m,int *s1,int *s2) | O(n) | O(1) |
void HuffmanTreeing(HuffmanTree *HT,Singal_word *SW) | O(n) | O(1) |
void HuffmanCoding(HuffmanTree *HT,HuffmanCode *HC,int n) | O(n*m) | O(n2) |
void Enter_and_Analysis(Singal_word *SW) | O(n) | O(n) |
void Printf_AResult(Singal_word *SW) | O(n) | O(1) |
void Printf_HuffmanTree(HuffmanTree *HT,int n) | O(n) | O(1) |
void Printf_HuffmanCode(HuffmanCode *HC,Singal_word *SW) | O(n) | O(1) |
4.3 改进设想
(1)“Status Analysis(Singal_word *SW,char *str)”函数中可以加入排序模块。按照字符顺序排序可以使输出字符及其权值时更加有序;按照字符权值大小排序可以简化后面“void Select(HuffmanTree *HT,int m,int *s1,int *s2)”函数中的内容。
(2)“void Enter_and_Analysis(Singal_word *SW)”函数中可以加入大小判断和空间重新分配模块。当SW的空间小于输入的字符串中各字符种类数量时,根据实际情况对SW重新分配空间。
4.4 经验体会
(1)指针变量在函数调用时的传值问题:指针变量在传递之前要实例化分配空间,否则指针为野指针无法使用。除此之外,当函数形参为指针,实参为指针变量时,指针变量不需要再取地址。
(2)C语言变量定义问题:C语言变量的定义一定要在最前方,即函数头部之后便应该定义变量。在变量定义与函数头部之间不能插入任何语句。例如将一系列printf提示语句放到变量定义之前便会造成问题,使程序无法运行。
5 测试数据与结果
5.1 正常输入
程序正常执行到结束
5.2 错误输入
终止运行并退出