【數據結構與算法】二叉樹——哈夫曼編碼
最近有很多的小朋友問我什麽是哈夫曼編碼,哈夫曼編碼是一種可變字長的編碼,那什麽是可變字長呢?就是一句話裏的每一個字符(ASCII碼)它的位數(長度)是不一樣的。就像我們一句話(AAAACCCCCDDDDBBE)有A,B,C,D,E五種字符,在這裏我們可以用01表示A字符,用001表示B字符,用11表示C字符,用10表示D字符,用000表示E字符。如下圖:
既然知道了哈夫曼編碼是什麽了,那又有好奇的小朋友又會問了:那麽哈夫曼編碼是按照什麽原理生成的呢?
在這裏我就要告訴大家,哈夫曼編碼是根據哈夫曼樹生成的,如果看到這裏有小朋友不知道什麽是樹的話可以先去學習一下二叉樹。哈夫曼樹是一種特殊的二叉樹,為什麽特殊?因為哈夫曼樹只有葉子節點保存了數據。
那接下來我們就來分析怎麽去對一句話的每一個字符生成編碼:
以前的編碼方式就是根據一句話中不同字符的個數,來確定字符編碼的長度,然後按照一定的順序對字符進行唯一的編碼。這樣雖然簡單,但也造成了數據存儲空間的高占用。哈夫曼編碼就是為了解決高占用的問題,從原來的基礎上縮短一句話的長度,可能有數學好的小朋友們已經猜到了,一句話中,絕大多數情況下每個字符出現的概率是不一樣的,竟然概率不一樣,那麽我們不就可以讓出現概率高的字符更短點,而讓出現概率低的字符更長點,不就可以讓整句話的編碼縮短嗎?
知道了原理,實現起來就簡單了,給出我的代碼:
#include<iostream> #include<vector> #include<string> #include<stack> using namespace std; struct TreeNode {//樹節點的構造 int val; char ch; struct TreeNode *left, *right; TreeNode(int v, char c) :val(v), ch(c), left(NULL), right(NULL) {} }; struct Node {//鏈節點的構造 TreeNode tree;//樹節點 struct Node *next; Node(int v,char c):tree(v,c),next(NULL) {} }; Node* sort(Node *list,Node *item) {//鏈表排序 Node *p = list, *r; if (p->tree.val > item->tree.val) { item->next = p; list = item; } else { r = p; p = p->next; while (p != NULL) { if (p->tree.val > item->tree.val) { r->next = item; item->next = p; break; } r = p; p = p->next; } r->next = item; } return list; } void code(TreeNode *t, string &cd) {//運用遞歸實現哈夫曼編碼的生成 if (t->left != NULL || t->right != NULL) { cd.push_back(‘0‘); code(t->left, cd); cd.pop_back(); cd.push_back(‘1‘); code(t->right, cd); cd.pop_back(); } else cout << t->ch << ‘ ‘ << cd << endl; } void getpower(string &str) {//獲取權值並創建有序鏈表生成哈夫曼樹 vector<int>recode(129, 0);//129位的數組記錄每個字符的出現次數 Node *list = NULL, *p, *r=NULL; for (int i = 0; i < int(str.length()); i++) recode[str[i]]++; int n = 0; for (int i = 0; i < int(recode.size()); i++) {//對每一個鏈節點進行初始化 if (recode[i]){ if (list == NULL) { list = (Node*)malloc(sizeof(Node)); list->tree.val = recode[i]; list->tree.ch = i; list->tree.left = NULL; list->tree.right = NULL; list->next = NULL; } else { p = (Node*)malloc(sizeof(Node(recode[i], i))); p->tree.val = recode[i]; p->tree.ch = i; p->tree.left = NULL; p->tree.right = NULL; p->next = NULL; list = sort(list, p);//排序 } n++; } } p = list; while (p != NULL) {//輸出檢驗 cout << p->tree.ch << ":" << p->tree.val << endl; p = p->next; } while (n != 1) { p = list->next; r = (Node*)malloc(sizeof(Node)); r->next = NULL; r->tree.val = list->tree.val + p->tree.val; r->tree.ch = 0; r->tree.left = &list->tree; r->tree.right = &p->tree; list = p->next; if(list!=NULL) list = sort(list, r); n--; } string s; cout << "\n生成編碼:\n"; code(&r->tree,s); } int main() { string str="Create HaffmanCode!"; getpower(str); return 0; }
生成如下哈夫曼樹:
在這裏我要說一下,一句話的哈夫曼編碼是不唯一的,因為有的字符概率一樣。而且有的哈夫曼編碼的長度甚至還不一樣,這就是另外一個問題了。
最後,聰明的小朋友可能還發現了哈夫曼編碼的另一好處就是任何一個字符的編碼都不會是其他字符的前綴,這就確保了即使傳輸的過程中沒有空格,依然能夠解碼出相應的內容,這也是哈夫曼編碼受到廣泛應用的原因。
最後是個人設計過程中的總結:雖然代碼看起來很多,但是只要思路清晰,而且用對了數據結構就很容易實現你想要的操作。以上代碼是我在原來的基礎上優化後的結果,本來還是想使用結構體的構造函數,還有在輸出編碼時使用叠代法實現。但都沒有完成。等以後回頭再看看。
【數據結構與算法】二叉樹——哈夫曼編碼