数据结构第三次实验报告

实验三:哈夫曼编/译码器

实验目的: 掌握哈夫曼树

实验内容: 利用哈夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本。但是,这要求在发送端通过一个编码系统对待传数据预先编码,在接收端将传来的数据进行译码(复原)。对于双工信道(即可以双向传输信息的信道),每端都需要一个完整的编/译码系统。试为这样的信息收发站写一个哈夫曼码的编/译码系统。

实验要求: 编写完整的系统,要求具有以下功能:
I:初始化(Initialization)。从终端读入字符集大小n,以及n个字符和n个权值,建立哈夫曼树,并将它存入文件hfmTree中。
E:编码(Encoding)。利用以建好的哈夫曼树(如不在内存,则从文件hfmTree中读入),对文件ToBeTran中的正文进行编码,然后将结果存入文件CodeFile中。
D:译码(Decoding)。利用已建好的哈夫曼树将文件CodeFile中的代码进行译码,结果存入文件TextFile中。
P:印代码文件(Print)。将文件CodeFile以紧凑格式显示在终端上,每行50个代码。同时将此字符形式的编码文件写入文件CodePrin中。
T:印哈夫曼树(Tree printing)。将已在内存中的哈夫曼树以直观的方式(树或凹入表形式)显示在终端上,同时将此字符形式的哈夫曼树写入文件TreePrint中。

