动态分配算法_实验#基于哈夫曼树的数据压缩算法#小思路

问题描述:输入一串字符串,根据给定的字符串中字符出现的频率建立相应的哈夫曼树,构造哈夫曼树编码表,在此基础上可以对压缩文件进行压缩(即编码),同时可以对压缩后的二进制编码文件进行解码(即译码)。

输入要求:多组数据,每组数据1行,为一个字符串(只考虑26个小写字母即可)。当输入字符串为“0”时,输入结束。

输出要求:每组数据输出2n+3行(n为输入串中字符类别的个数)。第一行为统计出来的字符出现频率(只输出存在的字符,格式为:字符:频度),每两个字符之间用一个空格分隔,字符按照ASCII码从小到大的顺序排列。第2行至第2n行为哈夫曼树的存储结构的终态(一行中的数据用空格分隔)。第2n+1行为每个字符的哈夫曼编码(只输出存在的字符,格式为:字符:编码),每两组字符之间用一个空格分隔,字符按照ASCII码从小到大的顺序排列。第2n+2行为编码后的字符串,2n+3行为解码后的字符串(与输入的字符串相同)。

输入样例:

aaaaaaabbbbbccdddd

aabccc

0

输出样例:

a:7 b:5 c:2 d:4

1 7 7 0 0

2 5 6 0 0

3 2 5 0 0

4 4 5 0 0

5 6 6 3 4

6 11 7 2 5

7 18 0 16

a:0 b:10 c:110 d:111

00000001010101010110110111111111111

aaaaaaabbbbbccdddd

a:2 b:1 c:3

1 2 4 0 0

2 1 4 0 0

3 3 5 0 0

4 3 5 2 1

5 6 0 3 4

a:11 b:10 c:0

111110000

aabccc

一、相关算法分析

(一)大致思路:

题目中要求构建哈夫曼数,则需要统计字母的权数(即出现次数),又因为在“输出字母和对应出现次数”以及“输出字母和对应的编码”的两个操作中,都要求“以ASCII码从小到大顺序”输出,所以想到用一个专门的变量来存放字母和出现次数的信息,并且对这些信息以ASCII码从小到大进行排序操作。

对于译码操作,首先要明确译码的对象,即对输入字符串的编码进行译码操作。那么对于如何获得译码操作对象,不妨在输出编码的同时用将编码存入字符串encode中,则在译码操作中对已获得encode数组中的字符串进行译码即可。

说明:)方便起见,字母信息表CH、哈夫曼树HT、存放编码的数组HC都从下标1开始使用。

程序主要用到的变量及函数如下:

1.字符数组str[100]存放输入的字符串。

2.顺序表CH,用以存放不同的字母的信息,数据元素包括字母和相应出现的次数两个数据项。

3.排序函数Arrange,用以将表CH中信息按字母从小到大顺序输出。

4.选择函数,选出无双亲节点中权值最小的两个结点的下标。

5.编码和译码函数。

6.找编码函数,对当前截取的编码是否存在对应的字符进行判断,并返回对应字符在CH表中的下标。

(二)相关算法描述:

1.输入多组数据

定义一个足够大的二维字符串数组,用以存放每组字符串,直到输入的字符串为"0"时结束。若当前字符串不为"0"(即当前字符串首元素不为'0'),在循环中完成:将字符串赋给一个一维字符数组、字母及个数输出、哈夫曼编码输出等一系列操作。

2.构建字母信息表CH并排序

构造一个空的顺序表CH,为其分配一个大小为27的数组空间(小写字母有26个,数组从下标1开始存入数据),空表长为0,数据元素由字母及出现次数count两个数据项组成。在Put函数中,依次遍历字符串中的字母,每遍历一个字母,都在表CH中进行一次查找,若未找到则为新字母,存入表CH中,count置为1,若找到则不是新字母,使该字母次数count加1。在Put函数中具体完成以下操作:

1)整型变量i为字符串数组下标,赋初值为0。

2)当前字符不为'0'时,在循环中完成以下操作:

[1] 定义整型变量j并赋初值为1,j用以表示当前表CH的数组元素下标;定义p指向表CH存放的第一个元素。

[2] 在表CH中查找当前字母。当j小于等于当前表长时,在循环中完成以下操作:

Ø 若p当前指向字母为要找的字母,则字母次数加1,结束循环;否则p指向表中下一个元素,j加1;

Ø 若j大于表长,则说明未找到该字母,将新字母存入表中,次数设为1,表长加1。

[3] 判断下一个字符,即i加1。

