演算法:哈夫曼編碼演算法(Java)
阿新 • • 發佈:2019-01-26
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);
}
}