python霍夫曼树

python的数据结构python的知识网络是非常重要的.而树的算法又是在数据结构里占有很重要的比例,昨天花了点时间写出了一个霍夫曼树算法,并且输出霍夫曼编码,因为网上的例子讲的太复杂了,所以我自己用这篇博客来告诉大家一个简单的办法.一开始我是用java写的,可是老师又让我用python写,所以我会重新再用java写一篇关于霍夫曼树的博客.让研究java和python的同学都能看懂,当然C++我也能写一个算法,就是没那么多时间.其实我的算法还是可以更新为霍夫曼森林的,就是K个叉的霍夫曼树,因为昨天就花了一点点时间搞这个输入任意字符串转化为霍夫曼树并且输出霍夫曼编码,所以就没搞这个K叉了。本人才疏学浅,面对各位大佬,大牛,如果我有什么错误,希望大家在我的博客下方留言。

看这一篇文章,你需要准备python的基础知识,类和对象的用法,递归还有数据结构基础.有问题也可以在我的博客下方留言

什么是霍夫曼树:

 给定n个权值作为n个叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。

哈夫曼树(霍夫曼树)又称为最优树.
1、路径和路径长度
在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1。
2、结点的权及带权路径长度
若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积
3、树的带权路径长度
树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL
假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,则哈夫曼树的构造规则为:
(1) 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);
(2) 在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;
(3)从森林中删除选取的两棵树,并将新树加入森林;
(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树
 
霍夫曼编码:
在数据通信中,需要将传送的文字转换成二进制的字符串,用0,1码的不同排列来表示字符。例如,需传送的报文为“AFTER DATA EAR ARE ART AREA”,这里用到的字符集为“A,E,R,T,F,D”,各字母出现的次数为{8,4,5,3,1,1}。现要求为这些字母设计编码。要区别6个字母,最简单的二进制编码方式是等长编码,固定采用3位二进制,可分别用000、001、010、011、100、101对“A,E,R,T,F,D”进行编码发送,当对方接收报文时再按照三位一分进行译码。显然编码的长度取决报文中不同字符的个数。若报文中可能出现26个不同字符,则固定编码长度为5。然而,传送报文时总是希望总长度尽可能短。在实际应用中,各个字符的出现频度或使用次数是不相同的,如A、B、C的使用频率远远高于X、Y、Z,自然会想到设计编码时,让使用频率高的用短码,使用频率低的用长码,以优化整个报文编码。
为使不等长编码为前缀编码(即要求一个字符的编码不能是另一个字符编码的前缀),可用字符集中的每个字符作为叶子结点生成一棵编码二叉树,为了获得传送报文的最短长度,可将每个字符的出现频率作为字符结点的权值赋予该结点上,显然字使用频率越小权值越小,权值越小叶子就越靠下,于是频率小编码长,频率高编码短,这样就保证了此树的最小带权路径长度效果上就是传送报文的最短长度。因此,求传送报文的最短长度问题转化为求由字符集中的所有字符作为叶子结点,由字符出现频率作为其权值所产生的哈夫曼树的问题。利用哈夫曼树来设计二进制的前缀编码,既满足前缀编码的条件,又保证报文编码总长最短。
哈夫曼静态编码:它对需要编码的数据进行两遍扫描:第一遍统计原数据中各字符出现的频率,利用得到的频率值创建哈夫曼树,并必须把树的信息保存起来,即把字符0-255(2^8=256)的频率值以2-4BYTES的长度顺序存储起来,(用4Bytes的长度存储频率值,频率值的表示范围为0--2^32-1,这已足够表示大文件中字符出现的频率了)以便解压时创建同样的哈夫曼树进行解压;第二遍则根据第一遍扫描得到的哈夫曼树进行编码,并把编码后得到的码字存储起来。
哈夫曼动态编码:动态哈夫曼编码使用一棵动态变化的哈夫曼树,对第t+1个字符的编码是根据原始数据中前t个字符得到的哈夫曼树来进行的,编码和解码使用相同的初始哈夫曼树,每处理完一个字符,编码和解码使用相同的方法修改哈夫曼树,所以没有必要为解码而保存哈夫曼树的信息。编码和解码一个字符所需的时间与该字符的编码长度成正比,所以动态哈夫曼编码可实时进行。
2、哈夫曼译码
在通信中,若将字符用哈夫曼编码形式发送出去,对方接收到编码后,将编码还原成字符的过程,称为哈夫曼译码。
概念知识希望大家先了解一下
代码从这里开始
class Node():
data=0
left=None
right=None
father=None
def __init__(self,data,left,right):
self.data=data
self.left=left
self.right=right
这里定义了一个Node类
声明了左右结点和父节点,父节点是用来输出霍夫曼编码用的。
初始化构造函数,给数据和左右结点赋值,父节点不在这初始化,self的话和this关键字差不多
root=None
声明根节点
def  zhuanhuan( arr,index,n):
if index>=len(arr):
return None
else:
left=Node(arr[index],None,None)
num1=arr[index]+n.data
node=Node(num1,left,n)
left.father=node
n.father=node
if index==0:
index+=1
if index==len(arr)-1:
global root
root=node
zhuanhuan(arr,index+1,node)
这是一个转换函数,把数组转化为霍夫曼树,传入的形参 arr是数组,index是下标,n是结点(定义结点是为了避免几个结点断节
如果index大于数组的长度,那么就退出
如果小于就继续构建霍夫曼树
根据霍夫曼树的构建原理,传入一个数组的值,把二者和的结点的传入构造函数,递归,
  left=Node(arr[index],None,None)
        num1=arr[index]+n.data
        node=Node(num1,left,n)
中间就是利用构造函数初始化的代码
  left.father=node
   n.father=node
把左右结点的父节点指向node
因为数组是从0开始到len(arr)-1
所以当index满足
 if index==len(arr)-1:时
就定义一个全局root作为根节点
   global  root
            root=node
然后递归zhuanhuan(arr,index+1,node)
flag=False
v=[]
声明一个标志,定义一个bool型数组
for i in range(100):
v.append(False)
初始化100个False
输出霍夫曼编码函数
def show(n):
global flag 全局标志
global arm 记录个数的数组
global ard 记录字符串不同的字符
if n!=None: 如果结点不为空
if (n.left==None and n.right==None)or flag==True://如果是叶子结点或者遍历父节点
if v[n.data]!=True and(n.left==None and n.right==None)://如果叶子结点没有输出过
for i in range(len(arm))://把数字转化为原来的字符码比如a出现了4次那么在霍夫曼树记录的就是4,这个循环就是把4再转化为a
if n.data==arm[i]:
print ard[i],":",
break
if n.father==None://如果父节点为空 那么就代表遍历可以结束了
flag=False;
return
else://如果父节点不为空就继续循环遍历
flag=True标志设为
show(n.father)//遍历父节点
if n==n.father.right:如果是右子树就输出1
print 1," ",
else://如果是左子树就输出0
print 0," ",
else:如果不是叶子结点就递归遍历霍夫曼树
show(n.left)
show(n.right)

print "请输入一段字符串:"
str=raw_input()
arr=[]#字符数组
arr=str
ard=[]//记录有几个不同字符的数组
for i in range(len(arr))://把不同的字符放入ard字符数组
flag=True
for j in range(len(ard)):
if ard[j]==arr[i]:
flag=False;
break
if flag!=False:
ard.append(arr[i])
arm=[]//权值数组
for i in range(len(ard))://更新权值
arm.append(0)
for i in range(len(arr)):
for j in range(len(ard)):
if arr[i]==ard[j]://每出现一次那个字符的权值层数组就自增1
arm[j]+=1;
break
for i in range(len(ard)-1)://插入排序
temp=ard[i+1]//记录下一个数
temp1=arm[i+1]
j=i
while j>-1 and temp1<arm[i]//把东西往后移动,如果没找到比他小的或者找到了比他小的就退出循环
arm[j+1]=arm[j]
ard[j+1]=ard[j]
j-=1
arm[j+1]=temp1
ard[j+1]=temp
print
print "每一个元素的最小编码为:",
zhuanhuan(arm,0,Node(arm[1],None,None))//把排序好的东西放入构建霍夫曼树
show(root)//递归输出霍夫曼编码
#生命本没有意义,我们的存在就是赋予它意义

posted @ 2018-02-03 11:58  自兴人工智能  阅读(587)  评论(0)    收藏  举报