一、运行环境:
Win7、python3.7
二、运行过程说明:
数据文件格式:txt、docx、csv、doc、pdf、xls等任意格式的文件。
输入格式:首先,在cmd中进入代码所在的目录;然后,输入python 0/1 被压缩文件名称 压缩后的文件名称 。(其中0代表压缩,1代表解压缩)
本次数据:
操作(以文件1为例):
压缩文件:python main.py 0 input_assgin01_01.txt out1.txt
解压缩文件:python main.py 1 input_assgin01_01.txt out1.txt
输出:
三、算法设计
3.1问题描述:
利用Huffman编码对文本文件进行压缩与解压。
输入:一个文本文件;
输出:压缩后的文件。
算法过程:
(1)统计文本文件中每个字符的使用频度;
(2)构造huffman编码;
(3)以二进制流形式压缩文件。
3.2解题思路:
(1)压缩
Step1:对需要被压缩的文件以二进制的方式读,然后以byte为单位读取里面的值,然后将其转换为int值,一共有[0-255]256个值,遍历文件中的所有byte,统计出现每个值的次数,作为我们后面叶节点里面额的weight(权重 );
Step2:使用上面的[0-255]每个值及其对应的权重,构造对应的Huffman编码树,然后分配[0-255]中每个值对应的Huffman编码;
Step3:在压缩文件的开始,将[0-255]及其对应的weight(权重)以二进制的方式保存到压缩文件当中,此举的目的是方便下次解压缩时根据原文件的[0-255]出现的频率去构造相应的huffman树,然后对压缩文件进行解压缩的操作;
Step4:遍历文件中的每一个byte,然后以Step2涨生成的Huffman编码,将该子街转换为Huffman编码对应的01组合;
Step5:将Step中的生成的01组合串,每8位为一个单位,将其转换为相应的byte,然后以二进制的方式写入到压缩文件当中,这样压缩就完成了。
(2)解压缩
Step1:以二进制方式读出压缩文件中保存的被压缩文件中[0-255]每个值出现频率;
Step2:使用上面[0-255]每个值及其对应的权重,构造对应的Huffman编码树,然后分配[0-255]中每一个值对应的Huffman编码;
Step3:接着以二进制的方式读取压缩文件的内容,独处二进制的01串;
Step4:使用Step3读取出来的01串在Step2中够早的Huffman编码树当中进行解压缩,进行解压锁的方法是:
1)最开始curr指针指向编码树的root;
2)循环取出01串的首位;
3)如果取出的值是0,curr指针指向左孩子;
4)如果取出的值是1,curr指针指向右孩子;
5)判断curr现在所指向的是否是叶节点:如果是,将该叶节点对应的字符写入到解压缩文件中,并且curr重置为编码树的root;如果不是,继续2,3,4的操作;
6)直到01串被取完,此时解压缩的工作完毕。
(3)对于如何借助Huffman树创建Huffman编码
Huffman编码将给字母分配编码。每个字母的编码的长度取决于在被压缩文件中对应字母的出现频率,称之为权重(weight)。每个字母的Huffman编码是从Huffman编码树的满二叉树(所有节点要么有左右两个子孩子,要么就没有子孩子)中得到的。Huffman编码树的每一个叶节点对应于一个字母,叶节点的权重 (weight)就是它对应的字母出现的频率。使用权重的目的是建立的Huffman编码树有最小外部路径权重WPL。
Step1:创建n个初始化的Huffman树,每个树只包含单一的叶节点,叶节点纪录对应的字母和该字母出现的频率(weight);
Step2:按照weight从小到大对其进行所有的Huffman树进行排序,取出其中weight最小的两棵树,构造一个新的Huffman树,新的Huffman树的weight等于两棵子树的weight之和,然后在原来的Huffman数组中删除这两棵树,并把新的Huffman树加入到原来的Huffman树数组当中;
Step3:反复上面的2中的操作,直到该数组当中只剩下一棵Huffman树,那么最后剩下来的那棵Huffman树就是我们构造好的Huffman编码树;
得到上面的Huffman编码树之后,就可以得到每个字符对应的编码了,方法是:从根节点找到该叶节点,如果向左子树前进一步,那么code + = '0',如果向右子树前进了一步,那么code+= '1',等到达该叶节点,code对应的内容,就是该叶节点对应字符的编码。
3.3数据结构的选择:
(1)节点虚类HuffNode。包含两个虚方法:1)获取节点的权重函数get_weight;2)判断此节点是否是叶节点的函数isleaf。
(2)树叶节点类LeafNode。包含方法: 1)初始化树节点的函数_init_;2)基类的方法isleaf;3)积累的方法get_weight;3)获取叶子节点的字符的值get_value。
(3)中间节点类IntlNode。包含方法:1)初始化中间节点的函数_init_;2)积累的方法isleaf;3)基类的方法get_weight;4)获取左孩子的函数get_left;5)获取右孩子的函数get_right。
(4)Huffman树类HuffTree。包含方法:1)初始化函数_init_;2)获取Huffman树的根节点的函数get_root;3)获取Huffman树的根节点的权重get_weight;4)便利Huffman_tree的函数traverse_huffman_tree。
(5)构造Huffman树的函数buildHuffmanTree。
四、算法详细描述:
4.1步骤:
Step1:实现Huffman编码树及其构造方法;
Step2:实现压缩文件函数compress();
Step3:实现解压缩文件函数decompress()。
4.2代码:
(1)Huffman编码树及其构造方法:
class HuffNode(object):
"""
定义一个HuffNode虚类,里面包含两个虚方法:
1. 获取节点的权重函数
2. 获取此节点是否是叶节点的函数
"""
def get_wieght(self):
raise NotImplementedError(
"The Abstract Node Class doesn't define 'get_wieght'")
def isleaf(self):
raise NotImplementedError(
"The Abstract Node Class doesn't define 'isleaf'")
class LeafNode(HuffNode):
"""
树叶节点类
"""
def __init__(self, value=0, freq=0,):
"""
初始化 树节点 需要初始化的对象参数有 :value及其出现的频率freq
"""
super(LeafNode, self).__init__()
# 节点的值
self.value = value
self.wieght = freq
def isleaf(self):
"""
基类的方法,返回True,代表是叶节点
"""
return True
def get_wieght(self):
"""
基类的方法,返回对象属性 weight,表示对象的权重
"""
return self.wieght
def get_value(self):
"""
获取叶子节点的 字符 的值
"""
return self.value
class IntlNode(HuffNode):
"""
中间节点类
"""
def __init__(self, left_child=None, right_child=None):
"""
初始化 中间节点 需要初始化的对象参数有 :left_child, right_chiled, weight
"""
super(IntlNode, self).__init__()
# 节点的值
self.wieght = left_child.get_wieght() + right_child.get_wieght()
# 节点的左右子节点
self.left_child = left_child
self.right_child = right_child
def isleaf(self):
"""
基类的方法,返回False,代表是中间节点
"""
return False
def get_wieght(self):
"""
基类的方法,返回对象属性 weight,表示对象的权重
"""
return self.wieght
def get_left(self):
"""
获取左孩子
"""
return self.left_child
def get_right(self):
"""
获取右孩子
"""
return self.right_child
class HuffTree(object):
"""
huffTree
"""
def __init__(self, flag, value =0, freq=0, left_tree=None, right_tree=None):
super(HuffTree, self).__init__()
if flag == 0:
self.root = LeafNode(value, freq)
else:
self.root = IntlNode(left_tree.get_root(), right_tree.get_root())
def get_root(self):
"""
获取huffman tree 的根节点
"""
return self.root
def get_wieght(self):
"""
获取这个huffman树的根节点的权重
"""
return self.root.get_wieght()
def traverse_huffman_tree(self, root, code, char_freq):
"""
利用递归的方法遍历huffman_tree,并且以此方式得到每个 字符 对应的huffman编码
保存在字典 char_freq中
"""
if root.isleaf():
char_freq[root.get_value()] = code
print ("it = %c and freq = %d code = %s")%(chr(root.get_value()),root.get_wieght(), code)
return None
else:
self.traverse_huffman_tree(root.get_left(), code+'0', char_freq)
self.traverse_huffman_tree(root.get_right(), code+'1', char_freq)
def buildHuffmanTree(list_hufftrees):
"""
构造huffman树
"""
while len(list_hufftrees) >1 :
# 1. 按照weight 对huffman树进行从小到大的排序
list_hufftrees.sort(key=lambda x: x.get_wieght())
# 2. 跳出weight 最小的两个huffman编码树
temp1 = list_hufftrees[0]
temp2 = list_hufftrees[1]
list_hufftrees = list_hufftrees[2:]
# 3. 构造一个新的huffman树
newed_hufftree = HuffTree(1, 0, 0, temp1, temp2)
# 4. 放入到数组当中
list_hufftrees.append(newed_hufftree)
# last. 数组中最后剩下来的那棵树,就是构造的Huffman编码树
return list_hufftrees[0]
(2)压缩文件函数:
def compress(inputfilename, outputfilename):
"""
压缩文件,参数有
inputfilename:被压缩的文件的地址和名字
outputfilename:压缩文件的存放地址和名字
"""
#1. 以二进制的方式打开文件
f = open(inputfilename,'rb')
filedata = f.read()
# 获取文件的字节总数
filesize = f.tell()
# 2. 统计 byte的取值[0-255] 的每个值出现的频率
# 保存在字典 char_freq中
char_freq = {}
for x in range(filesize):
# tem = six.byte2int(filedata[x]) #python2.7 version
tem = filedata[x] # python3.0 version
if tem in char_freq.keys():
char_freq[tem] = char_freq[tem] + 1
else:
char_freq[tem] = 1
# 输出统计结果
for tem in char_freq.keys():
print(tem,' : ',char_freq[tem])
# 3. 开始构造原始的huffman编码树 数组,用于构造Huffman编码树
list_hufftrees = []
for x in char_freq.keys():
# 使用 HuffTree的构造函数 定义一棵只包含一个叶节点的Huffman树
tem = HuffTree(0, x, char_freq[x], None, None)
# 将其添加到数组 list_hufftrees 当中
list_hufftrees.append(tem)
# 4. Step2中获取到的 每个值出现的频率的信息
# 4.1. 保存叶节点的个数
length = len(char_freq.keys())
output = open(outputfilename, 'wb')
# 一个int型的数有四个字节,所以将其分成四个字节写入到输出文件当中
a4 = length&255
length = length>>8
a3 = length&255
length = length>>8
a2 = length&255
length = length>>8
a1 = length&255
output.write(six.int2byte(a1))
output.write(six.int2byte(a2))
output.write(six.int2byte(a3))
output.write(six.int2byte(a4))
# 4.2 每个值 及其出现的频率的信息
# 遍历字典 char_freq
for x in char_freq.keys():
output.write(six.int2byte(x))
#
temp = char_freq[x]
# 同样出现的次数 是int型,分成四个字节写入到压缩文件当中
a4 = temp&255
temp = temp>>8
a3 = temp&255
temp = temp>>8
a2 = temp&255
temp = temp>>8
a1 = temp&255
output.write(six.int2byte(a1))
output.write(six.int2byte(a2))
output.write(six.int2byte(a3))
output.write(six.int2byte(a4))
# 5. 构造huffman编码树,并且获取到每个字符对应的 编码
tem = buildHuffmanTree(list_hufftrees)
tem.traverse_huffman_tree(tem.get_root(),'',char_freq)
# 6. 开始对文件进行压缩
code = ''
for i in range(filesize):
# key = six.byte2int(filedata[i]) #python2.7 version
key = filedata[i] #python3 version
code = code + char_freq[key]
out = 0
while len(code)>8:
for x in range(8):
out = out<<1
if code[x] == '1':
out = out|1
code = code[8:]
output.write(six.int2byte(out))
out = 0
# 处理剩下来的不满8位的code
output.write(six.int2byte(len(code)))
out = 0
for i in range(len(code)):
out = out<<1
if code[i]=='1':
out = out|1
for i in range(8-len(code)):
out = out<<1
# 把最后一位给写入到文件当中
output.write(six.int2byte(out))
# 6. 关闭输出文件,文件压缩完毕
output.close()
(3)解压缩文件函数:
def decompress(inputfilename, outputfilename):
"""
解压缩文件,参数有
inputfilename:压缩文件的地址和名字
outputfilename:解压缩文件的存放地址和名字
"""
# 读取文件
f = open(inputfilename,'rb')
filedata = f.read()
# 获取文件的字节总数
filesize = f.tell()
# 1. 读取压缩文件中保存的树的叶节点的个数
# 一下读取 4个 字节,代表一个int型的值
# python2.7 version
# a1 = six.byte2int(filedata[0])
# a2 = six.byte2int(filedata[1])
# a3 = six.byte2int(filedata[2])
# a4 = six.byte2int(filedata[3])
# python3 version
a1 = filedata[0]
a2 = filedata[1]
a3 = filedata[2]
a4 = filedata[3]
j = 0
j = j|a1
j = j<<8
j = j|a2
j = j<<8
j = j|a3
j = j<<8
j = j|a4
leaf_node_size = j
# 2. 读取压缩文件中保存的相信的原文件中 [0-255]出现的频率的信息
# 构造一个 字典 char_freq 一遍重建 Huffman编码树
char_freq = {}
for i in range(leaf_node_size):
# c = six.byte2int(filedata[4+i*5+0]) # python2.7 version
c = filedata[4+i*5+0] # python3 vesion
# 同样的,出现的频率是int型的,读区四个字节来读取到正确的数值
#python3
a1 = filedata[4+i*5+1]
a2 = filedata[4+i*5+2]
a3 = filedata[4+i*5+3]
a4 = filedata[4+i*5+4]
j = 0
j = j|a1
j = j<<8
j = j|a2
j = j<<8
j = j|a3
j = j<<8
j = j|a4
print(c, j)
char_freq[c] = j
# 3. 重建huffman 编码树,和压缩文件中建立Huffman编码树的方法一致
list_hufftrees = []
for x in char_freq.keys():
tem = HuffTree(0, x, char_freq[x], None, None)
list_hufftrees.append(tem)
tem = buildHuffmanTree(list_hufftrees)
tem.traverse_huffman_tree(tem.get_root(),'',char_freq)
# 4. 使用Step3中重建的huffman编码树,对压缩文件进行解压缩
output = open(outputfilename, 'wb')
code = ''
currnode = tem.get_root()
for x in range(leaf_node_size*5+4,filesize):
#python3
c = filedata[x]
for i in range(8):
if c&128:
code = code +'1'
else:
code = code + '0'
c = c<<1
while len(code) > 24:
if currnode.isleaf():
tem_byte = six.int2byte(currnode.get_value())
output.write(tem_byte)
currnode = tem.get_root()
if code[0] == '1':
currnode = currnode.get_right()
else:
currnode = currnode.get_left()
code = code[1:]
# 4.1 处理最后 24位
sub_code = code[-16:-8]
last_length = 0
for i in range(8):
last_length = last_length<<1
if sub_code[i] == '1':
last_length = last_length|1
code = code[:-16] + code[-8:-8 + last_length]
while len(code) > 0:
if currnode.isleaf():
tem_byte = six.int2byte(currnode.get_value())
output.write(tem_byte)
currnode = tem.get_root()
if code[0] == '1':
currnode = currnode.get_right()
else:
currnode = currnode.get_left()
code = code[1:]
if currnode.isleaf():
tem_byte = six.int2byte(currnode.get_value())
output.write(tem_byte)
currnode = tem.get_root()
# 4. 关闭文件,解压缩完毕
output.close()
五、算法分析
(1)生成效率(有效长度为n)
同步数据,时间复杂度为:O(n);
生成树,时间复杂度为:O(n-1);
生成压缩码,生成单个密码时间复杂度为:O(log(n));
故总密码生成效率为:O(n*log(n));
生成排序树,由于字符按频数排序,故字符码可视为基本随机。因此,易知平均时间复杂度为:O(n*log(n));
总效率为: n+n-1+2*O(n*log(n))=O(n*log(n))
(2)压缩解码效率(总长为m,生成频数链表有效长度为n的文本)
压缩效率:对单字的平均效率为二叉排序树效率,平均为O(log(n)),故总效率为O(m*log(n));
解码效率:对单字的解密效率同样为O(log(n)),故总效率为O(m*log(n));
总效率:由于m>>n本算法总效率为O(m*log(n)),远高于无二叉排序树加速时的期望效率O(m*n)。当m、n有至少一个较大时有较大提升,m、n都较大时有机可观的效率提升,而仅多占用2个额外空间,优势明显。
(3)加速策略:本算法通过对哈夫曼树底层重排,以2个额外空间为代价,借助二叉排序树极大的提升了压缩效率,同时维持解码效率不变。实现了加速以及性能提升。