C++詳解哈夫曼樹(shù)的概念與實(shí)現(xiàn)步驟
一、基本概念
結(jié)點(diǎn)的權(quán): 有某種現(xiàn)實(shí)含義的數(shù)值
結(jié)點(diǎn)的帶權(quán)路徑長(zhǎng)度: 從結(jié)點(diǎn)的根到該結(jié)點(diǎn)的路徑長(zhǎng)度與該結(jié)點(diǎn)權(quán)值的乘積
樹(shù)的帶權(quán)路徑長(zhǎng)度: 樹(shù)上所有葉結(jié)點(diǎn)的帶權(quán)路徑長(zhǎng)度之和
哈夫曼樹(shù): 在含有 n n n個(gè)帶權(quán)葉結(jié)點(diǎn)的二叉樹(shù)中, w p l wpl wpl 最小 的二叉樹(shù)稱為哈夫曼樹(shù),也稱最優(yōu)二叉樹(shù)(給定葉子結(jié)點(diǎn),哈夫曼樹(shù)不唯一)。
二、構(gòu)造哈夫曼樹(shù)
比較簡(jiǎn)單,此處不贅述步驟
三、哈夫曼樹(shù)的基本性質(zhì)
- 每個(gè)初始結(jié)點(diǎn)最終都是葉結(jié)點(diǎn),且權(quán)值越小的結(jié)點(diǎn)到根結(jié)點(diǎn)的路徑長(zhǎng)度越大
- 具有 n n n個(gè)根結(jié)點(diǎn)的哈夫曼樹(shù)的結(jié)點(diǎn)總數(shù)為 2 n − 1
- 哈夫曼樹(shù)中不存在度為1的結(jié)點(diǎn)
- 哈夫曼樹(shù)不唯一,但 w p l必然相同且最優(yōu)
四、哈夫曼編碼
目的:為給定的字符集合構(gòu)建二進(jìn)制編碼,使得編碼的期望長(zhǎng)度達(dá)到最短
在考試中,小渣利用哈夫曼編碼老渣發(fā)電報(bào)傳遞100道選擇題的答案,小渣傳遞了10個(gè)A、8個(gè)B、80個(gè)C、2個(gè)D,老渣利用哈夫曼編碼的方式解碼。
小渣構(gòu)造的哈夫曼樹(shù)如下:
可以發(fā)現(xiàn),A、B、C、D的編碼分別為10、111、0、110。
這樣小渣只要根據(jù)1~100題的答案順序發(fā)送01序列,老渣收到后進(jìn)行解碼就能正確收到答案了。而且哈夫曼編碼的方式不會(huì)有歧義,因?yàn)楣蚵幋a是一種前綴編碼。
前綴編碼: 沒(méi)有一個(gè)編碼是另一個(gè)編碼的前綴,因?yàn)閿?shù)據(jù)節(jié)點(diǎn)都是葉子節(jié)點(diǎn)。如果出現(xiàn)一個(gè)字符的編碼是另一個(gè)字符編碼的前綴,那這個(gè)字符一定處于內(nèi)部節(jié)點(diǎn),這是不可能的
由哈夫曼樹(shù)得到的哈夫曼編碼: 字符集中的每個(gè)字符都是以葉子結(jié)點(diǎn)出現(xiàn)在哈夫曼樹(shù)中,各個(gè)字符出現(xiàn)的頻率為結(jié)點(diǎn)的權(quán)值。
給字符串進(jìn)行編碼的時(shí)候,由于出現(xiàn)頻率越高(權(quán)值大)的字符距離根節(jié)點(diǎn)越進(jìn),編碼越短;只有出現(xiàn)頻率越低(權(quán)值?。┑淖址嚯x根節(jié)點(diǎn)較遠(yuǎn),編碼長(zhǎng)。沒(méi)關(guān)系,由于頻率高的字符編碼都短,所以哈夫曼編碼可以得到最短的編碼序列
五、哈夫曼解碼
哈夫曼編碼不同于ASCII和Unicode這些字符編碼,這些字符集中的碼長(zhǎng)都采用的是長(zhǎng)度相同的編碼方案,而哈夫曼編碼使用的是變長(zhǎng)編碼,而且哈夫曼編碼滿足立刻可解碼性(就是說(shuō)任一字符的編碼都不會(huì)是另一個(gè)更長(zhǎng)字符編碼的前綴),只要當(dāng)某個(gè)字符的編碼中所有位被全部接收時(shí),可以立即進(jìn)行解碼而無(wú)須等待后面接收的位來(lái)決定是否存在另一個(gè)合法的更長(zhǎng)的編碼
第一張表不滿足立刻可解碼性,第二張表滿足
我們接收100的時(shí)候,需要考慮是立刻解碼成D還是等待接收下一位,如果下一位是0就可以解碼成B,這就說(shuō)明表中的編碼不具有立刻可解碼性
第二張表就具有立刻可解碼性,因?yàn)槿我蛔址木幋a都不是另一個(gè)更長(zhǎng)字符編碼的前綴。只要收到的序列對(duì)應(yīng)了某個(gè)字符的編碼,直接解碼成對(duì)應(yīng)字符即可,無(wú)需等待后面的數(shù)據(jù)
我們的代碼實(shí)現(xiàn)是用字符串構(gòu)建哈夫曼樹(shù),只能針對(duì)由該字符串包含的字符組成的字符串進(jìn)行編解碼。代碼里使用字符串存儲(chǔ)的編碼,實(shí)際上應(yīng)該用bit進(jìn)行存儲(chǔ)
#include <iostream> #include <string> #include <vector> #include <functional> #include <unordered_map> #include <queue> using namespace std; using uint = unsigned int; class HuffmanTree { public: // 這里的lambda表達(dá)式用來(lái)初始化function函數(shù)對(duì)象 // priority_queue的構(gòu)造函數(shù)指出,如果傳入一個(gè)參數(shù),那這個(gè)參數(shù)用來(lái)初始化比較器對(duì)象 // 如果傳入兩個(gè)參數(shù),第一個(gè)是比較器對(duì)象,第二個(gè)是底層容器 HuffmanTree() :min_heap_([](Node* n1, Node* n2)->bool {return n1->weight_ > n2->weight_; }) , root_(nullptr) {} ~HuffmanTree() { init(); cout << "已釋放所有內(nèi)存!" << endl; } // 根據(jù)字符串創(chuàng)建哈夫曼樹(shù) void create(const string& str) { if (root_ != nullptr) { cout << "哈夫曼樹(shù)初始化..." << endl; init(); cout << "初始化完成!" << endl; } // 統(tǒng)計(jì)頻率(權(quán)重) unordered_map<char, uint> w_map; for (char c : str) { w_map[c]++; } // 遍歷w_map,把所有的字符對(duì)應(yīng)的權(quán)重放入小根堆,按照權(quán)重排序 for (pair<const char, uint>& p : w_map) { min_heap_.push(new Node(p.first, p.second)); } // 根據(jù)優(yōu)先級(jí)隊(duì)列,從小根堆中取出節(jié)點(diǎn),構(gòu)建哈夫曼樹(shù) while (min_heap_.size() > 1) { Node* n1 = min_heap_.top(); min_heap_.pop(); Node* n2 = min_heap_.top(); min_heap_.pop(); Node* node = new Node('\0', n1->weight_ + n2->weight_); // 內(nèi)部節(jié)點(diǎn)存\0 node->left_ = n1; node->right_ = n2; min_heap_.push(node); } root_ = min_heap_.top(); min_heap_.pop(); // 創(chuàng)建完哈夫曼樹(shù),直接對(duì)傳入的海量字符進(jìn)行編碼并存儲(chǔ)到code_map_ create_huffman_code(str); } string get_code(const string& str) { // 利用哈夫曼樹(shù)對(duì)str編碼并返回 string code; for (char c : str) { code += code_map_[c]; } return code; } void show_huffman_code() const { // 打印哈夫曼編碼 for (const auto& pair : code_map_) { cout << pair.first << " : " << pair.second << endl; } } string decode(const string& encode_str) { Node* cur = root_; string decode_str; for (char c : encode_str) { if (c == '0') { cur = cur->left_; } else { cur = cur->right_; } if (cur->left_ == nullptr && cur->right_ == nullptr) { // 到達(dá)葉子節(jié)點(diǎn) decode_str.push_back(cur->data_); cur = root_; } } return decode_str; } uint get_wpl() { if (root_ == nullptr) { return 0; } if (root_->left_ == nullptr && root_->right_ == nullptr) { // 對(duì)于葉子節(jié)點(diǎn),直接返回自己的weight * depth return root_->weight_ * 1; } else { // 對(duì)于內(nèi)部節(jié)點(diǎn),直接返回從子節(jié)點(diǎn)拿到的weight之和 return get_w(root_->left_, 2) + get_w(root_->right_, 2); } } private: struct Node { Node(char data, uint weight) :data_(data) , weight_(weight) , left_(nullptr) , right_(nullptr) {} char data_; uint weight_; Node* left_; Node* right_; }; private: // 防止當(dāng)前對(duì)象重新構(gòu)建哈夫曼樹(shù),釋放所有的節(jié)點(diǎn),然后初始化私有成員 void init() { // 釋放哈夫曼樹(shù)的節(jié)點(diǎn) if (root_ != nullptr) { queue<Node*> q; q.push(root_); while (!q.empty()) { Node* node = q.front(); q.pop(); if (node->left_ != nullptr) { q.push(node->left_); } if (node->right_ != nullptr) { q.push(node->right_); } delete node; } MinHeap empty([](Node* n1, Node* n2)->bool {return n1->weight_ > n2->weight_; }); swap(empty, min_heap_); code_map_.clear(); } } void create_huffman_code(const string& str) { string code; create_huffman_code(root_, code); } void create_huffman_code(Node* node, string code) { if (node->left_ == nullptr && node->right_ == nullptr) { code_map_[node->data_] = code; return; } create_huffman_code(node->left_, code + "0"); create_huffman_code(node->right_, code + "1"); } uint get_w(Node* node, int depth) { if (node == nullptr) { return 0; } if (node->left_ == nullptr && node->right_ == nullptr) { // 對(duì)于葉子節(jié)點(diǎn),直接返回自己的weight * depth return node->weight_ * depth; } else { // 對(duì)于內(nèi)部節(jié)點(diǎn),直接返回從子節(jié)點(diǎn)拿到的weight之和 return get_w(node->left_, depth + 1) + get_w(node->right_, depth + 1); } } private: Node* root_; unordered_map<char, string> code_map_; // 存儲(chǔ)字符對(duì)應(yīng)的哈夫曼編碼 using MinHeap = priority_queue<Node*, vector<Node*>, function<bool(Node*, Node*)>>; MinHeap min_heap_; // 構(gòu)建哈夫曼樹(shù)的時(shí)候使用小根堆 }; int main() { string str = "Aa"; HuffmanTree htree; htree.create(str); htree.show_huffman_code(); cout << htree.get_wpl() << endl; str = "ABC"; htree.create(str); htree.show_huffman_code(); cout << htree.get_wpl() << endl;; string encode = htree.get_code(str); cout << "encode:" << encode << endl; cout << "decode:" << htree.decode(encode) << endl; return 0; }
六、文件的壓縮和解壓縮
我們利用哈夫曼編碼壓縮文件的時(shí)候,如果文件是100M,我們可以壓縮成20M,如果文件時(shí)1K,我們可能壓縮成2K,當(dāng)文件較小的時(shí)候,我們得到的壓縮文件反而更大了,這是為什么?
文件的壓縮過(guò)程如下:
- 按字節(jié)讀取原文件的所有內(nèi)容,并統(tǒng)計(jì)字節(jié)數(shù)據(jù)的權(quán)值,構(gòu)建哈夫曼樹(shù)
- 通過(guò)哈夫曼樹(shù),得到文件的哈夫曼編碼
- 把文件的內(nèi)容按字節(jié)進(jìn)行編碼,將編碼內(nèi)容按bit存儲(chǔ)成壓縮文件,還要存儲(chǔ)文件字節(jié)數(shù)據(jù)以及權(quán)值
解碼的過(guò)程如下:
- 讀取原始文件的字節(jié)數(shù)據(jù)以及權(quán)值,構(gòu)建哈夫曼樹(shù)
- 讀取壓縮文件的01碼,利用哈夫曼樹(shù)對(duì)01進(jìn)行解碼,將解碼數(shù)據(jù)存儲(chǔ)成新的文件,就解碼出了原始文件
由于壓縮文件不僅存儲(chǔ)01碼,還需要存儲(chǔ)文件字節(jié)數(shù)據(jù)以及權(quán)值用來(lái)重建哈夫曼樹(shù)(就是代碼中的w_map)。當(dāng)原始文件較小時(shí),文件字節(jié)數(shù)據(jù)以及權(quán)值可能大于原始文件的大小,故小文件壓縮后可能變大
到此這篇關(guān)于C++詳解哈夫曼樹(shù)的概念與實(shí)現(xiàn)步驟的文章就介紹到這了,更多相關(guān)C++哈夫曼樹(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- C++深入講解哈夫曼樹(shù)
- C++實(shí)現(xiàn)哈夫曼樹(shù)的方法
- C++實(shí)現(xiàn)哈夫曼樹(shù)編碼解碼
- C++實(shí)現(xiàn)哈夫曼樹(shù)算法
- C++數(shù)據(jù)結(jié)構(gòu)與算法之哈夫曼樹(shù)的實(shí)現(xiàn)方法
- C++ 哈夫曼樹(shù)對(duì)文件壓縮、加密實(shí)現(xiàn)代碼
- C++數(shù)據(jù)結(jié)構(gòu)之文件壓縮(哈夫曼樹(shù))實(shí)例詳解
- 解析C++哈夫曼樹(shù)編碼和譯碼的實(shí)現(xiàn)
- C++實(shí)現(xiàn)哈夫曼樹(shù)簡(jiǎn)單創(chuàng)建與遍歷的方法
- C++使用數(shù)組來(lái)實(shí)現(xiàn)哈夫曼樹(shù)
相關(guān)文章
C語(yǔ)言putenv()函數(shù)和getenv()函數(shù)的使用詳解
這篇文章主要介紹了C語(yǔ)言putenv()函數(shù)和getenv()函數(shù)的使用詳解,用來(lái)進(jìn)行環(huán)境變量的相關(guān)操作,需要的朋友可以參考下2015-09-09C++實(shí)現(xiàn)RSA加密解密算法是示例代碼
非對(duì)稱加密方式可以使通信雙方無(wú)需事先交換密鑰就可以建立安全通信,因此被廣泛應(yīng)用于身份認(rèn)證、數(shù)字簽名、等信息交換領(lǐng)域。其中最具有代表性的非對(duì)稱加密方式就是RSA公鑰密碼體制。本文將用C++實(shí)現(xiàn)RSA加密解密算法,需要的可以參考一下2022-09-09深入剖析C語(yǔ)言中qsort函數(shù)的實(shí)現(xiàn)原理
這篇文章主要介紹了C語(yǔ)言中qsort函數(shù)的實(shí)現(xiàn)原理,本文將從回調(diào)函數(shù),qsort函數(shù)的應(yīng)用,qsort函數(shù)的實(shí)現(xiàn)原理三個(gè)方面進(jìn)行講解,并通過(guò)代碼示例講解的非常詳細(xì),需要的朋友可以參考下2024-03-03