实验过程

  1. 需求分析
    首先要确定n个字符及其权值。一篇英语短文包括字母的大小写以及基本的符号,从网上查找资料获得各个字母出现的平均概率,作为各个符号的权值。由于符号众多,我们选择将其保存到文件里面进行读取。
    其次,关于文件的写入操作。我们直接将屏幕上打印出来的内容保存到相应的txt文件中去。
    最后树的打印,我们采取中序遍历树的方式,将一棵树横向的打印到文件中去。

  2. 概要设计
    相关的数据结构设计:
    哈夫曼树的节点

    typedef struct Node {
    int weight;//权值
    int parent;//父节点的序号,为0表示根节点
    int lchild, rchild;//左右孩子节点的序号,为0的是叶子节点
    }HTNode, *HuffmanTree;
    

    编译结果的保存

    struct Ans {
    char ch; // 保存字符
    int wet; // 保存权值
    }ans[maxn];
    

    主程序的流程:

    1. 初始化:从文件中读取权值和字符,建立一棵哈夫曼树。
    2. 编码:编写函数HuffmanCoding确定每个字符的哈夫曼编码。
    3. 译码:利用事先准备好的一篇文章,打印出文章结果。
    4. 打印部分:打印包括代码和哈夫曼树。
  3. 详细设计

    1. 建立哈夫曼树的过程
    HuffmanTree create_HuffmanTree(struct Ans *p, int n)
    {
    
    int t = 2 * n - 1;
    HuffmanTree HT = (HuffmanTree)malloc(total * sizeof(HTNode));
    int i;
    for (i = 0; i < n; ++i) {//先构造n个叶节点
    	HT[i].parent = 0;//parent后面会改变
    	HT[i].lchild = 0;
    	HT[i].rchild = 0;
    	HT[i].weight = p->wet;
    	p++;
    }
    for (; i < total; ++i) {
    	HT[i].parent = 0;
    	HT[i].lchild = 0;
    	HT[i].rchild = 0;
    	HT[i].weight = 0;
    }
    
    int min1, min2;//用来保存每一轮选出的两个weight最小且parent为0的节点下标
    
    //每一轮比较后选择出min1和min2构成一课二叉树,最后构成一棵哈夫曼树
    for (i = n; i < t; ++i) {
    	select_min(HT, i, min1, min2); //另外编写的一个函数,用来寻找i之前的结点
    	HT[min1].parent = i;
    	HT[min2].parent = i;
    	//两个最小权值的结点构成子树
    
    
    	HT[i].lchild = min1;
    	HT[i].rchild = min2;
    	HT[i].weight = HT[min1].weight + HT[min2].weight;
    }
    /*
    for(int i=0;i<total;i++)
    {
        printf("%d-%d-%d\n",HT[i].lchild,HT[i].rchild,HT[i].weight);
        printf("%c\n",ans[i].ch);
    }*/
    return HT;
    }
    
    1. 译码过程
    void HuffmanCoding(HuffmanTree HT, int n)
    {
    char temp[maxn + 5];//临时用来保存每次求得的一个哈夫曼编码串
    temp[maxn] = '\0';//结束符
    
    for (int i = 0; i < n; ++i) {
    	int current = i;
    	int fa = HT[i].parent;
    	int start = maxn;  //初始为编码结束符的位置
    	
    	while (fa != 0)
    	{
    		if (HT[fa].lchild == current)
    			temp[--start] = '0';
    		else temp[--start] = '1';
    		current = fa;
    		fa = HT[current].parent;
    	}
    	strcpy(HC[i], temp + start);//将编码串从最后的start编码~\0复制到HC上
        }
    }
    
    1. 初始化(从文件中读入权值)
    int n;//需要编码的字符数
    void Initialzation()
    {
    FILE *fp1,*fpx;
    fp1 = fopen("./DataFile.data", "r");
    printf("从文件DataFile.txt中读取编码的字符种类个数\n");
    fscanf(fp1, "%d", &n);
    
    printf("以及这%d个字符及其权值(整型)\n", n);
    for (int i = 0; i < n; ++i) {
    	fscanf(fp1, " %c %d", &ans[i].ch, &ans[i].wet);
    }
    HuffmanTree HT = create_HuffmanTree(ans, n);
    HuffmanCoding(HT, n);//求解每个字符的编码
    
    printf("各字符对应权值的哈夫曼编码为(按照输入顺序):\n");
    for (int i = 0; i < n; ++i) {
    	printf("%c :", ans[i].ch);
    	puts(HC[i]);
    }
    printf("将它们存入htmtree.txt中去:\n");
    fpx=fopen("./htmtree.txt","w+");
    for(int i=0;i<n;i++)
    {
        fprintf(fpx,"%c %s\n",ans[i].ch,HC[i]);
    }
    fclose(fp1);
    fclose(fpx);
    printf("\n******************************************************\n\n");
    }
    
    1. 编码
    void EnCoding()
    {
    //char ch[maxn*maxn],temp;
    printf("下面对文件ToBeTran.data中的文本进行编码形成报文\n");
    printf("文章写在文件CodeFile.txt中\n");
    FILE *fp2, *fp3;
    fp2 = fopen("ToBeTran.txt", "rb");
    fp3 = fopen("CodeFile.txt", "w");
    //fscanf(fp2,"%s",temp);
    char ch;
    do
    {
    	ch = fgetc(fp2);
    	/* read a char from the file */
    	if (ch == ' ') fprintf(fp3, " ");
    	else if (ch == '\n') fprintf(fp3, "\n");
    	else if(ch-'a'>=0)
            fprintf(fp3, "%s", HC[ch - 'a']);
        else if(ch-'A'>=0)
            fprintf(fp3, "%s", HC[ch - 'A'+26]);
        else if(ch-'0'>=0&&'9'-ch>=0)
            fprintf(fp3, "%s", HC[ch - '0'+52]);
        else if(ch==',')
            fprintf(fp3, "%s", HC[62]);
        else if(ch=='.')
            fprintf(fp3, "%s", HC[63]);
    
    
    } while (ch != EOF);
    fclose(fp2);
    fclose(fp3);
    
    for(int i=0;i<n;i++)
        printf("%s\n",HC[i]);
    }
    
    1. 译码
    void Decoding()
    {
    printf("下面对文件CodeFile.data中的代码进行解码形成原文\n结果存入文件Textfile.txt中\n");
    FILE *fp4, *fp5;
    fp4 = fopen("CodeFile.txt", "rb");
    fp5 = fopen("Textfile.txt", "w");
    char ch;
    char temp[maxn];
    char *s = temp;
    memset(temp, 0, sizeof(temp));
    //while(fgets(temp,maxn*maxn,fp4))//读到换行符,并加上\0,存入temp
    do
    {
    	ch = fgetc(fp4);
    	if (ch == '\n') memset(temp, 0, sizeof(temp)), s = temp, fprintf(fp5, "\n");
    	else if (ch == ' ') memset(temp, 0, sizeof(temp)), s = temp, fprintf(fp5, " ");
    	else {
    		*s++ = ch;
    		if (fun(temp) >= 26&&fun(temp)<52) fprintf(fp5, "%c", 'A' + fun(temp)%26), memset(temp, 0, sizeof(temp)), s = temp;
    		else if(fun(temp)>=0&&fun(temp)<26) fprintf(fp5, "%c", 'a' + fun(temp)), memset(temp, 0, sizeof(temp)), s = temp;
    		else if(fun(temp)>=52&&fun(temp)<62) fprintf(fp5, "%c", '0' + fun(temp)%52), memset(temp, 0, sizeof(temp)), s = temp;
    		else if(fun(temp)==62) fprintf(fp5, "%c", ','), memset(temp, 0, sizeof(temp)), s = temp;
            else if(fun(temp)==63) fprintf(fp5, "%c", '.'), memset(temp, 0, sizeof(temp)), s = temp;
    		else continue;
    	}
    } while (ch != EOF);
    fclose(fp4);
    fclose(fp5);
    }
    
    1. 打印
    void print_tree(HuffmanTree HT,int t,int step)//横向打印哈夫曼树
    {
    
    int i;
    if(HT[t].rchild)
        print_tree(HT, HT[t].rchild, step + 1);
    for (i = 0; i < step; i++)
    {
        printf("       ");
        fprintf(pt,"       ");
    }
    printf("   -%d-▏\n", HT[t].weight);
    fprintf(pt,"   -%d-▏\n",HT[t].weight);
    if(HT[t].lchild)
        print_tree(HT, HT[t].lchild, step + 1);
    }
    void Output()
    {
    FILE *fp6;
    fp6=fopen("CodePrin.txt","w");
    for (int i = 0; i < n; ++i) {
    	printf("%c :%lf\n", ans[i].ch, ans[i].wet*1.0 / sum);
    }
    printf("\n******************************************************\n\n");
    printf("下面输出输出ToBeTran.data及其报文CodeFile.txt\n\n");
    FILE *fp2, *fp3;
    fp2 = fopen("ToBeTran.txt", "r");
    fp3 = fopen("CodeFile.txt", "r");
    char ch;
    printf("ToBeTran.data :\n");
    do
    {
    	ch = fgetc(fp2);
    	printf("%c", ch);
    } while (ch != EOF);
    printf("\n\n\n");
    printf("CodeFile.txt :\n");
    int cnt=0;
    do
    {
    	ch = fgetc(fp3);
    	printf("%c", ch);
    	fprintf(fp6,"%c",ch);
    	cnt+=1;
    	if(cnt%50==0)
        {
            printf("\n");
            fprintf(fp6,"\n");
        }
    
    } while (ch != EOF);
    printf("\n\n\n");
    
    printf("\n******************************************************\n\n");
    printf("下面输出CodeFile.data及其原文Textfile.txt\n\n");
    FILE *fp4, *fp5;
    fp4 = fopen("CodeFile.txt", "r");
    fp5 = fopen("Textfile.txt", "r");
    
    printf("CodeFile.data :\n");
    do
    {
    	ch = fgetc(fp4);
    	printf("%c", ch);
    } while (ch != EOF);
    printf("\n\n\n");
    printf("Textfile.txt :\n");
    do
    {
    	ch = fgetc(fp5);
    	printf("%c", ch);
    } while (ch != EOF);
    printf("\n\n\n");
    fclose(fp6);
    }
    
  4. 调试分析
    (1)调试过程中遇到的问题

    1. 大小写以及符号的问题。如果只有小写字母,那我们可以根据Ascii码-'a'来确定每个字符的相对大小,但是引入了大写和符号,所以我们就需要多增加几个条件分支来保存他们的权值。
    2. 如何打印树。如果直接选择常规的方法去遍历树就会像得十分的麻烦,所以我选择了横向打印树的方式,类似下图。
      在这里插入图片描述

    (2) 算法的时空复杂度。
    假设编码的文本字符串长度为n,字母为k个符号。
    由于我们的权值读入是无序的,因此需要先对权值进行排序,时间复杂度O(k*log(k))

    对于每个编码符号,必须遍历树以解码该符号。树包含k个节点,并且平均需要O(log k)个节点访问来解码符号。所以时间复杂度为O(n log k)。

    空间复杂度为树的O(k)和解码文本的O(n)。

