数据结构应用实例(三)——赫夫曼编码

一、问题描述

  对一篇英文文章,统计各字符(仅限于26个小写字母)出现的次数,并据此进行 Huffman 编码。

二、算法思想

  首先,打开文本文件,统计各个小写英文字母出现的次数;
  然后,统计出现过的字母个数 num;
  如果 num<=1,进行相关信息的显示,不进行 Huffman 编码;
  否则,进行 Huffman 编码:
  准备工作:存储出现过的字母编号及出现次数;
  1、根据出现次数,创建 Huffman 树:
  (1)根据求得的 num 个出现次数 { w 1 , w 2 , ⋯   , w n u m } \lbrace w_1,w_2,\cdots,w_{num} \rbrace {w1,w2,,wnum} 构成 num 棵二叉树的集合 F = { T 1 , T 2 , ⋯   , T n u m } F=\lbrace T_1,T_2,\cdots,T_{num} \rbrace F={T1,T2,,Tnum},其中每棵二叉树 T i T_i Ti 中只有一个带权为 w i w_i wi 的根节点,其左右子树均为空;
  (2)在 F 中选取两棵根节点权值最小的树作为左右子树构造一棵新二叉树,其根节点的权值为两棵子树根节点权值之和;
  (3)从 F 中删除这两棵树,并将新生成的树添加到 F 中;
  (4)重复(2)和(3),直到 F 中只含一棵树。所得到的树就是 Huffman 树;
  2、根据 Huffman 树进行 Huffman 编码
  约定 ‘0’ 表示左分支,‘1’ 表示右分支。从某个叶子节点开始,如果没有父节点,表示编码结束;如果其有父节点,如果为父节点的左孩子,编码尾部添加 ‘0’,如果为右孩子,尾部添加 ‘1’,然后向根节点移动:当前节点变为其父节点,父节点变为父节点的父节点,进行相同操作。
  编码结束之后,对编码进行逆置,得到正序编码;
  3、最后显示出现的字母及其出现的次数和 Huffman 编码;

三、代码实现

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#pragma warning(disable:4996)

typedef struct myhuffman//赫夫曼树的节点
{
	int weight;
	int parent,lchild,rchild;
}HTnode;

typedef struct mylist//存放序号和权重
{
	int index;
	int weight;
}LI;

int *statis();//统计小写英文字母出现的频次,返回存放频次的数组

char **huffmanCoding(int *w, int n);//创建huffman编码,w用于存放叶子节点的权重,n表示叶子节点个数

void select(HTnode *HT,int k,int *s1,int *s2);//在HT[1,,,k]中选取parent==0且weight最小的两个节点,返回其序号(*s1),(*s2)


int main(void)
{
	int i,j;
	int *w,num;
	int **A;//用于存放频次不为0的字母编号和频次
	char **HC;

	//获取小写字母出现次数
	w=statis();	
	//统计出现过的字母个数
	num=0;
	for (i = 1; i <= 26; i++)
	{
		if (w[i] != 0)
			num++;
	}
	
	printf("\n字母出现的次数和Huffman编码如下:\n");
	if (num == 0)
		printf("\n没有出现小写英文字母,无需进行Huffman编码\n");
	else // num>=1
	{
		A=(int **)malloc(2*sizeof(int *));
		A[0]=(int *)malloc((num+1)*sizeof(int));
		A[1]=(int *)malloc((num+1)*sizeof(int));
		//向A中存放数据
		j=1;
		for (i = 1; i <= 26 && j <= num; i++)
		{
			if (w[i] != 0)
			{
				A[0][j]=i;
				A[1][j]=w[i];
				j++;
			}
		}

		if (num == 1)
			printf("只出现了一个字母:%c,频次%d,其Huffman编码为1\n", A[0][1] + 'a' - 1, A[1][1]);
		else // num>=2
		{
			//创建huffman编码
			HC = huffmanCoding(A[1], num);
			//显示huffman编码			
			for (i = 1; i <= num; i++)
				printf("%c,频次:%2d,编码:%s\n", A[0][i] + 'a' - 1, A[1][i], HC[i]);
			free(HC);
		}
		free(A);
	}

	free(w);	
	return 0;
}


