1. 哈夫曼編解碼原理
霍夫曼編碼(Huffman Coding)是一種編碼方法,霍夫曼編碼是可變字長編碼(VLC)的一種。
霍夫曼編碼使用變長編碼表對源符號(如文件中的一個字母)進行編碼,其中變長編碼表是通過一種評估來源符號出現機率的方法得到的,出現機率高的字母使用較短的編碼,反之出現機率低的則使用較長的編碼,這便使編碼之後的字符串的平均長度、期望值降低,從而達到無損壓縮數據的目的。
霍夫曼編碼的具體步驟如下:
- 將信源符號的概率按減小的順序排隊。
- 把兩個最小的概率相加,並繼續這一步驟,始終將較高的概率分支放在右邊,直到最後變成概率1。
- 畫出由概率1處到每個信源符號的路徑,順序記下沿路徑的0和1,所得就是該符號的霍夫曼碼字。
- 將每對組合的左邊一個指定爲0,右邊一個指定爲1(或相反)。
例:現有一個由5個不同符號組成的30個符號的字符串:
BABACAC ADADABB CBABEBE DDABEEEBB
- 首先計算出每個字符出現的次數(概率):
字符 | 次數 |
---|---|
B | 10 |
A | 8 |
C | 3 |
D | 4 |
E | 5 |
- 把出現次數(概率)最小的兩個相加,並作爲左右子樹,重複此過程,直到概率值爲1
第一次:將概率最低值3和4相加,組合成7:
第二次:將最低值5和7相加,組合成12:
第三次:將8和10相加,組合成18:
第四次:將最低值12和18相加,結束組合:
- 將每個二叉樹的左邊指定爲0,右邊指定爲1。
- 沿二叉樹頂部到每個字符路徑,獲得每個符號的編碼。
字符 | 次數 | 編碼 |
---|---|---|
B | 10 | 11 |
A | 8 | 10 |
C | 3 | 010 |
D | 4 | 011 |
E | 5 | 00 |
我們可以看到出現次數(概率)越多的會越在上層,編碼也越短,出現頻率越少的就越在下層,編碼也越長。當我們編碼的時候,我們是按“bit”來編碼的,解碼也是通過bit來完成,如果我們有這樣的bitset “10111101100″ 那麼其解碼後就是 “ABBDE”。所以,我們需要通過這個二叉樹建立我們Huffman編碼和解碼的字典表。
這裏需要注意的是,Huffman編碼使得每一個字符的編碼都與另一個字符編碼的前一部分不同,不會出現像’A’:00, ’B’:001,這樣的情況,解碼也不會出現衝突。
霍夫曼編碼的侷限性
利用霍夫曼編碼,每個符號的編碼長度只能爲整數,所以如果源符號集的概率分佈不是2負n次方的形式,則無法達到熵極限;輸入符號數受限於可實現的碼錶尺寸;譯碼複雜;需要實現知道輸入符號集的概率分佈;沒有錯誤保護功能。
2.哈夫曼編解碼實現(C++)
#include<iostream>
#include<string>
using namespace std;
struct Node
{
double weight;
string ch;
string code;
int lchild, rchild, parent;
};
void Select(Node huffTree[], int *a, int *b, int n)//找權值最小的兩個a和b,且無父節點的兩個節點合併
{
int i;
double weight = 0; //找最小的數
for (i = 0; i < n; i++)
{
if (huffTree[i].parent != -1) //判斷節點是否已經選過
continue;
else
{
if (weight == 0)
{
weight = huffTree[i].weight;
*a = i;
}
else
{
if (huffTree[i].weight < weight)
{
weight = huffTree[i].weight;
*a = i;
}
}
}
}
weight = 0; //找第二小的數
for (i = 0; i < n; i++)
{
if (huffTree[i].parent != -1 || (i == *a))//排除已選過的數
continue;
else
{
if (weight == 0)
{
weight = huffTree[i].weight;
*b = i;
}
else
{
if (huffTree[i].weight < weight)
{
weight = huffTree[i].weight;
*b = i;
}
}
}
}
//int temp;
//if (huffTree[*a].weight > huffTree[*b].weight) //小的數放左邊
//{
// temp = *a;
// *a = *b;
// *b = temp;
//}
}
void Huff_Tree(Node huffTree[], int w[], string ch[], int n)
{
//初始過程 2倍的節點數
for (int i = 0; i < 2 * n - 1; i++)
{
huffTree[i].parent = -1;
huffTree[i].lchild = -1;
huffTree[i].rchild = -1;
huffTree[i].code = "";
}
for (int i = 0; i < n; i++)
{
huffTree[i].weight = w[i];
huffTree[i].ch = ch[i];
}
//構建哈夫曼樹
for (int k = n; k < 2 * n - 1; k++)
{
int i1 = 0;
int i2 = 0;
Select(huffTree, &i1, &i2, k); //將i1,i2節點合成節點k
huffTree[i1].parent = k;
huffTree[i2].parent = k;
huffTree[k].weight = huffTree[i1].weight + huffTree[i2].weight;
huffTree[k].lchild = i1;
huffTree[k].rchild = i2;
}
}
void Huff_Code(Node huffTree[], int n)
{
int i, j, k;
string s = "";
for (i = 0; i < n; i++)
{
s = "";
j = i;
while (huffTree[j].parent != -1) //從葉子往上找到根節點
{
k = huffTree[j].parent;
if (j == huffTree[k].lchild) //如果是根的左孩子,則記爲0
{
s = s + "0";
}
else
{
s = s + "1";
}
j = huffTree[j].parent;
}
cout << "字符 " << huffTree[i].ch << " 的編碼:";
for (int l = s.size() - 1; l >= 0; l--)
{
cout << s[l];
huffTree[i].code += s[l]; //保存編碼
}
cout << endl;
}
}
string Huff_Decode(Node huffTree[], int n, string s)
{
cout << "解碼後爲:";
string temp = "", str = "";//保存解碼後的字符串
for (int i = 0; i < s.size(); i++)
{
temp = temp + s[i];
for (int j = 0; j < n; j++)
{
if (temp == huffTree[j].code)
{
str = str + huffTree[j].ch;
temp = "";
break;
}
else if (i == s.size() - 1 && j == n - 1 && temp != "")//全部遍歷後沒有
{
str = "解碼錯誤!";
}
}
}
return str;
}
int main()
{
//編碼過程
const int n = 5;
Node huffTree[2 * n];
string str[] = { "A", "B", "C", "D", "E" };
int w[] = { 8, 10, 3, 4, 5 };
Huff_Tree(huffTree, w, str, n);
Huff_Code(huffTree, n);
//解碼過程
string s;
cout << "輸入編碼:";
cin >> s;
cout << Huff_Decode(huffTree, n, s) << endl;;
system("pause");
return 0;
}
結果:
以上博文整理自: