【數據結構之哈夫曼樹與編碼實現】

文章目錄

  • 前言
  • 一、哈夫曼樹與哈夫曼編碼簡介
    • 1. 什么是哈夫曼樹?
    • 2. 為什么需要哈夫曼編碼?
  • 二、哈夫曼編碼原理
  • 三、哈夫曼樹的構建步驟詳解
    • 1. 統計字符頻率
    • 2. 定義哈夫曼樹節點
    • 3. 最小堆(優先隊列)的構造
    • 4. 合并節點,構建哈夫曼樹
    • 5. 生成編碼表(遞歸/迭代遍歷)
    • 6. 編碼過程
    • 7. 解碼過程
  • 四、C++ 實現示例
      • 代碼說明
  • 五、測試
  • 六、復雜度分析
  • 七、應用場景與擴展


前言

隨著信息時代的發展,數據壓縮變得越來越重要。哈夫曼編碼(Huffman Coding)是一種經典的前綴編碼方法,能夠為出現頻率不同的符號分配不同長度的二進制編碼,從而達到壓縮的效果。


一、哈夫曼樹與哈夫曼編碼簡介

1. 什么是哈夫曼樹?

哈夫曼樹是一棵用于生成最優前綴編碼(二進制)的二叉樹,原理來源于 David A. Huffman 在 1952 年提出的貪心算法。對于待編碼的各個符號,根據其出現頻率(或權重)構造一顆二叉樹;較高頻次的符號被分配較短的編碼,較低頻次的符號被分配較長的編碼,從而在總的編碼長度上達到最小。

2. 為什么需要哈夫曼編碼?

  • 可變長度編碼:相比固定長度編碼(例如 ASCII 固定 8 位),哈夫曼編碼根據符號頻率分配長度,能顯著減少總編碼長度。
  • 前綴屬性:編碼具有前綴屬性,即任何一個編碼都不是另一個編碼的前綴,從而保證了解碼的唯一可區分性,不需分隔符即可逐位解析。
  • 廣泛應用:常見于文件壓縮(如 ZIP)、圖像壓縮(如 JPEG 的某些階段)等場景。

二、哈夫曼編碼原理

哈夫曼編碼的核心是構建一棵最優二叉樹(哈夫曼樹),步驟概括如下:

  1. 統計各符號的頻率:對待處理的數據進行掃描,記錄每個符號出現的次數。
  2. 初始化森林:將每個符號視為一棵單節點二叉樹,節點權重即頻率,將這些節點放入最小堆(或其他可快速取最小的結構)。
  3. 合并最小節點:每次從堆中取出兩個權重最小的節點,創建一個新節點,其權重為兩節點之和,新節點的左、右子節點指向這兩個取出的節點,然后將新節點插回堆中。
  4. 重復合并:直到堆中只剩下一個節點,該節點即哈夫曼樹的根。
  5. 生成編碼表:從根出發,對左右分支分別約定 “0”/“1” (或反之),遍歷到葉子時得到對應符號的二進制編碼。
  6. 編碼與解碼:使用編碼表將原始數據轉換為比特串;解碼時從根沿著比特(0/1)路徑走到葉子,即得到符號,直到處理完整個比特流。

三、哈夫曼樹的構建步驟詳解

1. 統計字符頻率

  • 對于待編碼的文本(或文件內容),遍歷每個字符,使用 std::unordered_map<char, int>std::map<char, int> 記錄出現次數。
  • 如果處理更廣泛的符號集(如擴展 ASCII,或多字節符號),可將鍵類型改為 std::stringuint8_t 等。

示例偽代碼:

std::unordered_map<char, int> freq;
for (char c : input) {freq[c]++;
}

2. 定義哈夫曼樹節點

  • 每個節點包含:

    • 符號(僅葉子節點有效)
    • 頻率/權重
    • 左右子節點指針

示例:

struct HuffmanNode {char ch;                  // 符號int freq;                 // 頻率HuffmanNode *left, *right;HuffmanNode(char c, int f) : ch(c), freq(f), left(nullptr), right(nullptr) {}HuffmanNode(int f, HuffmanNode* l, HuffmanNode* r) : ch('\0'), freq(f), left(l), right(r) {}
};

3. 最小堆(優先隊列)的構造

  • 使用 std::priority_queue,并提供自定義比較器,使得頻率小的節點優先級高(即堆頂為最小頻率節點)。
  • 比較器可定義為:
struct CompareNode {bool operator()(HuffmanNode* a, HuffmanNode* b) {return a->freq > b->freq; // 小頂堆}
};
  • 初始化時:對于頻率表中的每個 (字符, 頻率),創建一個 HuffmanNode* 并推入優先隊列。
std::priority_queue<HuffmanNode*, std::vector<HuffmanNode*>, CompareNode> pq;
for (auto &p : freq) {pq.push(new HuffmanNode(p.first, p.second));
}

4. 合并節點,構建哈夫曼樹

  • pq.size() > 1 時:

    1. 取出 node1 = pq.top(); pq.pop();
    2. 取出 node2 = pq.top(); pq.pop();
    3. 創建新節點 merged = new HuffmanNode(node1->freq + node2->freq, node1, node2);
    4. merged 推回 pq
  • 循環結束后,堆中唯一節點即哈夫曼樹根 root = pq.top();

5. 生成編碼表(遞歸/迭代遍歷)

  • 從根開始,維護一個字符串 code

    • 到左子節點時,code.push_back('0')
    • 到右子節點時,code.push_back('1')
    • 訪問到葉子節點(left==nullptr && right==nullptr),將 code 對應當前葉子符號保存到編碼表 std::unordered_map<char, std::string>
  • 遞歸示例:

void buildCodes(HuffmanNode* node, const std::string &prefix, std::unordered_map<char, std::string> &codeTable) {if (!node) return;if (!node->left && !node->right) {// 葉子節點codeTable[node->ch] = prefix;} else {buildCodes(node->left, prefix + '0', codeTable);buildCodes(node->right, prefix + '1', codeTable);}
}

6. 編碼過程

  • 遍歷原始輸入串,每個字符查表拼接對應二進制字符串,例如 std::string encoded; for (char c: input) encoded += codeTable[c];
  • 注意:實際壓縮通常需要把這些“字符 ‘0’/‘1’”轉換成位并打包存儲;這里示例為演示流程,可用 std::vector<bool> 或自定義位操作將比特寫入文件/內存。

7. 解碼過程

  • 給定哈夫曼樹根和編碼后的二進制串,逐位遍歷:

    • 從根開始,遇 ‘0’ 則走左子樹,遇 ‘1’ 則走右子樹。
    • 到達葉子節點時,輸出該葉子符號,指針重置到根,繼續處理后續比特,直到遍歷完整個比特串。

示例:

std::string decode(HuffmanNode* root, const std::string &encoded) {std::string result;HuffmanNode* node = root;for (char bit : encoded) {if (bit == '0') node = node->left;else node = node->right;if (!node->left && !node->right) {// 葉子result.push_back(node->ch);node = root;}}return result;
}

四、C++ 實現示例

#include <iostream>
#include <queue>
#include <unordered_map>
#include <string>
#include <vector>// 哈夫曼樹節點定義
struct HuffmanNode {char ch;int freq;HuffmanNode *left, *right;// 葉子節點構造HuffmanNode(char c, int f) : ch(c), freq(f), left(nullptr), right(nullptr) {}// 非葉子節點構造HuffmanNode(int f, HuffmanNode* l, HuffmanNode* r) : ch('\0'), freq(f), left(l), right(r) {}
};// 比較器:頻率小的優先
struct CompareNode {bool operator()(HuffmanNode* a, HuffmanNode* b) {return a->freq > b->freq;}
};// 遞歸釋放哈夫曼樹內存
void freeTree(HuffmanNode* node) {if (!node) return;freeTree(node->left);freeTree(node->right);delete node;
}// 構建頻率表
std::unordered_map<char, int> buildFrequency(const std::string &input) {std::unordered_map<char, int> freq;for (char c : input) {freq[c]++;}return freq;
}// 構建哈夫曼樹,返回根指針
HuffmanNode* buildHuffmanTree(const std::unordered_map<char,int> &freq) {std::priority_queue<HuffmanNode*, std::vector<HuffmanNode*>, CompareNode> pq;// 初始化:所有葉節點入堆for (auto &p : freq) {pq.push(new HuffmanNode(p.first, p.second));}// 特殊情況:只有一種字符if (pq.size() == 1) {// 復制一個節點使得樹有兩層HuffmanNode* only = pq.top();pq.pop();// 新建一個虛擬節點,頻率為 0HuffmanNode* dummy = new HuffmanNode('\0', 0);HuffmanNode* root = new HuffmanNode(only->freq + dummy->freq, only, dummy);return root;}// 合并過程while (pq.size() > 1) {HuffmanNode* n1 = pq.top(); pq.pop();HuffmanNode* n2 = pq.top(); pq.pop();HuffmanNode* merged = new HuffmanNode(n1->freq + n2->freq, n1, n2);pq.push(merged);}return pq.top();
}// 生成編碼表
void buildCodes(HuffmanNode* node, const std::string &prefix, std::unordered_map<char, std::string> &codeTable) {if (!node) return;if (!node->left && !node->right) {// 葉子節點codeTable[node->ch] = prefix.empty() ? "0" : prefix; // 若只有一個字符,prefix 可能為空,此處設為 "0"} else {buildCodes(node->left, prefix + '0', codeTable);buildCodes(node->right, prefix + '1', codeTable);}
}// 編碼
std::string encode(const std::string &input, const std::unordered_map<char, std::string> &codeTable) {std::string encoded;for (char c : input) {encoded += codeTable.at(c);}return encoded;
}// 解碼
std::string decode(HuffmanNode* root, const std::string &encoded) {std::string result;HuffmanNode* node = root;for (char bit : encoded) {if (bit == '0') node = node->left;else node = node->right;// 到達葉子if (!node->left && !node->right) {result.push_back(node->ch);node = root;}}return result;
}int main() {// 示例文本std::string text;std::cout << "請輸入要編碼的文本: ";std::getline(std::cin, text);if (text.empty()) {std::cout << "輸入為空,退出。\n";return 0;}// 1. 統計頻率auto freq = buildFrequency(text);// 2. 構建哈夫曼樹HuffmanNode* root = buildHuffmanTree(freq);// 3. 生成編碼表std::unordered_map<char, std::string> codeTable;buildCodes(root, "", codeTable);// 4. 打印編碼表std::cout << "字符  哈夫曼編碼\n";for (auto &p : codeTable) {// 注意:部分字符(如空格)打印可能不易區分,可轉義顯示if (p.first == ' ') std::cout << "' '";else std::cout << p.first;std::cout << "    " << p.second << "\n";}// 5. 編碼std::string encoded = encode(text, codeTable);std::cout << "編碼后比特串長度: " << encoded.size() << "\n";// 可視化輸出前若長度過長可只顯示前若干std::cout << "編碼比特串示例(前100位): " << encoded.substr(0, std::min(size_t(100), encoded.size())) << (encoded.size() > 100 ? "..." : "") << "\n";// 6. 解碼驗證std::string decoded = decode(root, encoded);std::cout << "解碼后文本: " << decoded << "\n";if (decoded == text) {std::cout << "解碼驗證通過,原文與解碼結果一致。\n";} else {std::cout << "解碼驗證失敗!\n";}// 7. 釋放內存freeTree(root);return 0;
}

代碼說明

  • 使用 std::getline 讀取整行文本,可包含空格。
  • 頻率統計用 unordered_map;若需要對多字節符號(如 UTF-8 多字節),需改為按字節或按寬字符處理。
  • 哈夫曼樹構建時,若僅一種符號,需要特別處理確保編碼至少一位。
  • 編碼表生成時,若 prefix 為空(僅一種葉子),指定 “0” 作為編碼。
  • 示例中直接以 std::string 存儲“0”“1”,實際壓縮應用中,需將這些比特打包成字節或寫入文件。
  • 解碼時遍歷比特流,走樹直到葉子,輸出字符。
  • 注意釋放動態分配的節點,避免內存泄漏;也可用智能指針(如 std::unique_ptr)做改進。

五、測試

  • 示例輸入
    "this is an example for huffman encoding"

  • 頻率統計
    統計各字符出現次數,如 ' ' 頻率最高等。

  • 編碼表
    輸出各字符的二進制編碼,頻率高的字符(空格、e、…)對應較短編碼。

  • 壓縮比估算

    • 原始:假設使用 ASCII,每字符 8 位,總長度 ≈ 輸入長度 × 8。
    • 哈夫曼:總比特長度為編碼后 encoded.size()。壓縮比 = encoded.size() / (input.size() * 8)
  • 測試
    在本地編譯運行,驗證編碼-解碼過程正確性,并對不同文本測試壓縮率。


六、復雜度分析

  • 時間復雜度

    • 頻率統計:O(N),N 為輸入長度。
    • 堆初始化:若 M 為不同符號個數,則 O(M) 節點推入堆,建堆 O(M)。
    • 合并過程:需要做 M-1 次合并,每次從堆中取兩次和插入一次,時間 O(log M),故總 O(M log M)。
    • 生成編碼表:遍歷哈夫曼樹,節點總數約 2M-1,時間 O(M)。
    • 編碼/解碼:編碼 O(N * average_code_length) → 實際拼接字符串為 O(N * L),但若打包為位操作可做到 O(N);解碼 O(encoded_length) ≈ O(N * average_code_length)。一般平均編碼長度有限。
  • 空間復雜度

    • 頻率表:O(M)。
    • 哈夫曼樹:O(M)節點。
    • 編碼表:O(M)存儲每個符號對應字符串。
    • 編碼輸出:O(encoded_length)。
  • M 通常遠小于 N(字符總種類有限),因此主要成本在 O(N) 級別。


七、應用場景與擴展

  • 文件壓縮:ZIP、GZIP 等,哈夫曼編碼常作為最后一步變長編碼,但通常結合其他算法(如 LZ77)先做模式替換,再做哈夫曼編碼。
  • 圖像/音頻壓縮:JPEG、MP3 等在某些階段使用哈夫曼編碼對量化后的符號進行壓縮。
  • 網絡傳輸:可對文本或協議字段做哈夫曼編碼以節省帶寬(需雙方約定編碼表)。
  • 擴展到字典外符號:若在線構建編碼表,可考慮動態哈夫曼(如 FGK 算法)或自適應哈夫曼。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/87832.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/87832.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/87832.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于Hadoop的京東廚具商品數據分析及商品價格預測系統的設計與實現

文章目錄有需要本項目的代碼或文檔以及全部資源&#xff0c;或者部署調試可以私信博主項目介紹數據采集用戶界面系統展示管理員界面每文一語有需要本項目的代碼或文檔以及全部資源&#xff0c;或者部署調試可以私信博主 項目介紹 本項目圍繞“京東廚具數據分析系統的設計與實…

深入解析TCP:可靠傳輸的核心機制與實現邏輯(三次握手、四次揮手、流量控制、滑動窗口、擁塞控制、慢啟動、延時應答、面向字節流、粘包問題)

Linux系列 文章目錄 Linux系列一、TCP連接的建立與斷開1.1 TCP 三次握手1.2 TCP四次揮手1. TCP連接的本質是應用層間的通信通道2. 斷開連接的核心是終止應用層通信3. 常見誤解澄清 二、TCP協議的機制2.1 流量控制2.2 滑動窗口2.2.1 滑動窗口的工作原理2.2.2 基于滑動窗口快重傳…

基于開源AI智能客服、AI智能名片與S2B2C商城小程序的微商服務質量提升路徑研究

摘要&#xff1a;在科技飛速發展的背景下&#xff0c;產品技術含量與復雜度顯著提升&#xff0c;客戶正確使用產品并體驗其價值愈發依賴代理的專業指導與服務。本文聚焦開源AI智能客服、AI智能名片與S2B2C商城小程序在微商服務中的應用&#xff0c;通過分析其技術原理與實踐案例…

[netty5: HttpHeaders HttpHeadersFactory]-源碼分析

HttpHeaders HttpHeaders 是用于存儲和操作HTTP請求或響應頭部字段的接口。 // DefaultHttpHeaders, HttpHeadersFactory.TrailingHttpHeaders public interface HttpHeaders extends Iterable<Entry<CharSequence, CharSequence>> {static HttpHeaders emptyHead…

基于Flink 1.20、StarRocks與TiCDC構建高效數據處理鏈路教程

在大數據處理領域&#xff0c;實現高效、實時的數據處理與分析至關重要。Flink作為強大的流批一體化計算框架&#xff0c;結合StarRocks這一高性能的實時分析型數據庫&#xff0c;再搭配TiCDC&#xff08;TiDB Change Data Capture&#xff09;用于捕獲數據變更&#xff0c;能夠…

便捷的Office批量轉PDF工具

軟件介紹 本文介紹的軟件是一款能實現Office批量轉換的工具&#xff0c;名為五五Excel word批量轉PDF。 軟件小巧 這款五五Excel word批量轉PDF軟件大小不到2M。 操作步驟一 使用該軟件時&#xff0c;只需把軟件和需要轉換的Word或Excel文件放在同一個文件夾里。 操作步驟…

tcp長連接與短連接

TCP連接本身是一個傳輸層協議&#xff0c;它既可以實現長連接&#xff0c;也可以實現短連接。這取決于應用層的使用方式。 短連接&#xff08;Short Connection&#xff09; 特點&#xff1a;每次請求都建立新的TCP連接&#xff0c;完成后立即關閉流程&#xff1a;建立連接 →…

llvm polly,親自測試

1&#xff09;下載并安裝 Polly - Getting Started git clone https://github.com/llvm/llvm-project.git 大概需要半個小時&#xff0c;有時候被墻掉就打不開 2&#xff09; mkdir build && cd build cmake -DLLVM_ENABLE_PROJECTSclang;polly ../llvm cmake --b…

Spring AI 項目實戰(十四):Spring Boot + Vue3 +AI + DeepSeek 實現空氣質量智能預測系統(附完整源碼)

系列文章 序號文章名稱1Spring AI 項目實戰(一):Spring AI 核心模塊入門2Spring AI 項目實戰(二):Spring Boot + AI + DeepSeek 深度實戰(附完整源碼)3Spring AI 項目實戰(三):Spring Boot + AI + DeepSeek 打造智能客服系統(附完整源碼)4

騰訊云 CDN 不支持 WebSocket 的現狀與華為云 CDN 的替代方案-優雅草卓伊凡

騰訊云 CDN 不支持 WebSocket 的現狀與華為云 CDN 的替代方案-優雅草卓伊凡 問題背景 卓伊凡今天發現&#xff0c;騰訊云 CDN 不支持 WebSocket 協議&#xff0c;而公司的部分業務&#xff08;如實時聊天、在線協作、游戲互動、股票行情推送等&#xff09;依賴長連接通信。昨…

MybatisPlus(一)擴展功能

擴展功能 一、靜態工具二、邏輯刪除三、通用枚舉1、定義枚舉2、配置枚舉處理器3、測試 四、JSON類型處理器1、定義實體2、使用類型處理器 五、分頁1、配置分頁插件2、分頁API3、示例 一、靜態工具 有的時候Service之間也會相互調用&#xff0c;為了避免出現循環依賴問題&#…

Redis哨兵模式之Sentinel模式(二)

一、多節點哨兵如何配置&#xff1f; 哨兵配置原理圖 注意&#xff1a;sentinel哨兵模式的搭建是建立在redis主從復制節點配置基礎而搭建&#xff0c;在主從配置中從庫需要配置好replicaof關聯上主庫并關閉安全模式&#xff0c;然后設置好bind端口才能關聯上機器&#xff0c;而…

基于Excel的數據分析思維與分析方法

數據分析一定要會Excel、SQL和Python&#xff1f;非常肯定地回答您&#xff0c;Python、R語言、Excel函數和VBA&#xff0c;以及高級數據分析軟件&#xff0c;都學不到&#xff0c;您將學到&#xff1a;5個有效的數據分析利器&#xff0c;以及分析思維 一、描述性統計分析 在…

計算機網絡筆記(不全)

一、計算機網絡體系結構1.計算機網絡的概念計算機網絡&#xff1a;由若干結點和連接這些結點的鏈路組成。結點可以是計算機、集線器、交換機、路由器等。互連網(internet)&#xff1a;多個計算機網絡通過路由器互相連接而成&#xff0c;可用任意協議通信。互聯網(因特網Interne…

XML Schema 復合元素

XML Schema 復合元素 引言 XML(可擴展標記語言)作為一種靈活的標記語言,廣泛應用于數據交換和存儲。XML Schema 是一種用于描述和定義 XML 文檔結構的語言,它定義了 XML 文檔的元素、屬性、類型和約束。本文將詳細介紹 XML Schema 中的復合元素,并探討其在實際應用中的重…

華為云Flexus+DeepSeek征文 | 彈性算力實戰:Flexus X實例自動擴縮容策略優化

華為云FlexusDeepSeek征文 | 彈性算力實戰&#xff1a;Flexus X實例自動擴縮容策略優化 &#x1f31f; 嗨&#xff0c;我是IRpickstars&#xff01; &#x1f30c; 總有一行代碼&#xff0c;能點亮萬千星辰。 &#x1f50d; 在技術的宇宙中&#xff0c;我愿做永不停歇的探索者…

【倉頡】運行環境配置VSCode + Win11

作者&#xff1a;大李子 團隊&#xff1a;堅果派 十年iOS&#xff0c;All in轉鴻蒙 前言 “倉頡編程語言是一款面向全場景智能的新一代編程語言&#xff0c;主打原生智能化、天生全場景、高性能、強安全。融入鴻蒙生態&#xff0c;為開發者提供良好的編程體驗。” ——摘自倉…

【K線訓練軟件研發歷程】【日常記錄向】1.K線滑動窗口

文章目錄 當前效果未來發展思路技術選型值得分享的技術點數據加載、解析的代碼echats的代碼當前效果 ??相當于有個hello world了。 未來發展思路 開源 技術選型 界面直接采用electron,等開源后,可以直接掛release,用戶下載安裝包后,一鍵安裝,一鍵運行,降低使用門檻…

抖音解析下載工具 v1.0.0:免安裝單文件,一鍵無水印保存高清視音頻

寶子們&#xff0c;今天給你們帶來一款超輕量的抖音下載神器——抖音解析下載工具 v1.0.0。 它只有單文件&#xff0c;雙擊就能用&#xff0c;免安裝、無廣告、完全免費&#xff0c;復制粘貼鏈接即可一鍵解析下載高清無水印視頻/音頻&#xff0c;簡直不要太方便&#xff01; 為…

Ingress——2

目錄 ?一. 域名重定向&#xff08;HTTP→HTTPS/舊域名跳轉&#xff09;? ?二. 前后端分離Rewrite&#xff08;路徑改寫&#xff09;? ?三. 混合配置示例&#xff08;重定向Rewrite&#xff09;? ?四. SSL/TLS配置&#xff08;HTTPS加密&#xff09;? ?五. 基本認…