int *statis()//统计小写英文字母出现的频次,返回存放频次的数组
{
	int i;
	int *w;
	FILE *fp;
	char ch,filename[50];
	
	//初始化
	w=(int *)malloc(27*sizeof(int));
	for (i = 1; i <= 26; i++)
		w[i]=0;

	//打开文件
	printf("请输入文件名:");
	scanf("%s",filename);
	fp=fopen(filename,"r");
	if (!fp)
	{
		printf("文件不存在!\n");
		exit(-1);
	}

	//统计小写字母出现次数
	printf("\n文本信息为:\n");
	while (!feof(fp))
	{
		ch=fgetc(fp);
		printf("%c",ch);
		if ('a' <= ch && ch <= 'z')
			w[ch - 'a' + 1]++;
	}
	printf("\n");
	//关闭文件
	fclose(fp);
	return w;
}

char **huffmanCoding(int *w, int n)//创建huffman编码,w用于存放叶子节点的权重,n表示叶子节点个数
{
	int i,j,k,l;
	HTnode *HT,p;
	int m;
	int s1,s2;
	char **HC;
	char *cd;

	//一、创建huffman树
	m=2*n-1;//总的节点个数
	HT=(HTnode *)malloc((m+1)*sizeof(HTnode));//HT为结构体数组,用于存放huffman树的节点

	//初始化
	for (i = 1; i <= n; i++)//叶子节点
	{
		p.weight=w[i];
		p.parent=0;
		p.lchild=p.rchild=0;
		HT[i]=p;
	}
	for (; i <= m; i++)//非叶子节点
	{
		p.weight=0;
		p.parent=0;
		p.lchild=p.rchild=0;
		HT[i]=p;
	}
	//合并子树
	for (i = n + 1; i <= m; i++)
	{
		select(HT,i-1,&s1,&s2);//在HT[1,,,i-1]中选取parent==0且weight最小的两个节点,并返回其序号
		HT[s1].parent=i;
		HT[s2].parent=i;

		HT[i].weight=HT[s1].weight+HT[s2].weight;
		HT[i].lchild=s1;
		HT[i].rchild=s2;
	}
	
	//二、生成huffman编码
	HC=(char **)malloc((n+1)*sizeof(char *));
	cd=(char *)malloc(n*sizeof(char));
	cd[n-1]='\0';//字符串结束标志
	for (i = 1; i <= n; i++)//对各个叶子节点进行编码
	{
		j=n-1;
		k=i;
		l=HT[i].parent;
		while (l != 0)
		{
			j--;
			if(HT[l].lchild == k)
				cd[j]='0';
			else
				cd[j]='1';

			//更新循环变量
			k=l;
			l=HT[l].parent;
		}
		//为第i个编码分配空间并赋值
		HC[i]=(char *)malloc((n-j)*sizeof(char));
		strcpy(HC[i],&cd[j]);
	}

	free(HT);
	free(cd);
	return HC;
}

void select(HTnode *HT, int k, int *s1, int *s2)//在HT[1,,,k]中选取parent==0且weight最小的两个节点,返回其序号(*s1),(*s2)
{
	int i,j;
	int n,lastindex;
	LI *T,temp;

	if(k <= 1)
		return;

	T=(LI *)malloc((k+1)*sizeof(LI));
	j=0;
	for (i = 1; i <= k; i++)//挑选parent==0的点
	{
		if (HT[i].parent == 0)
		{
			j++;
			T[j].index=i;
			T[j].weight=HT[i].weight;
		}
	}

	//对T冒泡排序,寻找weight最小的两个节点
	n=j;//parent==0的节点个数
	lastindex=n;
	while (lastindex > n - 2)//至多进行两趟冒泡排序
	{
		i=lastindex;
		lastindex=0;
		for (j = 1; j < i; j++)
		{
			if (T[j].weight < T[j + 1].weight)//交换两个数据,小的放在后面
			{
				temp=T[j];
				T[j]=T[j+1];
				T[j+1]=temp;
				lastindex=j;
			}
		}
	}

	//返回节点序号
	(*s1)=T[n].index;
	(*s2)=T[n-1].index;

	free(T);
}

四、小结

1、 由于 Huffman 树中没有出度为1的点,所以可以根据叶子节点的个数 n 0 n_0 n0 事先确定总的节点个数为 2 n 0 − 1 2n_0-1 2n01
2、 结构体变量之间可以直接赋值;
3、 在寻找 parent==0 且 weight 最小的两个节点时,只需进行至多两次的冒泡排序,大大提高了查找效率和算法性能;
4、 进行 Huffman 编码时,由于是从叶子节点开始向前回溯到根节点,所以编码时从后向前进行,这样就避免了逆置编码;
5、 对于特殊情况:出现的英文字母个数小于等于1,则不进行 Huffman 编码;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值