ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Python实现哈夫曼编码器,包含画出二叉树,可视化操作界面以及编码的传输与接收

2020-12-17 21:01:46  阅读:218  来源: 互联网

标签:哈夫曼 text self list tree 操作界面 二叉树 root def


Python实现哈夫曼编码,包含画出二叉树,可视化操作界面以及编码的传输与接收

包含功能:

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()  # 进入消息循环

标签:哈夫曼,text,self,list,tree,操作界面,二叉树,root,def
来源: https://blog.csdn.net/qq_45719995/article/details/111329701

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有