实验内容
编程实现 Huffman 编码问题,并理解其核心思想。
对字符串进行 01 编码,输出编码后的 01 序列,并比较其相对于定长编码的压缩率。例如对于字符串“AABBBEEEEGZ”,如果使用定长编码,‘A’、‘B’、‘E’、‘G’、‘Z’字符各需要 3 位 01 串编码,编码后的字符长度为 311=33 位,如果使用 Huffman 编码,可编码为下图,编码后的字符长度为 23+32+41+4+4=24,压缩率为 24/33=72.73%.
算法设计思路
首先,定义了一个Node
类,表示哈夫曼树中的节点。每个节点包含频率(freq
)、字符(symbol
)以及左子节点(left
)和右子节点(right
)。
接下来,定义了build_huffman_tree
函数,用于构建哈夫曼树。该函数接受一个频率映射freq_map
作为输入,其中键为字符,值为字符出现的频率。函数首先将频率映射中的每个字符和频率构建成一个节点,并使用优先队列(堆)进行排序。然后,不断从优先队列中取出频率最小的两个节点,合并为一个新的父节点,并将其插入优先队列中,直到优先队列中只剩下一个节点,即根节点,表示构建完成的哈夫曼树。
然后,定义了generate_huffman_codes
函数,用于递归生成哈夫曼编码。该函数接受一个节点root
、当前编码code
和保存编码的字典huffman_codes
作为输入。如果当前节点是叶子节点,即具有字符值,将该字符和对应的编码添加到字典huffman_codes
中。否则,递归地处理左子节点和右子节点,分别在当前编码基础上加上"0"和"1",继续生成编码。
接下来,定义了compress_string
函数,用于压缩输入的字符串。该函数首先根据输入字符串构建频率映射freq_map
,然后调用build_huffman_tree
函数构建哈夫曼树,并调用generate_huffman_codes
函数生成哈夫曼编码。接着,遍历输入字符串,根据哈夫曼编码将每个字符替换为对应的编码,生成压缩后的字符串。
然后,定义了calculate_initial_bit_length
函数,用于计算初始位数。该函数接受哈夫曼编码字典huffman_codes
作为输入,计算编码字典的长度,然后不断增加初始位数n
,直到能够表示所有编码的位数。
接下来,定义了calculate_compression_ratio
函数,用于计算压缩率。该函数接受原始字符串、压缩后的字符串和初始位数n
作为输入,计算原始字符串和压缩后的字符串的比特数,然后计算压缩率。
最后,代码从文件orignal.txt
中读取输入字符串,并调用上述函数进行压缩和计算。然后,将哈夫曼编码表和压缩率输出到文件table.txt
中,并打印每个字符的频率、哈夫曼编码以及压缩率。
源码及注释
import heapq
from collections import defaultdict
# 定义节点类
class Node:
def __init__(self, freq, symbol, 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_map):
pq = []
for symbol, freq in freq_map.items():
heapq.heappush(pq, Node(freq, symbol))
while len(pq) > 1:
left = heapq.heappop(pq)
right = heapq.heappop(pq)
parent = Node(left.freq + right.freq, None, left, right)
heapq.heappush(pq, parent)
return pq[0]
# 递归生成哈夫曼编码
def generate_huffman_codes(root, code, huffman_codes):
if root.symbol:
huffman_codes[root.symbol] = code
return
generate_huffman_codes(root.left, code + "0", huffman_codes)
generate_huffman_codes(root.right, code + "1", huffman_codes)
# 压缩字符串
def compress_string(input_string):
freq_map = defaultdict(int)
for char in input_string:
freq_map[char] += 1
huffman_tree = build_huffman_tree(freq_map)
huffman_codes = {}
generate_huffman_codes(huffman_tree, "", huffman_codes)
compressed_string = ""
for char in input_string:
compressed_string += huffman_codes[char]
return compressed_string, huffman_codes, freq_map
#计算初始位数
def calculate_initial_bit_length(huffman_codes):
l = len(huffman_codes)
n = 0
while l > pow(2, n):
n += 1
return n
# 计算压缩率
def calculate_compression_ratio(original_string, compressed_string, n):
original_bits = len(original_string) * n
compressed_bits = len(compressed_string)
return compressed_bits / original_bits
with open('orignal.txt', 'r') as f:
input_string = f.read()
f.close()
input_string = list(filter(lambda x: x != " " and x != "\n", input_string))
compressed_string, huffman_codes, freq_map = compress_string(input_string)
n = calculate_initial_bit_length(huffman_codes)
compression_ratio = calculate_compression_ratio(input_string, compressed_string, n)
with open("table.txt", "w",encoding='utf-8') as file:
file.write('字符 出现频率 编码\n')
for char, freq in freq_map.items():
file.write('{} {} {}\n'.format(char, freq, huffman_codes[char]))
for char, freq in freq_map.items():
print(char, ":", freq, "-", huffman_codes[char])
print("压缩率:", compression_ratio)