python实现信息论哈夫曼编码_哈夫曼压缩原理及python3实现(非面向对象结构)

1 哈夫曼编码综述

在计算机科学和信息论,哈夫曼编码是一种特殊类型的最优前缀码(prefix code),通常用于无损数据压缩(英文文本,更一般地说 ASCII 码位于 0-255 位的文本)。哈夫曼编码是一种变长编码,相比使用定长的 ASCII 码,哈夫曼编码可以节省很多的空间 (试想如果一篇文章中全为同一种字符,对应的哈夫曼编码为 "0" ,那么原先表达 1 个字符的 1 字节就能用来表示 8 个字符)。

哈夫曼压缩对频数最高的字符赋予较短的编码,实现压缩效率最大化

哈夫曼编码以二叉树为基础实现的,二叉树到每一个叶子节点的路径是唯一的,对应的编码也就唯一。

哈夫曼编码的前缀码唯一,从图 1 中可以看出,各编码的前缀码都是唯一的,这就保证了在字符表确定下,不断搜索一定可以定位到对应的字符。图 1 示例文本长度为 17,占用空间17字节。使用哈夫曼编码进行转码,对应的转码文本为:10110110001000111111111001000000000 ,长度为 35,按照 8 位一字节进行切割后,正文文本就变成了 5 字节 (不足 8 位需要补 0)

对转码文本进行还原时,读取 1 位字符,对其后的字符搜索。如读取 "1" 后接着读取 "0" 变成了 "10" ,编码表中没有 "10",则继续向后读取 "101",此时码表中有 "101"项,对文本进行还原得到 "a",继续向后搜索,直到匹配完全文。图 1 哈夫曼树及编码示例

2 哈夫曼压缩及解压基本原理

哈夫曼压缩基本思想是将二进制代码分配给字符,用于减少编码这些符号的字符串比特数(如上图 1 中,原先表达一个字符 "e",需要1字节数据 "01100101",而在哈夫曼编码中,只需要1位数据 "0" 即可表达)。构造过程可以参考图 1 ,简书中较好的资料详细介绍了算法原理(但是代码运行不了,报错...)。

算法的主要过程分为:构造哈夫曼编码表、转码压缩、转码解压。

压缩与解压中,数据根据哈夫曼编码表进行转换,因此写入编码表信息是必须的。此外,字节位数不足 8 位时,需要补充 "0" 达到 8 位,而补 "0" 的情况又对解码信息有影响,还需要在文件中声明补 "0"情况。每一步的大致过程如下所示(在本文第 4 部分结合代码有更详细的示例):构造哈夫曼编码表

Step1 遍历需要处理的字符串,得到每个字符出现的次数(频数)

Step2 将频数最低的两位字符作为叶子节点,左子树频数大于右子树,构造分支节点,同时将分支节点作为新的字符,频数即为子叶频数之和,进行重新排序(如图 1 中,将 "bd" 作为新字符,进行重新排序)

Step3 重复 Step1 和 Step2,直到所有字符编码完成

Step4 从树的顶端开始编码,左子树编为 0,右子树编为 1,直到树的底端

Step5 根据编码情况构造哈夫曼编码表转码压缩

Step1 根据哈夫曼编码表将正文转换为 0-1 编码

Step2 每 8 位编码进行一次切割,作为 1 个字节数据写入压缩文件

Step3 文末不足 8 位则需要补 "0",直到刚好达到 8 位编码

Step4 解码时避免补 "0" 干扰,需要将补 0 情况写入压缩文件中

Step5 将码表写入压缩文件转码解压

Step1 根据补 0 情况,删减文本

Step2 读取哈夫曼编码表,作为转码对照表

Step3 读取正文文本,与转码对照表进行比对,还原信息

3 分析及扩展应用

3.1 为什么哈夫曼编码是最优的?

参考:维基百科香农源编码定理https://en.wikipedia.org/wiki/Shannon%27s_source_coding_theorem​en.wikipedia.org

(其实也很像运筹学课本上提到的最优带权连接图,但是证明实在是太长了。。。)

也可以这样考虑:对频数最多的字符赋予最少的字符长度,依次类推。

3.2 其他压缩算法的"后端"

哈夫曼压缩作为数据流压缩的“先驱者”,由于哈夫曼编码简单、高速且无数据损失,常常被用于其他压缩算法的“后端”。如DEFLATE和多媒体编码器(如图像的JPEG,音频的MP3)都有自己的压缩算法,但都应用到了这种前缀码的思想。尽管大多数无损压缩算法都使用预定义的可变长度(如 LZW 算法)而不是使用哈夫曼算法,但这写算法也通常被称为 "Huffman codes"。