实验结果
首先准备好n个权值和一篇英文文章。

I:初始化
67个权值
在这里插入图片描述
一篇英语文章
在这里插入图片描述

实验终端打印
在这里插入图片描述
文件中打印
在这里插入图片描述
E:编码

将编码结果打印出来并且保存到CodeFile文件里
在这里插入图片描述
文章原文
在这里插入图片描述
CodeFile文件
在这里插入图片描述
D:译码

将译码之后的结果保存到TextFile文件里
在这里插入图片描述
在这里插入图片描述

P:打印

将文件CodeFile以紧凑格式显示在终端上,每行50个代码。同时将此字符形式的编码文件写入文件CodePrin中。
在这里插入图片描述
CodePrin文件
在这里插入图片描述

T:印哈夫曼树

将哈夫曼树打印出来
在这里插入图片描述
在这里插入图片描述

实验总结
本次实验花费时间比较长,有以下几点感悟:

  1. 注意内存空间的管理,很有可能就出现内存泄漏的情况。
  2. 对于文件的打印,最后一定要记得把文件关掉。
  3. 关于递归问题的文件打印,文件指针最好是要设置成全局变量,不然文件很有可能在中途就关闭。
  4. 哈夫曼树是一种效率很高的数据结构,如果输入权值前就进行好排序,可能时间复杂度会更好。
  5. 横向打印树可以是一种输出树结构的好方式。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章