第十一章:树结构应用之哈夫曼编码解码

哈夫曼树(Huffman Tree):

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

哈夫曼编码解码过程:

编码:
1.输入字符串,通过getWeight()获取其权重即每个字符出现的次数并利用权重及字符生成Node结点,组成sourceData列表。
2.调用makeHuffman()方法,通过getmin2()函数可获得最小权重的两个字符,再让其形成父亲结点,并赋予左子结点右子结点,遍历sourceData完生成huffman结点,即哈夫曼树的根结点。
3.调用traverse()方法,将以huffman为根结点的树遍历形成列表。
4.调用strToHuffmanCode()方法,会去调用getCode()方法,会去根据huffman形成路径表route2,再跟原字符串一个一个字符进行比对,找到就添加其路径最终生成huffmancode
5.获取二进制表示的哈夫曼编码后,调用huffmanCodeBytes函数可以将二进制树转为带符号的十进制树,以八位二进制数为一组例如11010110,第一位为符号位,1表示负转为十进制数应将其余位取反并加1,再转为十进制数再*-1,就可以得到最终的十进制数。
解码:
调用decimalToBinary()函数将带符号的十进制数转为二进制数,再调用byteToBitString()比对route路径表 得到字符

代码实现

def getWeight(string):
    #功能:文章中部分字母根据出现的概率规定权重,并让其生成Node对象
    #1.统计字符串出现字符的次数,并与字符组成字典
    sourceData = {x:string.count(x) for x in string }
    print(sourceData)
    # print("统计字符串出现字符的次数,并与字符组成字典",sourceData)
    #2.将字符串转为携带自身字符及次数(权重)组成的元组的列表
    #sourceData.items() : 以列表形式返回可遍历的(键, 值) 元组数组。
    #sourceData = sourceData.items() 也可以
    sourceData = [(key,val) for key,val in sourceData.items()]
    print(sourceData)
    # print("以列表形式返回可遍历的(键, 值) 元组数组",sourceData)
    #3.将每个元组中的第0个元素与第一个元素作为参数实例化Node对象
    sourceData = [Node(x[0], x[1]) for x in sourceData]
    print(sourceData)
    # print("将每个元组中的第0个元素与第一个元素作为参数实例化Node对象",sourceData)
    return sourceData

# 构成赫夫曼树的步骤:
# 1)从小到大进行排序,将每一个数据,每个数据都是一个节点,每个节点可以看成是一颗最简单的二叉树
# 2)取出根节点权值最小的两颗二叉树
# 3)组成一颗新的二叉树,该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和
# 4)再将这颗新的二叉树,以根节点的权值大小再次排序,不断重复1-2-3-4的步骤,直到数列中,所有的数据都被处理,就得到一颗赫夫曼树
class Node ():
    def __init__(self,data,value):
        self.value = value
        self.data = data
        self.left = None
        self.right = None

# 定义获取列表中权重最大的两个节点的方法:
#1.定义 result 列表 带有两个value为正无穷的 Node 对象
#2.将权重最小的两个结点放入 result 中
#3.将其他结点扔到list2中,包括从result淘汰的结点

def getmin2(list):
    result = [Node (None,float('inf')), Node (None,float('inf'))]
    list2 = []
    for i in range(len(list)):
        if list[i].value < result[0].value:
            if result[1].value != float('inf'):
                list2.append(result[1])
            result[0] , result[1] = list[i] , result[0]
        elif list[i].value < result[1].value:
            if result[1].value != float('inf'):
                list2.append(result[1])
            result[1] = list[i] 
        else:
            list2.append(list[i])
        # for i in range(len(result)):
        #     print("result",result[i].value)
        # for i in range(len(list2)):
        #     print("list2",list2[i].value)
    return result , list2

#定义生成哈夫曼树的方法:
#1.调用getmin2方法 获得 min2,分别为left、right结点,data 为剩下的结点
#2.创建一个father结点,并将left、right分别赋给 father.left father.right
#3.如果data里空了 说明没有剩余结点了 就可以返回 father了就是哈夫曼树
#4.如果data不是空的,那么就还得继续构造father结点, 并且记得把father结点扔到 data里面
#5.递归调用makeHuffman函数,也就继续调用getmin2函数,继续获得最小权重的两个结点
def makeHuffman(list):
    min2, data = getmin2(list)
    left = min2[0]
    right = min2[1]
    value = left.value + right.value
    father = Node(None, value)
    father.left = left
    father.right = right
    if data == []:
        return father
    data.append(father)
    return makeHuffman(data)

