BUAA(2021春)实验:树的构造与遍历——根据提示循序渐进(可惜提示有问题Ծ‸Ծ)

48 篇文章 154 订阅


——实验:树的构造与遍历)

看前须知

要点介绍和简要声明.

第五次上机题汇总

树叶节点遍历(树-基础题).

词频统计(BST)+二叉排序树的两种构造形式及快慢分析.

计算器(表达式树实现)(只要打出优先级表==有手就行).

网络打印机选择——伪树状数组(这题杀我)(ಥ_ಥ).

实验:树的构造与遍历——根据提示循序渐进(可惜提示有问题Ծ‸Ծ).

实验目的与要求

在学习和理解二叉树的原理、构造及遍历方法的基础上,应用所学知识来解决实际问题。

本实验将通过一个实际应用问题的解决过程掌握Huffman树的构造、Huffman编码的生成及基于所获得的Huffman编码压缩文本文件。

涉及的知识点包括树的构造、遍历及C语言位运算和二进制文件。

实验内容

Huffman编码文件压缩

问题描述

编写一程序采用Huffman编码对一个正文文件进行压缩。具体压缩方法如下:

  1. 对正文文件中字符(换行字符’\n’除外,不统计)按出现次数(即频率)进行统计。

  2. 依据字符频率生成相应的Huffman树(未出现的字符不生成)。

  3. 依据Huffman树生成相应字符的Huffman编码。

  4. 依据字符Huffman编码压缩文件(即将源文件字符按照其Huffman编码输出)。

说明:

  1. 只对文件中出现的字符生成Huffman树,注意:一定不要处理\n,即不要为其生成Huffman编码。

  2. 采用ASCII码值为0的字符作为压缩文件的结束符(即可将其出现次数设为1来参与编码)。

  3. 在生成Huffman树前,初始在对字符频率权重进行(由小至大)排序时,频率相同的字符ASCII编码值小的在前;新生成的权重节点插入到有序权重序列中时,若出现相同权重,则将新生成的权重节点插入到原有相同权重节点之后(采用稳定排序)。

  4. 在生成Huffman树时,权重节点在前的作为左孩子节点,权重节点在后的作为右孩子节点。

  5. 遍历Huffman树生成字符Huffman码时,左边为0右边为1。

  6. 源文件是文本文件,字符采用ASCII编码,每个字符占8个二进制位;而采用Huffman编码后,高频字符编码长度较短(小于8位),因此最后输出时需要使用C语言中的位运算将字符的Huffman码依次输出到每个字节中。

实验准备

1.文件下载

从教学平台(judge.buaa.edu.cn)课程下载区下载文件lab_tree2.rar,该文件中包括了本实验中用到的文件huffman2student.c和input.txt:

点击此处下载,提取码:1234.

huffman2student.c:该文件给出本实验程序的框架,框架中部分内容未完成(见下面相关实验步骤),通过本实验补充完成缺失的代码,使得程序运行后得到相应要求的运行结果;

  1. input.txt:为本实验的测试数据。

  2. huffman2student.c文件中相关数据结构说明

结构类型说明:

struct tnode {     //Huffman树结构节点类型

  char c;         

  int weight;

  struct tnode *left;

  struct tnode *right;

  } ;

结构类型struct tnode用来定义Huffman树的节点,其中;

1)对于树的叶节点,成员c和weight用来存放字符及其出现次数;对于非叶节点来说,c值可不用考虑,weight的值满足Huffman树非叶节点生成条件,若p为当前Huffman树节点指针,则有:

p->weight = p->left->weight + p->right->weigth;

2)成员left和right分别为Huffman树节点左右子树节点指针。

全局变量说明:

 int Ccount[128]={0};              

   struct tnode *Root=NULL;           

char HCode[128][MAXSIZE]={0};    

  int Step=0;                                        

FILE *Src, *Obj;

整型数组Ccount存放每个字符的出现次数,如Ccount[‘a’]表示字符a的出现次数。

变量Root为所生成的Huffman树的根节点指针。

数组HCode用于存储字符的Huffman编码,如HCode[‘a’]为字符a的Huffman编码,本实验中为字符串”1000”。

变量Step为实验步骤状态变量,其取值为1、2、3、4,分别对应实验步骤1、2、3、4。

变量Src、Obj为输入输出的文件指针,分别用于打开输入文件“input.txt”和输出文件“output.txt”。

实验步骤

【步骤1】

  1. 实验要求

在程序文件huffman2student.c中“//【实验步骤1】开始”和“ //【实验步骤1】结束”间编写相应代码,以实现函数statCount,统计文本文件input.txt中字符出现频率。

//【实验步骤1】开始

void statCount()

{

}

  //【实验步骤1】结束
  1. 实验说明

函数statCount用来统计输入文件(文件指针为全局变量Src)中字符的出现次数(频率),并将字符出现次数存入全局变量数组Ccount中,如Ccount[‘a’]存放字符a的出现次数。

注意:在该函数中Ccount[0]一定要置为1,即Ccount[0]=1。编码值为0(’\0’)的字符用来作为压缩文件的结束符。

  1. 实验结果

函数print1()用来打印输出步骤1的结果,即输出数组Ccount中字符出现次数多于0的字符及次数,编码值为0的字符用NUL表示。完成【步骤1】编码后,本地编译并运行该程序,并在标准输入中输入1,程序运行正确时在屏幕上将输出如下结果:
 图1步骤1运行结果
图1步骤1运行结果

在本地运行正确的情况下,将你所编写的程序文件中//【实验步骤1】开始”和“ //【实验步骤1】结束”间的代码拷贝粘贴到实验报告后所附代码【实验步骤1】下的框中,然后点击提交按钮,若得到如下运行结果(测试数据1评判结果为完全正确):
在这里插入图片描述
表明实验步骤1:通过,否则:不通过。

【步骤2】

  1. 实验要求

在程序文件huffman2student.c中的 “//【实验步骤2】开始”和“ //【实验步骤2】结束”间编写相应代码,实现函数createHTree,该函数生成一个根结点指针为Root的Huffman树。

   //【实验步骤2】开始

void createHTree( )

{

}
 //【实验步骤2】结束
  1. 实验说明

在程序文件huffman2student.c中函数createHTree将根据每个字符的出现次数(字符出现次数存放在全局数组Ccount中,Ccount[i]表示ASCII码值为i的字符出现次数),按照Huffman树生成规则,生成一棵Huffman树。

算法提示:

  1. 依据数组Ccout中出现次数不为0的( 即Ccount[i]>0)项,构造出树林F={T0, T1, ¼, Tm},初始时Ti(0≤i≤m)为只有一个根结构的树,且根结点(叶结点)的权值为相应字符的出现次数的二叉树(每棵树结点的类型为struct tnode,其成员c为字符,weight为树节点权值):

    for(i=0; i<128; i++)

    if(Ccount[i]>0){

    p = (struct tnode *)malloc(sizeof(struct tnode));

    p->c = i; p->weight = Ccount[i];

    p->left = p->right = NULL;

    add p into F;
    }

  2. 对树林F中每棵树按其根结点的权值由小至大进行排序(排序时,当权值weight相同时,字符c小的排在前面),得到一个有序树林F

  3. while 树个数>1 in F

a) 将F中T0和T1作为左、右子树合并成为一棵新的二叉树T’,并令T’->weight= T0->weight+ T1->wei

b) 删除T0和T1 from F,同时将T’加入F。要求加入T’后F仍然有序。若F中有树根结点权值与T’相同,则T’应加入到其后

4.Root = T0 (Root为Huffman树的根结点指针。循环结束时,F中只有一个T0)

注:在实现函数createHTree时,在框中还可根据需要定义其它函数,例如:

void myfun()

{

  …

}

void createHTree()

{

 …

 myfun();

 …

}
  1. 实验结果

函数print2()用来打印输出步骤2的结果,即按前序遍历方式遍历步骤2所生成(由全局变量Root所指向的)Huffman树结点字符信息。输出时编码值为0的字符用NUL表示、空格符用SP表示、制表符用TAB表示、回车符用CR表示。完成【步骤2】编码后,本地编译并运行该程序,并在标准输入中输入2,程序运行正确时在屏幕上将输出如下结果:
在这里插入图片描述
图2 步骤2运行结果

在本地运行正确的情况下,将你在本地所编写的程序文件中//【实验步骤2】开始”和“ //【实验步骤2】结束”间的代码拷贝粘贴到实验报告后所附代码【实验步骤2】下的框中,然后点击提交按钮,若得到如下运行结果(测试数据2评判结果为完全正确):
在这里插入图片描述
表明实验步骤2:通过,否则:不通过。

【步骤3】

  1. 实验要求

在程序文件huffman2student.c中的 “//【实验步骤3】开始”和“ //【实验步骤3】结束”间编写相应代码,实现函数makeHCode,该函数依据【实验步骤3】中所产生的Huffman树为文本中出现的每个字符生成对应的Huffman编码。遍历Huffman树生成字符Huffman码时,左边为0右边为1。

   //【实验步骤3】开始

void makeHCode( )

{
}

 //【实验步骤3】结束
  1. 实验说明

【步骤3】依据【步骤2】所生成的根结点为Root的Huffman树生为文本中出现的每个字符生成相应的Huffman编码。全局变量HCode定义如下:

char HCode[128][MAXSIZE];

HCode变量用来存放每个字符的Huffman编码串,如HCode[‘e’]存放的是字母e的Huffman编码串,在本实验中实际值将为字符串”011”。

算法提示:

可编写一个按前序遍历方法对根节点为Root的树进行遍历的递归函数,并在遍历过程中用一个字符串来记录遍历节点时从根节点到当前节点的路径(经过的边),经过左边时记录为’0’,经过右边时记录为’1’;当遍历节点为叶节点时,将对应路径串存放到相应的HCode数组中,即执行strcpy(HCode[p->c],路径串)。

注:在实现函数makeHCode时,在框中还可根据需要定义其它函数,如调用一个有类于前序遍历的递归函数来遍历Huffman树生成字符的Huffman编码:

void visitHTree()

{

  …

}

void makeHCode()

{

…

 visitHTree();

  …

}
  1.    实验结果
    

函数print3()用来打印输出步骤3的结果,即输出步骤3所生成的存储在全局变量HCode中非空字符的Huffman编码串。完成【步骤3】编码后,本地编译并运行该程序,并在标准输入中输入3,在屏幕上将输出ASCII字符与其Huffman编码对应表,冒号左边为字符,右边为其对应的Huffman编码,其中NUL表示ASCII编码为0的字符,SP表示空格字符编码值为0的字符用,程序运行正确时在屏幕上将输出如下结果:
在这里插入图片描述
图3 步骤3运行结果

在本地运行正确的情况下,将你在本地所编写的程序文件中//【实验步骤3】开始”和“ //【实验步骤3】结束”间的代码拷贝粘贴到实验报告后所附代码【实验步骤3】下的框中,然后点击提交按钮,若得到如下运行结果(测试数据3评判结果为完全正确):

在这里插入图片描述
表明实验步骤3:通过,否则:不通过。

【步骤4】

  1. 实验要求

在程序文件huffman2student.c函数中的 “//【实验步骤4】开始”和“ //【实验步骤4】结束”间编写相应代码,实现函数atoHZIP,该函数依据【实验步骤3】中所生成的字符ASCII码与Huffman编码对应表(存储在全局变量HCode中,如HCode[‘e’]存放的是字符e对应的Huffman编码串,在本实验中值为字符串”011”),将原文本文件(文件指针为Src)内容(ASCII字符)转换为Huffman编码文件输出到文件output.txt(文件指针为Obj)中,以实现文件压缩。同时将输出结果用十六进制形式(printf(“%x”,…))输出到屏幕上,以便检查和查看结果。

   //【实验步骤4】开始

void atoHZIP( )
{

}

 //【实验步骤4】结束
  1. 实验说明

Huffman压缩原理:在当前Windows、Linux操作系统下,文本文件通常以ASCII编码方式存储和显示。ASCII编码是定长编码,每个字符固定占一个字节(即8位),如字符’e’的ASCII编码为十进制101(十六进制65,二进制为01100101)。而Huffman编码属于可变长编码,本实验中其依据文本中字符的频率进行编码,频率高的字符的编码长度短(小于8位),而频率低的字符的编码长度长(可能多于8位),如在本实验中,字符’ ’(空格)的出现频率最高(出现65次),其Huffman编码为111(占3位,远小于一个字节的8位),其它出现频率较高的字符,如字符’e’的Huffman编码为011、字符’o’的Huffman编码为111;字符’x’出现频率低(出现1次),其Huffman编码为10011110(占8位,刚好一个字节)(注意,在其它问题中,字符最长Huffman编码可能会超过8位)。正是由于高频字符编码短,将使得Huffman编码文件(按位)总长度要小于ASCII文本文件,以实现压缩文件的目的。

然而,将普通ASCII文本文件转换为变长编码的文件不便之处在于C语言中输入/输出函数数据处理的最小单位是一个字节(如putchar()),无法直接将Huffman(不定长)编码字符输出,在输出时需要将不定长编码序列转换为定长序列,按字节输出。而对于不定长编码,频率高的字符其编码要比一个字节短(如本实验中字符’e’的Huffman编码为011,不够一个字节,还需要和其它字符一起组成一个字节输出),频率低的编码可能超过一个字节。如何将不定长编码字符序列转换成定长字符序列输出,一个简单方法是:

1)根据输入字符序列将其Huffman编码串连接成一个(由0、1字符组成的)串;

2)然后依次读取该串中字符,依次放入到一个字节的相应位上;

3)若放满一个字节(即8位),可输出该字节;剩余的字符开始放入到下一个字节中;

4)重复步骤2和3,直到串中所有字符处理完。

下面通过实例来说明:

原始文件input.txt中内容以“I will…”开始,依据所生成的Huffman码表,字母I对应的Huffman编码串为“0101111”,空格对应“111”,w对应“1001110”,i对应“01010”,l对应 “11001”。因此,将其转换后得到一个Huffman编码串“01011111111001110010101100111001…”,由于在C中,最小输出单位是字节(共8位),因此,要通过C语言的位操作符将每8个01字符串放进一个字节中,如第一个8字符串“01011111”中的每个0和1放入到一个字符中十六进制(即printf(”%x”,c)输出时,屏幕上将显示5f)(如下图所示)。下面程序段将Huffman编码串每8个字符串放入一个字节(字符变量hc)中:
在这里插入图片描述

	char hc;

…

for(i=0; s[i] != ‘\0’; i++) {

 hc = (hc << 1) | (s[i]-'0');

if((i+1)%8 == 0) {

fputc(hc,obj);     //输出到目标(压缩)文件中

printf("%x",hc);   //按十六进制输出到屏幕上

}

}
…

说明:

  1. 当遇到源文本文件输入结束时,应将输入结束符的Huffman码放到Huffman编码串最后,即将编码串HCode[0]放到Huffman编码串最后。

  2. 在处理完成所有Huffman编码串时(如上述算法结束时),处理源文本最后一个字符(文件结束符)Huffman编码串(其编码串为“01001010”)时,可能出现如下情况:其子串”010”位于前一个字节中输出,而子串“01010”位于另(最后)一个字节的右5位中,需要将这5位左移至左端的头,最后3位补0,然后再输出最后一个字节。

  3. 在这里插入图片描述
    注:在实现函数atoHZIP时,在框中还可根据需要定义其它函数或全局变量,如:

     void myfun()
    
     {
    
       …
    
     }
    
     void atoHZIP()
    
     {
    
     …
    
       myfun();
    
      …
    
     }
    
  1. 实验结果

函数print4()用来打印输出步骤4的结果,即根据输出步骤3所生成的存储在全局变量HCode中Huffman编码串,依次对源文本文件(input.txt)的ASCII字符转换为Huffman编码字符输出到文件output.txt中,同时按十六进行输出到屏幕上。完成【步骤4】编码后,本地编译并运行该程序,并在标准输入中输入4,在屏幕上将输出:
在这里插入图片描述
图4 步骤4运行结

说明:

从屏幕输出结果可以看出,由于采用了不定长的Huffman编码,且出现频率高的字符的编码长度短,压缩后,文件大小由原来的370字节变为200字节,文件压缩了45.95%。

在本地运行正确的情况下,将你在本地所编写的程序文件中//【实验步骤4】开始”和“ //【实验步骤4】结束”间的代码拷贝粘贴到教学平台实验报告后所附代码【实验步骤4】下的框中,然后点击提交按钮,若得到如下运行结果(测试数据4评判结果为完全正确):
在这里插入图片描述
表明实验步骤4:通过,否则:不通过。

程序主体

//文件压缩-Huffman实现
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAXSIZE 32

struct tnode {					//Huffman树结构
	char c;		
	int weight;					//树节点权重,叶节点为字符和它的出现次数
	struct tnode *left,*right;
} ; 
int Ccount[128]={0};			//存放每个字符的出现次数,如Ccount[i]表示ASCII值为i的字符出现次数 
struct tnode *Root=NULL; 		//Huffman树的根节点
char HCode[128][MAXSIZE]={{0}}; //字符的Huffman编码,如HCode['a']为字符a的Huffman编码(字符串形式) 
int Step=0;						//实验步骤 
FILE *Src, *Obj;
	
void statCount();				//步骤1:统计文件中字符频率
void createHTree();				//步骤2:创建一个Huffman树,根节点为Root 
void makeHCode();				//步骤3:根据Huffman树生成Huffman编码
void atoHZIP(); 				//步骤4:根据Huffman编码将指定ASCII码文本文件转换成Huffman码文件

void print1();					//输出步骤1的结果
void print2(struct tnode *p);	//输出步骤2的结果 
void print3();					//输出步骤3的结果
void print4();					//输出步骤4的结果

int main()
{
	if((Src=fopen("input.txt","r"))==NULL) {
		fprintf(stderr, "%s open failed!\n", "input.txt");
		return 1;
	}
	if((Obj=fopen("output.txt","w"))==NULL) {
		fprintf(stderr, "%s open failed!\n", "output.txt");
		return 1;
	}
	scanf("%d",&Step);					//输入当前实验步骤 
	
	statCount();						//实验步骤1:统计文件中字符出现次数(频率)
	(Step==1) ? print1(): 1; 			//输出实验步骤1结果	
	createHTree();						//实验步骤2:依据字符频率生成相应的Huffman树
	(Step==2) ? print2(Root): 2; 		//输出实验步骤2结果	
	makeHCode(Root);					//实验步骤3:依据Root为树的根的Huffman树生成相应Huffman编码
	(Step==3) ? print3(): 3; 			//输出实验步骤3结果
	(Step>=4) ? atoHZIP(),print4(): 4; 	//实验步骤4:据Huffman编码生成压缩文件,并输出实验步骤4结果	

	fclose(Src);
	fclose(Obj);

    return 0;
} 

//【实验步骤1】开始 

void statCount()
{


	
}

//【实验步骤1】结束

//【实验步骤2】开始

void createHTree()
{


	
}

//【实验步骤2】结束

//【实验步骤3】开始

void makeHCode()
{

	
	
} 

//【实验步骤3】结束

//【实验步骤4】开始

void atoHZIP()
{


	
}

//【实验步骤4】结束

void print1()
{
	int i;
	printf("NUL:1\n");
	for(i=1; i<128; i++)
		if(Ccount[i] > 0)
			printf("%c:%d\n", i, Ccount[i]);
}

void print2(struct tnode *p)
{
	if(p != NULL){
		if((p->left==NULL)&&(p->right==NULL)) 
			switch(p->c){
				case 0: printf("NUL ");break;
				case ' ':  printf("SP ");break;
				case '\t': printf("TAB ");break;
				case '\n':  printf("CR ");break;
				default: printf("%c ",p->c); break;
			}
		print2(p->left);
		print2(p->right);
	}
}

void print3()
{
	int i;
	
	for(i=0; i<128; i++){
		if(HCode[i][0] != 0){
			switch(i){
				case 0: printf("NUL:");break;
				case ' ':  printf("SP:");break;
				case '\t': printf("TAB:");break;
				case '\n':  printf("CR:");break;
				default: printf("%c:",i); break;
			}
			printf("%s\n",HCode[i]);
		}
	}
} 

void print4()
{
	long int in_size, out_size;
	
	fseek(Src,0,SEEK_END);
	fseek(Obj,0,SEEK_END);
	in_size = ftell(Src);
	out_size = ftell(Obj);
	
	printf("\n原文件大小:%ldB\n",in_size);
	printf("压缩后文件大小:%ldB\n",out_size);
	printf("压缩率:%.2f%%\n",(float)(in_size-out_size)*100/in_size);
}

题解

笔者的发泄(内含思维过程)和详解

之前我也还没写过这种程序片段题,这次是第一次尝试,感觉还不错,毕竟有思路讲解,而且还在必要的时候提供一小段算法(虽然有小问题哈哈哈笑死),比自己单独写是在是轻松了许多。但是我们要去思考一下为什么这道构建Huffman树需要一个实验来操作呢,为什么不可以放在前面的编程题里面直接写呢?那是因为独立完成一个构建Huffman树的程序是在是太难了(有可能是我太菜了),所以尽管有实验的步骤在指引,但是有些步骤真的令人头皮发麻。

步骤一,没得说,直接重定向读入就行了,记住该函数中Ccount[0]一定要置为1就行了。

步骤二,令人头皮发麻,首先我们需要构建一个含权值的森林,然后给森林排序,这里就有一个难点,如果你用传统的qsort传值会发现排序有问题,因为这里的森林F是指针构成的,与一般的结构体不同。排完序后就是烧脑的构建Huffman树了,题目中的提示和算法挺不错的,相比较与传统的构建Huffman树(传统的构建形式与这里不同,感兴趣的可以查一下),但是这里在插入的时候有一个细节要注意,就是到了后面随着权值的越来越大,插入都是在尾端的,不一定在中间

步骤三,递归就行,注意是前序遍历

步骤四,注意,这里题目提供的算法有问题!!!!,算法里面hc是char类型的,但是如果你按照它那么操作的话,你会出现很多fffff(原因很简单,题目要求的是16位的输入,但是如果转化为char型才8位,有另外8位是丢失的!!),所以要把其手动调为int型。还有就是重定向的问题,看参考代码里有解释,这里不在赘述。

总而言之,需要注意的地方很多,最好找一个空闲时间安安静静地写(强烈推荐五一哈哈哈哈哈),如果着急的话,很容易丢失条件的哦~

参考代码

//文件压缩-Huffman实现
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAXSIZE 32

struct tnode {					//Huffman树结构
	char c;		
	int weight;					//树节点权重,叶节点为字符和它的出现次数
	struct tnode *left,*right;
} ; 
int Ccount[128]={0};			//存放每个字符的出现次数,如Ccount[i]表示ASCII值为i的字符出现次数 
struct tnode *Root=NULL; 		//Huffman树的根节点
char HCode[128][MAXSIZE]={{0}}; //字符的Huffman编码,如HCode['a']为字符a的Huffman编码(字符串形式) 
int Step=0;						//实验步骤 
FILE *Src, *Obj;
	
void statCount();				//步骤1:统计文件中字符频率
void createHTree();				//步骤2:创建一个Huffman树,根节点为Root 
void makeHCode();				//步骤3:根据Huffman树生成Huffman编码
void atoHZIP(); 				//步骤4:根据Huffman编码将指定ASCII码文本文件转换成Huffman码文件

void print1();					//输出步骤1的结果
void print2(struct tnode *p);	//输出步骤2的结果 
void print3();					//输出步骤3的结果
void print4();					//输出步骤4的结果

int main()
{
	if((Src=fopen("input.txt","r"))==NULL) {
		fprintf(stderr, "%s open failed!\n", "input.txt");
		return 1;
	}
	if((Obj=fopen("output.txt","w"))==NULL) {
		fprintf(stderr, "%s open failed!\n", "output.txt");
		return 1;
	}
	scanf("%d",&Step);					//输入当前实验步骤 
	
	statCount();						//实验步骤1:统计文件中字符出现次数(频率)
	(Step==1) ? print1(): 1; 			//输出实验步骤1结果	
	createHTree();						//实验步骤2:依据字符频率生成相应的Huffman树
	(Step==2) ? print2(Root): 2; 		//输出实验步骤2结果	
	makeHCode(Root);					//实验步骤3:依据Root为树的根的Huffman树生成相应Huffman编码
	(Step==3) ? print3(): 3; 			//输出实验步骤3结果
	(Step>=4) ? atoHZIP(),print4(): 4; 	//实验步骤4:据Huffman编码生成压缩文件,并输出实验步骤4结果	

	fclose(Src);
	fclose(Obj);

    return 0;
} 

//【实验步骤1】开始 

void statCount()
{
	char s[202110];
		int i;
		Ccount[0]=1;
		fread(s,sizeof(char),202110,Src); //直接转变为二进制文件读入 
		for(i=0;s[i]!='\0';i++)
		{
			Ccount[s[i]]++;		//记录个数 
		}
}

//【实验步骤1】结束

//【实验步骤2】开始

int cmp(const void*p1,const void*p2)
{
	struct tnode **a=(struct tnode**)p1;
	struct tnode **b=(struct tnode**)p2;
	if((*a)->weight!=(*b)->weight)	return ((*a)->weight-(*b)->weight); //比较权重 
	else return (*a)->c-(*b)->c;    //比较字典序 
}
void createHTree()
{
	struct tnode *F[128],*FF[128],*p;
	int i,j,k,times;
	for(i=0,j=0; i<128; i++)			//构造森林 
	{
		if(Ccount[i]>0){
			p = (struct tnode *)malloc(sizeof(struct tnode)); //录入权重 
			p->c = i; p->weight = Ccount[i];
			p->left = p->right = NULL;
			F[j]=p;
			j++; //记录森林里树的个数 
		}
	}
	qsort(F,j,sizeof(struct tnode *),cmp);//根据权值排序 
	while(j>1)
	{
		p = (struct tnode *)malloc(sizeof(struct tnode));
		p->c = F[0]->c+F[1]->c; 				//合并
		p->weight = F[0]->weight+F[1]->weight; //合并 
		p->left=(struct tnode *)malloc(sizeof(struct tnode)); 
		p->right=(struct tnode *)malloc(sizeof(struct tnode));
		memcpy(p->left,F[0],sizeof(struct tnode));		//连接 
		memcpy(p->right,F[1],sizeof(struct tnode));		//连接
		F[0]->weight=F[1]->weight=times=0;
		for(i=0,k=0;i<j;i++)
		{
			if(F[i]!=NULL && F[i]->weight!=0)
			{
				FF[k]=(struct tnode *)malloc(sizeof(struct tnode));
				memcpy(FF[k],F[i],sizeof(struct tnode));
				k++;
			}								//插入合成节点************注意,有可能插在尾端  (笔者在这里脑子糊涂了一次) 
			if(times==0 && F[i]!=NULL && F[i]->weight!=0 && (F[i+1]==NULL || (p->weight<F[i+1]->weight))) 
			{
				FF[k]=(struct tnode *)malloc(sizeof(struct tnode));
				memcpy(FF[k],p,sizeof(struct tnode));
				times++;
				k++;
			}
		}
		memset(F,0,sizeof(F));
		for(i=0;i<k;i++)
		{
			F[i]=(struct tnode *)malloc(sizeof(struct tnode));
			memcpy(F[i],FF[i],sizeof(struct tnode));
		}
		memset(FF,0,sizeof(FF));
		j--;	
	}
	Root=p;	//由于最后一定的是 F[0]和 F[1] 合成 p所以直接用 p 就行了 
}

//【实验步骤2】结束

//【实验步骤3】开始
void visitHTree(struct tnode *p,char *s,int l)
{
	int i;
	if(p!=NULL)
	{
		if(p->left==NULL && p->right==NULL)
		{
			s[l]='\0';					//*************注意,一定要加,不然全部错误 
			strcpy(HCode[p->c],s);
			return ;
		}
		s[l]='0';
		visitHTree(p->left,s,l+1);
		s[l]='1';
		visitHTree(p->right,s,l+1);
	}
}
void makeHCode()
{
	char s[2021];
	s[0]='\0';
	visitHTree(Root,s,0); 
} 

//【实验步骤3】结束

//【实验步骤4】开始

void atoHZIP()
{
	Src=fopen("input.txt","r");  //**************注意由于之前在步骤一读入过,但是由于是不同函数,如果不加这句话会重定向失败,导致无法读入 
	char article[202110],s[202110],tmp[20];
	int i,j,hc=0,times=0; 
	fread(article,sizeof(char),202110,Src);
	for(i=0;article[i]!='\0';i++)
	{
		strcat(s,HCode[article[i]]);
	}
	strcat(s,HCode[0]);
	for(i=0; s[i] != '\0'; i++) 
	{
		hc = (hc << 1) | (s[i]-'0');
		if((i+1)%8 == 0) 
		{
			fputc(hc,Obj);     //输出到目标(压缩)文件中
			printf("%x",hc);   //按十六进制输出到屏幕上
			hc=0;
			times++;
		}
	}	
	if(strlen(s)%times!=0)		//判断是否是否有多余的编码 
	{
		for(i=8*times,j=0;s[i]!='\0';i++,j++)
		{
			tmp[j]=s[i];		//拷贝 
		}	
		for(i=j;i<8;i++)
		{
			tmp[i]='0';			//补零 
		}
		for(i=0;i<8;i++)
		{
			hc = (hc << 1) | (tmp[i]-'0');
		}
		fputc(hc,Obj);     //输出到目标(压缩)文件中
		printf("%x",hc);   //按十六进制输出到屏幕上
	}	
}

//【实验步骤4】结束

void print1()
{
	int i;
	printf("NUL:1\n");
	for(i=1; i<128; i++)
		if(Ccount[i] > 0)
			printf("%c:%d\n", i, Ccount[i]);
}

void print2(struct tnode *p)
{
	if(p != NULL){
		if((p->left==NULL)&&(p->right==NULL)) 
			switch(p->c){
				case 0: printf("NUL ");break;
				case ' ':  printf("SP ");break;
				case '\t': printf("TAB ");break;
				case '\n':  printf("CR ");break;
				default: printf("%c ",p->c); break;
			}
		print2(p->left);
		print2(p->right);
	}
}

void print3()
{
	int i;
	
	for(i=0; i<128; i++){
		if(HCode[i][0] != 0){
			switch(i){
				case 0: printf("NUL:");break;
				case ' ':  printf("SP:");break;
				case '\t': printf("TAB:");break;
				case '\n':  printf("CR:");break;
				default: printf("%c:",i); break;
			}
			printf("%s\n",HCode[i]);
		}
	}
} 

void print4()
{
	long int in_size, out_size;
	
	fseek(Src,0,SEEK_END);
	fseek(Obj,0,SEEK_END);
	in_size = ftell(Src);
	out_size = ftell(Obj);
	
	printf("\n原文件大小:%ldB\n",in_size);
	printf("压缩后文件大小:%ldB\n",out_size);
	printf("压缩率:%.2f%%\n",(float)(in_size-out_size)*100/in_size);
}

补充测试的数据

【样例输入】

in.txt中的信息如下:
Not for the first time, an argument had broken out over breakfast at number four, Privet Drive. Mr. Vernon Dursley had been woken in the early hours of the morning by a loud, hooting noise from his nephew Harry’s room. Third time this week! he roared across the table. If you can’t control that owl, it’ll have to go! Harry tried, yet again, to explain. She’s bored , he said. She’s used to flying around outside. If I could just let her out at night-Do I look stupid? snarled Uncle Vernon, a bit of fried egg dangling from his bushy mustache. I know what’ll happen if that owl’s let out. He exchanged dark looks with his wife, Petunia. Harry tried to argue back but his words were drowned by a long, loud belch from the Dursleys’ son, Dudley. I want more bacon. There’s more in the frying pan, sweetums, said Aunt Petunia, turning misty eyes on her massive son. We must build you up while we’ve got the chance I don’t like the sound of that school foodNonsense, Petunia, I never went hungry when I was at Smeltings, said Uncle Vernon heartily. Dudley gets enough, don’t you, son? Dudley, who was so large his bottom drooped over either side of the kitchen chair, grinned and turned to Harry. Pass the frying pan. You’ve forgotten the magic word, said Harry irritably. The effect of this simple sentence on the rest of the family was incredible: Dudley gasped and fell off his chair with a crash that shook the whole kitchen; Mrs. Dursley gave a small scream and clapped her hands to her mouth; Mr. Dursley jumped to his feet, veins throbbing in his temples. I meant please’! said Harry quickly. I didn’t mean-WHAT HAVE I TOLD YOU, thundered his uncle, spraying spit over the table, ABOUT SAYING THE M’WORD IN OUR HOUSE? But I-HOW DARE YOU THREATEN DUDLEY! roared Uncle Vernon, pounding the table with his fist. I just-I WARNED YOU! I WILL NOT TOLERATE MENTION OF YOUR ABNORMALITY UNDER THIS ROOF! Harry stared from his purple-faced uncle to his pale aunt, who was trying to heave Dudley to his feet. All right, said Harry, all rightUncle Vernon sat back down, breathing like a winded rhinoceros and watching Harry closely out of the corners of his small, sharp eyes. Ever since Harry had come home for the summer holidays, Uncle Vernon had been treating him like a bomb that might go off at any moment, because Harry Potter wasn’t a normal boy. As a matter of fact, he was as not normal as it is possible to be. Harry Potter was a wizard - a wizard fresh from his first year at Hogwarts School of Witchcraft and Wizardry. And if the Dursleys were unhappy to have him back for the holidays, it was nothing to how Harry felt. He missed Hogwarts so much it was like having a constant stomachache. He missed the castle, with its secret passageways and ghosts, his classes (though perhaps not Snape, the Potions master), the mail arriving by owl, eating banquets in the Great Hall, sleeping in his four-poster bed in the tower dormitory, visiting the gamekeeper, Hagrid, in his cabin next to the Forbidden Forest in the grounds, and, especially, Quidditch, the most popular sport in the wizarding world (six tall goal posts, four flying balls, and fourteen players on broomsticks). All Harry’s spellbooks, his wand, robes, cauldron, and top-of-the-line Nimbus Two Thousand broomstick had been locked in a cupboard under the stairs by Uncle Vernon the instant Harry had come home. What did the Dursleys care if Harry lost his place on the House Quidditch team because he hadn’t practiced all summer? What was it to the Dursleys if Harry went back to school without any of his homework done? The Dursleys were what wizards called Muggles (not a drop of magical blood in their veins), and as far as they were concerned, having a wizard in the family was a matter of deepest shame. Uncle Vernon had even padlocked Harry’s owl, Hedwig, inside her cage, to stop her from carrying messages to anyone in the wizarding world. Harry looked nothing like the rest of the family.