3)在Arrange函数中按字母ASCII码从小到大对表CH元素排序,采用选择法。

3.构造哈夫曼树函数

1) 初始化:动态申请2n(n为不同字母的个数,即CH表长)个单元;然后循环2n-1次,从一号单元开始,依次将1至2n-1所有单元中双亲、左孩子、右孩子的下标都初始化为0;最后再循环n次,输入前n个单元中叶子节点的权值,即各字母的次数。可见权值所在单元下标与对应CH中字母所在单元下标相同。

2) 创建树:循环n-1次,通过n-1次的选择、删除与合并来创建哈夫曼树。i为当前未存放数据的单元下标,取值范围为n+1——2n-1,每完成一次循环i加1,当i小于等于2n-1时,在循环中完成下列操作:

[1]选择:定义整型变量s1,s2;调用Select函数,将当前森林中双亲为0且权值最小的两个树根结点下标赋给s1,s2;

[2]删除:s1,s2单元的双亲域赋值为当前结点下标i;

[3]合并:当前结点的左孩子为s1,右孩子为s2,权值为s1与s2权值之和。

4.选择最小权值函数

需满足最小权值元素无双亲,以及s1,s2不同。

1) 确定s1:通过循环找到当前森林中首个无双亲的元素下标并赋值给s1;从s1+1开始,寻找无双亲且权值比s1小的下标,赋给s1。

2) 为保证s2与s1不同,先将s1的双亲置为1。

3) 确定s2:通过循环找到当前森林中首个无双亲的元素下标并赋值给s2;从s2+1开始,寻找无双亲且权值比s1小的下标,赋给s2。

4) 将s1的双亲重新置为0。

5.编码函数

定义typedef char **HuffmanCode,各字符的哈夫曼编码存储在由HuffmanCode定义的动态分配的数组HC中,从1号单元开始使用,数组长度为n+1。因为每个字符编码的长度事先不能确定,所以不能预先为每个字符分配大小合适的存储空间。为不浪费存储空间,动态分配一个长度为n的(由哈夫曼编码的特点可知,字符编码长度一定小于n)的一维数组cd,用来临时存放当前正在求解的第i(1≤i≤n)个字符的编码,当第n个字符的编码求解完后,根据数组cd字符串长度分配HC[i]的空间,然后将数组cd的编码复制到HC[i]中。

因为求解编码时是从哈夫曼树的叶子出发,向上回溯至根结点。所以对每个字符,得到的编码顺序是从右向左的,故将编码向数组cd存放的顺序也是从后向前的,即每个字符的第1个编码存放在cd[n-2]中(cd[n-1]存放字符串结束标志'0'),第2个编码存放在cd[n-3]中,依此类推,直到全部编码存放完毕。

6.输出编码函数

逐个遍历每个字母,在表CH中找到该字母的下标,此下标也是该字母的编码在编码表HC中的下标,输出对应编码,并将该编码连接在字符串encode中,为译码做准备。

7.译码函数

由哈夫曼树的特点可知,对于由n个叶子节点的哈夫曼树,每个叶子节点到根节点的路径数最多为n-1,即编码位数最多为n-1;此外,哈夫曼编码为前缀编码。

从以上两个特点想到如下思路:定义一个变量i用来表示当前截取长度。从路径数i=1开始,在编码字符串encode中截取长度为i的编码并存放在node字符串数组中;在HC中查找截取的编码,若找到,则记录下标,通过下标输出表CH中的字母,移动指针p以重新定位下次截取的起点,移动长度为当前截取长度i;若未找到,则将路径数加一再次截取编码进行查找,直到找到。重复上述操作直到所有编码都译完(即p指向数组首元素不为'0')。具体操作如下:

->使p指向数组encode;当p当前指向的首元素不为'0'时,在循环中完成以下操作:

1)定义字符串数组code[n],用以存放截取的编码;初始化i=1,表示截取的编码长度。

2)将p指向数组的前i个字母赋给code(调用strncpy函数),然后将下标为i的数组元素赋字符串结束标志'0'。

3)调用find_code函数,在HC中找code字符串。若查找成功,将下标赋给变量subscript,返回OK;若查找失败,则返回0。函数的返回值和i取值范围作为循环的条件,当查找失败时,在循环中进行以下操作:

[1]截取长度i加1;

[2]按新的截取长度将p指向数组的前i个字母赋给code(调用strncpy函数),然后将下表为i设为数组元素赋字符串结束标志'0'。

4)循环结束,即查找成功,按下标subscript在表CH输出对应字母。

5)p越过截取长度i,指向新的数组首部,即p=p+i。