# 递归方式实现广度优先遍历
#1.先将Node对象的list转为列表,只是把类型改了,list 的属性都还在
#2.将值一个一个扔到result里面,先扔根结点,
# 3.然后判断当前跟结点有没有左右子结点,有的话将根结点的左子结点,右子结点分别扔到nextlist里去
#4.没有的话判断当前根结点是是其父结点的右子结点吗,若是则判断nextlist是否为空,
# 4.1 若为空说明以当前结点为根结点的树遍历完了(没有叶子结点),可以返回了
# 4.2 若不为空,说明还没有遍历完,调整下参数以便继续遍历
#5.若不是右子结点,则将index + 1 使当前指向右子结点继续遍历
#6 全遍历完,返回result
#结果:
#[(None, 25), (None, 9), (None, 16), (None, 4), (None, 5), (None, 8), (None, 8), (None, 2), (None, 2), (None, 2), 
# (' ', 3), ('o', 4), ('l', 4), (None, 4), (None, 4), ('v', 1), ('p', 1), ('y', 1), ('t', 1), ('h', 1), ('n', 1), 
# ('e', 2), (None, 2), (None, 2), (None, 2), ('H', 1), ('W', 1), ('r', 1), ('d', 1), (',', 1), ('I', 1)]
def traverse(list,index= 0 ,nextlist = [] ,result = []):
    
    if type(list)  == Node:
        list = [list]    
    result.append((list[index].data,list[index].value))
    if list[index].left != None:
        nextlist.append((list[index].left))
    if list[index].right != None:
        nextlist.append((list[index].right))
    if index == len(list)-1:
        #这里可输出哈夫曼树每一行的情况
        # print( result)
        if nextlist == []:
            return
        else:
            list = nextlist
            nextlist = []
            index = 0
    else:
        index +=1      
    traverse(list,index,nextlist,result)
    return result
    
# 直接用前序遍历将其输出
# def traverse(list):
#     # if type(list)  == Node:
#     #     list = [list]  
#     print(list.data,list.value)
#     if list.left != None:
#         traverse(list.left)
#     if list.right != None:
#         traverse(list.right)

#构建霍夫曼编码   
#结果:
# [('v', '0000'), ('p', '0001'), ('y', '0010'), ('t', '0011'), ('h', '0100'), ('n', '0101'), (' ', '011'), ('o', '100'), 
# ('l', '101'), ('e', '1100'), ('H', '11010'), ('W', '11011'), ('r', '11100'), ('d', '11101'), (',', '11110'), ('I', '11111')]
def getCode(node,code,route,route2):
    #注意先后顺序。。。。。
    route = route + code
    #若该结点有数据,则必是叶子结点,因此只要没有数据就可以进行递归
    if node.data == None:
        getCode(node.left,'0',route,route2)
        getCode(node.right,'1',route,route2)
    #没数据就把当前数据及路径添加到route列表里
    else:
        # print(node.data,route,route2)
        route2.append((node.data,route))
    return route2

#字符串转哈夫曼编码:按照str的顺序
#结果:
# 11010110010110110001111011100111001011110111110111110111011000000110001100010010001101001000101
def strToHuffmanCode(huffman,string):
    route = ''#临时存放路径
    route2 = []#存放数据的路径
    route = getCode(huffman,'',route,route2)
    huffmancode =''
    for j in range(len(string)):
        for i in range(len(route)):
            if route[i][0] == string[j]:
                huffmancode += route[i][1]
    return route,huffmancode

#二进制转带符号的十进制函数
def binaryToDecimal(string):
    length = len(string)
    #若传进来的二进制首位为1,要进行其余位数(取反,并+1,转为十进制数)并取相反数
    if int(string)//pow(10,length-1) == 1:
        binary_input = list(string)
        binary_output = ''
        #其余位数取反
        for i in range(1,len(binary_input)):
            if binary_input[i] == '0':
                binary_input[i] = '1'
            else:
                binary_input[i] = '0'
        #将其组合成字符串
        for i in range(len(binary_input)):
            binary_output += binary_input[i]
        #对除首字符外的字符串转int型并+1 并转位十进制数 最后取相反数
        length =  len(binary_output)
        if int(binary_output)//pow(10,length-1) == 1:
            binary_output = binary_output[1:]
            _,byte = bin((int(binary_output,2))+1).split('b')
            byte = -1 * int(byte,2)
            return (byte)
    #若首位位0则找到第一个不位0的开始 计算其十进制数值
    else:
        for i in range(length):
            if int(string[i]) != 0:
                return (int((string[i:]),2))
            #解决了如果有一个二进制数全为0  那就返回0 
            elif i==length-1:
                return 0
