闽南师范大学
《数据结构》课程设计报告
课程设计题目:
Huffman
编码压缩和解压缩文件工具设计
摘 要
Huffman 编码将给字母分配编码。每个字母的编码的长度取决于
在被压缩文件中对应字母的出现频率,我们称之为
权重
。每个字母的
Huffman 编码是从称为
Huffman 编码树
的
满二叉树
(所有节点要么有
左右两个子孩子,要么就没有子孩子)中得到的。
Huffman 编码树
的
每一个叶节点对应于一个字母,叶节点的权重 (weight)就是它对
应的字母出现的频率。使用权重的目的是建立的
Huffman 编码树
有
最
小外部路径权重
。 通过对被压缩文件建立Huffman树得到对应字符的Huffman编码
再对文件进行转码再以 16 进制的方式写入解压后的文件中达到压缩
的目的,通过解压后的文件以及 Huffman 编码文件,反过来建立
Huffman 树得到 Huffman 编码进行解压。
关键词
:
Huffman
,树,
16
进制,压缩,解压
目录
摘要 .............................................................................................. 2
1 问题背景 ................................................................................. 4
2 设计内容 ................................................................................. 4
2.1 问题描述 ..................................................................... 4
2.2 需求分析 ..................................................................... 4
3 整体设计 ................................................................................. 5
3.1 系统整体功能图 ....................................................... 5
3.2 Huffman 树编码解码大致流程 ............................. 5
3.3 Huffman 树的建立 .................................................... 6
4 详细设计 ................................................................................. 7
4.1 压缩的设计 ........................................................................... 7
4.2 解压的设计 ........................................................................... 12
5 设计感受 ................................................................................... 13
附录 ................................................................................................ 13
-3-
1 问题背景
许多文件过于庞大占用过多的存储空间,通过压缩来使文件变小,
需要使用时再将文件解压使用,大大的缓解了存储空间不足的问题,
减少数据大小以节省保存空间和传输的时间,有助于释放多余的存储
空间。日常生活中也难免会遇到由于文件体积过大而无法发送的问题,
压缩文件则可以大大提高我们的工作效率。
2 设计内容
2.1
问题描述
设计该系统主要是为了解决文件过大的问题,提供压缩的工具以解决
问题,需要使用时再通过这个工具进行解压。
2.2
需求分析
1
、输入一个待压缩的文本文件路径名,统计文本文件中各字符的个
数作为权值,生成哈夫曼树,生成编码文件;
2
、输入一个待解压的压缩文件路径名称,并利用该压缩文件得到相
应的哈夫曼树,再将编码序列译码得到解码文本文件;
3
、在压缩时输出哈夫曼树中度为
2
的节点个数;
-4-
3 整体设计
3.1
系统整体功能图
3.2Huffman
树编码解码大致流程
编码:
1.
读入待编码源文件;
2.
第一次扫描:统计文件中各字符的出现频率;
3.
构建
Huffman
树;
4.
遍历
Huffman
树,获得各字符的码表;
5.
第二次扫描:对源文件的每个字符编码;
-5-
解码:
1.
读入编码后的文件;
2.
获取
Huffman
树;
3.
从根节点开始依据从文件中读取的
Huffman
码值沿树行走,至叶结点时完成一个字符的解码,
并返回根节点;
4.
重复上述过程,完成所有字符的解码;
3.3 Huffman
树的建立
1.
建立
Huffman
树的节点,一个节点有
lchild
,
rchild
,
parent
,
weight
,
flag
等参数。
class HTNode:
def __init__(self,d="",w=None):
self.data=d
self.weight=w
self.parent=-1
self.lchild=-1
self.rchild=-1
self.flag=True
2.
统计文本字符出现的次数作为每个字符的权值,每次将字符权值最小的取出
建立
Huffman
树然后再添加到原数组当中,直到所有字符都取出,
Huffman
树就
建立好。
-6-
4
详细设计
4.1
压缩的设计
1.
统计文本字符出现的次数作为每个字符的权值,每次将字符权值最小的取出
建立
Huffman
树然后再添加到原数组当中,直到所有字符都取出,
Huffman
树就
建立好。
def weight(self,txt):
global ht,n,D,W
D=[]
W=[]
j=0
Str =str(txt)
resoult = {}
for i in Str:
resoult[i] = Str.count(i)
for key in resoult:
D.append(key)
W.append(resoult[key])
j+=1
n=j
def Creat(self):
global ht,n,D,W,k1
heap=[]
ht=[None]*(2*n-1)
for i in range(n):
heapq.heappush(heap,[W[i],i])
ht[i]=HTNode(D[i],W[i])
for k in range(n,2*n-1):
p1=heapq.heappop(heap)
p2=heapq.heappop(heap)
ht[k]=HTNode()
ht[k].weight=ht[p1[1]].weight+ht[p2[1]].weight
ht[p1[1]].parent=k
ht[k].lchild=p1[1]
ht[p1[1]].flag=True
ht[p2[1]].parent=k
ht[k].rchild=p2[1]
ht[p2[1]].flag=False
heapq.heappush(heap,[ht[k].weight,k])
-7-
2.
先定义一个
count=0
,遍历
Huffman
树所有节点如果一个节点的左孩子和右孩
子都不为
None
,
count
则加
1
。最后遍历完后输出
count
。
k1=k
count=0
for y in range(int(1+k1/2),k1):
if ht[y].lchild!=None:
if ht[y].rchild!=None:
print(ht[y].weight)
count+=1
print("
度为
2
的节点有:
")
print(count)
3. Huffman
树建立好了之后,将左子树分支编码为
0
,右子树分支编码为
1
,每
个字符都得到固定的
01
编码。经过这个编码设置之后我们可以发现,出现频率
越高的字符越会在上层,这样它的编码越短;出现频率越低的字符越会在下层,
编码越短。经过这样的设计,最终整个文本存储空间才会最大化的缩减。
def CreateH(self):
global n,ht,hcd
hcd=[]
for i in range(n):
conum=[]
j=i
while ht[j].parent!=-1:
if ht[j].flag:
conum.append('0')
else:
conum.append('1')
j=ht[j].parent
conum.reverse()
hcd.append(''.join(conum))
print(hcd)
4.
把文件内容对应编码表对应的
01
编码得到对应的一整个文本的
01
编码,将
01
编码
8
个为一个单位转化成相应的
byte
。把
Huffman
树的信息以二进制的方
式写入文件,把转化成的
byte
以二进制的方式写入文件,压缩完成。
def zuhe(self,txt,wj):
global kd
by=''
de=[]
-8-
-9-
num=0
file=open(wj+'yasuo.txt',"wb")
global hcd,D,W
for i in txt:
for j in range(len(D)):
if i==D[j]:
by=by+hcd[j]
ti=len(by)
if ti%8!=0:
t1=ti%8
by=by+'0'*(8-t1)
kd=1
head=max(W)
if head> 255:
kd = 2
if head > 65535:
kd = 3
if head > 16777215:
kd = 4
while 1:
temp=hex(int(by[num:num+8],2))
num=num+8
de.append(temp)
if num==len(by):
break
file.write(int.to_bytes(len(D) ,2 ,byteorder = 'big'))
#
写出结点数量
file.write(int.to_bytes(kd,1 ,byteorder = 'big'))
for x in range(len(D)):
file.write(D[x].encode())
file.write(int.to_bytes(W[x],kd,byteorder = 'big'))
for li in de:
s = struct.pack('B',int(li,16))
file.write(s)
-10-
5.
压缩成功占用空间减小
4.2
解压的设计
1.
根据文件头的信息得到
Huffman
树的基本信息,重建
Huffman
树
class jieya:
def Creat2(self,wj):
global D1,W1,lis,j,n,ht
D1=[]
W1=[]
j=1
f=open(wj,"rb")
f.seek(0,2)
f.seek(0)
n = int.from_bytes(f.read(2), byteorder = 'big')
#
取出结点数量
bit_width = int.from_bytes(f.read(1), byteorder = 'big')
i=0
while i < n:
D1.append(f.read(1).decode())
W1.append(int.from_bytes(f.read(bit_width), byteorder = 'big'))
i+=1
print(D1)
print(W1)
heap=[]
ht=[None]*(2*n-1)
for i in range(n):
heapq.heappush(heap,[W1[i],i])
ht[i]=HTNode(D1[i],W1[i])
for k in range(n,2*n-1):
p1=heapq.heappop(heap)
p2=heapq.heappop(heap)
ht[k]=HTNode()
ht[k].weight=ht[p1[1]].weight+ht[p2[1]].weight
ht[p1[1]].parent=k
ht[k].lchild=p1[1]
ht[p1[1]].flag=True
ht[p2[1]].parent=k
ht[k].rchild=p2[1]
ht[p2[1]].flag=False
heapq.heappush(heap,[ht[k].weight,k])
3.
重新对每个字符编码,将左子树分支编码为
0
,右子树分支编码为
1
,得到每
个字符的
01
编码。
hcd1=[]
for i in range(n):
code=[]
j=i
while ht[j].parent!=-1:
if ht[j].flag:
code.append('0')
else:
code.append('1')
j=ht[j].parent
code.reverse()
hcd1.append(''.join(code))
4.
文件内容以二进制读出,对照编码表,得到编码对应的原字符,直到所有的
huffman
编码都比较完,完成解压缩还原文件原内容。
Q=''
for i in f.read():
i='{:0>8}'.format(str(bin(i))[2:])
Q=Q+i
code = ""
ans = ""
-11-
for ch in Q:
code += ch
for j in range(len(hcd1)):
if code==hcd1[j]:
code = ""
ans +=str(D1[j])
break
f=open(wj+'jieya.txt',"wb")
f.write(ans.encode("utf-8"))
5.
解压成功还原成原文件。
5.
设计感受
通过
Huffman
树编码得到的文件占用空间大大减少,对于压缩文件十分适用,
通过这份课设我对于
Huffman
的理解更加深刻,对
Huffman
树的构造使用更加
了解,更加能理解
Huffman
编码会成为一种非常高效,压缩率高的编码方式的原
因。对
Huffman
树的构造使用更加了解,这是一个非常有实际效益,非常有意义
的一个压缩和解压的工具,代码上还有很多地方可以进行完善,但是这份课设真
的让我收益匪浅。感谢老师提出的更多的要求,让我克服很多困难完成这份课设。
附录 代码
import heapq
import struct
import sys
sys.setrecursionlimit(1000000)
-12-
class HTNode:
def __init__(self,d="",w=None):
self.data=d
self.weight=w
self.parent=-1
self.lchild=-1
self.rchild=-1
self.flag=True
class node:
def weight(self,txt):
global ht,n,D,W
D=[]
W=[]
j=0
Str =str(txt)
resoult = {}
for i in Str:
resoult[i] = Str.count(i)
for key in resoult:
D.append(key)
W.append(resoult[key])
j+=1
n=j
def Creat(self):
global ht,n,D,W,k1
heap=[]
ht=[None]*(2*n-1)
for i in range(n):
heapq.heappush(heap,[W[i],i])
ht[i]=HTNode(D[i],W[i])
for k in range(n,2*n-1):
p1=heapq.heappop(heap)
p2=heapq.heappop(heap)
ht[k]=HTNode()
ht[k].weight=ht[p1[1]].weight+ht[p2[1]].weight
ht[p1[1]].parent=k
ht[k].lchild=p1[1]
ht[p1[1]].flag=True
ht[p2[1]].parent=k
ht[k].rchild=p2[1]
ht[p2[1]].flag=False
-13-
heapq.heappush(heap,[ht[k].weight,k])
k1=k
e=0
for y in range(int(1+k1/2),k1):
if ht[y].lchild!=None:
if ht[y].rchild!=None:
print(ht[y].weight)
e+=1
print("
度为
2
的节点有:
")
print(e)
def CreateH(self):
global n,ht,hcd
hcd=[]
for i in range(n):
conum=[]
j=i
while ht[j].parent!=-1:
if ht[j].flag:
conum.append('0')
else:
conum.append('1')
j=ht[j].parent
conum.reverse()
hcd.append(''.join(conum))
def zuhe(self,txt,wj):
global kd
by=''
de=[]
num=0
file=open(wj+'yasuo.txt',"wb")
global hcd,D,W
for i in txt:
for j in range(len(D)):
if i==D[j]:
by=by+hcd[j]
ti=len(by)
if ti%8!=0:
t1=ti%8
by=by+'0'*(8-t1)
kd=1
-14-
-15-
head=max(W)
if head> 255:
kd = 2
if head > 65535:
kd = 3
if head > 16777215:
kd = 4
while 1:
temp=hex(int(by[num:num+8],2))
num=num+8
de.append(temp)
if num==len(by):
break
file.write(int.to_bytes(len(D) ,2 ,byteorder = 'big'))
#
写出结点数量
file.write(int.to_bytes(kd,1 ,byteorder = 'big'))
for x in range(len(D)):
file.write(D[x].encode())
file.write(int.to_bytes(W[x],kd,byteorder = 'big'))
for li in de:
s = struct.pack('B',int(li,16))
file.write(s)
def ya(self,txt,wj):
self.weight(txt)
self.Creat()
self.CreateH()
self.zuhe(txt,wj)
print("
压缩成功!
")
class jieya:
def Creat2(self,wj):
global D1,W1,lis,j,n,ht
D1=[]
W1=[]
j=1
f=open(wj,"rb")
f.seek(0,2)
f.seek(0)
n = int.from_bytes(f.read(2), byteorder = 'big')
#
取出结点数量
bit_width = int.from_bytes(f.read(1), byteorder = 'big')
i=0
while i < n:
D1.append(f.read(1).decode())
W1.append(int.from_bytes(f.read(bit_width), byteorder = 'big'))
i+=1
print(D1)
print(W1)
heap=[]
ht=[None]*(2*n-1)
for i in range(n):
heapq.heappush(heap,[W1[i],i])
ht[i]=HTNode(D1[i],W1[i])
for k in range(n,2*n-1):
p1=heapq.heappop(heap)
p2=heapq.heappop(heap)
ht[k]=HTNode()
ht[k].weight=ht[p1[1]].weight+ht[p2[1]].weight
ht[p1[1]].parent=k
ht[k].lchild=p1[1]
ht[p1[1]].flag=True
ht[p2[1]].parent=k
ht[k].rchild=p2[1]
ht[p2[1]].flag=False
heapq.heappush(heap,[ht[k].weight,k])
global hcd1
hcd1=[]
for i in range(n):
code=[]
j=i
while ht[j].parent!=-1:
if ht[j].flag:
code.append('0')
else:
code.append('1')
j=ht[j].parent
code.reverse()
hcd1.append(''.join(code))
Q=''
for i in f.read():
i='{:0>8}'.format(str(bin(i))[2:])
Q=Q+i
code = ""
ans = ""
for ch in Q:
code += ch
for j in range(len(hcd1)):
if code==hcd1[j]:
-16-
code = ""
ans +=str(D1[j])
break
f=open(wj+'jieya.txt',"wb")
f.write(ans.encode("utf-8"))
print("
欢迎使用!
")
print("1.
压缩
")
print("2.
解压
")
print("0.
退出
")
b=int(input("
请选择
:"))
while 1:
if b==1:
wj=input("
请输入要压缩的文件路径
:").strip()
t=open(wj,'r',encoding='UTF-8')
txt=t.read()
t.close()
a=node()
a.ya(txt,wj)
b=int(input("
请选择
:"))
if b==2:
wj=input("
请输入要解压的文件路径
:").strip()
f=open(wj,"rb")
file=f.read()
a=jieya()
a.Creat2(wj)
f.close()
print("
解压成功
!")
b=int(input("
请选择
:"))
if b==0:
break
-17