第十一章:樹結(jié)構(gòu)應(yīng)用之哈夫曼編碼解碼

哈夫曼樹(Huffman Tree):

給定n個權(quán)值作為n個葉子結(jié)點,構(gòu)造一棵二叉樹,若該樹的帶權(quán)路徑長度達(dá)到最小,稱這樣的二叉樹為最優(yōu)二叉樹,也稱為哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權(quán)路徑長度最短的樹,權(quán)值較大的結(jié)點離根較近。

哈夫曼編碼解碼過程:

編碼:
1.輸入字符串,通過getWeight()獲取其權(quán)重即每個字符出現(xiàn)的次數(shù)并利用權(quán)重及字符生成Node結(jié)點,組成sourceData列表。
2.調(diào)用makeHuffman()方法,通過getmin2()函數(shù)可獲得最小權(quán)重的兩個字符,再讓其形成父親結(jié)點,并賦予左子結(jié)點右子結(jié)點,遍歷sourceData完生成huffman結(jié)點,即哈夫曼樹的根結(jié)點。
3.調(diào)用traverse()方法,將以huffman為根結(jié)點的樹遍歷形成列表。
4.調(diào)用strToHuffmanCode()方法,會去調(diào)用getCode()方法,會去根據(jù)huffman形成路徑表route2,再跟原字符串一個一個字符進(jìn)行比對,找到就添加其路徑最終生成huffmancode
5.獲取二進(jìn)制表示的哈夫曼編碼后,調(diào)用huffmanCodeBytes函數(shù)可以將二進(jìn)制樹轉(zhuǎn)為帶符號的十進(jìn)制樹,以八位二進(jìn)制數(shù)為一組例如11010110,第一位為符號位,1表示負(fù)轉(zhuǎn)為十進(jìn)制數(shù)應(yīng)將其余位取反并加1,再轉(zhuǎn)為十進(jìn)制數(shù)再*-1,就可以得到最終的十進(jìn)制數(shù)。
解碼:
調(diào)用decimalToBinary()函數(shù)將帶符號的十進(jìn)制數(shù)轉(zhuǎn)為二進(jìn)制數(shù),再調(diào)用byteToBitString()比對route路徑表 得到字符

代碼實現(xiàn)

def getWeight(string):
    #功能:文章中部分字母根據(jù)出現(xiàn)的概率規(guī)定權(quán)重,并讓其生成Node對象
    #1.統(tǒng)計字符串出現(xiàn)字符的次數(shù),并與字符組成字典
    sourceData = {x:string.count(x) for x in string }
    print(sourceData)
    # print("統(tǒng)計字符串出現(xiàn)字符的次數(shù),并與字符組成字典",sourceData)
    #2.將字符串轉(zhuǎn)為攜帶自身字符及次數(shù)(權(quán)重)組成的元組的列表
    #sourceData.items() : 以列表形式返回可遍歷的(鍵, 值) 元組數(shù)組。
    #sourceData = sourceData.items() 也可以
    sourceData = [(key,val) for key,val in sourceData.items()]
    print(sourceData)
    # print("以列表形式返回可遍歷的(鍵, 值) 元組數(shù)組",sourceData)
    #3.將每個元組中的第0個元素與第一個元素作為參數(shù)實例化Node對象
    sourceData = [Node(x[0], x[1]) for x in sourceData]
    print(sourceData)
    # print("將每個元組中的第0個元素與第一個元素作為參數(shù)實例化Node對象",sourceData)
    return sourceData

# 構(gòu)成赫夫曼樹的步驟:
# 1)從小到大進(jìn)行排序,將每一個數(shù)據(jù),每個數(shù)據(jù)都是一個節(jié)點,每個節(jié)點可以看成是一顆最簡單的二叉樹
# 2)取出根節(jié)點權(quán)值最小的兩顆二叉樹
# 3)組成一顆新的二叉樹,該新的二叉樹的根節(jié)點的權(quán)值是前面兩顆二叉樹根節(jié)點權(quán)值的和
# 4)再將這顆新的二叉樹,以根節(jié)點的權(quán)值大小再次排序,不斷重復(fù)1-2-3-4的步驟,直到數(shù)列中,所有的數(shù)據(jù)都被處理,就得到一顆赫夫曼樹
class Node ():
    def __init__(self,data,value):
        self.value = value
        self.data = data
        self.left = None
        self.right = None

# 定義獲取列表中權(quán)重最大的兩個節(jié)點的方法:
#1.定義 result 列表 帶有兩個value為正無窮的 Node 對象
#2.將權(quán)重最小的兩個結(jié)點放入 result 中
#3.將其他結(jié)點扔到list2中,包括從result淘汰的結(jié)點

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.調(diào)用getmin2方法 獲得 min2,分別為left、right結(jié)點,data 為剩下的結(jié)點
#2.創(chuàng)建一個father結(jié)點,并將left、right分別賦給 father.left father.right
#3.如果data里空了 說明沒有剩余結(jié)點了 就可以返回 father了就是哈夫曼樹
#4.如果data不是空的,那么就還得繼續(xù)構(gòu)造father結(jié)點, 并且記得把father結(jié)點扔到 data里面
#5.遞歸調(diào)用makeHuffman函數(shù),也就繼續(xù)調(diào)用getmin2函數(shù),繼續(xù)獲得最小權(quán)重的兩個結(jié)點
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)

