Huffman樹——編解碼
介紹:
Huffman樹可以根據輸入的字符串中某個字符出現的次數來給某個字符設定一個權值,然后可以根據權值的大小給一個給定的字符串編碼,或者對一串編碼進行解碼,可以用於數據壓縮或者解壓縮,和對字符的編解碼。
可是Huffman樹的優點在哪?
1、就在於它對出現次數大的字符(即權值大的字符)的編碼比出現少的字符編碼短,也就是說出現次數越多,編碼越短,保證了對數據的壓縮。
2、保證編的碼不會出現互相涵括,也就是不會出現二義性,比如a的編碼是00100,b的編碼是001,而c的編碼是00,,這樣的話,對於00100就可能是a,也可能是bc,而Huffman樹編碼方式不會出現這種問題。
如何實現
實現Huffman樹的編解碼需要三種數據類型,一個是優先級隊列,用來保存樹的結點,二是樹,用來解碼,三是表,用來當作碼表編碼。下面我們先一一介紹一下三種數據結構:
1、優先級隊列
優先級隊列里存放的是一個一個的樹的結點,根據樹結點中存放的字符的權值來確定其優先級,權重越小,優先級越小,放的位置越靠前。也就是說第一個結點存放的優先級最小,權值最小。
數據類型
//優先級隊列,struct TNode表示樹的結點,在后面介紹
typedef struct QNode
{
struct TNode* val; //樹的結點,其實也就是數據域
int priority; //優先級
struct QNode* next; //指針域
}*Node;
typedef struct Queue
{
int size; //隊列大小
struct QNode* front; //隊列頭指針
}queue;
2、樹
樹里面存放的是字符,以及指向自己的左右孩子結點的指針。比如下圖,雖然下圖中看起來書中存放了該字符的優先級,但其實可以不加,感覺比較繁瑣,所以我取了,但是為了理解方便起見,我在圖上標注了出來。
數據類型
//樹
typedef struct TNode
{
char data; //字符值
struct TNode* left; //左孩子
struct TNode* right; //右孩子
}*Tree;
3、表
這個表其實就是一張編碼表,里面存放了字符和該字符的編碼,用於編碼的時候查看。
數據類型
//表
typedef struct BNode
{
char code[256]; //編碼
char symbol; //字符
struct BNode* next; //指向下一個
}*bNode;
typedef struct Table
{
struct BNode* first; //表頭
struct BNode* last; //表尾
}*table;
思路
為了簡單起見我們講述的時候就先將權值設置為用戶輸入而不是根據出現頻率統計,因為我們作業也剛好是用戶輸入,文章最后我會貼出根據出現頻率統計的代碼,有興趣可以看看。因為用到了很多數據類型所以可能寫到一半會覺得有點暈,所以我們開始之前先理一下思路:
先設定a,b,c三個數據,它們的權值分別為6,1,2
1、首先要根據用戶輸入的每個字符的權值,創建出一個一個的樹結點,然后將其按照優先級的大小存入優先級隊列中,按從小到大的順序,具體實現我會在后面貼。
2、根據優先級隊列中存放的樹的結點構建起一棵樹。
先出隊前兩個結點,然后創建一個新的樹的結點,新的樹的結點的權值就等於出隊的兩個結點的權值之和,但其沒有字符域,也就是說它不是一個真正的樹的結點,我們稱其為假樹結點,對應稱為真樹結點。
讓出隊的兩個真樹結點作為新得到的假樹結點的左右孩子,優先級小的真樹結點(也就是先出隊的真樹結點)作為左孩子,另一個為右孩子。
出隊后
b和c為真樹結點,最上面權值為3的為假樹結點
最后將新創建的假樹結點又入隊,繼續循環操作,直到隊列只剩一個結點,那個結點就是假樹結點,最后也要作為Huffman樹的根節點root。
新的假樹結點入隊后
到最后就是下面這樣
隊列只剩最后一個假樹結點,而且作為所構建Huffman樹的根節點root
3、遍歷整棵樹建起一張碼表,通過觀察我們發現,真正有意義的真樹結點其實都是葉子節點,所以我們在遍歷的時候將所有的葉子節點的編碼和字符存入表中即可。
我們規定遍歷樹建立表的時候,往左孩子訪問一層給碼值加0,往右就加1。比如剛剛介紹樹的時候貼的那張圖,b是00,c是01,a是1。
下面是建立起來的碼表
構建Huffman樹和創建編碼表的實現過程
看完思路之后再看實現過程,我們先看創建隊列時候的一系列操作:
因為為了方便我用了部分C++語法,所以分配內存會是用new,釋放內存就是delete,就和C語言里malloc和free是一個作用,其他的都一樣。
隊列的初始化:
queue Init_queue()
{
queue q;
q.size = 0;
q.front = new struct QNode;
if (!q.front)
{
printf("分配失敗!\n");
exit(1);
}
q.front->next = NULL;
return q;
}
隊列的插入:
//插入,根據優先級
bool EnQueue(queue& q, Tree avl, int weight)
{
Node newp = new struct QNode;
newp->val = avl;
newp->priority = weight;
if (q.size == 0 || q.front == NULL) //空表
{
newp->next = NULL;
q.front = newp;
q.size = 1;
return true;
}
else //中間位置,需要迭代
{
if (weight <= q.front->priority) //比第一個都小
{
newp->next = q.front;
q.front = newp;
q.size++;
return true;
}
else //中間位置
{
Node beforp = q.front;
while (beforp->next != NULL)
{
if (weight <= beforp->next->priority)
{
newp->next = beforp->next;
beforp->next = newp;
q.size++;
return true;
}
else
{
beforp = beforp->next;
}
}
//需要插在隊列最后
if (beforp->next == NULL)
{
newp->next = NULL;
beforp->next = newp;
q.size++;
return true;
}
}
}
return true;
}
創建一個隊列:
需要用戶輸入每個字符和對應的優先級
//創建隊列
queue Create_Queue()
{
queue q = Init_queue();
while (1)
{
char symbol;
int weight;
cin >> symbol >> weight; //C++里的輸入,輸入symnol和weight
if (weight == 0) //如果輸入的權值為0,表示輸入結束
break;
Tree t = new struct TNode;
t->data = symbol;
t->left = NULL;
t->right = NULL;
EnQueue(q, t, weight);
}
return q;
}
彈出隊列中優先級最小的結點:
//彈出隊列優先級最小的
Tree Dequeue(queue& q)
{
if (q.front == NULL)
{
cout << "空隊!" << endl;
exit(1);
}
Node p = q.front;
q.front = p->next;
Tree e = p->val;
q.size--;
delete[] p;
return e;
}
樹的函數,根據優先級隊列創建一棵樹:
//樹的函數
//創建一棵樹
Tree Create_Tree(queue& q)
{
while (q.size != 1)
{
int priority = q.front->priority + q.front->next->priority;
Tree left = Dequeue(q);
Tree right = Dequeue(q);
Tree newTNode = new struct TNode;
newTNode->left = left;
newTNode->right = right;
EnQueue(q, newTNode, priority);
}
Tree root = new struct TNode;
root = Dequeue(q);
return root;
}
表的函數,根據樹創建一張表:
//創建一張表
table Create_Table(Tree root)
{
table t = new struct Table;
t->first = NULL;
t->last = NULL;
char code[256];
int k = 0;
travel(root, t, code, k);
return t;
}
表的函數,對travel函數的實現:
travel函數表示對樹的遍歷,從而建立起表,采用表尾插入法
void travel(Tree root, table& t, char code[256], int k)
{
if (root->left == NULL && root->right == NULL)
{
code[k] = '\0';
bNode b = new struct BNode;
b->symbol = root->data;
strcpy(b->code, code);
b->next = NULL;
//尾部插入法
if (t->first == NULL) //空表
{
t->first = b;
t->last = b;
}
else
{
t->last->next = b;
t->last = b;
}
}
if (root->left != NULL)
{
code[k] = '0';
travel(root->left, t, code, k + 1);
}
if (root->right != NULL)
{
code[k] = '1';
travel(root->right, t, code, k + 1);
}
}
編解碼
至此,Huffman樹以及編碼表已經構建完畢,現在就來實現編解碼的函數來檢驗上述的Huffman樹。
編碼:
需要傳入編碼表來進行編碼
void EnCode(table t, char* str)
{
cout << "EnCodeing............./" << endl;
int len = strlen(str);
for (int i = 0; i < len; i++)
{
bNode p = t->first;
while (p != NULL)
{
if (p->symbol == str[i])
{
cout << p->code;
break;
}
p = p->next;
}
}
cout << endl;
}
解碼:
需要傳入Huffman樹來進行編碼
void DeCode(Tree root, char* str)
{
cout << "DeCode............./" << endl;
Tree p = root;
int len = strlen(str);
for (int i = 0; i < len; i++)
{
if (p->left == NULL && p->right == NULL)
{
cout << p->data;
p = root;
}
if (str[i] == '0')
p = p->left;
if (str[i] == '1')
p = p->right;
if (str[i] != '0' && str[i] != '1')
{
cout << "The Input String Is Not Encoded correctly !" << endl;
return;
}
}
if (p->left == NULL && p->right == NULL)
cout << p->data;
cout << endl;
}
測試數據
int main()
{
queue q = Create_Queue();
Tree root = Create_Tree(q);
table t = Create_Table(root);
char str[256];
cout << "請輸入要編碼的字符:" << endl;
cin >> str;
EnCode(t, str);
cout << "請輸入要解碼的碼值:" << endl;
char str1[256];
cin >> str1;
DeCode(root, str1);
}
附上截圖: