赫夫曼编码(综合性实验)

赫夫曼编码(综合性实验)

1. 需求分析

需求:
设某编码系统共有n个字符,使用频率分别为w1, w2, …, wn,设计一个不等长的编码方案,使得该编码系统的空间效率最好。
基本要求:
一个完整的系统应具有以下功能。
(1) I:初始化(Initialization)。从终端读入字符集大小n,以及n个字符和n个权值,建立赫夫曼树,并将它存于文件hfmTree中。
(2) E:编码(Encoding)。利用已建好的赫夫曼树对文件ToBeTran中的正文进行编码,然后将结果存入文件CodeFile中。
(3) D:译码(Decoding)。利用已建好的赫夫曼树将文件CodeFile中的代码进行译码,结果存入文件TextFile中。
(4) P:打印代码文件(Print)。将文件CodeFile以紧凑格式显示在终端上,每行50个字符。同时将此字符形式的编码文件写入文件CodePrint中。
(5) T:打印赫夫曼树(Tree printing)。将已在内存中的赫夫曼树以直观的方式显示在终端上,同时将此字符形式的赫夫曼树写入文件TreePrint中。
测试数据
由读者依据软件工程的测试技术自己确定。注意测试边界数据。
实现提示
利用赫夫曼编码树求得 佳的编码方案。
(1) 文件 CodeFile 的基类型可以设为字节型。
(2) 用户界面可以设计为“菜单”方式,除显示上述功能符号外,还应显示“Q”(Quit),表示退出运行。请用户键入一个选择功能符。此功能执行完毕后再显示此菜单,直至某次用户选择了“E” 为止。在程序的一次执行过程中,第一次执行 I、D 或 C 命令之后,赫夫曼树已经在内存了,不必再读入。每次执行时不一定执行 I 命令,因为文件 hfmTree 可能早已建好。

2. 概要设计

为了实现程序功能,需要定义树的抽象数据类型。

ADT BinaryTree{
    数据对象D:D是具有相同特性的数据元素的集合
    数据关系R:
      若D = Φ,则R = Φ,称BinaryTree为空二叉树
      若D ≠ Φ,则R = {H},H有如下二元关系:
        (1)在D中存在唯一的称为根的数据元素root,它在关系H下无前驱;
        (2)若D-{root} ≠ Φ,则存在D - {root} = {Dl,Dr},且Dl ∩ Dr = Φ;
        (3)若Dl ≠ Φ,则Dl中存在唯一的元素xl,<root,xl>∈H,且存在Dl上的关系Hl⊂H;
             若Dr ≠ Φ,则Dr中存在唯一的元素xr,<root,xr>∈H,且存在Dr上的关系Hr⊂H;
             H = {<root,xl><root,xr>,Hl,Hr};
        (4(Dl,{Hl})是一棵符合本定义的二叉树,称为根的左子树,
             (Dr,{Hr})是一棵符合本定义的二叉树,称为根的右子树;

typedef struct{
	int weigth;      //权值 
	int parent;      //父母 
	int lchild;      //左儿子 
	int rchild;      //右儿子 
}HTNode,*HuffmanTree;   //哈夫曼树结构

    基本操作P:
     InitBiTree(&T);
        操作结果:构造空的二叉树T。
     DestoryBiTree(&T);
        初始条件:二叉树T存在。
        操作结构:销毁二叉树T。
     CreateBiTree(&T,definition);
        初始条件:definition给出二叉树T的定义。
        操作结果:按definition构造二叉树T。
     ClearBiTree(&T);
        初始条件:二叉树T存在。
        操作结果:将二叉树清为空树。
     BiTreeEmpty(T);
        初始条件:二叉树T存在。
        操作结果:若T为空二叉树,返回TRUE,否则返回FALSE。
     BiTreeDepth(T);
        初始条件:二叉树T存在。
        操作结果:返回T的深度。
     Root(T);
        初始条件:二叉树T的根存在。
        操作结果:返回T的根。
     Value(T,e);
        初始条件:二叉树T存在,e是T中某个结点。
        操作结果:返回e的值。
     Assign(T,&e,value);
        初始条件:二叉树T存在,e是T中的某个结点。
        操作结果:结点e赋值为value。
     Parent(T,e);
        初始条件:二叉树T存在,e是T中的结点。
        操作结果:若e是T的非根结点,则返回它的双亲,否则返回"空"LeftChild(T,e);
        初始条件:二叉树T存在,e是T中的某个结点。
        操作结果:返回e的左孩子。若e无左孩子,则返回"空"RightChild(T,e);
        初始条件:二叉树T存在,e是T中的某个结点。
        操作结果:返回e的右孩子。若e无右孩子,则返回"空"LeftSibling(T,e);
        初始条件:二叉树T存在,e是T中的某个结点。
        操作结果:返回e的左兄弟。若e是T的左孩子或无左兄弟,则返回"空"RightSibling(T,e);
        初始条件:二叉树T存在,e是T中的某个结点。
        操作结果:返回e的右兄弟。若e是T的右孩子或无右兄弟,则返回"空"InsertChild(T,p,LR,c);
        初始条件:二叉树T存在,p指向T中的某个结点,LR为01,非空二叉树c与T不想交且右子树为空。
        操作结果:根据LR为01,插入c为T中p所指结点的左或右子树。p所指结点的原有左或右子树则成为c的右子树。
     DeleteChile(T,p,LR);
        初始条件:二叉树T存在,p指向T中的某个结点,LR为01.
        操作结果:根据LR为01,删除T中p所指结点的左或右子树。
     PreOrderTraverse(T,visit());
        初始条件:二叉树T存在,visit()是对结点操作的应用函数。
        操作结果:先序遍历T,对每个结点调用函数visit一次且仅一次。一旦visit失败,则操作失败。
     InOrderTraverse(T,visit());
        初始条件:二叉树T存在,visit()是对结点操作的应用函数。
        操作结果:中序遍历T,对每个结点调用函数visit一次且仅一次。一旦visit失败,则操作失败。
     PostOrderTraverse(T,visit());
        初始条件:二叉树T存在,visit()是对结点操作的应用函数。
        操作结果:后序遍历T,对每个结点调用函数visit一次且仅一次。一旦visit失败,则操作失败。
     LevelOrderTraverse(T,visit());
        初始条件:二叉树T存在,visit()是对结点操作的应用函数。
        操作结果:层序遍历T,对每个结点调用函数visit一次仅且一次。一旦visit失败,则操作失败。
}ADT BinaryTree

3.具体代码

  • 哈夫曼树.h:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//#include"哈夫曼树.h"
typedef struct{
	int weigth;      //权值 
	int parent;      //父母 
	int lchild;      //左儿子 
	int rchild;      //右儿子 
	//静态三叉链表 【用数组来表示链接关系】 
}HTNode,*HuffmanTree;   //哈夫曼树结构 

void select(HuffmanTree p,int n,int *a,int *b)    //选择权值最小的 
{
	*a = 0;
	*b = 0;//两个指针先初始化 
	for(int z = 1;z <= n;z++)//遍历 
	{
		if(p[z].parent == 0&&*a == 0)//如果父节点为空,且*a还没赋值 
		{
			*a = z;
			continue;
		}
		if(p[z].parent == 0)
		{
			*b = z;
			break;
		}
	}
	if(p[*a].weigth >= p[*b].weigth)//a的权值大于等于b 
	{
		int i = *a;
		*a = *b;
		*b = i;//a,b互换 
	}
	for(int m = 1;m <=  n;m++)//遍历 
	{
		if(p[m].parent != 0)//如果父节点已经不为0了 
		{
			continue;//跳过 (舍弃掉) 
		}
		if(p[*a].weigth > p[m].weigth&&*a != m&&*b != m)
		{
			*b = *a;
			*a = m;
		}
		else if(p[*b].weigth > p[m].weigth&&*b != m&&*a != m)
		{
			*b = m;
		}
	}
}
void HuffmanCoding(HuffmanTree *HT,int *w,int n)     //建哈夫曼树 
{
	if(n <= 1)
	{
		return;//如果不超过1个字符,直接返回 
	}
	int m = 2 * n - 1;//n个叶子节点,需要结合n-1次,共有2n-1个节点 
	*HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode));//为树分配 空间 (第一个位置不用) 
	int i = 1;//用于for循环 
	HuffmanTree p = *HT + 1;//指向已有的树的后一个空间 (用于遍历树) 
	for(;i <= n;++i,++p,++w)//遍历 
	{
		//对前n个节点位置,用来放有效的编码 
		p->lchild = 0;
		p->parent = 0;
		p->rchild = 0;
		p->weigth = *w;//初始化 
	}
	for(;i <= m;++i,++p)
	{
		//n之后的位置 ,放用来结合的空根 
		p->lchild = 0;
		p->parent = 0;
		p->rchild = 0;
		p->weigth = 0;
	} 
	for(int k = n + 1;k <= m;++k)
	{
		//n+1的空根开始遍历,直到所有的根遍历完成 
		int s1,s2;
		select(*HT,k - 1,&s1,&s2);//k-1代表的是选择的上限位置 
		(*HT)[s1].parent = k;
		(*HT)[s2].parent = k;//把选中的两个权值最小的置为k的左右孩子 
		(*HT)[k].lchild = s1;
		(*HT)[k].rchild = s2;
		(*HT)[k].weigth = (*HT)[s1].weigth + (*HT)[s2].weigth;//修改k的权值 
	}
}
void coding(char ***p,int n,HuffmanTree t)            //编码 
{
	*p = (char **)malloc((n + 1) * sizeof(char *));//分配空间 
	char *cd;
	cd = (char *)malloc(n * sizeof(char));//分配空间 
	cd[n - 1] = '\0'; //字符串 
	for(int i = 1;i <= n;++i)//遍历 
	{
		int start = n - 1;
		for(int c = i,f = t[i].parent;f != 0;c = f,f = t[f].parent)
		{
			if(t[f].lchild == c)
			{
				//左孩子就赋值为0 
				cd[--start] = '0';
			}
			else
			{
				//右孩子就赋值为1 
				cd[--start] = '1';
			}
		}
		(*p)[i] = (char*)malloc((n - start)*sizeof(char));
		strcpy((*p)[i],&cd[start]);
	}
	free(cd);
}

void decoding(FILE *r,FILE *w,HuffmanTree t,int n,char *a)    //译码 
{
	int num = 2 * n - 1;//根节点 
	for(int i;fscanf(r,"%1d",&i) != EOF;)
	{
		if(i == 0)
		{
			int j = t[num].lchild;//0就是左孩子 
			if(t[j].lchild == 0)//如果已经没有子树了(此处不一定为左孩子,因为左右成对出现) 
			{
				fprintf(w,"%c",a[j - 1]);//输出 
				num = 2 * n - 1;//重新给num 
			}
			else
			{
				num = j;//如果还有左孩子,又继续从j的位置往下 
			}
		}
		else if(i == 1)
		{
			int j = t[num].rchild;//1就是右孩子 
			if(t[j].rchild == 0)//如果已经没有子树了(此处不一定为右孩子,因为左右成对出现) 
			{
				fprintf(w,"%c",a[j - 1]);//输出 
				num = 2 * n - 1;//重新给num 
			}
			else
			{
				num = j;
			}
		}
	} 
} 
  • 哈夫曼编码.c:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include"哈夫曼树.h"

void visit(HuffmanTree t,FILE *w,int n)          //打印哈夫曼树 
{
	HuffmanTree p = t + 1;
	for(int m = 0;m < 2 * n - 1;m++)
	{
		printf("%d\t%d\t%d\t%d\n",p->weigth,p->parent,p->lchild,p->rchild);
		fprintf(w,"%d %d %d %d\n",p->weigth,p->parent,p->lchild,p->rchild);
		++p;
	}
}
void read_weigth(int *w,char *ch,int number)          //读取边 
{
	char q;
	for(int n = 0;n < number;n++)
	{
		scanf("%d%c",&w[n],&ch[n]);//权值-字符 
		scanf("%c",&q);//空格 
		//权值存在w,字符存在ch 
	}
}
void found(FILE *r1,FILE *w2,char **p,int n,char *zh)     //将字符串转化为二进制代码 
{
	//r1为原文,w2为编码加密之后的文,r2为哈夫曼树 
	char ch;
//	fscanf(r2,"%s",zh);//先读取全部的编码 
	for(;(fscanf(r1,"%c",&ch)) != EOF;)
	{//读取原文 
		int i=0;
		while(zh[i] != ch)//找到对应的编码 
		{
			i++;
		}
		fprintf(w2,"%s",p[i+1]);//写到r2文件 
	}
}