【样例输出】

步骤一

NUL:1
:762
!:6
':20
(:3
):3
,:60
-:10
.:34
::1
;:2
?:5
A:15
B:3
D:22
E:13
F:4
G:2
H:35
I:22
L:6
M:7
N:12
O:17
P:8
Q:2
R:10
S:8
T:18
U:16
V:9
W:10
Y:8
a:231
b:49
c:78
d:120
e:320
f:64
g:66
h:171
i:187
j:3
k:29
l:128
m:69
n:188
o:219
p:48
q:2
r:210
s:199
t:234
u:84
v:21
w:64
x:4
y:74
z:7

步骤二

SP p NUL : ( ! N L ) B E b k M z A r o a t d , f l w j F P U S Y O g m . H y e x ; G V T Q q ? - ’ c u R W v D I h i n s

步骤三

NUL:01000100000
SP:00
!:010001001
':11010011
(:0100010001
):0100011010
,:100110
-:110100101
.:1011100
::01000100001
;:11010000010
?:1101001001
A:01001111
B:0100011011
D:11011110
E:01000111
F:1010110001
G:11010000011
H:1011101
I:11011111
L:010001100
M:010011100
N:01000101
O:10101111
P:101011001
Q:11010010000
R:110111000
S:101011100
T:11010001
U:10101101
V:110100001
W:110111001
Y:101011101
a:0111
b:010010
c:110101
d:10010
e:1100
f:100111
g:101100
h:11100
i:11101
j:1010110000
k:0100110
l:10100
m:101101
n:11110
o:0110
p:010000
q:11010010001
r:0101
s:11111
t:1000
u:110110
v:11011101
w:101010
x:1101000000
y:101111
z:010011101

步骤四