# 遞歸方式實現(xiàn)廣度優(yōu)先遍歷
#1.先將Node對象的list轉(zhuǎn)為列表,只是把類型改了,list 的屬性都還在
#2.將值一個一個扔到result里面,先扔根結(jié)點,
# 3.然后判斷當(dāng)前跟結(jié)點有沒有左右子結(jié)點,有的話將根結(jié)點的左子結(jié)點,右子結(jié)點分別扔到nextlist里去
#4.沒有的話判斷當(dāng)前根結(jié)點是是其父結(jié)點的右子結(jié)點嗎,若是則判斷nextlist是否為空,
# 4.1 若為空說明以當(dāng)前結(jié)點為根結(jié)點的樹遍歷完了(沒有葉子結(jié)點),可以返回了
# 4.2 若不為空,說明還沒有遍歷完,調(diào)整下參數(shù)以便繼續(xù)遍歷
#5.若不是右子結(jié)點,則將index + 1 使當(dāng)前指向右子結(jié)點繼續(xù)遍歷
#6 全遍歷完,返回result
#結(jié)果:
#[(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)

#構(gòu)建霍夫曼編碼   
#結(jié)果:
# [('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
    #若該結(jié)點有數(shù)據(jù),則必是葉子結(jié)點,因此只要沒有數(shù)據(jù)就可以進(jìn)行遞歸
    if node.data == None:
        getCode(node.left,'0',route,route2)
        getCode(node.right,'1',route,route2)
    #沒數(shù)據(jù)就把當(dāng)前數(shù)據(jù)及路徑添加到route列表里
    else:
        # print(node.data,route,route2)
        route2.append((node.data,route))
    return route2

#字符串轉(zhuǎn)哈夫曼編碼:按照str的順序
#結(jié)果:
# 11010110010110110001111011100111001011110111110111110111011000000110001100010010001101001000101
def strToHuffmanCode(huffman,string):
    route = ''#臨時存放路徑
    route2 = []#存放數(shù)據(jù)的路徑
    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

#二進(jìn)制轉(zhuǎn)帶符號的十進(jìn)制函數(shù)
def binaryToDecimal(string):
    length = len(string)
    #若傳進(jìn)來的二進(jìn)制首位為1,要進(jìn)行其余位數(shù)(取反,并+1,轉(zhuǎn)為十進(jìn)制數(shù))并取相反數(shù)
    if int(string)//pow(10,length-1) == 1:
        binary_input = list(string)
        binary_output = ''
        #其余位數(shù)取反
        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]
        #對除首字符外的字符串轉(zhuǎn)int型并+1 并轉(zhuǎn)位十進(jìn)制數(shù) 最后取相反數(shù)
        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的開始 計算其十進(jìn)制數(shù)值
    else:
        for i in range(length):
            if int(string[i]) != 0:
                return (int((string[i:]),2))
            #解決了如果有一個二進(jìn)制數(shù)全為0  那就返回0 
            elif i==length-1:
                return 0
#將霍夫曼編發(fā)二進(jìn)制壓縮成十進(jìn)制
#結(jié)果:
#[-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的倍數(shù),編碼表會多一個數(shù)字的問題
    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)

#解碼
#將帶符號十進(jìn)制的數(shù)轉(zhuǎn)為二進(jìn)制的數(shù)再根據(jù)路徑表(route)還原字符
#結(jié)果:
# 11010110
# 01011011
# 00011110
# 11100111...
def decimalToBinary(decimal,flag,bit= 8):
    #flag == 1 說明不是最后一個數(shù)需要補(bǔ)碼到8位
    if flag == 1:
        if decimal<0:
            decimal = abs(decimal)
            # 將十進(jìn)制的a 轉(zhuǎn)為2進(jìn)制并-1,并切片,獲取其二進(jìn)制數(shù)
            _,byte = (bin(int(bin(decimal),2)-1)).split('b')
            # 補(bǔ)碼,因為之前編碼的時候1變成了0  這里0就丟了 ,因此得補(bǔ)碼
            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
    #是最后一位數(shù),如果需要補(bǔ)碼就看還剩幾位
    else:
        if decimal<0:
            # 將十進(jìn)制的a 轉(zhuǎn)為2進(jìn)制并-1,并切片,獲取其二進(jìn)制數(shù)
            # 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
# 將字節(jié)轉(zhuǎn)為bit字符串再比對 route路徑表 得到字符
#Hello World,I love python
def byteToBitString(huffmancodebytes,route,huffmancodelength):
    bitstring = ''
    character = ''
    string = ''
    #將字節(jié)轉(zhuǎn)為bit字符串
    # 結(jié)果:
    #11010110010110110001111011100111001011110111110111110111011000000110001100010010001101001000101
    for i in range(len(huffmancodebytes)):
        #解決了  當(dāng)哈夫曼編碼長度為8的倍數(shù)的時候,最后一個數(shù)也當(dāng)成之前的數(shù)去處理就可以了
        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)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容