int main()
{
	FILE *ri,*wi,*re,*we,*rd,*wd,*wp,*wt;//定义一系列文件指针 
	HuffmanTree t;//创建一个哈夫曼树 
	int n;//字符集的大小 
	char name[500]; 
	for(;;)
	{
	    printf("\n请选择: I:初始化   E:编码   D:译码   P:打印代码文件   T:打印赫夫曼树   Q:退出\n"); 
		char select;//用来存放命令 
		scanf("%c",&select);
		getchar();
		switch(select)
		{
			case 'Q':    //退出 
				return 0;
			case 'I':          //初始化  
				do 
				{
					if((wi = fopen("hfmTree.txt","w")) == NULL)
	                {
		                exit(-1);
	                }
	                printf("请输入字符集大小:");
	                scanf("%d",&n);
	                getchar();
	                int w[n];//创建一个大小为n的数组,储存加权字符 
	                char ch[n+1];
	                ch[n] = '\0';//为字符串 
	                printf("请输入%d个权值和字符:",n);
	                read_weigth(&w[0],&ch[0],n);//读取节点 
	                HuffmanCoding(&t,&w[0],n);//哈夫曼编码 
	                strcpy(name,ch);
//	                printf("%s\n",ch);
//	                printf("%s\n",name);
	                fprintf(wi,"字符集长度为:%d\n",n);
//	                fprintf(wi,"%s\n",ch);
	                for(int i = 0;i < n;i++)
	                {
	                	fprintf(wi,"%d. ",i+1);
	                	fprintf(wi,"字符%c对应的权值为%d \n",ch[i],w[i]);
					}
					fclose(wi); 
					printf("初始化成功!结果存在文件'hfmTree.txt'中!\n");
//	                fprintf(wi,"%s\n",ch);
//	                fprintf(wi,"%d\n",n);
//	                for(int i = 0;i < n;i++)
//	                {
//	                	fprintf(wi,"%d ",w[i]);
//					}
//					fclose(wi); 
//					printf("初始化成功!结果存在文件'hfmTree.txt'中!\n");
				}while(0);
				break;
			case 'E':           //编码 
			    do
				{
					printf("请将文件放入'ToBeTran.txt'中(放好请按ENTER继续):");
					getchar(); 
					if((ri = fopen("hfmTree.txt","r")) == NULL)
	                {
		                exit(-1);
	                }
					if((re = fopen("ToBeTran.txt","r")) == NULL)
	                {
		                exit(-1);
	                }
	                if((we = fopen("CodeFile.txt","w")) == NULL)
	                {
		                exit(-1);
	                }
	                char **dh;
	                coding(&dh,n,t);//编码 ,dh为一个数组,n为字符集的长度,t为一个哈夫曼树 
	                found(re,we,dh,n,name);
	                fclose(ri);
	                fclose(we);
	                fclose(re);
	                printf("编码成功。结果存在文件'CodeFile.txt'中。\n");
				}while(0);
				break;	
			case 'D':          //译码 
				do
				{
					printf("请将文件放入'CodeFile.txt'中(放好请按ENTER继续):");
					getchar(); 
					if((rd = fopen("CodeFile.txt","r")) == NULL)
	                {
		                exit(-1);
	                }
					if((wd = fopen("TextFile.txt","w")) == NULL)
	                {
		                exit(-1);
	                }
	                if((ri = fopen("hfmTree.txt","r")) == NULL)
	                {
		                exit(-1);
	                }
	                char zh[n + 1];
//                	fscanf(ri,"%s",zh);
                	decoding(rd,wd,t,n,name);
                	fclose(rd);
	                fclose(wd);
	                fclose(ri);
	                printf("译码成功。结果存在文件'TextFile.txt'中。\n");
				}while(0);
				break;
			case 'P':               //打印代码文件 
			    do
				{
					if((ri = fopen("hfmTree.txt","r")) == NULL)
	                {
		                exit(-1);
	                }
					if((rd = fopen("CodeFile.txt","r")) == NULL)
	                {
		                exit(-1);
	                }
	                if((wp = fopen("CodePrint.txt","w")) == NULL)
	                {
		                exit(-1);
	                }
	                char **dh;
	                coding(&dh,n,t);
//	                char zh[n + 1];
//                	fscanf(ri,"%s",zh);
                	for(int z = 0;z < n;z++)
                	{
                		fprintf(wp,"%c %s\n",name[z],dh[z + 1]);
					}
					int number = 0;
					for(char z;fscanf(rd,"%c",&z) != EOF;)
					{
						if(number >= 49)
						{
							printf("%c\n",z);
							number = 0;
						}
						else
						{
							printf("%c",z);
							number++;
						}
					}
					printf("\n");
					fclose(rd);
	                fclose(wp);
	                fclose(ri);
	                printf("打印代码成功。结果存在文件'CodePrint.txt'中。\n");
				}while(0);
				break;	
			case 'T':             //打印哈夫曼树 
				do
				{
					if((wt = fopen("TreePrint.txt","w")) == NULL)
	                {
		                exit(-1);
	                }
	                printf("weigth\tparent\tlchild\trchild\n");
	                visit(t,wt,n);
	                fclose(wt);
	                printf("打印赫夫曼树成功。结果存在文件'TreePrint.txt'中。\n");
				}while(0);
				break;
			default:{
				printf("你的输入有误!请重试!\n");
				break;
			} 
		}
	} 
	return 0;
}
 
  • 7
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值