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
    评论
### 回答1: 以下是哈夫曼树构造代码实现: ```python from heapq import heappush, heappop, heapify class Node: def __init__(self, freq, symbol=None, left=None, right=None): self.freq = freq self.symbol = symbol self.left = left self.right = right def __lt__(self, other): return self.freq < other.freq def build_huffman_tree(freq_dict): heap = [Node(freq, sym) for sym, freq in freq_dict.items()] heapify(heap) while len(heap) > 1: node1 = heappop(heap) node2 = heappop(heap) new_node = Node(node1.freq + node2.freq, left=node1, right=node2) heappush(heap, new_node) return heappop(heap) def traverse_tree(node, prefix="", encoding={}): if node is None: return if node.symbol is not None: encoding[node.symbol] = prefix traverse_tree(node.left, prefix + "0", encoding) traverse_tree(node.right, prefix + "1", encoding) return encoding if __name__ == "__main__": freq_dict = {'a': 5, 'b': 9, 'c': 12, 'd': 13, 'e': 16, 'f': 45} huffman_tree = build_huffman_tree(freq_dict) encoding = traverse_tree(huffman_tree) print(encoding) ``` 此代码实现了哈夫曼树构造,使用了优先队列(heapq)和递归遍历树的方法。其中,`build_huffman_tree`函数先将每个字符作为一个节点加入堆中,然后每次取堆中频率最小的两个节点作为左右子节点构造新的节点,直到堆中只有一个节点,即为哈夫曼树的根节点。`traverse_tree`函数用于递归遍历哈夫曼树,并生成每个字符的编码。最后通过调用`traverse_tree`函数生成编码。 ### 回答2: 哈夫曼树是一种用来构造最优前缀编码的方法,可以通过频率统计来确定字符的编码方式,其中出现频率越高的字符编码越短,出现频率越低的字符编码越长。 在Python中,可以使用哈夫曼树构造算法来实现: 首先,需要对字符的频率进行统计。可以使用字典来记录每个字符的出现次数。 然后,根据字符的频率构造一颗哈夫曼树哈夫曼树构建也可以使用堆来实现,将所有的字符频率放入最小堆中。 接着,我们可以通过不断合并堆中最小的两个节点,构建哈夫曼树。每次合并最小的两个节点,生成一个新的父节点,其权值为两个子节点的权值之和。然后将新生成的父节点插入堆中,重复这个过程直到只剩下一个节点为止。 最后,通过遍历哈夫曼树的路径,为每个字符生成其对应的哈夫曼编码。对于每个节点,向左走的路径标记为0,向右走的路径标记为1。通过递归的方式,可以生成每个字符的哈夫曼编码。 通过以上步骤,我们可以得到每个字符的哈夫曼编码,从而实现字符的最优前缀编码。 ### 回答3: 哈夫曼树是一种常用于数据压缩、编码和解码的树形数据结构。以下是用Python实现哈夫曼树构造过程: 首先,我们需要定义一个节点类来表示哈夫曼树的节点。节点类包含一个频率属性和两个指针分别指向左子树和右子树。 接下来,我们需要统计每个字符在给定文本中出现的频率。可用字典或列表保存字符及其频率。 然后,我们将每个字符创建为一个独立的节点,并按照频率对节点进行排序。 接着,从频率最低的两个节点中选择出来,创建一个新的父节点,并将频率之和赋值给父节点。 将这个父节点插入到节点列表中,并删除原先的两个节点。重复这个过程,直到只剩下一个节点时,即为哈夫曼树的根节点。 最后,可以通过遍历哈夫曼树来获取每个字符的编码。左子树路径上添加0,右子树路径上添加1。 以上就是用Python实现哈夫曼树构造过程。通过这个算法,我们能够根据字符出现的频率来构建出一个高效的编码方式,从而实现数据的压缩和编码解码的效果。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值