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_theoremen.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-hW4QNFUWlo6YJQpan.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
转载请声明出处