網(wǎng)站首頁 編程語言 正文
一、基本概念
結(jié)點(diǎn)的權(quán): 有某種現(xiàn)實(shí)含義的數(shù)值
結(jié)點(diǎn)的帶權(quán)路徑長度: 從結(jié)點(diǎn)的根到該結(jié)點(diǎn)的路徑長度與該結(jié)點(diǎn)權(quán)值的乘積
樹的帶權(quán)路徑長度: 樹上所有葉結(jié)點(diǎn)的帶權(quán)路徑長度之和
哈夫曼樹: 在含有 n n n個(gè)帶權(quán)葉結(jié)點(diǎn)的二叉樹中, w p l wpl wpl 最小 的二叉樹稱為哈夫曼樹,也稱最優(yōu)二叉樹(給定葉子結(jié)點(diǎn),哈夫曼樹不唯一)。
二、構(gòu)造哈夫曼樹
比較簡單,此處不贅述步驟
三、哈夫曼樹的基本性質(zhì)
- 每個(gè)初始結(jié)點(diǎn)最終都是葉結(jié)點(diǎn),且權(quán)值越小的結(jié)點(diǎn)到根結(jié)點(diǎn)的路徑長度越大
- 具有 n n n個(gè)根結(jié)點(diǎn)的哈夫曼樹的結(jié)點(diǎn)總數(shù)為 2 n ? 1
- 哈夫曼樹中不存在度為1的結(jié)點(diǎn)
- 哈夫曼樹不唯一,但 w p l必然相同且最優(yōu)
四、哈夫曼編碼
目的:為給定的字符集合構(gòu)建二進(jìn)制編碼,使得編碼的期望長度達(dá)到最短
在考試中,小渣利用哈夫曼編碼老渣發(fā)電報(bào)傳遞100道選擇題的答案,小渣傳遞了10個(gè)A、8個(gè)B、80個(gè)C、2個(gè)D,老渣利用哈夫曼編碼的方式解碼。
小渣構(gòu)造的哈夫曼樹如下:
可以發(fā)現(xiàn),A、B、C、D的編碼分別為10、111、0、110。
這樣小渣只要根據(jù)1~100題的答案順序發(fā)送01序列,老渣收到后進(jìn)行解碼就能正確收到答案了。而且哈夫曼編碼的方式不會有歧義,因?yàn)楣蚵幋a是一種前綴編碼。
前綴編碼: 沒有一個(gè)編碼是另一個(gè)編碼的前綴,因?yàn)閿?shù)據(jù)節(jié)點(diǎn)都是葉子節(jié)點(diǎn)。如果出現(xiàn)一個(gè)字符的編碼是另一個(gè)字符編碼的前綴,那這個(gè)字符一定處于內(nèi)部節(jié)點(diǎn),這是不可能的
由哈夫曼樹得到的哈夫曼編碼: 字符集中的每個(gè)字符都是以葉子結(jié)點(diǎn)出現(xiàn)在哈夫曼樹中,各個(gè)字符出現(xiàn)的頻率為結(jié)點(diǎn)的權(quán)值。
給字符串進(jìn)行編碼的時(shí)候,由于出現(xiàn)頻率越高(權(quán)值大)的字符距離根節(jié)點(diǎn)越進(jìn),編碼越短;只有出現(xiàn)頻率越低(權(quán)值小)的字符距離根節(jié)點(diǎn)較遠(yuǎn),編碼長。沒關(guān)系,由于頻率高的字符編碼都短,所以哈夫曼編碼可以得到最短的編碼序列
五、哈夫曼解碼
哈夫曼編碼不同于ASCII和Unicode這些字符編碼,這些字符集中的碼長都采用的是長度相同的編碼方案,而哈夫曼編碼使用的是變長編碼,而且哈夫曼編碼滿足立刻可解碼性(就是說任一字符的編碼都不會是另一個(gè)更長字符編碼的前綴),只要當(dāng)某個(gè)字符的編碼中所有位被全部接收時(shí),可以立即進(jìn)行解碼而無須等待后面接收的位來決定是否存在另一個(gè)合法的更長的編碼
第一張表不滿足立刻可解碼性,第二張表滿足
我們接收100的時(shí)候,需要考慮是立刻解碼成D還是等待接收下一位,如果下一位是0就可以解碼成B,這就說明表中的編碼不具有立刻可解碼性
第二張表就具有立刻可解碼性,因?yàn)槿我蛔址木幋a都不是另一個(gè)更長字符編碼的前綴。只要收到的序列對應(yīng)了某個(gè)字符的編碼,直接解碼成對應(yīng)字符即可,無需等待后面的數(shù)據(jù)
我們的代碼實(shí)現(xiàn)是用字符串構(gòu)建哈夫曼樹,只能針對由該字符串包含的字符組成的字符串進(jìn)行編解碼。代碼里使用字符串存儲的編碼,實(shí)際上應(yīng)該用bit進(jìn)行存儲
#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á)式用來初始化function函數(shù)對象
// priority_queue的構(gòu)造函數(shù)指出,如果傳入一個(gè)參數(shù),那這個(gè)參數(shù)用來初始化比較器對象
// 如果傳入兩個(gè)參數(shù),第一個(gè)是比較器對象,第二個(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)建哈夫曼樹
void create(const string& str) {
if (root_ != nullptr) {
cout << "哈夫曼樹初始化..." << endl;
init();
cout << "初始化完成!" << endl;
}
// 統(tǒng)計(jì)頻率(權(quán)重)
unordered_map<char, uint> w_map;
for (char c : str) {
w_map[c]++;
}
// 遍歷w_map,把所有的字符對應(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)先級隊(duì)列,從小根堆中取出節(jié)點(diǎn),構(gòu)建哈夫曼樹
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)建完哈夫曼樹,直接對傳入的海量字符進(jìn)行編碼并存儲到code_map_
create_huffman_code(str);
}
string get_code(const string& str) {
// 利用哈夫曼樹對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) {
// 對于葉子節(jié)點(diǎn),直接返回自己的weight * depth
return root_->weight_ * 1;
}
else {
// 對于內(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)前對象重新構(gòu)建哈夫曼樹,釋放所有的節(jié)點(diǎn),然后初始化私有成員
void init() {
// 釋放哈夫曼樹的節(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) {
// 對于葉子節(jié)點(diǎn),直接返回自己的weight * depth
return node->weight_ * depth;
}
else {
// 對于內(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_; // 存儲字符對應(yīng)的哈夫曼編碼
using MinHeap = priority_queue<Node*, vector<Node*>, function<bool(Node*, Node*)>>;
MinHeap min_heap_; // 構(gòu)建哈夫曼樹的時(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í)候,我們得到的壓縮文件反而更大了,這是為什么?
文件的壓縮過程如下:
- 按字節(jié)讀取原文件的所有內(nèi)容,并統(tǒng)計(jì)字節(jié)數(shù)據(jù)的權(quán)值,構(gòu)建哈夫曼樹
- 通過哈夫曼樹,得到文件的哈夫曼編碼
- 把文件的內(nèi)容按字節(jié)進(jìn)行編碼,將編碼內(nèi)容按bit存儲成壓縮文件,還要存儲文件字節(jié)數(shù)據(jù)以及權(quán)值
解碼的過程如下:
- 讀取原始文件的字節(jié)數(shù)據(jù)以及權(quán)值,構(gòu)建哈夫曼樹
- 讀取壓縮文件的01碼,利用哈夫曼樹對01進(jìn)行解碼,將解碼數(shù)據(jù)存儲成新的文件,就解碼出了原始文件
由于壓縮文件不僅存儲01碼,還需要存儲文件字節(jié)數(shù)據(jù)以及權(quán)值用來重建哈夫曼樹(就是代碼中的w_map)。當(dāng)原始文件較小時(shí),文件字節(jié)數(shù)據(jù)以及權(quán)值可能大于原始文件的大小,故小文件壓縮后可能變大
原文鏈接:https://blog.csdn.net/qq_42500831/article/details/124235652
相關(guān)推薦
- 2022-06-11 C#實(shí)現(xiàn)文件Move和Copy操作_C#教程
- 2023-03-23 Python?eval()與exec()函數(shù)使用介紹_python
- 2022-02-04 Linux中查看進(jìn)程命令ps aux,ps -ef,ps -A,ps -a
- 2023-10-10 Promise同時(shí)獲取n個(gè)接口數(shù)據(jù)的幾種方式
- 2022-09-26 Linux安裝jdk,安裝tomcat,Linux中發(fā)布項(xiàng)目
- 2022-05-08 使用Visual?Studio編寫單元測試_實(shí)用技巧
- 2022-12-22 Object?arrays?cannot?be?loaded?when?allow_pickle=F
- 2022-07-11 Python如何獲取多線程返回結(jié)果_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支