基本介绍
要想了解赫夫曼编码,先要了解什么是赫夫曼树。
基本介绍
重要概念
赫夫曼树创建思路
给你一个数列 {13, 7, 8, 3, 29, 6, 1},要求转成一颗赫夫曼树.
{13, 7, 8, 3, 29, 6, 1}
构成赫夫曼树的步骤:
(1)先定义好赫夫曼树的最小单元node,node具有三个属性:值、左节点、右节点;其次为了能够进行排序和输出,node对象中应该还具有两个函数compareTo(node) 和 preOrder()。前者用来比较自身节点的值和其他节点的值,后者可以按照根左右的顺序即前序遍历来输出节点。
class Node(object):
def __init__(self,value):
self.value=value
self.left=None
self.right=None
def compareTo(self,node):
#如果自身大于为正,否则为负
return self.value-node.value
def preOrder(self):
print(self.value)
if self.left!=None:
self.left.preOrder()
if self.right!=None:
self.right.preOrder()
(2)根据前面所讲的赫夫曼树的构造方法,我们还需要一个能够根据节点的值对节点进行排序的算法,这里我们使用最简单冒泡算法来进行排序。
def BubleSort2(arr):
temp = 0
flag = False
#总共要进行数组长度减1的大循环
for i in range(len(arr)-1):
flag = False
#每次内循环都会冒出一个最小或者最大值,故下次循环比较次数-1
for j in range(len(arr)-1-i):
if arr[j].compareTo(arr[j+1])>0:
flag = True
t=arr[j]
arr[j]=arr[j+1]
arr[j+1]=t
#如果没有一次进入交换语句,说明后面的数字都已经有序
if flag == False:
break
(3)接下来只需要严格按照前面所说的步骤就可以构造一个赫夫曼树了。构造函数createHuffmanTree(arr),其中arr是赫夫曼树节点中的值的集合。
nodes = []
for i in range(len(arr)):
nodes.append(Node(arr[i]))
while len(nodes)>1:
#排序
BubleSort2(nodes)
'''
测试是否排序成功
for i in range(len(nodes)):
print('node=%d'%(nodes[i].value))
'''
#取出最小的两个构成二叉树
left = nodes[0]
right = nodes[1]
#构成新的二叉树
parent = Node(left.value+right.value)
parent.left=left
parent.right=right
while len(nodes)>1:
'''
省略
'''
#在nodes里面删除使用过的元素
nodes.remove(left)
nodes.remove(right)
#加入parent
nodes.append(parent)
for i in range(len(nodes)):
print('node=%d'%(nodes[i].value))
class Node(object):
def __init__(self,value):
self.value=value
self.left=None
self.right=None
def compareTo(self,node):
#如果自身大于为正,否则为负
return self.value-node.value
def preOrder(self):
print(self.value)
if self.left!=None:
self.left.preOrder()
if self.right!=None:
self.right.preOrder()
arr = [13,7,8,3,29,6,1]
def BubleSort2(arr):
temp = 0
flag = False
#总共要进行数组长度减1的大循环
for i in range(len(arr)-1):
flag = False
#每次内循环都会冒出一个最小或者最大值,故下次循环比较次数-1
for j in range(len(arr)-1-i):
if arr[j].compareTo(arr[j+1])>0:
flag = True
t=arr[j]
arr[j]=arr[j+1]
arr[j+1]=t
#如果没有一次进入交换语句,说明后面的数字都已经有序
if flag == False:
break
def createHuffmanTree(arr):
nodes = []
for i in range(len(arr)):
nodes.append(Node(arr[i]))
while len(nodes)>1:
#排序
BubleSort2(nodes)
'''
for i in range(len(nodes)):
print('node=%d'%(nodes[i].value))
'''
#取出最小的两个构成二叉树
left = nodes[0]
right = nodes[1]
#构成新的二叉树
parent = Node(left.value+right.value)
parent.left=left
parent.right=right
#在nodes里面删除使用过的元素
nodes.remove(left)
nodes.remove(right)
#加入parent
nodes.append(parent)
for i in range(len(nodes)):
print('node=%d'%(nodes[i].value))
return nodes[0]
node = createHuffmanTree(arr)
node.preOrder()
假设原始数据为 content = “哈哈哈,我欲乘风归去,归去”。
把原始数据中的每个字符当作值构造一个赫夫曼树,得到的对应的赫夫曼编码为:
{‘风’: ‘111’, ‘。’: ‘110’, ‘哈’: ‘10’, ‘归’: ‘011’, ‘去’: ‘010’, ‘,’: ‘0011’, ‘我’: ‘0010’, ‘欲’: ‘0001’, ‘乘’: ‘0000’}
这个编码并不是唯一的,但是你能根据每个编码对应了一个确定的位置,既不会出现二义性。并且你会发现使用频率越高的元素它的编码越简短。
要实现这个目标很简单,你只需要在原先的node对象中添加一个getCodes(node,code)方法,它的作用是根据目前的赫夫曼编码得到下一层节点的赫夫曼编码。运用递归的思想,假设你想知道节点<哈>的赫夫曼编码,你可以说这个编码等于节点<哈>的根节点赫夫曼编码加上到节点<哈>的编码,向左编码为1,向右编码为0。
def getCodes(self,node,code):
self.pathStr=self.pathStr+code
#print('到达'+str(node.data)+':'+str(node.weight))
if node!=None:
if node.data==None:#不是叶子节点
#左递归
self.getCodes(node.left,"1")
#右递归
self.getCodes(node.right,"0")
else:#叶子结点
self.dic[str(node.data)]=self.pathStr
self.pathStr=self.pathStr[0:-1]
return self.dic
这个步骤如果不考虑细节的话,其实两行代码就完成了。不就是根据原先得到值和赫夫曼编码的字典连接起来就好了吗?
#先将数据转换成对应的字符串
StrHuffman = ""
for i in range(len(content)):
StrHuffman += dic[content[i]]
print('二进制的赫夫曼编码:')
print(StrHuffman)
但是实际的二进制数据都是8的倍数,也就是说如果得到的二进制编码不是刚好为8的倍数时,需要在数据尾部补0。然后根据二进制数转换为对应的十进制数,达到数据压缩的效果。所以最终的代码如下:
#根据得到的赫夫曼编码对数据进行压缩
def Zip(dic,byte):
#先将数据转换成对应的字符串
StrHuffman = ""
for i in range(len(content)):
StrHuffman += dic[content[i]]
print('二进制的赫夫曼编码:')
print(StrHuffman)
lenth = int((len(StrHuffman)+7)/8)
byte = [0 for i in range(lenth)]#存储字节
index=0#记录第几个字节
for i in range(0,len(StrHuffman),8):
#print(i)
if i+8>len(StrHuffman):
strByte = StrHuffman[i:]
else:
strByte = StrHuffman[i:i+8]
byte[index]=int(strByte,2)
#print('-------'+str(strByte))
index+=1
return byte
想要把压缩后的十进制数据转换成原始数据,需要先得到对应的二进制编码。
#先得到Bytes对应的二进制编码
bitStr = ''
result = ''
for i in range(len(Bytes)):
byte = Bytes[i]
flag = not(i==len(Bytes)-1)
bitStr += zipToBit(flag,byte)
print(bitStr)
之后要把赫夫曼编码字典进行反转,也就是键值对的位置互换位置。在python中可以使用dict(zip(dic.values(),dic.keys()))这个方法来进行反转。
dic = dict( zip(dic.values(),dic.keys()))
接下来的工作就简单,根据二进制编码查找对应的字典值即可。解码完整代码如下:
#将二进制编码转换成原始数据
def bitToData(Bytes,dic):
#先得到Bytes对应的二进制编码
bitStr = ''
result = ''
for i in range(len(Bytes)):
byte = Bytes[i]
flag = not(i==len(Bytes)-1)
bitStr += zipToBit(flag,byte)
print(bitStr)
#print(bitStr[:3])
#print(bitStr[3:])
#将二进制编码按照赫夫曼编码进行阶码
#要反向查询,需要将字典反转
dic = dict( zip(dic.values(),dic.keys()))
#print(dic)
i=1
while len(bitStr)>0:
#print(bitStr[:i])
if bitStr[:i] in dic.keys():
result+=dic[bitStr[:i]]
bitStr=bitStr[i:]
length=len(bitStr)
i=0
i+=1
print(result)
return result