霍夫曼編碼是一種不等長非前綴編碼方式,於1951年由MIT的霍夫曼提出。
用於對一串數字/符號編碼獲取最短的結果,獲取最大的壓縮效率。
特點:不等長、非前綴
等長式編碼
等長編碼,意思是對出現的元素採用相同位數的序號進行標定,如下列所示:(這裏我們採用三位)
假設有這樣一串數據:
編碼後的數據量:12 x 3bit=36bit.
思考:對5個數編碼沒必要每個數是同樣的長度,只要五個字符對應的五個編碼各自具有可區分性就行了。
非等長編碼就是對不同的字符進行不同長度的編碼
非等長式編碼
如下列所示,我們採用非等長編碼:
拿剛纔說的字符串舉例:
編碼後的數據量是:3x2+1x5+2x3+41+14=25bit.
數據量減少了。
思考:如何確定哪種字符使用比較長的編碼,哪種字符使用比較短的編碼?
出現次數越多的字符我們使用更加短的編碼,出現次數越少的字符我們使用更加長的編碼。
現在可能又有疑問:爲何不能像這樣,B=0,A=1,類似這樣,我們可以獲取更加短的編碼。
這裏就要說到霍夫曼編碼的另外一個特性:非前綴編碼。
非前綴編碼
以下圖爲例:
任何一個數據的編碼都不與其他數據的編碼的前綴重複。
如果B的編碼爲1,則和其他編碼的前綴重複。
又如,C爲11的話就和A、D、E的前綴重複了。
非前綴編碼的優點:
在編碼的時候其實和前綴編碼一樣,並沒有什麼簡化的步驟。
但是在解碼的時候將會有不同的效果:
假設,我們現在有一串數據:
解碼得:
1110 110 1111 0 10 0 110 10 0 0 0 10
D A E B C B A C B B B C
已知碼錶,對編碼後的信息進行解碼,不需要知道斷位信息,即可解碼。也就是說我們不需要知道哪幾個字符屬於同一個就可以進行解碼.
前綴編碼
假設編碼方式爲如下。
當獲取的數據串是:
在不知道斷位信息的前提下,我們是無法對這串數據進行編碼的。
霍夫曼編碼
霍夫曼編碼提供一種自動的方式獲取非前綴非等長的編碼,通過二叉樹進行編碼。
1)將信源符號的概率按減小的順序排隊。
2)把兩個最小的概率相加,並繼續這一步驟,始終將較高的概率分支放在右邊,直到最後變成概率1。
3)畫出由概率1處到每個信源符號的路徑,順序記下沿路徑的0和1,所得就是該符號的霍夫曼碼字。
4)將每對組合的左邊一個指定爲0,右邊一個指定爲1(或相反)。
例子講解:
1、計算每個字符出現次數
input | 出現次數 |
---|---|
B | 10 |
A | 8 |
C | 3 |
D | 4 |
E | 5 |
2、把出現次數(概率)最小的兩個相加,並作爲左右子樹,重複此過程,直到概率值爲1
第一次:將概率最低值3和4相加,組合成7:
第二次:將最低值5和7相加,組合成12:
第三次:將8和10相加,組合成18:
第四次:將最低值12和18相加,結束組合:
3 將每個二叉樹的左邊指定爲0,右邊指定爲1
4 沿二叉樹頂部到每個字符路徑,獲得每個符號的編碼
output | 編碼 |
---|---|
B | 11 |
A | 10 |
C | 010 |
D | 011 |
E | 00 |
霍夫曼編碼的缺陷:
(1)哈夫曼編碼所形成的碼字不是復唯一的,但編碼效率是唯一的在對最小的兩個概率符號賦值時,可以規定爲大的爲“1”、小的爲“0”,反之也可以。如果兩個符號的出現概率相等時,排列時無論哪個在前都是可以的,所以哈夫曼所構造的碼字不是唯一的,對於制同一個信息源,無論上述的前後順序如何排列,它的平均碼長是不會改變的,所以編碼效率是唯一的。
(2)只有當信息源各符號出現的概率很不平百均的時候,哈夫曼編碼的效果才明顯。
(3)哈夫曼編碼必須精確地統度計出原始文件中每個符號的出現頻率,如果沒有這些精確的統計,將達不到預期的壓縮效果。霍夫曼編通常要經過兩遍操作,第一遍進行統計,第二遍產生編碼,所以編碼速度相對慢。另外實現的電路複雜,各問種長度的編碼的譯碼過程也是較複雜的,因此解壓縮的過程也比較慢。
(4)哈夫曼編碼只能用整數來表示單個符號而不能用小數,這很大程度上限制了壓縮效果。
(5)哈夫曼所有位都是合在一起的,如果改動其中一位就可以答使其數據變得面目全非
編程實現
代碼摘自博客:霍夫曼編碼(Huffman Coding)
#include <stdio.h>
#include<stdlib.h>
#include<string>
#include <iostream>
#define MAXBIT 100
#define MAXVALUE 10000
#define MAXLEAF 30
#define MAXNODE MAXLEAF*2 -1
typedef struct
{
int bit[MAXBIT];
int start;
} HCodeType; /* 編碼結構體 */
typedef struct
{
int weight;
int parent;
int lchild;
int rchild;
char value;
} HNodeType; /* 結點結構體 */
/* 構造一顆哈夫曼樹 */
void HuffmanTree (HNodeType HuffNode[MAXNODE], int n)
{
/* i、j: 循環變量,m1、m2:構造哈夫曼樹不同過程中兩個最小權值結點的權值,
x1、x2:構造哈夫曼樹不同過程中兩個最小權值結點在數組中的序號。*/
int i, j, m1, m2, x1, x2;
/* 初始化存放哈夫曼樹數組 HuffNode[] 中的結點 */
for (i=0; i<2*n-1; i++)
{
HuffNode[i].weight = 0;//權值
HuffNode[i].parent =-1;
HuffNode[i].lchild =-1;
HuffNode[i].rchild =-1;
HuffNode[i].value=' '; //實際值,可根據情況替換爲字母
} /* end for */
/* 輸入 n 個葉子結點的權值 */
for (i=0; i<n; i++)
{
printf ("Please input char of leaf node: ", i);
scanf ("%c",&HuffNode[i].value);
getchar();
} /* end for */
for (i=0; i<n; i++)
{
printf ("Please input weight of leaf node: ", i);
scanf ("%d",&HuffNode[i].weight);
getchar();
} /* end for */
/* 循環構造 Huffman 樹 */
for (i=0; i<n-1; i++)
{
m1=m2=MAXVALUE; /* m1、m2中存放兩個無父結點且結點權值最小的兩個結點 */
x1=x2=0;
/* 找出所有結點中權值最小、無父結點的兩個結點,併合並之爲一顆二叉樹 */
for (j=0; j<n+i; j++)
{
if (HuffNode[j].weight < m1 && HuffNode[j].parent==-1)
{
m2=m1;
x2=x1;
m1=HuffNode[j].weight;
x1=j;
}
else if (HuffNode[j].weight < m2 && HuffNode[j].parent==-1)
{
m2=HuffNode[j].weight;
x2=j;
}
} /* end for */
/* 設置找到的兩個子結點 x1、x2 的父結點信息 */
HuffNode[x1].parent = n+i;
HuffNode[x2].parent = n+i;
HuffNode[n+i].weight = HuffNode[x1].weight + HuffNode[x2].weight;
HuffNode[n+i].lchild = x1;
HuffNode[n+i].rchild = x2;
printf ("x1.weight and x2.weight in round %d: %d, %d\n", i+1, HuffNode[x1].weight, HuffNode[x2].weight); /* 用於測試 */
printf ("\n");
} /* end for */
} /* end HuffmanTree */
//解碼
void decodeing(char string[],HNodeType Buf[],int Num)
{
int i,tmp=0,code[1024];
int m=2*Num-1;
char *nump;
char num[1024];
for(i=0;i<strlen(string);i++)
{
if(string[i]=='0')
num[i]=0;
else
num[i]=1;
}
i=0;
nump=&num[0];
while(nump<(&num[strlen(string)]))
{tmp=m-1;
while((Buf[tmp].lchild!=-1)&&(Buf[tmp].rchild!=-1))
{
if(*nump==0)
{
tmp=Buf[tmp].lchild ;
}
else tmp=Buf[tmp].rchild;
nump++;
}
printf("%c",Buf[tmp].value);
}
}
int main(void)
{
HNodeType HuffNode[MAXNODE]; /* 定義一個結點結構體數組 */
HCodeType HuffCode[MAXLEAF], cd; /* 定義一個編碼結構體數組, 同時定義一個臨時變量來存放求解編碼時的信息 */
int i, j, c, p, n;
char pp[100];
printf ("Please input n:\n");
scanf ("%d", &n);
HuffmanTree (HuffNode, n);
for (i=0; i < n; i++)
{
cd.start = n-1;
c = i;
p = HuffNode[c].parent;
while (p != -1) /* 父結點存在 */
{
if (HuffNode[p].lchild == c)
cd.bit[cd.start] = 0;
else
cd.bit[cd.start] = 1;
cd.start--; /* 求編碼的低一位 */
c=p;
p=HuffNode[c].parent; /* 設置下一循環條件 */
} /* end while */
/* 保存求出的每個葉結點的哈夫曼編碼和編碼的起始位 */
for (j=cd.start+1; j<n; j++)
{ HuffCode[i].bit[j] = cd.bit[j];}
HuffCode[i].start = cd.start;
} /* end for */
/* 輸出已保存好的所有存在編碼的哈夫曼編碼 */
for (i=0; i<n; i++)
{
printf ("%d 's Huffman code is: ", i);
for (j=HuffCode[i].start+1; j < n; j++)
{
printf ("%d", HuffCode[i].bit[j]);
}
printf(" start:%d",HuffCode[i].start);
printf ("\n");
}
printf("Decoding?Please Enter code:\n");
scanf("%s",&pp);
decodeing(pp,HuffNode,n);
getchar();
return 0;
}
Reference:
霍夫曼編碼(HuffmanCoding)
哈夫曼編碼和二進制編碼優缺點比較
《數字圖像處理PPT.李竹版》