包含功能:
1.哈夫曼编码文本
2.matplotlib包画出哈弗曼树
3.tkinter实现可视化操作页面
4.简单的socket实现编码传输功能
部分功能展示:
用matplotlib可以进行局部放大,右下角节点的显示效果同样很好!
PS:
1. 此项目使用的模板只包含大写字母与空格,如需要解码其他内容,
请自行添加模板列表。
2. py版本为3.7,matplotlib为3.3.3
3. 监听代码按钮按下去之后点别的地方代码会直接去世,
应该加一个线程或者用别的方法可以解决。
4. 个人觉得用matplotlib画二叉树图像的算法挺值得看的,网上有关内容并不多。
(通过调整算法中的参数理论上可以避免二叉树随深度增加而造成间距太小而重合)
5. 本人为初学者,代码写的有很多不规范的地方,敬请指正!
6.转载请标明出处。
源码:
1.编码功能实现及画图功能实现
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
class HTcoder():
def __init__(self):
# 最终模板
self.CharList = [' ', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
self.list0 = [186, 64, 13, 22, 32, 103, 21, 15, 47, 57, 1, 5, 32, 20, 57, 63,
15, 2, 48, 51, 80, 23, 8, 18, 1, 16, 1]
self.length = len(self.list0) # 获得初始数组长度
# 初始化数组
self.list = self.initData(self.length)
# 进行计算,获得哈夫曼表
self.list = self.HToperate(self.list, self.length)
# 获得编码数组
self.CodeList = self.CalCode(self.list, self.length)
# 获得编码表
self.CODELIST = self.GetEncodeList(self.CharList, self.CodeList)
self.deepth = self.findTreedeepth(self.CodeList)
def findMin(self,list):
min = []
temp = 0
tempindex = 0
flag = 0
# 从未使用过的元素中找出第一个元素的位置,以便下一个找最小值循环的初始比较
for index, i in enumerate(list):
# 跳过第一次循环.之后的同理
if flag == 0:
flag = flag + 1
continue
if i[4] == 0:
temp = i[0]
tempindex = index
break
# 重置跳过第一次循环的标志
flag = 0
#找到最小值,记录位置和值
for index, i in enumerate(list):
if flag == 0:
flag = flag + 1
continue
if i[4] == 0 and i[0] != 0:
if temp > i[0]: #可能会出现权值一样找不到最小值的问题
temp = i[0]
tempindex = index
min.append(temp)
min.append(tempindex)
list[tempindex][4] = 1
return min, list
def findIndex(self,list,element):
for index, item in enumerate(list):
if item == element:
return index
def CalCode (self,list,length):
res = []
for i in range(length):
#每一次for循环 计算一个元素的编码(从该元素所在的叶子结点往上,直到根节点)
code = []
p = list[i+1]
while p[1]!=0:
if (list[p[1]][2] == self.findIndex(list,p)):
#表示左子树,返回值:0
code.append(0)
elif (list[p[1]][3] == self.findIndex(list,p)):
#表示右子树,返回值:1
code.append(1)
p = list[p[1]]
code.reverse()
res.append(code)
return res
def initData(self,length1):
list = []
# 创建的二维数组分别表示 权重 父母节点index 左节点index 右节点index 是否被使用过的标识(1表示已被使用)
for i in range(length1 + 1):
list.append([])
for j in range(5):
list[i].append(0)
# 初始化数据
for i in range(length1):
list[i + 1][0] = round(self.list0[i] * 100)
return list
def delSame(self,list):
for i in range(len(list)):
for j in range(i+1,len(list)):
if list[j] == list[i]:
list[j][4] = list[j][4] + 1
return list
def HToperate(self,list,length1):
# 循环次数 :7次
# n-1
cout = length1 - 1
while cout > 0:
# 返回列表中目前未被使用过的数据的最小值的 值 和 下标
min1, list = self.findMin(list)
min2, list = self.findMin(list)
list.append([(min1[0] + min2[0]), 0, min1[1], min2[1], 0])
list[min1[1]][1] = length1 + 1
list[min2[1]][1] = length1 + 1
length1 = length1 + 1
cout = cout - 1
list = self.delSame(list)
return list
def GetEncodeList(self,Char,Code):
res = []
for i in range(len(Code)):
res.append([])
res[i].append(Char[i])
res[i].append(Code[i])
return res
def EnCode(self,content,code):
CODERES = ''
for i in range(len(content)):
for j in range(len(code)):
temp = ''
if content[i] == code[j][0]:
for k in code[j][1]:
temp = temp + str(k)
CODERES = CODERES + temp
break
return CODERES
def DeCode(self,content,code):
res = ''
i = 0
while i < (len(content)):
for j in code:
flag = 0
for kindex,k in enumerate(j[1]):
#该步骤判断十分重要,当解码到最后一个字符时,该字符编码长度可能比编码表里用来对比的短,
#就会造成超出content长度的情况,系统直接报错
if (i+kindex) > (len(content)-1):
break
if content[i+kindex] == str(k):
flag = flag + 1
if flag == kindex+1:
res = res + j[0]
i = i+kindex
break
i = i + 1
return res
#该函数是为了查找二叉树深度,方便画图
def findTreedeepth(self,codelist):
deepth = len(codelist[0])
for i in codelist:
if len(i)>deepth:
deepth = len(i)
return deepth
def drawTree(self,data,deepth):
fig, axes = plt.subplots(nrows=1, ncols=1, figsize=(8, 10))
axes.set(xlim=[0, 2000], ylim=[0, 500], title='HT-BinTree')
tree_deepth = deepth + 1 # 深度等于最大编码长度+1
height = round(500 / (1 + tree_deepth))
Radius = 5
#因为该函数用到了函数外的变量,所以要定义在该函数里
def findXloc(node, x_loc, y_loc, isleft):
if node[0] == 0:
return
# 求当前节点所在深度
deeptemp = round((500 - y_loc) / height)
# 求出按照深度计算得来的每一行的元素之间的宽度的 1/2
width = round(2000 / (1 + 2 ** (deeptemp - 1)) / 2 + deeptemp*0.5)
# 求父母节点连线坐标
line_loc0x = x_loc
line_loc0y = y_loc + height - Radius
# 求该节点与父母节点连线点的坐标
line_loc1y = y_loc + Radius
# 判断是左子树还是右子树
if isleft == 1:
x_loc = round(x_loc - width * 1.3)
elif isleft == -1:
pass
else:
x_loc = round(x_loc + width * 1.3)
# 求该节点与父母节点连线点的坐标
line_loc1x = x_loc
list_tree.append([x_loc, y_loc, round(node[0] / 100), line_loc0x, line_loc0y, line_loc1x, line_loc1y])
findXloc(data[node[2]], x_loc, y_loc - height, 1)
findXloc(data[node[3]], x_loc, y_loc - height, 0)
list_tree = [] # 每一个列表元素中所含元素代表含义分别为
# 节点x坐标, 节点y坐标, 节点值, 该节点连线点坐标x,y,该节点连到父母节点的点坐标x,y
len1 = len(data)
findXloc(data[len1-1], 1000, 500 - height, -1) # -1表示根节点
for i in range(len(list_tree)):
circle = Circle(xy=(list_tree[i][0], list_tree[i][1]), radius=Radius, alpha=0.2, color='b')
axes.add_patch(circle)
plt.text(list_tree[i][0] - 2, list_tree[i][1] - 5, list_tree[i][2], weight="bold", color="0")
if i != 0:
plt.plot([list_tree[i][3], list_tree[i][5]], [list_tree[i][4], list_tree[i][6]], linewidth=0.5)
# 计算线上中点坐标
x1 = (list_tree[i][3] + list_tree[i][5]) / 2
y1 = (list_tree[i][4] + list_tree[i][6]) / 2
if list_tree[i][3] < list_tree[i][5]:
plt.text(x1, y1, 1, weight="bold", color="r")
else:
plt.text(x1, y1, 0, weight="bold", color="r")
plt.show()
def Doencode(self,CODELIST):
# 哈夫曼编译
file = open('./text.txt', "r")
TEXT = file.read()
CODERES = self.EnCode(TEXT, CODELIST)
file.close()
file1 = open('./output.txt', 'w')
file1.write(CODERES)
print("编码成功,结果保存在output.txt中!")
file1.close()
def Dodecode(self,CODELIST):
# 哈夫曼解码
file = open('./output.txt', "r")
CODE = file.read()
TEXTRES = self.DeCode(CODE, CODELIST)
file.close()
file1 = open('./text.txt', 'w')
file1.write(TEXTRES)
print("解码成功,结果保存在text.txt中!")
file1.close()
#操作界面调用的接口
def useEncode(self):
self.Doencode(self.CODELIST)
def useDecode(self):
self.Dodecode(self.CODELIST)
def useDrawTree(self):
self.drawTree(self.list, self.deepth)
# 该部分代码做了分装。若想要单独调用,请自行修改部分函数
2.UI实现及通信功能实现:
from tkinter import *
import socket
from function_body import HTcoder
from tkinter import messagebox
root = Tk() # 创建窗口对象的背景色
root.title('哈夫曼编/解码器v1.0---by HuDX')
root.geometry('800x600+350+150')
# root.minsize(800,600)
root["bg"] = "gray"
root.attributes("-alpha",1)
#实例功能类
a = HTcoder()
def ClickEncodeBTN():
text = ety1.get('0.0','end')
file1 = open('./text.txt', 'w')
file1.write(text)
file1.close()
a.useEncode()
file = open('./output.txt', "r")
codes = file.read()
file.close()
ety2.delete('1.0', 'end')
ety2.insert('end', codes)
messagebox.showinfo('提示', '解码成功!\n输入文本和输出编码会同步保存至该项目根目录的text.txt和output.txt中!')
def ClickDecodeBTN():
codes = ety2.get('0.0','end')
file1 = open('./output.txt', 'w')
file1.write(codes)
file1.close()
a.useDecode()
file = open('./text.txt', "r")
text = file.read()
file.close()
ety1.delete('1.0', 'end')
ety1.insert('end', text)
messagebox.showinfo('提示', '解码成功!\n输入编码和输出文本会同步保存至该项目根目录的output.txt和text.txt中!')
def ClickDrawBTN():
a.useDrawTree()
def ClickSendBTN():
flag = messagebox.askquestion(title='警告',message='发送编码须确保接收方打开监听按钮,\n'
'是否继续发送?')
if flag == 'yes':
codes = ety2.get('0.0','end')
IP = ety3.get('0.0','end')
IP = IP[:(len(IP)-1)] #去掉IP后面的一个回车符号
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_socket.sendto(codes.encode(), (IP, 6666))
udp_socket.close()
messagebox.showinfo('提示', '发送成功!')
else:
pass
def ClickRecvBTN():
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_socket.bind(('', 6666))
recv_data, ip_port = udp_socket.recvfrom(1024)
codes = recv_data.decode()
ety2.delete('1.0', 'end')
ety2.insert('end', codes)
messagebox.showinfo('提示', '来自ip:', ip_port, '的编码接收成功!')
udp_socket.close()
bt1 = Button(root, text='编码', activebackground='red',command=ClickEncodeBTN)
bt1.place(x=675,y=285)
bt2 = Button(root, text='解码', activebackground='red',command=ClickDecodeBTN)
bt2.place(x=725,y=285)
bt3 = Button(root, text='显示哈夫曼二叉树',activebackground='red',command=ClickDrawBTN)
bt3.place(x=550,y=285)
bt4 = Button(root, text='发送编码',activebackground='red',command=ClickSendBTN)
bt4.place(x=420,y=285)
bt5 = Button(root, text='开启监听',activebackground='red',command=ClickRecvBTN)
bt5.place(x=480,y=285)
ety1 = Text(root, bg='white',width=107,height=19)
ety1.place(x=25,y=25)
ety2 = Text(root, bg='white',width=107,height=19)
ety2.place(x=25,y=325)
ety3 = Text(root, bg='white',width=15,height=1)
ety3.place(x=265,y=292)
ety4 = Text(root, bg='white',width=5,height=1)
ety4.insert('end', 6666)
ety4.place(x=380,y=292)
title3 = Label(root, text="输入需要发送的ip和端口号:")
title3.place(x=100,y=290)
title1 = Label(root, text="文本:")
title1.place(x=26,y=2)
title2 = Label(root, text="编码:")
title2.place(x=26,y=300)
root.mainloop() # 进入消息循环