1. 程式人生 > >演算法:哈夫曼編碼演算法(Java)

演算法:哈夫曼編碼演算法(Java)

1、問題描述

  哈夫曼編碼是廣泛地用於資料檔案壓縮的十分有效的編碼方法。其壓縮率通常在20%~90%之間。哈夫曼編碼演算法用字元在檔案中出現的頻率表來建立一個用0,1串表示各字元的最優表示方式。一個包含100,000個字元的檔案,各字元出現頻率不同,如下表所示。

這裡寫圖片描述

有多種方式表示檔案中的資訊,若用0,1碼錶示字元的方法,即每個字元用唯一的一個0,1串表示。若採用定長編碼表示,則需要3位表示一個字元,整個檔案編碼需要300,000位;若採用變長編碼表示,給頻率高的字元較短的編碼;頻率低的字元較長的編碼,達到整體編碼減少的目的,則整個檔案編碼需要(45×1+13×3+12×3+16×3+9×4+5×4)×1000=224,000位,由此可見,變長碼比定長碼方案好,總碼長減小約25%。

 字首碼:對每一個字元規定一個0,1串作為其程式碼,並要求任一字元的程式碼都不是其他字元程式碼的字首。這種編碼稱為字首碼。編碼的字首性質可以使譯碼方法非常簡單;例如001011101可以唯一的分解為0,0,101,1101,因而其譯碼為aabe。

 譯碼過程需要方便的取出編碼的字首,因此需要表示字首碼的合適的資料結構。為此,可以用二叉樹作為字首碼的資料結構:樹葉表示給定字元;從樹根到樹葉的路徑當作該字元的字首碼;程式碼中每一位的0或1分別作為指示某節點到左兒子或右兒子的“路標”。

這裡寫圖片描述

 從上圖可以看出,表示最優字首碼的二叉樹總是一棵完全二叉樹,即樹中任意節點都有2個兒子。圖a表示定長編碼方案不是最優的,其編碼的二叉樹不是一棵完全二叉樹。在一般情況下,若C是編碼字符集,表示其最優字首碼的二叉樹中恰有|C|個葉子。每個葉子對應於字符集中的一個字元,該二叉樹有|C|-1個內部節點。

 給定編碼字符集C及頻率分佈f,即C中任一字元c以頻率f(c)在資料檔案中出現。C的一個字首碼編碼方案對應於一棵二叉樹T。字元c在樹T中的深度記為dT(c)。dT(c)也是字元c的字首碼長。則平均碼長定義為:![這裡寫圖片描述](https://img-blog.csdn.net/20151220143712799)使平均碼長達到最小的字首碼編碼方案稱為C的最優字首碼。     


 2、構造哈弗曼編碼

 哈夫曼提出構造最優字首碼的貪心演算法,由此產生的編碼方案稱為哈夫曼編碼。其構造步驟如下:

 (1)哈夫曼演算法以自底向上的方式構造表示最優字首碼的二叉樹T。

 (2)演算法以|C|個葉結點開始,執行|C|-1次的“合併”運算後產生最終所要求的樹T。

 (3)假設編碼字符集中每一字元c的頻率是f(c)。以f為鍵值的優先佇列Q用在貪心選擇時有效地確定演算法當前要合併的2棵具有最小頻率的樹。一旦2棵具有最小頻率的樹合併後,產生一棵新的樹,其頻率為合併的2棵樹的頻率之和,並將新樹插入優先佇列Q。經過n-1次的合併後,優先佇列中只剩下一棵樹,即所要求的樹T。

  構造過程如圖所示:

這裡寫圖片描述

/*-------------------------------------------------------------------------
 * Name:   哈夫曼編碼源
 * Date:   2015.12.20
 * Author: Ingrid 
 * 實現過程:
 *      //初始化huffmanTree,huffmanCode
 *      initHuffmanTree(huffmanTree,m);
 *      initHuffmanCode(huffmanCode,n);
 *      
 *      //獲取huffmanCode的符號
 *      getHuffmanCode(huffmanCode,n);
 *
 *      //獲取huffmanTree的頻數
 *      getHuffmanWeight(huffmanTree,n);
 *      
 *      //建立huffmanTree
 *      createHaffmanTree(huffmanTree,n);
 *      //建立huffmanCode
 *      createHaffmanCode(huffmanTree,huffmanCode,n);
 *      
 *      //輸出huffmanCode編碼
 *      ouputHaffmanCode(huffmanCode,n);    
 *------------------------------------------------------------------------*/
import java.util.Scanner; public class HuffmanCode{ //建立數的節點類 static class Node{ int weight;//頻數 int parent; int leftChild; int rightChild; public Node(int weight,int parent,int leftChild,int rightChild){ this.weight=weight; this.parent=parent; this.leftChild=leftChild; this.rightChild=rightChild; } void setWeight(int weight){ this.weight=weight; } void setParent(int parent){ this.parent=parent; } void setLeftChild(int leftChild){ this.leftChild=leftChild; } void setRightChild(int rightChild){ this.rightChild=rightChild; } int getWeight(){ return weight; } int getParent(){ return parent; } int getLeftChild(){ return leftChild; } int getRightChild(){ return rightChild; } } //新建哈夫曼編碼 static class NodeCode{ String character; String code; NodeCode(String character,String code){ this.character=character; this.code=code; } NodeCode(String code){ this.code= code; } void setCharacter(String character){ this.character=character; } void setCode(String code){ this.code=code; } String getCharacter(){ return character; } String getCode(){ return code; } } //初始化一個huffuman樹 public static void initHuffmanTree(Node[] huffmanTree,int m){ for(int i=0;i<m;i++){ huffmanTree[i] = new Node(0,-1,-1,-1); } } //初始化一個huffmanCode public static void initHuffmanCode(NodeCode[] huffmanCode,int n){ for(int i=0;i<n;i++){ huffmanCode[i]=new NodeCode("",""); } } //獲取huffmanCode的符號 public static void getHuffmanCode(NodeCode[] huffmanCode , int n){ Scanner input = new Scanner(System.in); for(int i=0;i<n;i++){ String temp = input.next(); huffmanCode[i] = new NodeCode(temp,""); } } //獲取huffman樹節點頻數 public static void getHuffmanWeight(Node[] huffmanTree , int n){ Scanner input = new Scanner(System.in); for(int i=0;i<n;i++){ int temp = input.nextInt(); huffmanTree[i] = new Node(temp,-1,-1,-1); } } //從n個結點中選取最小的兩個結點 public static int[] selectMin(Node[] huffmanTree ,int n) { int min[] = new int[2]; class TempNode { int newWeight;//儲存權 int place;//儲存該結點所在的位置 TempNode(int newWeight,int place){ this.newWeight=newWeight; this.place=place; } void setNewWeight(int newWeight){ this.newWeight=newWeight; } void setPlace(int place){ this.place=place; } int getNewWeight(){ return newWeight; } int getPlace(){ return place; } } TempNode[] tempTree=new TempNode[n]; //將huffmanTree中沒有雙親的結點儲存到tempTree中 int i=0,j=0; for(i=0;i<n;i++) { if(huffmanTree[i].getParent()==-1&& huffmanTree[i].getWeight()!=0) { tempTree[j]= new TempNode(huffmanTree[i].getWeight(),i); j++; } } int m1,m2; m1=m2=0; for(i=0;i<j;i++) { if(tempTree[i].getNewWeight()<tempTree[m1].getNewWeight())//此處不讓取到相等,是因為結點中有相同權值的時候,m1取最前的 m1=i; } for(i=0;i<j;i++) { if(m1==m2) m2++;//當m1在第一個位置的時候,m2向後移一位 if(tempTree[i].getNewWeight()<=tempTree[m2].getNewWeight()&& i!=m1)//此處取到相等,是讓在結點中有相同的權值的時候, //m2取最後的那個。 m2=i; } min[0]=tempTree[m1].getPlace(); min[1]=tempTree[m2].getPlace(); return min; } //建立huffmanTree public static void createHaffmanTree(Node[] huffmanTree,int n){ if(n<=1) System.out.println("Parameter Error!"); int m = 2*n-1; //initHuffmanTree(huffmanTree,m); for(int i=n;i<m;i++) { int[] min=selectMin(huffmanTree,i); int min1=min[0]; int min2=min[1]; huffmanTree[min1].setParent(i); huffmanTree[min2].setParent(i); huffmanTree[i].setLeftChild(min1); huffmanTree[i].setRightChild(min2); huffmanTree[i].setWeight(huffmanTree[min1].getWeight()+ huffmanTree[min2].getWeight()); } } //建立huffmanCode public static void createHaffmanCode(Node[] huffmanTree,NodeCode[] huffmanCode,int n){ Scanner input = new Scanner(System.in); char[] code = new char[10]; int start; int c; int parent; int temp; code[n-1]='0'; for(int i=0;i<n;i++) { StringBuffer stringBuffer = new StringBuffer(); start=n-1; c=i; while( (parent=huffmanTree[c].getParent()) >=0 ) { start--; code[start]=((huffmanTree[parent].getLeftChild()==c)?'0':'1'); c=parent; } for(;start<n-1;start++){ stringBuffer.append(code[start]); } huffmanCode[i].setCode(stringBuffer.toString()); } } //輸出hufmanCode public static void ouputHaffmanCode(NodeCode[] huffmanCode,int n){ System.out.println("字元與編碼的對應關係如下:"); for(int i=0;i<n;i++){ System.out.println(huffmanCode[i].getCharacter()+":"+huffmanCode[i].getCode()); } } //主函式 public static void main(String[] args){ Scanner input = new Scanner(System.in); int n; int m; System.out.print("請輸入字元個數:"); n = input.nextInt(); m=2*n-1; Node[] huffmanTree = new Node[m]; NodeCode[] huffmanCode = new NodeCode[n]; //初始化huffmanTree,huffmanCode initHuffmanTree(huffmanTree,m); initHuffmanCode(huffmanCode,n); //獲取huffmanCode的符號 System.out.print("請輸入哈夫曼編碼的字元:"); getHuffmanCode(huffmanCode,n); //獲取huffmanTree的頻數 System.out.print("請輸入哈夫曼編碼字元對應的頻數:"); getHuffmanWeight(huffmanTree,n); //建立huffmanTree createHaffmanTree(huffmanTree,n); //建立huffmanCode createHaffmanCode(huffmanTree,huffmanCode,n); //輸出huffmanCode編碼 ouputHaffmanCode(huffmanCode,n); } }