#将霍夫曼编发二进制压缩成十进制
#结果:
#[-42, 91, 30, -25, 47, 125, -9, 96, 99, 18, 52, -59]
def huffmanCodeBytes(huffmancode):
    huffmancodebytes = []
    # print(len(huffmancode))
    # huffmancodelist = [huffmancode]
    for i in range(0,len(huffmancode),8):
        if i != len(huffmancode)//8*8:
            byte =  binaryToDecimal(huffmancode[i:i+8])
            huffmancodebytes.append(byte)
    # 解决了 哈夫曼编码长度为8的倍数,编码表会多一个数字的问题
    if len(huffmancode)%8 != 0:
        byte = binaryToDecimal(huffmancode[i:])
    # print(huffmancode[i:])
        huffmancodebytes.append(byte)
    return huffmancodebytes

# string = "Hello World,I love python"
string = input("请输入字符串(至少两个字符):")
# print("原字符串:",string)
sourceData = getWeight(string)
huffman = makeHuffman(sourceData)
huffmantree = traverse(huffman)
print("哈夫曼树:",huffmantree)
route,huffmancode = strToHuffmanCode(huffman,string)
print("路径表:",route)
print("哈夫曼编码",huffmancode)
huffmancodebytes = huffmanCodeBytes(huffmancode)
print("编码表:",huffmancodebytes)

#解码
#将带符号十进制的数转为二进制的数再根据路径表(route)还原字符
#结果:
# 11010110
# 01011011
# 00011110
# 11100111...
def decimalToBinary(decimal,flag,bit= 8):
    #flag == 1 说明不是最后一个数需要补码到8位
    if flag == 1:
        if decimal<0:
            decimal = abs(decimal)
            # 将十进制的a 转为2进制并-1,并切片,获取其二进制数
            _,byte = (bin(int(bin(decimal),2)-1)).split('b')
            # 补码,因为之前编码的时候1变成了0  这里0就丢了 ,因此得补码
            byte2 = '1'
            for i in range(len(byte),7):
                byte2 += '1'
            byte = list(byte)
            # 取反码
            for i in range(len(byte)):
                if byte[i] == '1':
                    byte2 += '0'
                else:
                    byte2 += '1'
        else :
            decimal = abs(decimal)
            byte2 = '0'
            _,byte = (bin(int(bin(decimal),2))).split('b')
            for i in range(len(byte),7):
                byte2 += '0'
            byte2 += byte
    #是最后一位数,如果需要补码就看还剩几位
    else:
        if decimal<0:
            # 将十进制的a 转为2进制并-1,并切片,获取其二进制数
            # print(bin(int(bin(decimal),2)))
            _,byte = (bin(int(bin(decimal),2)+1)).split('b')
            length = len(str(byte))
            byte2 = '1'
            for i in range(length,bit-1):
                byte2 += '1'
            byte = list(byte)
            # 取反码
            for i in range(len(byte)):
                if byte[i] == '1':
                    byte2 += '0'
                else:
                    byte2 += '1'            
        else :
            decimal = abs(decimal)
            byte2 = '0'
            _,byte = (bin(int(bin(decimal),2))).split('b')
            byte2 += byte
    return byte2
# 将字节转为bit字符串再比对 route路径表 得到字符
#Hello World,I love python
def byteToBitString(huffmancodebytes,route,huffmancodelength):
    bitstring = ''
    character = ''
    string = ''
    #将字节转为bit字符串
    # 结果:
    #11010110010110110001111011100111001011110111110111110111011000000110001100010010001101001000101
    for i in range(len(huffmancodebytes)):
        #解决了  当哈夫曼编码长度为8的倍数的时候,最后一个数也当成之前的数去处理就可以了
        if i != len(huffmancodebytes)-1 or huffmancodelength%8 == 0:
            bitstring += decimalToBinary(int(huffmancodebytes[i]),1)
        else:
            bitstring += decimalToBinary(int(huffmancodebytes[i]),0,huffmancodelength%8)
    #比对 route路径表 得到字符
    print("哈夫曼解码",bitstring)
    for i in range(len(bitstring)):
        character += bitstring[i]
        for j in range(len(route)):
          if character == route[j][1]:
              string += route[j][0]
              character = ''
    return string
string = byteToBitString(huffmancodebytes,route,len(huffmancode))
print("解码后字符串",string)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352

推荐阅读更多精彩内容