數據結構(二十七)Huffman樹和Huffman編碼

  Huffman樹是一種在編碼技術方面獲得普遍應用的二叉樹,它也是一種最優二叉樹。算法

  1、霍夫曼樹的基本概念數組

  1.結點的路徑和結點的路徑長度:結點間的路徑是指從一個結點到另外一個結點所經歷的結點和分支序列。結點的路徑長度是指從根結點到該結點間的路徑上的分支數目。優化

  2.結點的權和結點的帶權路徑長度:結點的權是指結點被賦予一個具備某種實際意義的數值。結點的帶權路徑長度是該結點的路徑長度與結點的權值的乘積。this

  3.樹的長度和樹的帶權路徑長度:樹的長度就是從根結點到每一結點的路徑長度之和。樹的帶權路徑長度就是全部葉結點的帶權路徑長度之和。編碼

  4.最優二叉樹:帶權路徑長度WPL最小的二叉樹稱爲霍夫曼樹(Huffman Tree)或最優二叉樹。spa

  

  二叉樹a的WPL = 5 x 1 + 15 x 2 + 40 x 3 + 30 x 4 + 10 x 5 = 315設計

  二叉樹b的WPL = 5 x 3 + 15 x 3 + 40 x 2 + 30 x 2 + 10 x 2 = 220code

 

  2、霍夫曼樹的構造方法blog

  • 由給定的n個權值{w1,w2,... ,wn}構成由n棵二叉樹所構成的森林F={T1,T2,...,Tn},其中每棵二叉樹只有一個根結點,而且根結點的權值對應於w1,w2,...,wn
  • 在F中選取根結點的權值最小的兩棵樹,分別把它們做爲左子樹和右子樹去構造一棵新的二叉樹,新的二叉樹的各結點的權值爲左、右子樹的權值之和
  • 在F中刪除上一步中選中的兩棵樹,並把新產生的二叉樹加入到F中
  • 重複第2步和第3步,直到F只含一棵樹爲止,這棵樹就是霍夫曼樹。

  

 

  3、霍夫曼編碼ip

  霍夫曼樹更大的目的是解決了當年遠距離通訊(電報)的數據傳輸的最優化問題。

  霍夫曼編碼的定義:設須要編碼的字符集爲(d1,d2,...,dn)各個字符在電文中出現的次數或者頻率集合爲{w1,w2,...,wn},以d1,d2,...,dn爲葉子結點,w1,w2,...,wn做爲相應葉子結點的權值來構造一棵霍夫曼樹。規定霍夫曼樹的左分支表明0,右分支表明1,則從根結點到葉子結點所通過的路徑分支組成的0和1序列邊爲該結點對應字符的編碼,這就是霍夫曼編碼。

  假設六個字母的頻率爲A 27,B 8,C 15, D 15, E 30, F 5,則對應的霍夫曼編碼爲:A 01, B 1001,C 101,D 00,E 11,F 1000

  

  設計非等長碼的時候,必須是任一字符的編碼都不是另外一個字符的編碼的前綴,這種編碼稱爲前綴編碼。

 

  4、構造霍夫曼樹和霍夫曼編碼的實現思路:

  (1)算法思路

  考慮到,在進行霍夫曼譯碼時,要求能方便地實現從雙親結點到左、右孩子結點的操做;而在霍夫曼編碼時,又要求可以方便地從孩子結點到雙親結點的操做,所以,須要將霍夫曼樹的結點存儲結構設計爲三叉鏈式(二叉鏈表結點結構加上雙親parent域(數據域data是weight權值域))存儲結構。此外,每個結點還要設置權值域。爲了判斷一個結點是否已經加入到霍夫曼樹中,每一個結點還須要設置一個標誌域flag,當flag爲0時,表示該結點還沒有加入到霍夫曼樹中;反之,表示該結點已經加入到霍夫曼樹中。  

  綜上,霍夫曼樹的結點存儲結構爲:weight   flag   parent  rchild lchild

  (2)其中,最爲關鍵的就是從結點當中選擇兩個權重最小和權重第二小的結點,算法以下:

// 在霍夫曼樹結點數組的[0],[1]...[end]中選擇不在霍夫曼樹中且weight最小的兩個結點
    private HuffmanNode[] selectMinWeight(HuffmanNode[] HN, int end) { HuffmanNode[] min = {new HuffmanNode(100),new HuffmanNode(100)}; for (int i = 0; i <= end; i++) { HuffmanNode h = HN[i]; if (h.flag == 0 && h.weight < min[0].weight) { min[1] = min[0]; min[0] = h; } else if (h.weight < min[1].weight && h.flag == 0) { min[1] = h; } } return min; }

  其中,flag==1說明該結點已經被選入到霍夫曼樹當中了。

  分析一下上述算法,爲了從結點數組HN當中的下標爲[0]...[end]選擇權值最小的兩個結點。首先創建了一個結點數組用來存儲權值最小的兩個結點,這兩個結點的初始權值設爲最大值100,是爲了便於比較,而後min[0]做爲權重最小的結點,而min[1]最爲權重第二小的結點,算法思路是這樣的:從數組下標爲0開始:

  • 若是新結點的權值小於min[0]的權值,那麼將新結點賦值給min[0],並將以前最小的min[0]賦值給以前第二小的min[1]。
  • 若是新結點的權值大於min[0]的權值而小於min[1]的權值,那麼最小權值結點仍是min[0],而將第二小的min[1]賦值爲新結點。

  (3)同時還須要注意的是,在霍夫曼樹中求葉結點的霍夫曼編碼,實際就是從葉結點到根結點的路徑分支的逐個遍歷,沒通過一個分支就獲得一位霍夫曼編碼值。所以,霍夫曼編碼須要保存在一個整型數組中,而且因爲求每個字符的霍夫曼編碼是從葉結點到根結點的一個逆向處理過程,因此對獲取到的霍夫曼編碼,應該按位從數組的結尾位置開始進行存放。又因爲是不等長的編碼,因此還須要設置一個標識來表示每一個霍夫曼編碼在數組中的起始位置。

  以六個字母的頻率爲A 27,B 8,C 15, D 15, E 30, F 5,則對應的霍夫曼編碼爲:A 01, B 1001,C 101,D 00,E 11,F 1000,爲例

  

  其霍夫曼編碼在數組中的存儲形式應該是:

  

  代碼實現爲(遍歷的時候是從葉結點到根結點,而讀序列的時候是從根結點到葉結點):

int[][] Huffcode = new int[n][n];        // 創建霍夫曼編碼二維數組
        for (int j = 0; j < n; j++) {            // 一共n個結點
            int start = n - 1;                    // 編碼開始的位置,初始化爲數組的結尾 // 
            for (HuffmanNode c = HN[j], p = c.parent ; p != null ; c = p, p = p.parent) { if (p.lchild.equals(c)) { Huffcode[j][start--] = 0;        // 左孩子編碼爲0
                } else { Huffcode[j][start--] = 1;        // 右孩子編碼爲1
 } } Huffcode[j][start] = -1;                // 編碼的開始標識爲-1,即從-1開始後面纔是編碼序列
 } return Huffcode;

  

  5、構造霍夫曼樹和霍夫曼編碼的Java語言實現

  • 霍夫曼結點類:
package bigjun.iplab.huffmanTree; /** * Huffman Tree的結點存儲結構 */
public class HuffmanNode { public int weight;                            // 結點權值
    public short flag;                            // 結點是否加入到霍夫曼樹的標識
    public HuffmanNode parent, lchild, rchild;    // 結點的雙親、左孩子和右孩子
    
    public HuffmanNode() {                        // 構造一個空結點
        this(0); } public HuffmanNode(int weight) {            // 構造一個非空結點
        this.weight = weight; flag = 0; parent = lchild = rchild = null; } }
  • 霍夫曼樹及霍夫曼編碼實現類:
package bigjun.iplab.huffmanTree; public class HuffmanTree { public int[][] huffmanCoding(int[] W){ int n = W.length;                        // 權重數組的長度
        int m = 2 * n - 1;                        // 霍夫曼樹結點的總個數
        HuffmanNode[] HN = new HuffmanNode[m];    // 霍夫曼樹的結點數組
        int i;                                    // 霍夫曼樹的結點數組的下標
        for (i = 0; i < n; i++) {                // 構造n個具備給定權值的結點,放在結點數組的前n個位置
            HN[i] = new HuffmanNode(W[i]); } for (i = n; i < m; i++) {                // 從數組下標n開始,存放其餘的結點
            HuffmanNode[] minNode = selectMinWeight(HN, i - 1);    // 從結點數組中的[0],[1]...[i-1]中選擇權值最小的一個
            HuffmanNode min1 = minNode[0];        // 選出數組結點中權值最小的結點
            HuffmanNode min2 = minNode[1];        // 選出數組結點中權值第二小的結點
 min1.flag = 1;                        // 標記已經被選中到結點數組中
            min2.flag = 1; HN[i] = new HuffmanNode();            // 新建霍夫曼結點,放在數組下標爲i的位置
            min1.parent = HN[i];                // 權值最小結點和第二小結點的雙親爲新結點
            min2.parent = HN[i]; HN[i].lchild = min1;                // 新結點的左、右孩子爲權值最小的兩個結點
            HN[i].rchild = min2; HN[i].weight = min1.weight + min2.weight;// 新結點的權重就是兩個孩子的權值之和
 } int[][] Huffcode = new int[n][n];        // 創建霍夫曼編碼二維數組
        for (int j = 0; j < n; j++) {            // 一共n個結點
            int start = n - 1;                    // 編碼開始的位置,初始化爲數組的結尾 // 
            for (HuffmanNode c = HN[j], p = c.parent ; p != null ; c = p, p = p.parent) { if (p.lchild.equals(c)) { Huffcode[j][start--] = 0;        // 左孩子編碼爲0
                } else { Huffcode[j][start--] = 1;        // 右孩子編碼爲1
 } } Huffcode[j][start] = -1;                // 編碼的開始標識爲-1,即從-1開始後面纔是編碼序列
 } return Huffcode; } // 在霍夫曼樹結點數組的[0],[1]...[end]中選擇不在霍夫曼樹中且weight最小的兩個結點
    private HuffmanNode[] selectMinWeight(HuffmanNode[] HN, int end) { HuffmanNode[] min = {new HuffmanNode(100),new HuffmanNode(100)}; for (int i = 0; i <= end; i++) { HuffmanNode h = HN[i]; if (h.flag == 0 && h.weight < min[0].weight) { min[1] = min[0]; min[0] = h; } else if (h.weight < min[1].weight && h.flag == 0) { min[1] = h; } } return min; } public static void main(String[] args) { int[] W = {27, 8, 15, 15, 30, 5}; HuffmanTree hTree = new HuffmanTree(); int[][] HN = hTree.huffmanCoding(W); for (int i = 0; i < HN.length; i++) { System.out.print(W[i] + "的霍夫曼編碼爲 "); for (int j = 0; j < HN[i].length; j++) { if (HN[i][j] == -1) { for (int k = j + 1; k < HN[i].length; k++) { System.out.print(HN[i][k]); } break; } } System.out.println(); } } }
  • 輸出:
27的霍夫曼編碼爲 01 8的霍夫曼編碼爲 1001 15的霍夫曼編碼爲 101 15的霍夫曼編碼爲 00 30的霍夫曼編碼爲 11 5的霍夫曼編碼爲 1000
相關文章
相關標籤/搜索