二、源程序清单

//环境:devc++
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<string.h>

#define MAXSIZE 100
#define OK 1
#define OVERFLOW -2

typedef int Status;

typedef struct
{
	char c;
	int count;
}LTNode;  //字母表的数据元素由字符和出现次数两个数据项组成

typedef struct
{
	LTNode *letter;
	int length;  //字母表长度 
}LTList;  

typedef struct
{
	int weight;  //结点的权值
	int parent, lchild, rchild;  //结点的双亲、左孩子、右孩子的下标
}HTNode, *HuffmanTree;

typedef char **HuffmanCode;  //动态分配数组存储哈夫曼编码表

Status InitLTList(LTList &CH);  //初始化字母信息表 
void Put(LTList &CH, char str[]);
void Arrange(LTList &CH);  //排序 
void CreateHuffmanTree(HuffmanTree &HT, int n, LTList CH);  //构造哈夫曼树 
void Select(HuffmanTree HT, int len, int &s1, int &s2);  //选择两个无双亲条件下权数最小的下标 
void printHuffmanTree(HuffmanTree HT, int n);  //输出哈夫曼树 
void CreateHuffmanCode(HuffmanTree HT, HuffmanCode &HC, int n);  // 构造编码表 
void printEncode(HuffmanCode HC, char str[], LTList CH, char encode[]);  //编码函数 
void printDecode(HuffmanCode HC, char encode[], LTList CH);  //译码函数 
Status find_code(char node[], HuffmanCode HC, int &subscript, int len);  //在HC中寻找该编码是否存在 

int main()
{
	char ch[MAXSIZE][MAXSIZE];  //定义一个足够大的二维数组,用来存放输入的每组字符串 
	int t=0;
	scanf("%s", ch[t]);
	while(ch[t][0]!='0')  //输入字符串,直到输入字符串为0时结束 
	{
		t++;
		scanf("%s", ch[t]);
	}
	
	t=0;
	while(ch[t][0]!='0')  //当某组字符串不为零时,执行以下操作 
	{
		char str[MAXSIZE];  //定义一个数组,用来存放当前进行操作的字符串 
		strcpy(str, ch[t]);
		t++;  //t加1,为下次循环做准备 
	
		LTList CH;  //初始化字母信息表,用以存放字母以及出现的次数,从下标为1开始存放
		InitLTList(CH);  //初始化字母信息表 
		Put(CH, str);  //构造字母信息表 
	
		Arrange(CH);  //按字母顺序从小到大重新排序
		int i;
		i=1;
		while(i<=CH.length)   //输出字母及次数 
		{
			if(i!=1) printf(" ");  //每两个信息间有一个空格 
			printf("%c:%d", CH.letter[i].c, CH.letter[i].count);
			i++;
		}
		printf("n");

		HuffmanTree HT;   
		int len=CH.length;  //哈夫曼树HT的叶子节点长度即为字母信息表长度 
		CreateHuffmanTree(HT, len, CH);  //构造哈夫曼树

		printHuffmanTree(HT, 2*len-1);  //输出哈夫曼树终态 
	
		HuffmanCode HC;
		CreateHuffmanCode(HT, HC, CH.length);  //构造编码表 
	
		i=1;
		while(i<=len)  //输出字母及对应编码 
		{
			if(i!=1) printf(" ");  //每两组信息间有一个空格 
			printf("%c:%s", CH.letter[i].c, HC[i]);
			i++;
		}
		printf("n");
	
		char encode[MAXSIZE]="0";
		printEncode(HC, str, CH, encode);

		printf("n");
		printDecode(HC, encode, CH);
		printf("n");
	}
}

 Status InitLTList(LTList &CH)
 {
	CH.letter = new LTNode[27];
	if(!CH.letter) exit(OVERFLOW);
	CH.length=0;
	return OK;
 }

void Put(LTList &CH, char str[])
{
	int i=0;
	while(str[i]!='0') 
	{
		int j=1;
		LTNode *p=CH.letter;
		p++;
		while(j<=CH.length)
		{
			if(p->c==str[i])
			{
				p->count++;
				break;
			}
			else
			{
				p++;
				j++;
			}
		}
			if(j>CH.length)  //未找到相同字母,是一个新的字母 
			{
				p->c=str[i];  //将新字母放入新的单元
				p->count=1;  //新字母出现的次数设为1
				CH.length=CH.length+1;  //表长加1 
			}
			i++;
	}
}

