• 重学数据结构之哈夫曼树


    一、哈夫曼树

    1.带权扩充二叉树的外部路径长度

      扩充二叉树的外部路径长度,即根到其叶子节点的路径长度之和。

      例如下面这两种带权扩充二叉树:

      

      左边的二叉树的外部路径长度为:(2 + 3 + 6 + 9) * 2 = 38。

      右边的二叉树的外部路径长度为:9 + 6 * 2 + (2 + 3) * 3 = 36。

    2.哈夫曼树

      哈夫曼树(Huffman Tree)是一种重要的二叉树,在信息领域有重要的理论和实际价值。

      设有实数集 W = {W0 ,W1 ,···,Wm-1 },T 是一颗扩充二叉树,其 m 个外部节点分别以 Wi (i = 1, 2, n - 1) 为权,而且 T 的带权外部路径长度在所有这样的扩充二叉树中达到最小,则称 T 为数据集 W 的最优二叉树或者哈夫曼树。

    二、哈夫曼算法

    1.基本概念

      哈夫曼(D.A.Huffman)提出了一个算法,它能从任意的实数集合构造出与之对应的哈夫曼树。这个构造算法描述如下:

    • 算法的输入为实数集合 W = {W0 ,W1 ,···,Wm-1 }。
    • 在构造中维护一个包含 k 个二叉树集合的集合 F,开始时k = m 且 F = {T0 ,T1 ,···,Tm-1 },其中每个 Ti 是一颗只包含权为 Wi 的根节点的二叉树。

      该算法的构造过程中会重复执行以下两个步骤,直到集合 F 中只剩下一棵树为止:

    1. 构造一颗二叉树,其左右子树是从集合 F 中选取的两颗权值最小的二叉树,其根节点的权值设置为这两颗子树的根节点的权值之和。
    2. 将所选取的两颗二叉树从集合 F 中删除,把新构造的二叉树加入到集合 F 中。 

      注意:给定集合 W 上的哈夫曼树并不唯一!

    2.示例

      对于实数集合 W = {2, 1, 3, 7, 8, 4, 5},下面的图1到图7表示了从这个实数集合开始,构造一个哈夫曼树的过程:

      图1:

       图2:

     

       图3:

     

      图4:   

      图5: 

     

      图6: 

     

      图7: 

     

    三、哈夫曼算法的实现

    1.实现思路

      要实现哈夫曼算法,需要维护一组二叉树,而且要知道每颗二叉树的根节点的权值 ,这个可以使用前面定义的二叉树的节点来构造哈夫曼树,只需要在根节点处记录该树的权值。而在执行算法时

      在算法开始时,需要根据传入的实数集和来创建一组单节点的二叉树,并以权值作为其优先级存入一个优先级队列之中,在之后的过程中反复执行以下两步,直至队列中只有一颗二叉树:

      1)从该优先级队列中取出两颗权值最小的二叉树;

      2)创建一颗新的二叉树,其权值为选出的两棵树的权值之和,其左右子树分别为选出的两颗树,并将创建好的二叉树加入到优先级队列中。

      当整个优先级队列中只剩下一颗二叉树的时候,就得到我们需要的哈夫曼树了。

    2.实现代码

      首先是要对哈夫曼树的节点进行定义,主要是增加一个权值,定义哈夫曼树节点的代码如下:

    1 # 哈夫曼树节点
    2 class HNode(Node):
    3     def __init__(self, value=None, left=None, right=None, weight=None):
    4         super(HNode, self).__init__()
    5         self.value = value
    6         self.left = left
    7         self.right = right
    8         self.weight = weight

      然后还需要一个优先级的队列,在我前面写过的一篇队列的博客中有提到,只不过那篇博客里的优先级队列用的是一个最大堆,而在这里需要用最小堆,这样每次才能取出权值最小的树。

      最后,下面就是实现哈夫曼算法的主要代码了:

     1 def create(weights: list):
     2     """
     3     根据传入的权值列表创建一个哈夫曼树
     4     :param weights: 实数集合
     5     """
     6     queue = PriorityQueue()
     7     # 将节点添加到优先级队列中
     8     for weight in weights:
     9         node = HNode(weight=weight)
    10         queue.enqueue(node, weight)
    11     while queue.size() > 1:
    12         node1 = queue.dequeue()
    13         node2 = queue.dequeue()
    14         new_node = HNode(left=node1, right=node2, weight=node1.weight + node2.weight)
    15         queue.enqueue(new_node, new_node.weight)
    16     return queue.dequeue()

    四、哈夫曼编码问题

    1.问题描述 

      最优编码问题,给定两个集合 C 和 W,C 为基本数据集合,W 为 C 中各个字符在实际信息传递中使用的频率,要求设定一套编码规则,要求: 1)使用这种编码规则的开销最小; 2)对任意一对不同字符 Ci 和 Cj,要求 Ci 的编码不是 Cj 编码的前缀。

    2.问题分析

      使用哈夫曼算法构建一颗哈夫曼树,这棵树的叶子节点的数量和字符数量一致,叶子节点的值就是字符的值,叶子节点的权值就是该字符对应的使用频率。然后从根节点开始遍历,往左子树遍历时标记为0,往右子树遍历时标记为1,这样就能保证走到叶子节点时所标记的路径结果是不一样的了,最后将每个叶子节点的值和对应的标记结果返回,就是题目所求的最优编码。

      例如输入的数据为:{"A": 2, "b": 3, "c": 5, "d": 6, "e": 9} 。

      则构造出来的哈夫曼树如下图:

      最后得到的编码为:{"A": "000", "b": "001", "c": "01", "d": "10", "e": "11"} 

    3.代码实现

      下面就是使用哈夫曼算法来求解编码问题的主要代码了:

     1 from Tree.tree import Node
     2 from Queue.queue import PriorityQueue
     3 
     4 
     5 # 哈夫曼树节点
     6 class HNode(Node):
     7     def __init__(self, value=None, left=None, right=None, weight=None):
     8         super(HNode, self).__init__()
     9         self.value = value
    10         self.left = left
    11         self.right = right
    12         self.weight = weight
    13 
    14 
    15 # 自定义哈夫曼树
    16 class HuffmanTree:
    17     def __init__(self):
    18         self.root = HNode()
    19         self.code = {}
    20 
    21     def get_leaves(self, node: HNode, code: str):
    22         """
    23         获取所有叶节点,对树中的分支节点向左子节点的路径标记为0,向右子节点的路径标记为1
    24         :param node: 哈夫曼树的节点
    25         :param code: 字符使用的编码
    26         :return:
    27         """
    28         if not node:
    29             return
    30         code_ = code  # 因为要分别向左向右探索路径,所以需要复制一份
    31         if node.left:
    32             code += "0"
    33             self.get_leaves(node.left, code)
    34         if node.right:
    35             code_ += "1"
    36             self.get_leaves(node.right, code_)
    37         # 没有左右子节点,表明是叶子节点
    38         if not node.left and not node.right:
    39             self.code[node.value] = code
    40 
    41     def create(self, char_data: dict):
    42         """
    43         根据传入的权值列表创建一个哈夫曼树
    44         :param char_data: 字符和其对应频率的字典
    45         """
    46         queue = PriorityQueue()
    47         # 将节点添加到优先级队列中
    48         for char, weight in char_data.items():
    49             node = HNode(value=char, weight=weight)
    50             queue.enqueue(node, weight)
    51         while queue.size() > 1:
    52             node1 = queue.dequeue()
    53             node2 = queue.dequeue()
    54             new_node = HNode(left=node1, right=node2, weight=node1.weight + node2.weight)
    55             queue.enqueue(new_node, new_node.weight)
    56         self.root = queue.dequeue()
    57 
    58 
    59 def solution(char_data: dict):
    60     """
    61     解决哈夫曼编码问题
    62     :param char_data: 字符和其对应频率的字典
    63     :return:
    64     """
    65     tree = HuffmanTree()
    66     tree.create(char_data)
    67     tree.get_leaves(tree.root, "")
    68     return tree.code
  • 相关阅读:
    SSL/TLS原理详解
    HTTPS 为什么更安全,先看这些
    浏览器缓存策略
    HTTPS的中那些加密算法
    双飞翼圣杯布局
    Bootstrap中container与container-fluid的区别
    连接无线设备——与Wi-Fi直接连接
    Android网络通信之WiFi Direct
    【Android】 Android-wifi 直连 wifi direct wifi p2p
    django 过滤器 、日期格式化参数
  • 原文地址:https://www.cnblogs.com/TM0831/p/12904855.html
Copyright © 2020-2023  润新知