C++實現Huffman的編解碼

Huffman編碼主要是通過統計各元素出現的頻率,進而生成編碼最終達到壓縮的目的。

這裏是Huffman樹中節點的結構。

typedef struct Tree
{
    int freq;//頻率
    int key;//鍵值
    struct Tree *left, *right;
    Tree(int fr=0, int k=0,Tree *l=nullptr, Tree *r=nullptr):
        freq(fr),key(k),left(l),right(r){};
}Tree,*pTree;

首先用一個名爲freq的hashtable來記錄各個元素的頻率:

void read(){
    int a;

    std::ios::sync_with_stdio(false);
    while(cin>>a){
        if(freq.find(a)==freq.end()) {freq[a]=1;}
        else {freq[a]++;}
    }

}


Huffman樹的構建過程如下代碼所示:

void huffman()
{
    int i;
    string c;
    int fr;
    auto it = freq.begin();
    while(it!=freq.end()){
        Tree *pt= new Tree;
        pt->key = it->first;
        pt->freq = it->second;
        it++;
        th.Insert(pt);//此處的th爲一種優先隊列
    }

    while(true)//構建哈夫曼樹
    {

        Tree *proot = new Tree;
        pTree pl,pr;
        pl = th.findMin();
        th.Delete(0);
        if(th.isEmpty()){
                th.Insert(pl);
                break;
        }

        pr = th.findMin();
        th.Delete(0);
        //合併節點
        proot->freq = pl->freq + pr->freq;
        std::ios::sync_with_stdio(false);
        proot->left = pl;
        proot->right = pr;
        th.Insert(proot);
        //合併後再插入
    }

    string s;

    print_Code(th.findMin(), s);

    del(th.findMin());
}

其中print_Code和del函數如下:

void print_Code(Tree *proot, string st)//從根節點開始打印,左0右1
{
    if(proot == NULL)
        return ;
    if(proot->left)
    {
        st +='0';
    }
    print_Code(proot->left, st);
    std::ios::sync_with_stdio(false);
    if(!proot->left && !proot->right)
    {
        cout<<proot->key<<" ";
        for(size_t i=0; i<st.length(); ++i){
            cout<<st[i];ml+=st[i];
        }
        cout<<endl;encoded[proot->key]=ml;ml="";
    }
    st.pop_back();
    if(proot->right)
        st+='1';
    print_Code(proot->right, st);
}


void del(Tree *proot)
{
    if(proot == nullptr)
        return ;
    del(proot->left);
    del(proot->right);
    delete proot;
}
至此就完成了Huffman的編碼。

當然,由於huffman的編碼都是0或1,因此需要進行位的表示才能完成壓縮。注意,位需要以8個爲一組進行寫入:

while(in>>a){
            int x=atoi(a.c_str());
            auto m=encoded.find(x);
            //encoded是另外一個哈希表用於記錄元素及它的編碼
            if(m==encoded.end()) continue;
            else {
                    string t=m->second;
                    ss+=t;
            }
            
    }
    unsigned char buf = 0;
    int count = 0;
    int i = 0;
    while(ss[i] != '\0')//位寫入,8個爲一組
    {
        buf = buf | ((ss[i++]-'0') << (7 - count));
        count++;
        if (count == 8)
        {
            count = 0;
            fout << buf;
            buf = 0;
        }
    }
    if(count != 0)
        fout << buf;

至此就完成了Huffman的編碼以及壓縮,效果十分可觀:

當對69.6M的txt文件(其中含有大約10000000個數據)進行壓縮時,產生的encoded.bin文件僅爲24.6MB:Ubuntu測試環境:



下面進行Huffman解碼的解說:

Huffman解碼首先是根據編碼表進行huffman樹的重建:

void decode(){
    auto it=decoded.begin();
    Tree *p=proot;
    while(it!=decoded.end()){
            string s=it->first;int t=it->second;int i=0;
            while(i<s.length()){
                if(s[i]=='0') {
                    if(proot->left==nullptr) proot->left=new Tree(5);
                    proot=proot->left;
                    }
                else{
                    if(proot->right==nullptr) proot->right=new Tree(5);
                    proot=proot->right;
                    }
                i++;
            }
            proot->data=t;
            proot=p;
            it++;
    }

}

然後讀取bin文件的一系列位:

while (f.get(c)){
            stringstream a;
            for (int i = 7; i > 0; i--)
                a<<((c >> i) & 1);
            a<<(c&1);
            m+=a.str();//將位轉爲字符串
    }

然後用Huffman樹根據左0右1進行查找並輸出:

int i=0;Tree *p=proot;

    while(i<m.length()){
            if(m[i]=='0'&&proot->left!=nullptr)
                {proot=proot->left;i++;}
            else if(m[i]=='1'&&proot->right!=nullptr)
                {proot=proot->right;i++;}
            else
                {cout<<proot->data<<endl;proot=p;}
    }
至此就完成了Huffman樹的解碼:


總的來說,Huffman對於大數據的壓縮效果是很好的,運行時間也很快,大概需要20s就可以完成對1000000個數據的編碼壓縮。

難點在於位的寫入與讀取,花了相當多的精力進行操作。

發佈了38 篇原創文章 · 獲贊 5 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章