哈夫曼树及其应用

哈夫曼树

1、一些基本概念

路径
指从一个结点到另一个结点之间的分支序列。
路径长度
指从一个结点到另一个结点所经过的分支数目。
结点的权
给树的每个结点赋予一个具有某种实际意义的实数。
带权路径长度
从树根到某一结点的路径长度与该结点的权的乘积。
树的带权路径长度
树中所有叶子结点的带权路径长度之和。
在这里插入图片描述
在这里插入图片描述
研究路径长度PL和带权路径长度WPL目的在于寻找最优。

思考:什么样的二叉树的路径长度PL最小?
路径长度为K结点至多只有 2 k 2^k 2k个(满二叉树),所以n个结点二叉树其路径长度至少等于如下序列的前n项之和。

在这里插入图片描述
结点n对应的路径长度至少为[ log ⁡ 2 n \log_2n log2n],所以前n项之和最小为 ∑ k = 1 n [ log ⁡ 2 k ] \displaystyle\sum_{k=1}^{n}[\log_2k] k=1n[log2k].
完全二叉树的路径长度等于 ∑ k = 1 h [ log ⁡ 2 k ] \displaystyle\sum_{k=1}^{h}[\log_2k] k=1h[log2k](h为树的深度),具有最小路径长度的性质,但不具有唯一性。

在这里插入图片描述
思考:什么样的树带权路径长度最小?在这里插入图片描述
给定权值序列,带权路径长度最小的二叉树,即为哈夫曼树。

2、构造哈夫曼树

哈夫曼树:又叫最优二叉树,由n个带权叶子结点构成的带权路径长度WPL最短的二叉树。

构造哈夫曼树的算法步骤:
1)初始化:用给定的n个权值{w1,w2, … ,wn}对应的n个结点构成n棵二叉树的森林F={T1,T2, …,Tn},其中每一棵二叉树Ti (1≤i≤n)都只有一个权值为wi的根结点,其左、右子树为空。
2)找最小树:在森林F中选择两棵根结点权值最小的二叉树,作为一棵新二叉树的左、右子树,标记新二叉树的根结点权值为其左右子树的根结点权值之和。
3)删除与加入:从F中删除被选中的那两棵二叉树,同时把新构成的二叉树加入到森林F中。
4)判断:重复2)、3)操作,直到森林中只含有一棵二叉树为止,此时得到的这棵二叉树就是哈夫曼树。

直观地看,在哈夫曼树中权越大的叶子离根越近,则其具有最小带权路径长度,是一种典型的贪心法

哈夫曼树的手工构造的方法也非常简单:
给定数列{W1…Wn},以n个权值构成n棵树的森林F;将F={T1…Tn}按权从小到大排列;取T1和T2合并组成一棵树,使其根结点的权值T=T1+T2,再按大小插入F,反复此过程直到只有一棵树为止。

3、哈夫曼树的类型定义

(1)存储结构
n个叶结点,n-1个非叶结点,共有2n-1个结点。除最后一非叶(分支)结点,每个非叶有且仅有一个叶孩子;
存为2n-1个元素的一维数组:静态三叉链表。
在这里插入图片描述
在这里插入图片描述
类型定义:

#define N 20		//叶结点数最大值
#define M 2*N-1		//叶结点数最大值
typedef struct
{
	int weight;
	int parent;
	int LChild;
	int Rchild;
    }HTNode, HuffmanTree[M + 1]; 	//0号单元不用

4. 哈夫曼树创建算法实现

void CrtHuffmanTree(HuffmanTree ht , int w[ ], int n) 
/*w存放n个权值,构造哈夫曼树ht */
{
	int m, i, s1, s2;  
    m = 2 * n - 1;
	for(i = 1; i <= n; i++)  
	ht[i] = {w[i], 0, 0, 0};/*叶结点初始化*/
	for(i = n + 1; i <= m; i++)
    ht[i] = {0, 0, 0, 0};/*非叶结点初始化*/
	for(i = n + 1; i <= m; i++)    /*创建非叶结点,建哈夫曼树*/
	{  	
	    select(ht, i-1, &s1, &s2); /*在ht[1]~ht[i-1]范围内选择两个parent为0且weight最小的结点,其序号分别赋值给s1、s2返回*/
		ht[i].weight = ht[s1].weight + ht[s2].weight;
		ht[s1].parent = i;
		ht[s2].parent = i;
		ht[i].LChild = s1; 
		ht[i].RChild = s2; 
	} 
}

在这里插入图片描述

在这里插入图片描述
左边是初态,右边是终
(左边是初态,右边是终态)。

哈夫曼编码

1、哈夫曼编码的概念

哈夫曼树最典型的应用:哈夫曼编码。
编码:使用二进制来表达信息。

定长编码
判断题答案:1位;
选择题答案:2位;
问答题:编码文字,如ASCII码。
不定长编码
为缩短信息的长度,节约存储和传输开销,可采用不定长编码:
使用频度高的字符编为较短的编码;
是数据压缩技术的基本思想。

如何不定长编码,最优化问题:
(1)前缀编码:任一编码都不是其他任何编码的前缀(最左子串);

(2)哈夫曼编码
对一颗具有n个叶子的哈夫曼树,每个左分支赋予0,右分支赋予1(或反之);
从根到每个叶子都得到一个二进制串,该二进制串就是叶子的哈夫曼编码。

可以得到两个结论:
① 哈夫曼编码是前缀编码;
② 哈夫曼编码是最优前缀编码。

2、哈夫曼编码的作用

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
哈夫曼编码中虽然大部分编码长度大于定长编码的长度3,但程序总位数变小了,可以算出平均码长是2.2.

构造满足哈夫曼编码的最短最优性质:
(1)若di≠dj(字母不同),则对应的树叶不同。因此前缀码不同,一个路径不可能是其他路径的一部分,所以字母之间可以完全区别;
(2)将所有字符变成二进制的哈夫曼编码,使带权路径长度最短,相当总的通路长度最短。

3、哈夫曼编码算法的实现

静态三叉链表的描述如下:

typedef struct 
{
	unsigned int weight ;   /* 用来存放各个结点的权值*/
    unsigned int parent, LChild, RChild ;  /*父、孩子结点指针*/
}HTNode, *HuffmanTree;   /*动态分配数组,存储哈夫曼树*/

typedef char  * HuffmanCode[N+1] ; /*哈夫曼编码串头指针数组*/ 

数组ht的前n个分量表示叶子结点,最后一个分量表示根结点。每个叶子结点对应的编码长度不等,但最长不超过n。

哈夫曼编码的算法:

void CrtHuffmanCode(HuffmanTree ht, HuffmanCode hc, int n)/*从叶到根,逆向求每个叶结点的哈夫曼编码*/
{	char *cd; int i, start, p; unsigned int c;
	cd = (char * )malloc(n * sizeof(char ));  /*分配求当前编码的工作空间*/
	cd[n - 1] = '\0';   /*从右向左逐位存放编码,首先存放编码结束符*/
	for(i = 1; i <= n; i++)  /*求n个叶结点哈夫曼编码*/
	{
		start = n-1;    /*初始化编码起始指针*/
		c = i; p = ht[i].parent; /*从叶子到根结点求编码*/
		while(p!=0)
		{ 	--start;
			if( ht[p].LChild == c)  
			cd[start]='0';  /*左分支标0*/
			else   cd[start]='1';  /*右分支标1*/
			c = p, p = ht[p].parent;//向上倒推}
		hc[i]=(char *)malloc((n-start)*sizeof(char));  /*为第i个编码分配空间*/
		strcpy(hc[i],&cd[start]);
	}
	free(cd);	
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值