void Arrange(LTList &CH)  //选择法排序
{
	int i, j, k;  //i用于外循环,j用于内循环,k用于记录每层内循环中最小元素的下标
	for(i=1; i<CH.length; i++)
	{
		k=i;
		for(j=i+1; j<=CH.length; j++)
			if(CH.letter[k].c>CH.letter[j].c) k=j;
		if(k!=i)
		{
			LTNode temp;
			temp=CH.letter[i];
			CH.letter[i]=CH.letter[k];
			CH.letter[k]=temp;
		}
	}
}

void CreateHuffmanTree(HuffmanTree &HT, int n, LTList CH)
{
	if(n<=1) return;
	int m=2*n-1;
	int i;
	HT = new HTNode[m+1];
	for(i=1; i<=m; i++)
	{
		HT[i].parent=0;
		HT[i].lchild=0;
		HT[i].rchild=0;
	}

	for(i=1; i<=n; i++)
		HT[i].weight = CH.letter[i].count;
		
	for(i=n+1; i<=m; i++)
	{
		int s1, s2;
		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 len, int &s1, int &s2)
{
	int i=1;
	while(HT[i].parent) i++;  //找到HT中首个无双亲的元素下标
	s1=i;
	for(i=s1+1; i<=len; i++)
		if(!HT[i].parent && HT[i].weight<HT[s1].weight) s1=i;
	HT[s1].parent=1;  //将1双亲暂时赋值1,为了避免s1与s2相同 
	i=1;
	while(HT[i].parent) i++;
	s2=i;
	for(i=s2+1; i<=len; i++)
		if(!HT[i].parent && HT[i].weight<HT[s2].weight) 
			s2=i;   
	HT[s1].parent=0;  //将s1双亲重新赋值0
}

void printHuffmanTree(HuffmanTree HT, int n)
{
	int i;
	for(i=1; i<=n; i++)
		printf("%d %d %d %d %dn",i, HT[i].weight, HT[i].parent, HT[i].lchild, HT[i].rchild);
}

void CreateHuffmanCode(HuffmanTree HT, HuffmanCode &HC, int n)
{
	HC=new char*[n+1];  //分配存储n个字符编码的编码表空间 
	char *cd=new char[n];  //分配临时存放每个字符编码的动态数组空间 
	cd[n-1]='0';  //编码结束符 
	int i;
	for(i=1; i<=n; i++)  //逐个字符求哈夫曼编码 
	{
		int start=n-1;  //start开始指向最后 
		int c=i, f=HT[i].parent;  //f指向结点c的双亲结点 
		while(f)  //从叶子节点开始向上回溯,直到根结点 
		{
			--start;  //回溯一次start向前指一个位置 
			if(HT[f].lchild==c) cd[start]='0';  //结点c是f的左孩子,则生成代码0 
			else cd[start]='1';  //结点c是f的右孩子,则生成代码1 
			c=f; f=HT[f].parent;  //继续向上回溯 
		}
		HC[i]=new char[n-start];  //为第i个字符编码分配空间 
		strcpy(HC[i], &cd[start]);  //将求得的编码从临时空间cd复制到HC的当前行中 
	}
	delete cd;  //释放临时空间 
}

void printEncode(HuffmanCode HC, char str[], LTList CH, char encode[])
{
	int i=0;
	while(str[i]!='0')  //遍历每个字母,在LTList中找到对应字母下标,根据下标在HC中定位下标,输出编码 
	{
		int temp=1;
		while(CH.letter[temp].c!=str[i]) temp++;
		printf("%s", HC[temp]);
		strcat(encode, HC[temp]);  //将译码粘到encode里 
		i++;
	}
}

void printDecode(HuffmanCode HC, char encode[], LTList CH)  //译码
{
	char *p=encode; //p指向编码首部 
	while(p[0]!='0')  //将所有编码译码 
	{
		int i=1;
		char code[CH.length];
		strncpy(code, p, i);
		code[i]='0'; 
		int subcript;  //用以记录该编码对应字母的下标 
		while(!find_code(code, HC, subcript, CH.length))  
		{
			i++;  //增加一位 
			strncpy(code, p, i);
			code[i]='0';
		}
		printf("%c", CH.letter[subcript].c);
		p=p+i;  //使p指向当前未判断的编码的首部 
	}
}

Status find_code(char code[], HuffmanCode HC, int &subscript, int len)
{
	for(subscript=1; subscript<=len; subscript++)
	{
		if(strcmp(code, HC[subscript])==0) return OK;
	}
	return 0;
}

三、运行结果

3ba53e99baa6389abb00481c4d9a8e47.png
前三行为输入字符串,后面为输出结果
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值