最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C++詳解哈夫曼樹的概念與實現(xiàn)步驟

 更新時間:2022年04月24日 16:37:41   作者:BugMaker-shen  
給定N個權(quán)值作為N個葉子結(jié)點,構(gòu)造一棵二叉樹,若該樹的帶權(quán)路徑長度達到最小,稱這樣的二叉樹為最優(yōu)二叉樹,也稱為哈夫曼樹(Huffman?Tree)。哈夫曼樹是帶權(quán)路徑長度最短的樹,權(quán)值較大的結(jié)點離根較近

一、基本概念

結(jié)點的權(quán): 有某種現(xiàn)實含義的數(shù)值

結(jié)點的帶權(quán)路徑長度: 從結(jié)點的根到該結(jié)點的路徑長度與該結(jié)點權(quán)值的乘積

樹的帶權(quán)路徑長度: 樹上所有葉結(jié)點的帶權(quán)路徑長度之和

哈夫曼樹: 在含有 n n n個帶權(quán)葉結(jié)點的二叉樹中, w p l wpl wpl 最小 的二叉樹稱為哈夫曼樹,也稱最優(yōu)二叉樹(給定葉子結(jié)點,哈夫曼樹不唯一)。

二、構(gòu)造哈夫曼樹

比較簡單,此處不贅述步驟

三、哈夫曼樹的基本性質(zhì)

  • 每個初始結(jié)點最終都是葉結(jié)點,且權(quán)值越小的結(jié)點到根結(jié)點的路徑長度越大
  • 具有 n n n個根結(jié)點的哈夫曼樹的結(jié)點總數(shù)為 2 n − 1
  • 哈夫曼樹中不存在度為1的結(jié)點
  • 哈夫曼樹不唯一,但 w p l必然相同且最優(yōu)

四、哈夫曼編碼

目的:為給定的字符集合構(gòu)建二進制編碼,使得編碼的期望長度達到最短

在考試中,小渣利用哈夫曼編碼老渣發(fā)電報傳遞100道選擇題的答案,小渣傳遞了10個A、8個B、80個C、2個D,老渣利用哈夫曼編碼的方式解碼。

小渣構(gòu)造的哈夫曼樹如下:

可以發(fā)現(xiàn),A、B、C、D的編碼分別為10、111、0、110。

這樣小渣只要根據(jù)1~100題的答案順序發(fā)送01序列,老渣收到后進行解碼就能正確收到答案了。而且哈夫曼編碼的方式不會有歧義,因為哈夫曼編碼是一種前綴編碼。

前綴編碼: 沒有一個編碼是另一個編碼的前綴,因為數(shù)據(jù)節(jié)點都是葉子節(jié)點。如果出現(xiàn)一個字符的編碼是另一個字符編碼的前綴,那這個字符一定處于內(nèi)部節(jié)點,這是不可能的

由哈夫曼樹得到的哈夫曼編碼: 字符集中的每個字符都是以葉子結(jié)點出現(xiàn)在哈夫曼樹中,各個字符出現(xiàn)的頻率為結(jié)點的權(quán)值。

給字符串進行編碼的時候,由于出現(xiàn)頻率越高(權(quán)值大)的字符距離根節(jié)點越進,編碼越短;只有出現(xiàn)頻率越低(權(quán)值?。┑淖址嚯x根節(jié)點較遠,編碼長。沒關(guān)系,由于頻率高的字符編碼都短,所以哈夫曼編碼可以得到最短的編碼序列

五、哈夫曼解碼

哈夫曼編碼不同于ASCII和Unicode這些字符編碼,這些字符集中的碼長都采用的是長度相同的編碼方案,而哈夫曼編碼使用的是變長編碼,而且哈夫曼編碼滿足立刻可解碼性(就是說任一字符的編碼都不會是另一個更長字符編碼的前綴),只要當(dāng)某個字符的編碼中所有位被全部接收時,可以立即進行解碼而無須等待后面接收的位來決定是否存在另一個合法的更長的編碼

第一張表不滿足立刻可解碼性,第二張表滿足

我們接收100的時候,需要考慮是立刻解碼成D還是等待接收下一位,如果下一位是0就可以解碼成B,這就說明表中的編碼不具有立刻可解碼性

第二張表就具有立刻可解碼性,因為任一字符的編碼都不是另一個更長字符編碼的前綴。只要收到的序列對應(yīng)了某個字符的編碼,直接解碼成對應(yīng)字符即可,無需等待后面的數(shù)據(jù)

我們的代碼實現(xiàn)是用字符串構(gòu)建哈夫曼樹,只能針對由該字符串包含的字符組成的字符串進行編解碼。代碼里使用字符串存儲的編碼,實際上應(yīng)該用bit進行存儲

#include <iostream>
#include <string>
#include <vector>
#include <functional>
#include <unordered_map>
#include <queue>
using namespace std;
using uint = unsigned int;
class HuffmanTree {
public:
    // 這里的lambda表達式用來初始化function函數(shù)對象
    // priority_queue的構(gòu)造函數(shù)指出,如果傳入一個參數(shù),那這個參數(shù)用來初始化比較器對象
    // 如果傳入兩個參數(shù),第一個是比較器對象,第二個是底層容器
    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)計頻率(權(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)先級隊列,從小根堆中取出節(jié)點,構(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é)點存\0
            node->left_ = n1;
            node->right_ = n2;
            min_heap_.push(node);
        }
        root_ = min_heap_.top();
        min_heap_.pop();
        // 創(chuàng)建完哈夫曼樹,直接對傳入的海量字符進行編碼并存儲到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) {
                // 到達葉子節(jié)點
                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é)點,直接返回自己的weight * depth
            return root_->weight_ * 1;
        }
        else {
            // 對于內(nèi)部節(jié)點,直接返回從子節(jié)點拿到的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é)點,然后初始化私有成員
    void init() {
        // 釋放哈夫曼樹的節(jié)點
        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é)點,直接返回自己的weight * depth
            return node->weight_ * depth;
        }
        else {
            // 對于內(nèi)部節(jié)點,直接返回從子節(jié)點拿到的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)建哈夫曼樹的時候使用小根堆
};
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;
}

六、文件的壓縮和解壓縮

我們利用哈夫曼編碼壓縮文件的時候,如果文件是100M,我們可以壓縮成20M,如果文件時1K,我們可能壓縮成2K,當(dāng)文件較小的時候,我們得到的壓縮文件反而更大了,這是為什么?

文件的壓縮過程如下:

  • 按字節(jié)讀取原文件的所有內(nèi)容,并統(tǒng)計字節(jié)數(shù)據(jù)的權(quán)值,構(gòu)建哈夫曼樹
  • 通過哈夫曼樹,得到文件的哈夫曼編碼
  • 把文件的內(nèi)容按字節(jié)進行編碼,將編碼內(nèi)容按bit存儲成壓縮文件,還要存儲文件字節(jié)數(shù)據(jù)以及權(quán)值

解碼的過程如下:

  • 讀取原始文件的字節(jié)數(shù)據(jù)以及權(quán)值,構(gòu)建哈夫曼樹
  • 讀取壓縮文件的01碼,利用哈夫曼樹對01進行解碼,將解碼數(shù)據(jù)存儲成新的文件,就解碼出了原始文件

由于壓縮文件不僅存儲01碼,還需要存儲文件字節(jié)數(shù)據(jù)以及權(quán)值用來重建哈夫曼樹(就是代碼中的w_map)。當(dāng)原始文件較小時,文件字節(jié)數(shù)據(jù)以及權(quán)值可能大于原始文件的大小,故小文件壓縮后可能變大

到此這篇關(guān)于C++詳解哈夫曼樹的概念與實現(xiàn)步驟的文章就介紹到這了,更多相關(guān)C++哈夫曼樹內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C++智能指針實例詳解

    C++智能指針實例詳解

    這篇文章主要介紹了C++智能指針實例詳解,需要的朋友可以參考下
    2014-07-07
  • C++編程使用findfirst和findnext查找及遍歷文件實現(xiàn)示例

    C++編程使用findfirst和findnext查找及遍歷文件實現(xiàn)示例

    這篇文章主要為大家介紹了C++編程如何使用findfirst和findnext查找及遍歷文件實現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2021-10-10
  • C++實現(xiàn)堆排序示例

    C++實現(xiàn)堆排序示例

    這篇文章主要介紹了C++實現(xiàn)堆排序示例,全文運用大量代碼完成堆排序,需要了解的朋友可以參考一下這篇文章
    2021-08-08
  • 數(shù)據(jù)結(jié)構(gòu)與算法:單向鏈表實現(xiàn)與封裝

    數(shù)據(jù)結(jié)構(gòu)與算法:單向鏈表實現(xiàn)與封裝

    今天小編就為大家分享一篇關(guān)于數(shù)據(jù)結(jié)構(gòu)與算法:單向鏈表實現(xiàn)與封裝,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2018-12-12
  • C++?中的類型詳細

    C++?中的類型詳細

    這篇文章主要介紹了C++?中的類型,C++的類型很復(fù)雜,往往一個類型匹配錯誤就會導(dǎo)致程序報錯,本篇主要講解一些常用類型的概念以及細節(jié),需要的朋友可以參考一下,希望對你有所幫助
    2021-12-12
  • C語言實現(xiàn)的雙鏈表功能完整示例

    C語言實現(xiàn)的雙鏈表功能完整示例

    這篇文章主要介紹了C語言實現(xiàn)的雙鏈表功能,結(jié)合完整實例形式分析了基于C語言實現(xiàn)的雙鏈表定義、添加、刪除、排序等相關(guān)操作實現(xiàn)技巧,需要的朋友可以參考下
    2018-04-04
  • C++?實現(xiàn)讀寫鎖的代碼詳解

    C++?實現(xiàn)讀寫鎖的代碼詳解

    C++讀寫鎖是一種多線程同步機制,它允許多個線程同時讀取共享數(shù)據(jù),但只允許一個線程寫入共享數(shù)據(jù),這種鎖可以提高程序的并發(fā)性和性能,本文給大家介紹了C++?實現(xiàn)讀寫鎖的代碼,需要的朋友可以參考下
    2023-10-10
  • C++二維數(shù)組螺旋加密信息

    C++二維數(shù)組螺旋加密信息

    大家好,本篇文章主要講的是C++二維數(shù)組螺旋加密信息,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽
    2021-12-12
  • 利用c++編寫簡易版2048小游戲

    利用c++編寫簡易版2048小游戲

    這篇文章主要介紹了如何讓利用c++編寫簡易版的2048小游戲,感興趣的小伙伴請參考下面文章的具體內(nèi)容
    2021-09-09
  • 基于VC 6.0使用C語言實現(xiàn)俄羅斯方塊

    基于VC 6.0使用C語言實現(xiàn)俄羅斯方塊

    這篇文章主要為大家詳細介紹了基于VC 6.0使用C語言實現(xiàn)俄羅斯方塊,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-06-06

最新評論

黑河市| 河西区| 高雄市| 甘孜| 辽宁省| 洛宁县| 陇南市| 无极县| 泽普县| 富阳市| 来宾市| 青川县| 仙游县| 晋州市| 南岸区| 英德市| 将乐县| 田东县| 齐河县| 日喀则市| 大宁县| 赤峰市| 繁昌县| 江油市| 新丰县| 华容县| 修水县| 新乡市| 个旧市| 济宁市| 陈巴尔虎旗| 波密县| 铜鼓县| 周口市| 禄丰县| 当涂县| 静宁县| 丹阳市| 清原| 循化| 武清区|