4568276523984fd5fc11db7261fc3ad9b5b9e838f212564d9e1b681b77144971d34eff81e0f6d6a58a4edb2cc5657bbb906f2f7772e09c5b868717cde37b65fd32f38f212ccf15326cf1df11cc31d6979c6d97e34e4730b597ddf582578e51b694c719a3beb0f377f84ead69cefcf621cca8baeab7e9fc566b6e68f3ab223b6e11cefcab3132243982b3ae487d55bff239843a54cb86fce5edb1abfb4ed5bd0ad4238f0355498ec69d2871f77862c644975d56f217b9298bf27b1fbe9886334428fdf5c0ae7334fe24cb92263987dfb2b857399a7f36fe488627a5fdf583ab6de90db47fb2cb86fce6f9ab6d491586df829973146da783ddb391a5de637ca33263f1b21d96921ff3ad32456fb5a61a1c5f37a61c4bb034e4ebdc91965849fd653beb9d5ad39df896dfe5e5bb7e1f5e65c37c4de6a8ab8f1a74a1c741cf1d9c8e3c0d5534fe5326da2e5dc3340d78feb32449d5314664df2aec7e77e55d9f262b391b7baf702ebaade42f724430eb66d824faa612dae77e54cb2f9562e1256abd9212bc728deb2628db484b29af09d5ad23986f6cbfa65ffd33ede98deda54cbee6f953fab597049f56f5cd1e62e69fcb5970ef88e613adfdf582fe98fd5991b5bf98fbf6427ede82b391b7baf311b2fbbebb7bf8bccbf3e378e6296bfffdddc3edeb806e70b76fc096dda48bdb6364ab9da615669eee163411cc35e3fb5c37c937b4ea753611cc3edb7a434e471efebc66a13b34915bdfcf7f262b391b7baf31be7b37714ab3d736f58b795733c6f953fc782b96e523beb3f31f7ec8adf6b4c3438be6f1cc758ed2fb806f6d2a65e5991f33cdb59c98937b4ebdb698fb7b4937b69532f98ab8c54ff3ec51d6cc39df8934435a49599c90dbb8a676398a7fb2c1a7239826ec6bccf1af1fab3162f7dec9fe911b2fb2443175d56fb81597ffc8e613adfdf5820feb815d6db4f779d96c688cf11cc2d7b3ba9532ca63efd9175d56f3aabd874a97dcd1e6193cf9ac0d391cefcff6d42987f3d19ed706f11cc173f34e47309deded2f2a7f9df6ab92ea54c4426f6d2a65e58ff43243fa44f94ad3ce73bf35e3f54abb1c1cd55ffc238f07f8cc988e61571a9826ec6bccf682138bfb86f6cbfa65e58fbb839fb5e943faae3da3fa46b47410c91cc538fe97c86398a5adb473419c5b86f6cbfa65e561b5a86488639df93e644c6ee77df238ac925df5877c73bf232d4299fb81be5b8fe810a63ff34d121f7ec8baeab79a476eea9a97dc37c9765ed382dc7f69773753f44ba9fa14737cd1af466f15dafad988e6de962e48e77e6ded69931f415efefac3e87606ddc5239843a54c984f46ebeb744ae27d777d17419a3751c4e69ee6bf71bc6fa295f5bb82ed7d6d711f4911bdadfd2ddafdc9bc9fb8472bb5f5a68dddc23a7e8a3a29bd5bbc8c47aea24159d72456fb5a61a1c5f37a616dbd2efac239843a54c2aec70e77e4fdfc5c0df2bdbf1a5df3729fb84547de2bb5f5a890df373be8c4608b5fa268d7a311f79fa28e2711d17477ebd14afac4aed7d6ee04f46d16bf79c4f466fe8d74ad45de47dc1a3777eb86e2bebeb144975d56f3f0eb922756b4e77e21b2a14cd2cefae48dbdad308639df883d307dbd1315718a9fe42dfdf58431cc7ddc37b69532f218e77e4f991713e9417b672263efd9175d56f981e9417b6722b7dad30d0e2f9bc7de049f54c49aaf4c24b8f1cefac29d4d839577d2c90bcef9b5c56f8fe9153c6bcefac2ebaade6b46fe52f1b681a723986acbec5f8d39cefcfdaf4a4c7f8ea8197e7ee23eee29fefb5c2ebaade71e46ad6e1c6b709d948e61fdadb714e353b27bff315bed69868717cde38f212ccf10b8f1df5873b694ea6c1c49ad488e3c16f6ce4163d3ce3c0febcb5adcf44c259abedfc2ebaade565a2314a9ffed381cf32daf4126bee09ff1cb5e231469c9df5898e6153fc7f9e683ccb6bd7f9d83bf16fffa95308612cb8175d56f2b2d118a54ff1caba9d7591a51caba9d75913ae7f84ead69cefc9fabf82fc751e0bad654eb1f2b9af19a834e6e7b1af35579e07f48dcf53aeb25bee09fe91d9c8e61bdb2fe997fcab17dbdc741bc8638fbb873b6893ea989d948e61c6a764f7fe63b054ff3cd1cefac218e3545d755bc9f291702ee16f7ffc9175aca9d63e7d8b76d78760a9fe53a9b0e3eef7d6e6adefc3fa0fc35afaf1f5e65c02ee16f7ffc911cc357fc53262aec70ec7cfe6ab92fffbd9953dff1fd22ce37e3f31cefcd68fffe7c44638db59c10c5e3a1f3cd573ce864c4730acb475bdf2d7fc62a35311cc2d7ed0755eeef7d695e355498c78efac127f691db23e77c4730d06b8f5d7a5263f4cc43bebef8e77e4edb2e956fc6289648ef88e61d5629265b7bcb7cc6ef7fd8efac239858f6e26cc431662ebd8bd94c77c73bf3574bbe3d9a041c4730ac5952eca59e2b165cfc1df11cc2c56dbd2fcc3fa5319f4335ebd297cc69dbb297635e4c473b5bf020c86d4753e832c1df11cc2aea75d65df5854cb49088ffba1f4a1633d041bf1f989db6527a5fdf5824f4a7e61fd2276d96333c2147bf17e3784959adfc7754df46ae27d285d755bf4fe7dca512664df98e77e54fe94c2b259f98d5f6a495bd30fe91c8695a7d2c7334b4efb45eda96df346a63478db7dfd212566b7f1dd531c79096678a36a9b2477c39aec824ceb236f4b148e61f87eafc4af2b7dad3de2f9bc473efbf0fe82ebaade71e46ad6e1c6b72e1b9e3c12ec88e61bdb2fe997fcd5d70ece5d755bca37ee77e2147d706f11cc2eb6dfc3486dd94bb1af8c7b44b357dbf8730e3cbda7020afac775c90f4a1fdadb71749dcf1ea9fe7608623986f6cbfa65ff3b3975d56f2acf493ea98863faf19a855d8e36d03faf1a739df9c6b72a654c49bd9a4868f3ded97f4cbfe558b855c782aea75d65f357a53242736b2ca67c447cd392564069cb5eceeaf412a3348ef88e6754ddcefbe8d4c3fa43fc9dd47f91ccbcab17d5bdae2fb2531c7ddefac1caba9d7591df11cc277b7b4bca9fe396bc4628d392cc433f07f8f6e5c2b7dad3de2f9bc71e466ee7841e546d53648baeab7e9fc6aa9317725576c98efbfd961cc5357b326218fc3273149d5ad35755bfbebb73ff7b33e430febdbd877c4730aba9d75977d61532d2572ebaade51993648f3473beba753611cc173f34e47309deded2fb8220
原文件大小:4022B
压缩后文件大小:2321B
压缩率:42.29%

  • 22
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值