• 哈夫曼树、哈夫曼编码/解码


    哈夫曼树

    哈夫曼树的基本介绍

    哈夫曼树构建步骤图解

    创建哈夫曼树代码实现

    1. """ 创建哈夫曼树 """
    2. class EleNode:
    3. """ 节点类 """
    4. def __init__(self, value: int):
    5. self.value = value
    6. self.left = None # 指向左子节点
    7. self.right = None # 指向右子节点
    8. def __str__(self):
    9. return f"Node [value={self.value}]"
    10. def pre_order(self):
    11. """前序遍历二叉树"""
    12. if self is None:
    13. return
    14. # 先输出父节点
    15. print(self, end=' => ')
    16. # 左子树不为空则递归左子树
    17. if self.left is not None:
    18. self.left.pre_order()
    19. # 右子树不为空则递归右子树
    20. if self.right is not None:
    21. self.right.pre_order()
    22. class HuffmanTree:
    23. arr: list = None
    24. def __init__(self, arr):
    25. self.arr = arr
    26. def create_huffman_tree(self) -> EleNode:
    27. # 对 arr 列表进行升序排序
    28. self.arr.sort()
    29. # 遍历数组,将每个数组元素构建成一个 Node 节点
    30. # 将 Node 节点加入到一个新列表中
    31. node_list = []
    32. for i in self.arr:
    33. node_list.append(EleNode(i))
    34. # 循环进行以下步骤,直到列表中只剩下一棵二叉树,此时该二叉树就是哈夫曼树
    35. while len(node_list) > 1:
    36. # 取出权值序列中最小的两课二叉树(即列表中的前两个元素)构建一棵新的二叉树
    37. left = node_list.pop(0)
    38. right = node_list.pop(0)
    39. # 新二叉树的根节点的权值=两个节点权值之和
    40. parent = EleNode(left.value + right.value)
    41. # 让新二叉树的左右节点分别指向两个最小的节点
    42. parent.left = left
    43. parent.right = right
    44. # 将新二叉树根据根节点的大小插入到序列中的指定位置
    45. i = 0
    46. n = len(node_list)
    47. while i < n:
    48. if node_list[i].value >= parent.value:
    49. node_list.insert(i, parent) # 找到了根节点存放的位置
    50. break
    51. i += 1
    52. else:
    53. # 循环结束表示新的二叉树的权值最大,在node_list列表中没有比它大的
    54. # 所以添加到列表最后
    55. node_list.append(parent)
    56. # 此时列表中只有一棵二叉树,即所求的哈夫曼树
    57. # 返回哈夫曼树的根结点
    58. return node_list[0]
    59. huffman = HuffmanTree([13, 7, 8, 3, 29, 6, 1])
    60. node = huffman.create_huffman_tree()
    61. node.pre_order()

    哈夫曼编码

    基本介绍

    哈夫曼编码原理剖析

    哈夫曼编码的实例

    思路分析

    代码实现

    1. """ 哈夫曼编码 """
    2. class CodeNode:
    3. def __init__(self, data: int, weight: int):
    4. self.data = data # 存放字符对应的ASCII码值
    5. self.weight = weight # 存放字符在字符串中出现的次数
    6. self.left = None
    7. self.right = None
    8. def __str__(self):
    9. return f"Node[data={chr(self.data) if self.data else None}, weight={self.weight}]"
    10. def pre_order(self):
    11. """二叉树的前序遍历"""
    12. print(self)
    13. if self.left:
    14. self.left.pre_order()
    15. if self.right:
    16. self.right.pre_order()
    17. class HuffmanCode:
    18. # 哈夫曼编码表
    19. # 存储每个字符对应的编码
    20. # key为对应的字符,val为字符对应的编码
    21. huffman_code_tab = {}
    22. # 记录哈夫曼二进制编码字符串最后一个段的长度
    23. # 即会将哈夫曼二进制字符串按八位进行分割,分割到最后一个时长度可不为8
    24. # 所以用一个变量存储最后一段二进制字符串的长度,在解码的时候会用到
    25. last_char_len = 0
    26. def create_huffman_tree(self, s: str) -> CodeNode:
    27. """
    28. 构建哈夫曼编码二叉树
    29. :param s: 要编码的字符串
    30. :return:
    31. """
    32. # 遍历字符串,统计每一个字符出现的次数,并将结果放入字典
    33. kw = {}
    34. for ch in s:
    35. ascii_code = ord(ch)
    36. if kw.get(ascii_code): # 如果该字符出现过,则直接将其次数加1
    37. kw[ascii_code] += 1
    38. else: # 如果没出现过,则出现次数为1
    39. kw[ascii_code] = 1
    40. # 按照字符出现的次数对字典进行排序
    41. kw = sorted(kw.items(), key=lambda kv: (kv[1], kv[0]))
    42. # 遍历字典,将每个元素构建成一个 Node 节点
    43. # 将 Node 节点加入到一个新列表中
    44. node_list = []
    45. for k, v in kw:
    46. # print(chr(k),'=', v, end=', ')
    47. node_list.append(CodeNode(k, v))
    48. # 循环进行以下步骤,直到列表中只剩下一棵二叉树,此时该二叉树就是哈夫曼树
    49. while len(node_list) > 1:
    50. # 取出权值序列中最小的两课二叉树(即列表中的前两个元素)构建一棵新的二叉树
    51. left = node_list.pop(0)
    52. right = node_list.pop(0)
    53. # 新二叉树的根节点的权值=两个节点权值之和
    54. parent = CodeNode(None, left.weight + right.weight)
    55. # 让新二叉树的左右节点分别指向两个最小的节点
    56. parent.left = left
    57. parent.right = right
    58. # 将新二叉树根据根节点的大小插入到序列中的指定位置
    59. n = len(node_list)
    60. i = 0
    61. while i < n:
    62. if node_list[i].weight >= parent.weight:
    63. node_list.insert(i, parent) # 找到了根节点存放的位置
    64. break
    65. i += 1
    66. else:
    67. # 循环结束表示新的二叉树的权值最大,在node_list列表中没有比它大的
    68. # 所以添加到列表最后
    69. node_list.append(parent)
    70. # 此时列表中只有一棵二叉树,即所求的哈夫曼树
    71. # 返回哈夫曼树的根结点
    72. return node_list[0]
    73. def get_huffman_code_tab(self, ele_node: CodeNode, code: str, code_str: str):
    74. """
    75. 遍历所创建的哈夫曼树,得到所有叶子节点(叶子结点即要得到的字符)的编码
    76. 这里规定左节点为0,右节点为1
    77. :param ele_node: 传入的要遍历的树的根节点,初始为根节点
    78. :param code: 表示所选择的路径是左节点还是右节点
    79. :param code_str: 每个字符对应的编码
    80. :return:
    81. """
    82. code_str += code # 拼接编码
    83. if ele_node.data is None:
    84. # 表示是非叶子节点,因为在创建哈夫曼树时设置了为叶子结点的data为空
    85. # code_str += code
    86. if ele_node.left:
    87. self.get_huffman_code_tab(ele_node.left, '0', code_str)
    88. if ele_node.right:
    89. self.get_huffman_code_tab(ele_node.right, '1', code_str)
    90. else: # 是叶子节点
    91. self.huffman_code_tab[chr(ele_node.data)] = code_str
    92. def huffman_zip(self, s: str) -> list:
    93. """
    94. 利用哈夫曼编码表把字符串中的每一个字符转换成对应的编码
    95. 即将一个字符串根据哈夫曼编码进行压缩,得到一个压缩后的结果
    96. :param s: 要转换的字符串
    97. :return: 返回编码后的列表
    98. """
    99. res = ''
    100. # 遍历字符串,将每一个字符转换成对应的编码,并将所有编码拼接起来
    101. # "i like like like java do you like a java" => 以下形式
    102. # 1100111111101110001011111110111000101111111011100010100001011000101011
    103. # 001100001011001000011001110111111101110001011010100001011000101
    104. for i in s:
    105. res += self.huffman_code_tab[i]
    106. # 将得到的编码字符串按八位进行分割,将每八位转换成一个int,并将int存放到列表中
    107. code_list = []
    108. i = 0
    109. n = len(res)
    110. while i < n:
    111. num = int(res[i:i + 8], 2) # 将二进制字符串转换为整数
    112. code_list.append(num)
    113. i += 8
    114. if i < n <= i + 8: # 已经分割到了最后一部分,记录该部分的长度
    115. self.last_char_len = n - i
    116. return code_list
    117. def huffman_decode(self, code_list) -> str:
    118. """
    119. 将哈夫曼编码进行解压,得到一个可阅读的字符串
    120. :param code_list: 要解压的哈夫曼编码列表
    121. :return: 解码后的字符串
    122. """
    123. # 将哈夫曼编码列表转换成对应的二进制字符串
    124. # [415, 476, 95, 476, 95, 476, 80, 177, 345, 389, 400, 206, 254, 226, 212, 44, 5] =>
    125. # 1100111111101110001011111110111000101111111011100010100001011000101011
    126. # 001100001011001000011001110111111101110001011010100001011000101
    127. code_str = '' # 存储对应的二进制字符串
    128. count = 0
    129. n = len(code_list)
    130. for i in code_list:
    131. t = "{:08b}".format(i) # 将整数转换为二进制字符串
    132. code_str += t
    133. count += 1
    134. if count == n - 1:
    135. break
    136. code_str += "{:0{k}b}".format(code_list[count], k=self.last_char_len)
    137. # 将哈夫曼编码表的键值互换
    138. # 比如原来的是'a': '001' => 变成 '001': 'a'
    139. code_tab = {}
    140. for k, v in self.huffman_code_tab.items():
    141. code_tab[v] = k
    142. # 遍历二进制字符串
    143. j = 0
    144. i = 1
    145. n = len(code_str)
    146. res_code = '' # 解码后的字符串
    147. while i <= n:
    148. t = code_str[j:i]
    149. ch = code_tab.get(t)
    150. if ch:
    151. res_code += ch
    152. j = i
    153. i += 1
    154. return res_code
    155. s = "i like like like java do you like a java"
    156. # s = "I love python hahha nihao"
    157. huffman = HuffmanCode()
    158. root_node = huffman.create_huffman_tree(s)
    159. huffman.get_huffman_code_tab(root_node, '', '')
    160. huffman_code_list = huffman.huffman_zip(s)
    161. decode_str = huffman.huffman_decode(huffman_code_list)
    162. print(decode_str)

    使用哈夫曼编码压缩文件的注意事项(代码胜省略)

  • 相关阅读:
    【Java】从Java代码到网络编程,三次握手又该如何理解
    解决Vue发布后新旧包切换点击路由报错问题
    hashSet解析
    学会这些Jmeter插件,才能设计出复杂性能测试场景
    6W+字记录实验全过程 | 探索Alluxio经济化数据存储策略
    一次较波折的MySQL调优
    oracle connect by详解
    day02 Nacos集群配置、Feign远程调用和统一网关Gateway
    Linux系统的定时任务
    7 SpringBoot与Elasticsearch
  • 原文地址:https://blog.csdn.net/2301_77659011/article/details/133962929