阅读目录
基本概述
- 概念
- 解析
举例,如上图中 权值为13 的结点,那么它的“结点带权路径长度”为:13*(3-1)=26
上图中,中间的二叉树,wpl最小,而且权值为13的结点,满足“权值越大离根结点越近” 那么它就是赫夫曼树!
构建赫夫曼树 思路
-
构建赫夫曼树的步骤
以数列:[13,7,8,3,29,6,1 ] 为例
注释:
(1)每一个结点可以看成一个二叉树,二叉树的分类中有说到
(2)取出排序后,前两个数;就是取出根结点权值最小的两颗二叉树 -
图解
(1)原数组 [13,7,8,3,29,6,1 ],第一次升序后:[1,3,6,7,8,13,29],取出前两个数(看下图的最底层),权值的和为新的根结点 4
(2)重新将数组连同刚刚产生的根结点4进行升序,数组为[4,6,7,8,13,29],此时选出的前两个数是4和6(看下图中的倒数第二层),让它们权值的和为新的根结点 10,将10放入剩下的未处理数组中:[10,7,8,13,29]
(3)再次升序时,会发现 10 应该排在 7和8的后面,所以升序后的数组为:[7,8,10,13,29],取出的前两个数是7和8,此时它们 应该和 根结点10 位于同一层,即倒数第三层,而且应该在10的前面(看下图的倒数第三层),因为它们是两个数 独立形成一个新的二叉树 那么在同一层的 [10,13,29] 前两个应该再生成一个新的根结点即是 23 (看下图倒数第四层)
(4)让7和8相加,形成新的根结点15 ,将15放入剩下的未处理数组中:[15,23,29],以此类推,具体看下图,下图是从底层向上操作的↓↓↓
Python实现构建赫夫曼树
- 先解决:每次从列表中选出最小的2个数
(1)第一种思路:遍历数组,进行比较;思路很好,但是没调用内置排序方法效率高
array = [4, 20, 3, 4, 5, 6, 7]
num1 = num2 = float('inf')
for x in range(0, len(array)):
if num1 > array[x]:
num2 = num1
num1 = array[x]
elif num2 > array[x]:
num2 = array[x]
print(num1, num2)
补充知识点:列表中如何按照元素的对象、类进行排序?
- Java中通过实现 Comparable接口,可以给对象排序,Python中也可以在列表中按照元素的对象、类进行排序!
- 两种方法: operator模块中实现列表中的对象排序 和 如何用内置的sort()函数实现对象的排序
import operator # 导入operator 包,pip install operator
lis = [] # 待排序列表
class Infor: # 自定义的元素
def __init__(self, stu_id, name, age):
self.stu_id = stu_id
self.name = name
self.age = age
# 将对象加入列表中
lis.append(Infor(1, 'cbc', '11'))
lis.append(Infor(15, 'acd', '13'))
lis.append(Infor(6, 'bcd', '16'))
lis.append(Infor(6, 'acd', '18'))
lis.append(Infor(8, 'acd', '18'))
'''operater模块:适用于数据量大'''
# ----排序操作
# 参数为排序依据的属性,可以有多个,这里优先id,使用时按需求改换参数即可
temp = operator.attrgetter('stu_id', 'name')
lis.sort(key=temp) # 使用时改变列表名即可
# ----排序操作
# 此时lis已经变成排好序的状态了,排序按照stu_id优先,其次是name,遍历输出查看结果
for obj in lis:
print(obj.stu_id, obj.name, obj.age)
'''内置的sort()'''
lis.sort(cmp=None, key=lambda x:x.stu_id, reverse=False)
# lis.sort(cmp=None, key=lambda x:(x.stu_id,x.name,...), reverse=False) # 多个指标,前主后次
for obj in lis:
print(obj.stu_id, obj.name, obj.age)
注意:这两种方式都不能对空值进行操作,如果有空值会报错:TypeError: ‘<’ not supported between instances of ‘NoneType’ and ‘int’
解决办法是:将空值换成“0”,这点相对于java实现 Comparable接口方式,有点不方便
实现 创建赫夫曼树
import operator
class TreeNode(object):
def __init__(self, val):
self.val = val
self.left = None
self.right = None
class HuffmanTree(object):
def create_huffman_tree(self, array):
'''
创建赫夫曼树的方法
:param array: 需要创建成赫夫曼树的数组
:return: 创建好后的赫夫曼树的root结点
'''
# 为了操作方便,先遍历array数组,将array的每个元素构成一个TreeNode
# 将TreeNode 对象放到 一个新的列表中
nodes = []
for item in array:
nodes.append(TreeNode(item))
# 对象排序
while len(nodes) > 1: # 到最后只有个新的根结点就停止
temp = operator.attrgetter("val")
nodes.sort(key=temp)
# 取出根结点权值最小的两颗二叉树(结点)
# left_node = nodes[0]
# right_node = nodes[1]
# 可以直接用pop第一个元素,下面的remove就不用写了
left_node = nodes.pop(0)
right_node = nodes.pop(0)
# 构建一棵新的二叉树
parent_node = TreeNode(left_node.val + right_node.val)
parent_node.left = left_node # 将父结点和左结点链接起来
parent_node.right = right_node
# 从nodes删除处理过的二叉树
# nodes.remove(left_node)
# nodes.remove(right_node)
# 将parent_node加入nodes
nodes.append(parent_node)
return nodes[0] # 返回根结点即可
def pre_order(self, node): # node 为根结点,前序遍历
if node is None:
return
print(node.val, end=" ")
self.pre_order(node.left)
self.pre_order(node.right)
if __name__ == '__main__':
li = [13, 7, 8, 3, 29, 6, 1]
h = HuffmanTree()
huff_root = h.create_huffman_tree(li)
h.pre_order(huff_root)
'''输出结果:
67 29 38 15 7 8 23 10 4 1 3 6 13
'''
- 可以将上述结果,对照下图的前序遍历结果
赫夫曼编码
- 介绍
- 原理剖析
变长编码:
- 赫夫曼编码 实现步骤
- 赫夫曼编码生成的就是前缀编码:因为 统计的每个字符 都是赫夫曼树的叶子结点,路径都是唯一的,所以生成的编码是唯一的
数据压缩:创建赫夫曼树
- 步骤
Python 实现
补充知识点:如何获取字符串的字节数?
(1)java中可以先用 getBytes 然后.length 获取字符串的字节数,Python可以通过 len()函数获取字符串长度或字节数
(2)len 函数的基本语法格式为:len(string) ,它的结果是获取字符串的长度
(3)在 Python 中,不同的字符所占的字节数不同,数字、英文字母、小数点、下划线以及空格,各占一个字节,而一个汉字可能占 2~4 个字节,具体占多少个,取决于采用的编码方式。例如,汉字在 GBK/GB2312 编码中占用 2 个字节,而在 UTF-8 编码中一般占用 3 个字节。
以 UTF-8 编码为例,字符串“人生苦短,我用Python”所占用的字节数如下图所示。
可以通过使用 encode() 方法,将字符串进行编码后再获取它的字节数。例如,采用 UTF-8 编码方式,计算“人生苦短,我用Python”的字节数:
# utf-8
str1 = "人生苦短,我用Python"
print(len(str1.encode())) # 27
# 汉字加中文标点符号共 7 个,占 21 个字节,而英文字母和英文的标点符号占 6 个字节,
# 一共占用 27 个字节
# gbk
str1 = "人生苦短,我用Python"
print(len(str1.encode('gbk'))) # 20
补充知识点:如何统计出字符串中每个字符的次数?
- 方法一:直接使用字典
'''法一'''
s = 'helloworld'
d = dict()
for x in s:
if x not in d:
d[x] = 1
else:
d[x] = d[x] + 1
print(d) # {'h': 1, 'e': 1, 'l': 3, 'o': 2, 'w': 1, 'r': 1, 'd': 1}
'''法二'''
s = 'helloworld'
d2 = dict()
for x in s:
d2[x] = d2.get(x, 0) + 1
print(d2) # {'h': 1, 'e': 1, 'l': 3, 'o': 2, 'w': 1, 'r': 1, 'd': 1}
'''法三:字符串count结合使用'''
s = 'helloworld'
d3 = dict()
for x in s:
d3[x] = s.count(x)
print(d3) # {'h': 1, 'e': 1, 'l': 3, 'o': 2, 'w': 1, 'r': 1, 'd': 1}
# 至于先用set()集合去重,然后遍历原字符串用count统计出现次数的方法,类似就不加赘述了
- 方式二:制表
def count_char(str):
str = str.lower() # 化成小写
ans = []
for i in range(26): # 列表赋初值 26 个 0
ans.append(0)
for i in str:
if (ord(i) >= ord('a') and ord(i) <= ord('z')):
ans[ord(i) - ord('a')] = ans[ord(i) - ord('a')] + 1 # 统计个数
return ans
if __name__ == "__main__":
str = input()
print(count_char(str))
#-------
def count_char(st): # 定义数个数的函数
keys = [chr(i + 97) for i in range(26)] # 生成26个字母的key列表
di = dict().fromkeys(keys, 0) # 赋给每个key初始值0
new = [] # 建立一个新列表用于存放有序的key
st = st.lower() # 将所有输入的字符改为小写
for s in st: # 遍历字符串
di[s] = st.count(s) # 输出每个字符的个数,存放到字典里
for k