貪心演算法實現霍夫曼編解碼
版權所有,轉載請註明出處!
霍夫曼編碼是一種被廣泛應用而且非常有效的資料壓縮技術,根據待壓縮資料的特徵,一個可壓縮掉20%~90%。這裡考慮的資料指的是字串序列。要理解霍夫曼編碼,先要理解霍夫曼樹,即最優二叉樹,是一類帶權路徑長度最短的樹。
路徑是指從樹中一個結點到另一個結點之間的通路,路徑上的分支數目稱為路徑長度。
樹的路徑長度是從樹根到每一個葉子之間的路徑長度之和。結點的帶權路徑長度為從該結點到樹根之間的路徑長度與該結點權的乘積,樹的帶權路徑長度為樹中所有葉子結點的帶權路徑長度之和.
假設有一個包含100 000個字元的資料檔案要壓縮儲存。各字元在該檔案中的出現頻度見表1。僅有6種不同字元出現過,字元a出現了45000次。
a b c d e f |
頻度(千字) 45 13 12 16 9 5 固定程式碼字 000 001 010 011 100 101 變長程式碼字 0 101 100 111 1101 1100 |
表1 一個字元編碼問題。大小為100 000個字元的一個數據檔案僅包含字元a~f,每個字元出現的頻度如表中所示。如果對每個字元賦予一個三位的編碼,則該檔案可被編碼為300000位。如果利用表中的可變長度編碼,該檔案可被編碼為224000位。
可以用很多種方式來表示這樣一個檔案。採用固定長度編碼,則需要三位二進位制數字來表示六個字元:a=000,b=001,…,f=101。這種方法需要300 000來對整個原檔案編碼。
而可變長度編碼是對頻度高的字元賦以短編碼,而對頻度低的字元賦以較長一些的編碼。表1顯示了這種編碼,其中一位串0表示a,四位串1100表示f。這種編碼方式需要
(45*1+13*3+12*3+16*3+9*4+5*4)*1000 = 224 000 位
來表示整個檔案,即可壓縮掉約25%。這其實就是最優字元編碼(霍夫曼編碼)
字首編碼
我們這裡考慮的編碼方案中,沒有一個編碼是另一個編碼的字首。這樣的編碼稱為字首編碼(或許“無字首編碼“是個更好的名字,但是字首編碼是標準的書面語)。
對任何一種二進位制字元編碼來說編碼總是簡單的,這隻要將檔案中表示每個字元的編碼並置起來即可。利用表1的可變長度編碼,把包含三個字元的檔案abc編成
0 . 101 . 100 = 0 101 100,其中“.“表示並置。
在字首編碼中解碼也是很方便的。因為沒有一個碼是其他碼的字首,故被編碼檔案的開始處的編碼是確定的。我們只要識別出第一個編碼,將它翻譯成原文字元,再對餘下的編碼檔案重複這個解碼過程即可。在我們的例子中,可將串001 011 101唯一地分析為0.0.101.1101,因此可解碼為aabe。
下面是一個程式碼例項,實現了霍夫曼編碼和解碼功能:
#include <iostream>
#include <list>
#include <stack>
#include <vector>
#define N 6
using namespace std;
char C[] = {'f','e','c','b','d','a'};//字元集合
int F[] = {5, 9, 12, 13, 16, 45};//出現頻率
typedef struct node
{
char ch;
bool inCode;
int priority;
node *left;
node *right;
node *parent;
}Node;
void insertAsPriorityList(list<node*> &priorityList, Node * node);
void insertAsPriorityList(list<node*> &priorityList, Node * node)//維護一個優先順序佇列,這裡引數一定要用引用形式
{
std::list<Node*>::iterator it = priorityList.begin();
while (it != priorityList.end() && (*it)->priority < node->priority)
++ it;
priorityList.insert(it, node);
}
void HuffmanEncode( Node * root);
void HuffmanEncode( Node * leaf)//實現霍夫曼編碼
{
//使用回溯的方法得到霍夫曼編碼
char ch = leaf->ch;
stack<bool> isOne;
while (leaf->parent)
{
if (leaf == leaf->parent->left)
isOne.push(false);
else
isOne.push(true);
leaf = leaf->parent;
}
cout <<ch<<" 的編碼是: ";
while (!isOne.empty())
{
cout << isOne.top();
isOne.pop();
}
cout << endl;
}
void HuffmanDecode(vector<bool> &code , Node *root);
void HuffmanDecode(vector<bool> &code , Node *root)//霍夫曼解碼
{
vector<char> chs;
int i = 0;
while (i < code.size()) {
Node *temp = root;
while (temp->left != NULL && temp->right != NULL && i < code.size()) {
if (code[i])
temp = temp->right;
else
temp = temp->left;
++ i;
}
if (temp->left == NULL && temp->right == NULL)
chs.push_back(temp->ch);
else
{
cout <<"編碼出錯!"<<endl;
return;
}
}
for (i =0; i < code.size(); ++ i )
cout << code[i];
cout << " 的解碼結果是:"<<endl;
for (i =0 ; i < chs.size(); ++ i) {
cout << chs[i] << " ";
}
cout <<endl;
}
void clearTree(Node * root);
void clearTree(Node * root)
{
//釋放空間
if (!root -> left && !root->right)
{
delete root;
root = NULL;
}
else
{
clearTree(root->left);
clearTree(root->right);
delete root;
}
}
int main(int argc, char **argv)
{
list<bool> huffCode;//用來記錄霍夫曼編碼
list<node*> HuffmanList;
list<node*> leaf;//儲存葉子節點
for (int i = 0; i < N; ++ i)
{
Node *tempPtr = new Node();
tempPtr->ch = C[i];
tempPtr->priority = F[i];
tempPtr->left = tempPtr->right = tempPtr->parent = NULL;
insertAsPriorityList(HuffmanList, tempPtr);
leaf.push_back(tempPtr);
}
//進行霍夫曼編碼
while (HuffmanList.size() > 1) {
Node *ptr1, *ptr2;
ptr1 = HuffmanList.front();
HuffmanList.pop_front();
ptr2 = HuffmanList.front();
HuffmanList.pop_front();
Node *newPtr = new Node();
newPtr->priority = ptr1->priority + ptr2->priority;
newPtr->left = ptr1;
newPtr->right = ptr2;
newPtr->parent = NULL;
ptr1->parent = ptr2->parent = newPtr;
insertAsPriorityList(HuffmanList, newPtr);
}
while (!leaf.empty()) {
HuffmanEncode(leaf.front());
leaf.pop_front();
}
cout << endl;
vector<bool> codes;
codes.push_back(0);//a
codes.push_back(1);//b
codes.push_back(0);
codes.push_back(1);
codes.push_back(1);//c
codes.push_back(0);
codes.push_back(0);
codes.push_back(1);//d
codes.push_back(1);
codes.push_back(1);
codes.push_back(1);//e
codes.push_back(1);
codes.push_back(0);
codes.push_back(1);
codes.push_back(1);//f
codes.push_back(1);
codes.push_back(0);
codes.push_back(0);
HuffmanDecode(codes, HuffmanList.front());
clearTree(HuffmanList.front());
return 0;
}
程式的執行結果如下:
f 的編碼是: 1100
e 的編碼是: 1101
c 的編碼是: 100
b 的編碼是: 101
d 的編碼是: 111
a 的編碼是: 0
010110011111011100 的解碼結果是:
a b c d e f