6.9哈夫曼树及其构造

6.9哈夫曼树及其构造

哈夫曼树可用来构造最优编码,用于信息传输、数据压缩等方面,哈夫曼树是一种应用广泛的二叉树

一、 哈夫曼树

1.哈夫曼树的基本概念

在介绍哈夫曼树之前,先给出几个基本概念。

  • 结点间的路径和路径长度:路径是指从一个结点到另一个结点之间的分支序列,路径长度是指从一个结点到另一个 结点所经过的分支数目。
  • 结点的权和带权路径长度 :在实际的应用中,人们常常给树的每个结点赋予一个具有某种实际意义的实数,称该实数为这个结点的权。在树型结构中,把从树根到某一结点的路径长度与该结点的权 的乘积,叫做该结点的带权路径长度。
  • 树的带权路径长度:树的带权路径长度为树中从根到所有叶子结点的各个带权路径长度之和,通常记为:
    在这里插入图片描述
    其中 n 为叶子结点的个数,wi为第 i 个叶子结点的权值,li为第 i 个叶子结点的路径长度。
    例 6-5 计算下图中三棵二叉树的带权路径长度。
    WPL(a)=7×2+5×2+2×2+4×2=36
    WPL(b)=4×2+7×3+5×3+2×1=46
    WPL©=7×1+5×2+2×3+4×3=35
    在这里插入图片描述
    研究树的路径长度 PL 和带权路径长度 WPL,目的在于寻找最优分析。

问题 1: 什么样的二叉树的路径长度 PL 最小?
一棵树的路径长度为 0 结点至多只有 1 个 (根)
路径长度为 1 结点至多只有 2 个 (两个孩子)
路径长度为 2 结点至多只有 4 个

依此类推: 路径长度为 K 结点至多只有 2k个
所以 n 个结点二叉树其路径长度至少等于如下图所示序列的前 n 项之和。
在这里插入图片描述
在这里插入图片描述
所以完全二叉树具有最小路径长度的性质,但不具有惟一性。有些树并不是完全二叉树,但也可以具有最小路径长度。如下图所示。
在这里插入图片描述

问题 2:什么样的带权的树路径长度最小?

例如:给定一个权值序列{2,3,4,7},可构造如下图所示的多种二叉树的形态。
在这里插入图片描述
在这里插入图片描述

2. 构造哈夫曼树

哈夫曼树:它是由 n 个带权叶子结点构成的所有二叉树中带权路径长度最短的二叉树。 因为这种树最早由哈夫曼(Huffman)研究,所以称为哈夫曼树,又叫最优二叉树,图 6.38 (c)所示的二叉树就是一棵哈夫曼树。

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

直观地看,先选择权小的,所以权小的结点被放置在树的较深层,而权较大的离根较近, 这样自然在哈夫曼树中权越大叶子离根越近,这样一来,在计算树的带权路径长度时,自然 会具有最小带权路径长度,这种生成算法就是一种典型的贪心法。

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

给定一组权值 {7,4,3,2 },用上述方法构造哈夫曼树,将得到图 6.38(c)所示的 二叉树。

3. 哈夫曼树的类型定义

(1) 存储结构

哈夫曼树是一种二叉树,当然可以采用前面已经介绍过的通用存储方法,而哈夫曼树 是求某种最优方案,由于哈夫曼树中没有度为 1 的结点,因此一棵有 n 个叶子的哈夫曼树 共有 2×n-1 个结点,可以用一个大小为 2×n-1 的一维数组存放哈夫曼树的各个结点。 由于每个结点同时还包含其双亲信息和孩子结点的信息,所以构成一个静态三叉链表。

静态三叉链表中:每个结点的结构如下图所示:

在这里插入图片描述
各结点存储在一维数组中,0 号单元不使用,从 1 号位置开始使用。 下图给出了一棵二叉树及其静态三叉链表。
在这里插入图片描述
对于有 n 个叶子结点的哈夫曼树,结点总数为 2n-1 个,为实现方便,将叶子结点集 中存储在前面部分 1~n 个位置,而后面的 n-1 个位置中存储其余非叶子结点。

(2)哈夫曼树的类型定义

用静态三叉链表实现的哈夫曼树类型定义如下:

#define  N  20           /* 叶子结点的最大值。*/ 
#define  M  2*N-1        /* 所有结点的最大值。*/ 
typedef struct  
{ 
	int  weight ;  /* 结点的权值*/ 
	int  parent ;  /* 双亲的下标*/ 
	int  LChild ;  /* 左孩子结点的下标*/ 
	int  RChild ;  /* 右孩子结点的下标*/ 
} HTNode,  HuffmanTree[M+1];   /*  HuffmanTree 是一个结构数组类型,0 号单元不用。 */ 
4.哈夫曼树的算法实现

算法描述】 创建哈夫曼树算法

void CrtHuffmanTree(HuffmanTree  ht,  int  w[ ],  int n)
{ /*构造哈夫曼树 ht[M+1], w[ ]存放 n 个权值。*/ 
	for(i=1;i<=n;i++) 
		ht[i] ={ w[i],0,0,0};   /* 1 ~ n 号单元存放叶子结点,初始化*/ 
	m=2*n-1; 
	for(i=n+1;i<=m;i++) 
		ht[i] ={0,0,0,0};   /* n+1 ~ m 号单元存放非叶结点,初始化 */  
	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; 
	}  /*哈夫曼树建立完毕*/      
} 

该算法分成两大部分,其中第一部分是初始化,先初始化 ht 的前 1-n 号元素,存放叶子结 点(相当初始森林),它们都没有双亲与孩子。再初始化 ht 的后 n-1 个(从 n+1~2n-1)非叶 结点元素;第二部分为实施选择、删除合并 n-1 次(相当步骤(2)-(4)):选择是从当前 森林中(在森林中树的根结点的双亲为 0)选择两棵根的权值最小的树;删除合并是将选到 的两棵树的根权和存入 ht 的当前最前面的空闲元素中(相当于合并树中新结点),并置入相应的双亲与孩子的位置指示。

例 数据传送中的二进制编码。要传送数据 state, seat, act, tea, cat, set, a ,eat ,如何使传送的 长度最短?

为了保证长度最短,先计算各个字符出现的次数,将出现次数当作权,如表 6-2 所示。
在这里插入图片描述
在这里插入图片描述

  • 3
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值