3.3 层次聚类算法聚类树构造

哈夫曼树构造过程与层次聚类算法思想也极其相似,将权值改为“样本距离”,并重新定义节点权值更新函数,即可得到层次聚类的聚类树构造。下图中展示了笔者使用哈夫曼树实现的层次聚类,并进行可视化的效果。图 2 经典层次聚类(Hierarchical Clustering)算法图 3 使用哈夫曼树构造的聚类树(试验数据为17年国赛建模B题数据,使用欧式距离度量权值)

3.4 数据加密

哈夫曼压缩中,最重要的哈夫曼编码表是能否解读数据关键。在压缩的时候,将码表与文本分离(或者打乱码表的表头、表文顺序,自定义一定规则进行匹配),可以实现数据加密。同时,若将码表的表头与表文、表文长度进行分别处理,则可以实现多端口数据加密验证。

3.5 流式数据压缩

哈夫曼编码可以认为是基于统计的压缩算法,统计过程是算法的核心,而流式数据随着文本不断扩展,权值可能发生变化,此时使用哈夫曼压缩不一定能取得很好的压缩效果,但在特定场景下,通过预先设定字符编码,也能取得较好的情况。如据统计,在英文小说中,文本使用 "e"、"t"、"a" 等字符的频数较高,则可以对这些字符进行预先编码,再根据各文本差异进行后续编码扩充。当然,其他编码算法如 LZW 编码、RLE 算法也给出了更好的实现方案,这些算法压缩效果好、速度快,但是性能不够好。

3.6 压缩汉字构思

汉字在 GB2312 编码存储占 2 字节,而英文文本、数字占 1 字节,因此可以考虑在 GB2312 编码下构造汉字映射表,实现汉字压缩。(只是构思,写不写的出来就不知道咯~)

4 python3逐步实现哈夫曼压缩(附注 2 提供测试数据及完整源码)

4.1 说明

学习哈夫曼压缩过程中参考了许多资料,其中不少简书、CSDN 博文都给出了很漂亮的代码示例,但是也存在一些问题,如 python2 代码、结构混乱、使用类思想不便于新手理解(python的编程风格不同于JAVA)、没有给出压缩解压细节等。因此,本文以算法思想为蓝本逐步用代码进行实现,并在本文对每个部分的功能进行了详细说明。部分代码语句可能较为啰嗦,结构也不完美,但是可读性较强,便于理解。(完整代码见附注 2)

使用的编程语言:python3.6.4 (Anaconda3)

使用的编辑器:pycharm

使用的模块:os、six、tkinteros 模块:使用了 os 模块的 path.splitext 函数,用于分割文件名与扩展名。如"test.txt",分割为 ('test', '.txt'),实现重写扩展名功能

six 模块:使用了 six 模块的 int2byte 函数,用于将数字转化为字节存入文件

tkinter 模块:选用,使用了 tkinter 模块的 filedialog.askopenfilenames 函数,用于实现弹窗打开文件的功能 (如图4)图 4 tkinter.filedialog.askopenfilenames 弹窗打开文件

4.2 导入模块

import os

import six

import tkinter

4.3 打开文件

f = open(file_name, 'r') # file_name为文件名

file_data = check_binary(f.read())

f.close()

由于本文实现的是英文文本的压缩,此处仅考虑 ASCII 码在 [0, 255] 范围内的字符。自定义函数 check_binary 进行字符检查替换。check_binary:用于检查文件字符 ASCII 编码是否在 [0, 255] 范围,不在此范围则替换为空格

def check_binary(input_data):

# 检查文件编码,ASCII码超出255的字符替换为空格

output_data = ''

for word_index in range(len(input_data)):

if ord(input_data[word_index]) >= 256:

output_data += ' '

else:

output_data += input_data[word_index]

return output_data

4.4 统计各字符出现的频数

统计各字符出现的频数,并保存在字典 char_freq 中setdefault(word, 0):创建键为 word,,初始值为 0 的对象,若字典中已存在此键,则不产生影响。

char_freq = {}

for word in file_data:

char_freq.setdefault(word, 0)

char_freq[word] += 1

4.5 编码哈夫曼树

编码哈夫曼树两个重要的过程:更新字符频数排序,更新字符编码,对这两个过程分别自定义函数 sort_tuple 和 get_coding_schedulesort_tuple(dist):传入一个字典 dist ,按照值大小顺序进行排序,并返回元组

def sort_tuple(dist):

# 传入字典,按照键大小顺序重排序

return sorted(dist.items(), key=lambda x: x[1], reverse=True)get_coding_schedule(end1, end2, sort_list, code_schedule):传入排序表中频数最低的两位字符的 (键, 值) 元组、剔除传入的 end1, end2 后的字符排序列表、哈夫曼编码表,返回更新后的哈夫曼编码表哈夫曼表构造过程解析传入 end1 作为右子树,end2 作为左子树

分别判断 end1 和 end2 的字符长度,如果长度为 1 说明该字符是叶子节点,否则说明该字符是分支节点如果 end1 是叶子节点,则设置编码值为 "1",如果 end2 是叶子节点,则设置编码值为 "0"

如果 end1 是分支节点,则根据分支节点的字符串进行遍历,为每一个子叶编码值都添加前缀字符 "1",如果 end2 是分支节点,则根据分支节点的字符串进行遍历,为每一个子叶编码值都添加前缀字符 "0"

在 sort_list 中添加由 end1 和 end2 构成的分支节点信息,结点信息包含所有子叶字符,所有子叶累计频数

def get_coding_schedule(end1, end2, sort_list, code_schedule):

# 传入 末端2位字符组 频数 序列列表(剔除末端字符) 哈夫曼编码表

if len(end1[0]) == 1:

code_schedule.setdefault(end1[0], '1')

else:

for k in end1[0]:

code_schedule[k] = '1' + code_schedule[k]

if len(end2[0]) == 1:

code_schedule.setdefault(end2[0], '0')

else:

for k in end2[0]:

code_schedule[k] = '0' + code_schedule[k]

sort_list.append((end2[0] + end1[0], end1[1] + end2[1]))

return code_schedule

通过调用上面两个函数,完成哈夫曼编码的构造

# 初始 字符--频数 列表

sort_list = sort_tuple(char_freq)

# 初始化哈夫曼编码表

code_schedule = {}

# 不断重排序,更新哈夫曼编码表及节点信息

for i in range(len(sort_list) - 1):

sort_list = sort_tuple(dict(sort_list))

code_schedule = get_coding_schedule(sort_list.pop(), sort_list.pop(), sort_list, code_schedule)图 5 哈夫曼压缩及编码示例

以图 5 为例,展示哈夫曼树及哈夫曼编码的构造过程:初次排序:[('e', 9), ('c', 4), ('a', 2), ('b', 2), ('d', 1)]

传入 end1 = ('d', 1), end2 = ('b', 2), sort_list = [('e', 9), ('c', 4), ('a', 2)], code_schedule = {}

end1 和 end2 的字符长度都为 1 ,分别设置编码 "1", "0"

得到 sort_list = [('e', 9), ('c', 4), ('a', 2), ('bd', 3)], code_schedule = {'d': '1', 'b': '0'}

第二次排序:[('e', 9), ('c', 4), ('bd', 3), ('a', 2)]

传入 end1 = ('a', 2), end2 = ('bd', 3), sort_list = [('e', 9), ('c', 4)], code_schedule = {'d': '1', 'b': '0'}

end1 字符长度为 1,设置编码 "1";end2 字符长度为 2 ,取 end2 的字符 "bd" ,对子叶的字符编码分别加上前缀字符 "0"

得到 sort_list = [('e', 9), ('c', 4), ('bda', 5)], code_schedule = {'d': '01', 'b': '00', 'a': '1'}

第三次排序:[('e', 9), ('bda', 5), ('c', 4)]

传入 end1 = ('c', 4), end2 = ('bda', 5), sort_list = [('e', 9)], code_schedule = {'d': '01', 'b': '00', 'a': '1'}

end1 字符长度为 1,设置编码 "1";end2 字符长度为 3,取 end2 的字符 "bda",对子叶的字符编码分别加上前缀字符 "0"

得到 sort_list = [('e', 9), ('bdac', 9)], code_schedule = {'d': '001', 'b': '000', 'a': '01', 'c': '1'}

第四次排序:[('e', 9), ('bdac', 9)]

传入 end1 = ('bdac', 9), end2 = ('e', 9), sort_list = [], code_schedule = {'d': '001', 'b': '000', 'a': '01', 'c': '1'}

end1 字符长度为 4,取 end1 的字符 "bdac",对子叶的字符编码分别加上前缀字符 "1";end2 字符长度为 1,设置编码 "0"

得到 sort_list = [('ebdac', 18)],code_schedule = {'d': '1001', 'b': '1000', 'a': '101', 'c': '11', 'e': '0'}

通过上面四次重复过程即完成了哈夫曼树及哈夫曼编码的构造

4.6 文本信息转哈夫曼编码

在 4.5 中构造了哈夫曼编码表,接下来要做的工作就是对照哈夫曼编码表,将文本信息转码并保存。要写入作为正文的信息有哈夫曼编码表、正文编码、补 0 。其中哈夫曼编码表只需要写入表文信息,正文部分需要进行转码处理,补 0 根据哈夫曼编码表表文信息+正文编码信息长度确定。如图 5 案例中,哈夫曼编码表表文长度 14,正文转码长度35,需要补 7 个 0 。正文信息存储结构如图 6 所示:图 6 待写入的文本信息

# 文本信息转哈夫曼码

# 哈夫曼 0-1 编码转码 + 正文文本

code = ''.join(list(code_schedule.values()))

for word in file_data:

code += code_schedule[word]

# 不足 8 位补 0,记录在 code_sup 中

code_sup = 8 - len(code) % 8

code += code_sup * '0'

4.7 创建压缩文件并写入信息

python 默认的存储数据以字符串形式存入,若要进行字节文件写入,需要使用二进制文件格式打开,还需要使用 six 模块下的 int2byte 函数对信息进行转码。

依次将:补 0 情况,码表总长度,每一个字符的表文长度,表头字符写入文件,作为文件头,用于声明信息。随后再将 4.6 中正文文本信息写入文件。图 6 待写入文件头信息

# 1.创建压缩文件

f = open(os.path.splitext(file_name)[0] + '.qlh', 'wb')

# 2.写入补 0 情况

f.write(six.int2byte(code_sup))

# 3.写入哈夫曼编码表(总长度+每一个编码长度+每一个编码对应的字符+转码信息)

# 3.1 码表总长度(字符个数,与指针读取定位有关,分割码表与正文)

f.write(six.int2byte(len(code_schedule)))

# 3.2 储存每一个哈夫曼编码的位长

for v in code_schedule.values():

f.write(six.int2byte(len(v)))

# 3.3 储存每一个哈夫曼编码配对字符 字符 ==> ASCII 码

for k in code_schedule.keys():

f.write(six.int2byte(ord(k)))

# 3.4 以 8 为长度单位,将 0-1 字符转为对应的十进制数,映射为 ASCII 符号,写入正文文本

for i in range(len(code) // 8):

f.write(six.int2byte(int(code[8 * i:8 + 8 * i], 2)))

# 4.关闭文件

f.flush()

f.close()

print('压缩完成', file_name, '>>', os.path.splitext(file_name)[0] + '.qlh')

4.8 实验示例

本次使用英文 txt 文件 5 部作品进行实验测试:图 7 实验原文件,作品分别为:《哈利波特》4-6,《共产党宣言》,《一千零一夜》

压缩效果:图 8 压缩效果对比

可以看到,压缩效果显著。

4.9 解压的实现

解压是压缩的逆过程,怎么写入的就怎么读取。按照写入的过程分别读取以下信息:补 0 情况,用于删除正文信息中末尾补充的 "0"

码表总长度(设为

)

码表表文长度:表文长度以 1 字节形式存储在文件中,根据码表总长度向后截取

个字节即得到所有码表表文长度

码表表头:码表表文长度之后就是码表表文(编码对应的原字符),也是向后继续截取

个字节,每一个字节对应的 ASCII 码都对应着一个字符,将其转译作为哈夫曼编码表的表头信息

码表表文:根据表文长度,在码表表头之后不断搜索截取表文长度指示的位数,获取到每个表头对应的表文,写入哈夫曼编码表。

正文信息:所有表文读取结束后即复原了哈夫曼编码表,根据补 0 情况删除末尾的字符,剩余的文本即为原始文本信息。对于原始文本信息,每次对编码向后搜索、拼接,并与哈夫曼编码表进行匹配,若编码存在哈夫曼编码表表文中,则使用自定义函数 get_keys 进行转译得到对应的表头

def get_keys(dict, value):

# 传入字典,值,获取对应的键

for k, v in dict.items():

if v == value:

return k

解码过程不涉及太多技术性问题,此处直接给出所有的代码及简要注释,按照写入方式逆向操作就能还原文本。

import os

# 1.打开文件

f = open(file_name, 'rb')

# 2.读取信息

file_data = f.read()

f.close()

# 3.分割信息

# 3.1 获取补 0 位数

code_sup = file_data[0]

# 3.2 获取码表长度

code_schedule_length = file_data[1]

# 3.3 指针跳过 补0+码长+码符

pointer = 2 * code_schedule_length + 2

# 3.4 获取码表中每一个编码的长度

code_word_len = [file_data[2 + i] for i in range(code_schedule_length)]

# 3.5 编码表中字符长度总和,用于切割码表与正文

sum_code_word_len = sum(code_word_len) // 8 + 1 if sum(code_word_len) % 8 != 0 else sum(code_word_len) // 8

# 4.还原码表

# 4.1 码表转译

code_schedule_msg = ''

for i in range(sum_code_word_len):

code_schedule_msg += '0' * (10 - len(bin(file_data[pointer + i]))) + bin(file_data[pointer + i])[2:]

# 4.2 初始化指针

pointer = 0

# 4.3 创建码表

code_schedule = {}

for i in range(code_schedule_length):

code_word = chr(file_data[code_schedule_length + 2 + i]) # 码符

code_schedule[code_word] = code_schedule_msg[pointer:pointer + code_word_len[i]] # 码符码文匹配,还原码表

pointer += code_word_len[i]

# 5.提取正文

code = code_schedule_msg[pointer:]

pointer = 2 * code_schedule_length + 2 + sum_code_word_len

for number in file_data[pointer:]:

code += '0' * (10 - len(bin(number))) + bin(number)[2:]

# 删去补0

code = code[:-code_sup]

# 6.文本转译

pointer = 0 # 指针归零

# 初始化文本

letter = ''

# 限制最大搜索长度,提高效率

max_length = max([len(list(code_schedule.values())[i]) for i in range(len(code_schedule.values()))])

while pointer != len(code):

for i in range(max_length):

if code[pointer:pointer + i + 1] in code_schedule.values():

letter += get_keys(code_schedule, code[pointer:pointer + i + 1])

pointer += i + 1

break

# 7.创建解压文件

f = open(os.path.splitext(file_name)[0] + '.txt', 'w+')

f.write(letter)

print('解压完成', file_name, '>>', os.path.splitext(file_name)[0] + '.txt')

将上文中的压缩函数命名为 compress,解压函数命名为 decompress,并引入 tkinter 模块中的 filedialog.askopenfilenames 函数,即可实现弹窗点击文件并压缩、解压的功能。此外,通过自定义函数 compress_all、decompress_all、get_request 函数,进一步实现批量文件压缩解压功能。

def compress_all(file_names):

# 批量压缩文件

for file_name in file_names:

compress(file_name)

def decompress_all(file_names):

# 批量解压文件

for file_name in file_names:

decompress(file_name)

class Inputerror(Exception):

# 自定义异常

def __init__(self, messages):

super().__init__(messages)

def get_request():

file_name = tkinter.filedialog.askopenfilenames()

ask = input('Compress or Decompress ? (C/D)').lower()

if ask == 'd':

decompress_all(file_name) # 解压文件

elif ask == 'c':

compress_all(file_name) # 压缩文件

else:

raise Inputerror("accept unknown command ,routine haven't started doing anything, please run it again")

写在运行当前文件中执行的部分

if __name__ == '__main__':

import tkinter.filedialog

get_request()

while input('Continue ? (Y/N)').lower() == 'y':

get_request()

4.10 实验示例

继续对 4.8 中的文件进行解压。先前提到,本文中的程序是对 ASCII 范围在 [0,255] 的字符进行压缩,并对超过范围的字符进行空格替换处理,因此文件中出现的少量数据损失属于正常现象。(哈利波特 5 文件中没有超过范围的字符,因此实现了完整的数据还原)图 9 压缩前与解压后文件对比

5 附注 1:罗塞塔代码提供的哈夫曼编码树构造

from heapq import heappush, heappop, heapify

from collections import defaultdict

def encode(symb2freq):

"""Huffman encode the given dict mapping symbols to weights"""

heap = [[wt, [sym, ""]] for sym, wt in symb2freq.items()]

heapify(heap)

while len(heap) > 1:

lo = heappop(heap)

hi = heappop(heap)

for pair in lo[1:]:

pair[1] = '0' + pair[1]

for pair in hi[1:]:

pair[1] = '1' + pair[1]

heappush(heap, [lo[0] + hi[0]] + lo[1:] + hi[1:])

return sorted(heappop(heap)[1:], key=lambda p: (len(p[-1]), p))

txt = "this is an example for huffman encoding"

symb2freq = defaultdict(int)

for ch in txt:

symb2freq[ch] += 1

huff = encode(symb2freq)

print ("Symbol\tWeight\tHuffman Code")

for p in huff:

print("%s\t%s\t%s" % (p[0], symb2freq[p[0]], p[1]))

测试文本:this is an example for huffman encoding

输出结果:

6 附注 2:实验数据及程序

附件为百度网盘链接。其中,对 "data/实验数据" 中的文件进行压缩可以得到 "data/压缩文件" 中的文件,对 "data/压缩文件" 中的文件进行解压可以得到 "data/解压效果" 中的文件https://pan.baidu.com/s/1dgIAnIS-hW4QNFUWlo6YJQ​pan.baidu.com

7 附注 3:LZW 算法简单实现

LZW 对流式数据具有较好的压缩性能,基本思想为进一步对连续字符进行压缩替换(如 "abcd832abcd841abcd818" ,若用 "e" 代替 "abcd8" ,则原文本可以转化为 "e32e41e18",从而实现压缩),下面是压缩算法代码的简单实现。在上述测试数据中压缩效果表现不理想(重复文本过少),删去 write_file 部分(即不初始化码表)则性能极优,但是只能输出配对字符。这里产生的问题可能是我对 LZW 的存储机制理解有误,也可能是写入码表的方式赘余太严重。以后有时间改改再回来填坑~

def get_keys(dict, value):

# 传入字典,值,获取对应的键

for k, v in dict.items():

if v == value:

return k

def check_binary(input_data):

# 检查文件编码,ASCII码超出255的字符替换为空格

output_data = ''

for word_index in range(len(input_data)):

if ord(input_data[word_index]) >= 256:

output_data += ' '

else:

output_data += input_data[word_index]

return output_data

def write_file(f,code_schedule,code):

import six

# 声明码表长度

code_schedule_len = '0' * (18 - len(bin(len(code_schedule)))) + bin(len(code_schedule))[2:]

f.write(six.int2byte(int(code_schedule_len[:8], 2))) # 声明码表长度 1

f.write(six.int2byte(int(code_schedule_len[8:], 2))) # 声明码表长度 2

# 声明符长(1 字节),以 1 字节储存字符,前255位不需要声明 字符 ==> ASCII 码

# 文本转码 2 字节

for letter in code:

letter_code = '0' * (18 - len(bin(letter))) + bin(letter)[2:]

f.write(six.int2byte(int(letter_code[:8], 2))) # 文本长度 1

f.write(six.int2byte(int(letter_code[8:], 2))) # 文本长度 2

# 使用 -1 作为分隔符

f.write(six.int2byte(ord('-')))

f.write(six.int2byte(ord('1')))

def compress(file_name):

import os

# 1.打开文件

f = open(file_name, 'r')

# 2.读取信息

file_data = check_binary(f.read())

f.close()

# 3.创建压缩文件

f = open(os.path.splitext(file_name)[0] + '.qlh', 'wb')

# 创建初始码表,储存 0-255 ASCII 码表信息

code_schedule = dict([[chr(i), i] for i in range(256)])

code_size = 255

code = []

prefix = '' # 前缀词

for postfix in file_data:

vocabulary = prefix + postfix # 前缀+后缀构成匹配词组

if vocabulary in code_schedule.keys():

prefix = vocabulary

else:

if len(code_schedule) <= 65535:

code.append(code_schedule[prefix])

code_size += 1

code_schedule[vocabulary] = code_size

prefix = postfix

else:

write_file(f, code_schedule, code)

# 初始化

code_schedule = dict([[chr(i), i] for i in range(256)])

code_size = 255

code = []

prefix = ''

if code != []:

write_file(f, code_schedule, code)

# 关闭文件

f.flush()

f.close()

print('压缩完成', file_name, '>>', os.path.splitext(file_name)[0] + '.qlh')

本人第 1 篇技术博客,仅是分享个人学习心得及相关代码。在算法实现上,如果有更好的优化方式,欢迎同我联系探讨。

作者:张柳彬

如有疑问,请联系QQ:965579168

转载请声明出处

你可能感兴趣的:(python实现信息论哈夫曼编码_哈夫曼压缩原理及python3实现(非面向对象结构))