1. 程式人生 > >利用哈弗曼樹實現檔案壓縮

利用哈弗曼樹實現檔案壓縮

一、預備知識

  給定n個權值作為n個葉子結點,構造一棵二叉樹,若該樹的帶權路徑長度達到最小,稱這樣的二叉樹為最優二叉樹,也稱為哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。
  本專案使用的Huffman Tree的K-V裡面存放的是字元以及字元出現的次數,構建成Huffman Tree 大體如下:
這裡寫圖片描述
  根據這棵樹,我們可以得到每個字元的編碼,例如a的編碼就是1,d的編碼就是000,c的編碼就是001。那麼問題來了,我們要如何得到這棵數呢?得到的編碼又該如何處理呢?

二、具體思路

1. 使用雜湊表統計字元出現的次數

  構建Huffman Tree我們採用貪心演算法。首先要生成一張關於字元的K-V模型的雜湊表(採用直接定值法)。

CharInfo _hashInfos[256];
//雜湊表儲存元素的資訊:
struct configInfo{
    char _ch;        //字元
    LongType _count; //字元出現的次數
};

注意:此處雜湊表的範圍是[0, 255],而字元的範圍是[-127, 128],所以統計字元個數時要先把字元強轉成無符號型別的,例如:_hashTable[(unsigned char)ch]._count++

2. 生成Huffman Tree

  • 把雜湊表中的元素插入到優先順序佇列中,構建小堆。
  • 從小堆每次拿兩個元素進行相加,將相加後的結果依舊放回到優先順序佇列中,並保持小堆。
HufmanTree(W* w, size_t n, W& invalid)
{
    // 構建哈弗曼樹
    priority_queue<Node*, vector<Node*>, NodeCompare> minHead;
    size_t i = 0;
    for (i = 0; i < n; ++i){
        if (w[i] != invalid)
            minHead.push(new Node(w[i]));
    }
        while (minHead.size() > 1){
        Node* left = minHead.top();
        minHead.pop();
        Node* right = minHead.top();
        minHead.pop();
        Node* parent = new
Node(left->_w + right->_w); parent->_left = left; parent->_right = right; minHead.push(parent); } _root = minHead.top(); }

3. 生成Huffman Code

void generateHuffmanCode(Node* root)
{
    if(root == nullptr)
        return;

    if(!root->_left && !root->_right){
        _hashInfos[(unsigned char)root->_w._ch]._code = root->_w._code;
           return;
    }

    // 往左邊走,把左孩子的 code 加上'0'
    if(root->_left){
        root->_left->_w._code = root->_w._code + '0';
        generateHuffmanCode(root->_left);
    }

    // 往右邊走,把右孩子的 code 加上'1'
    if(root->_right){
        root->_right->_w._code = root->_w._code + '1';
        generateHuffmanCode(root->_right);
    }
}

4. 開始壓縮

  • 把雜湊表中的內容也寫到到壓縮檔案中,方便解壓時重建二叉樹。
  • 把字元及其對應的字元編碼寫到壓縮檔案中,逐位元組寫入。
  • 如果寫完最後一個字元且不滿一位元組,往裡面填充0。

5. 解壓縮

  • 讀取檔案中的雜湊表,重建Huffman Tree。
  • 根據Huffman Tree和Huffman Code還原對應字元,寫到解壓縮檔案中。
  • 根據Huffman Tree的根節點,確定解壓縮的字元的個數。

三、原始碼

// HuffmanTree.h

#ifndef __HUFFMANTREE_H__
#define __HUFFMANTREE_H__

#include <iostream>
#include <stdio.h>
#include <queue>
#include <vector>

// 哈弗曼樹節點內容
template<class W>
class HuffmanTreeNode{
public:
    HuffmanTreeNode<W>* _left;
    HuffmanTreeNode<W>* _right;
    HuffmanTreeNode<W>* _parent;
    W _w;

    HuffmanTreeNode(const W& w)
        :_left(NULL)
        , _right(NULL)
        , _parent(NULL)
        , _w(w)
    {}
};

// 哈弗曼樹
template<class W>
class HuffmanTree{
    typedef HuffmanTreeNode<W> Node;
public:
    HuffmanTree()
        :_root(NULL)
    {}

    // 仿函式(根據字元出現的次數進行比較)
    struct NodeCompare{
        bool operator()(const Node* l, const Node* r){
            return l->_w > r->_w;
        }
    };

    HuffmanTree(W* w, size_t n, W& invalid)
    {
        // 構建哈弗曼樹
        priority_queue<Node*, vector<Node*>, NodeCompare> minHead;
        size_t i = 0;
        for (i = 0; i < n; ++i){
            if (w[i] != invalid)
                minHead.push(new Node(w[i]));
        }

        while (minHead.size() > 1){
            Node* left = minHead.top();
            minHead.pop();
            Node* right = minHead.top();
            minHead.pop();
            Node* parent = new Node(left->_w + right->_w);
            parent->_left = left;
            parent->_right = right;
            minHead.push(parent);
        }
        _root = minHead.top();
    }

    ~HuffmanTree()
    {
        Destory(_root);
        _root = NULL;
    }

    void Destory(Node* root)
    {
        if (root == NULL){
            return;
        }
        Destory(root->_left);
        Destory(root->_right);
        delete root;
    }

    Node* GetRoot()
    {
        return _root;
    }
private:
    HuffmanTree(const HuffmanTree<W>& t);
    HuffmanTree& operator=(const HuffmanTree<W>& t);
protected:
    Node* _root;
};

#endif //__HUFFMANTREE_H__

// FileCompress.h

#ifndef __FILECOMPRESS_H__
#define __FILECOMPRESS_H__

#include "HuffmanTree.h"
#include <iostream>
#include <stdio.h>
#include <fstream>
#include <assert.h>
#include <string>
using namespace std;

typedef long long LongType;

struct CharInfo{
    char _ch;        //字元
    string _code;    //編碼
    LongType _count; //出現次數

    // 過載+ 是為了在構建哈夫曼樹時,parent = left+right
    CharInfo operator+(const CharInfo& info)
    {
        CharInfo ret;
        ret._count = _count + info._count;
        return ret;
    }

    bool operator>(const CharInfo& info) const
    {
        return _count > info._count;
    }

    bool operator != (const CharInfo& invalid)
    {
        return _count != invalid._count;
    }
};

class FileCompress{
    typedef HuffmanTreeNode<CharInfo> Node;

public:
    struct configInfo
    {
        char _ch;
        LongType _count;
    };
    FileCompress()
    {
        size_t i = 0;
        for (i = 0; i < 256; ++i){
            _hashInfos[i]._ch = i;
            _hashInfos[i]._count = 0;
        }
    }
    void Compress(const char* file)
    {
        // 1.統計檔案中字元出現的次數
        ifstream ifs(file, ios_base::in | ios_base::binary);
        char ch;
        while (ifs.get(ch)){
            // 因為 char的範圍是-127-128,而使用負數做下標對雜湊表進行訪問會出錯,所以強轉成無符號的
            _hashInfos[(unsigned char)ch]._count++;
        }

        // 2.生成哈弗曼樹
        CharInfo invalid;
        invalid._count = 0;
        HuffmanTree<CharInfo> tree(_hashInfos, 256, invalid);

        // 3.生成 Huffman code
        generateHuffmanCode(tree.GetRoot());

        // 4.壓縮
        string compressFile = file;
        // 給壓縮檔案新增字尾
        compressFile += ".huffman";
        ofstream ofs(compressFile.c_str(), ios_base::out | ios_base::binary);

        // 把字元和次數也寫到壓縮檔案中,方便解壓時生成 _hashInfos
        for (size_t i = 0; i < 256; ++i){
            if (_hashInfos[i]._count > 0){
                configInfo info;
                info._ch = _hashInfos[i]._ch;
                info._count = _hashInfos[i]._count;
                ofs.write((char*)&info, sizeof(configInfo));
            }
        }
        configInfo end;
        end._count = 0;
        ofs.write((char*)&end, sizeof(configInfo));

        ifs.clear();  //清理一下,下面seekg才起作用
        ifs.seekg(0);
        char value = 0;
        int pos = 0;

        //把編碼寫入到壓縮檔案中
        while (ifs.get(ch)){
            string& code = _hashInfos[(unsigned char)ch]._code;
            for (size_t i = 0; i < code.size(); ++i){
                if (code[i] == '0') value &= (~(1 << pos));
                else if (code[i] == '1') value |= (1 << pos);
                else assert(false);

                ++pos;
                if (pos == 8){
                    ofs.put(value);
                    pos = 0;
                    value = 0;
                }
            }
        }
        // 如果最後一個位元組沒填滿,直接放進去
        if (pos > 0) ofs.put(value);
    }


    void generateHuffmanCode(Node* root)
    {
        if(root == nullptr)
            return;

        if(!root->_left && !root->_right){
            _hashInfos[(unsigned char)root->_w._ch]._code = root->_w._code;
            return;
        }

        // 往左邊走,把左孩子的 code 加上'0'
        if(root->_left){
            root->_left->_w._code = root->_w._code + '0';
            generateHuffmanCode(root->_left);
        }

        // 往右邊走,把右孩子的 code 加上'1'
        if(root->_right){
            root->_right->_w._code = root->_w._code + '1';
            generateHuffmanCode(root->_right);
        }
    }

    void Uncompress(const char* file)
    {
        // 1. 開啟壓縮檔案,進行解壓
        ifstream ifs(file, ios_base::in | ios_base::binary);
        string uncompressfile = file;
        size_t pos = uncompressfile.rfind('.');
        assert(pos != string::npos);
        uncompressfile.erase(pos);

        uncompressfile += ".unhuffman";
        ofstream ofs(uncompressfile.c_str(), ios_base::out | ios_base::binary);

        // 重構 _hashInfos
        while (1){
            configInfo info;
            ifs.read((char*)&info, sizeof(configInfo));
            if (info._count > 0)
            {
                _hashInfos[(unsigned char)info._ch]._count = info._count;
            }
            else
            {
                break;
            }
        }

        // 2. 重建Huffman Tree
        CharInfo invalid;
        invalid._count = 0;
        HuffmanTree<CharInfo> tree(_hashInfos, 256, invalid);

        // 3. 根據 Huffman Code 解壓縮
        char ch;
        Node* root = tree.GetRoot();
        LongType fileCount = root->_w._count;  //記錄了檔案總字元的個數,為解壓做準備

        Node* cur = root;
        while (ifs.get(ch)){
            for (size_t pos = 0; pos < 8; ++pos){

                if (ch & (1 << pos)) cur = cur->_right;  // 1
                else cur = cur->_left;                   // 0

                if (!cur->_left && !cur->_right) {       //
                    ofs.put(cur->_w._ch);
                    cur = root;

                    if (--fileCount == 0) break; // 解壓完成
                }
            }
        }
    }
private:
    CharInfo _hashInfos[256];
};

void TestFileCompress()
{
    FileCompress fc;
    fc.Compress("Input.txt");
}

void TestFileUncompress()
{
    FileCompress fc;
    fc.Uncompress("Input.txt.huffman");
}

#endif //__FILECOMPRESS_H__

四、壓縮率

原始檔型別 原始檔大小 壓縮檔案大小 壓縮率
txt文字文件 3502kb 2546kb 72.70%
視訊檔案 56.5mb 56.24mb 99.53%

相關推薦

利用實現檔案壓縮

一、預備知識   給定n個權值作為n個葉子結點,構造一棵二叉樹,若該樹的帶權路徑長度達到最小,稱這樣的二叉樹為最優二叉樹,也稱為哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。   本專案使用的Huffma

利用實現檔案壓縮和解壓縮

利用庫中的優先順序佇列實現哈夫曼樹,最後基於哈夫曼樹最終實現檔案壓縮。 描述: 1.統計檔案中字元出現的次數,利用優先順序佇列構建Haffman樹,生成Huffman編碼。 構造過程可以使用priority_queue輔助,每次pq.top

利用進行檔案壓縮

專案描述: 專案簡介:利用哈夫曼編碼的方式對檔案進行壓縮,並且對壓縮檔案可以解壓 開發環境:windows vs2013 專案概述:         1.壓縮             a.讀取檔案,將每個字元,該字元出現的次數和權值構成哈夫曼樹             b

【資料結構與演算法】 利用進行檔案壓縮 (部分借鑑網上內容)

哈夫曼編碼(Huffman Coding),又稱霍夫曼編碼,是一種編碼方式,哈夫曼編碼是可變字長編碼(VLC)的一種。Huffman於1952年提出一種編碼方法,該方法完全依據字元出現概率來構造異字頭的平均長度最短的碼字,有時稱之為最佳編碼,一般就叫做Huffman編碼(

以及檔案壓縮實現

一、HuffmanTree 哈夫曼樹也稱為最優二叉樹,是加權路徑長度最短的二叉樹。在講述哈夫曼樹之前先給出幾個概念: 路徑:從一個結點到一個結點之間的分支構成這兩個結點之間的路徑 路徑長度:路徑上分支

基於檔案壓縮

基本思想: 壓縮: 1、統計出檔案中相同字元出現的次數 2、獲取哈夫曼編碼 次數作為權值構建哈夫曼樹 3、重新編碼,寫回壓縮檔案 儲存標頭檔案: 原始檔字尾 編碼資訊的行數 每個字元的權 儲存編碼 解壓縮: 1、獲取原

資料結構————檔案壓縮利用編碼實現

檔案壓縮原理: 首先檔案壓縮是通過HuffmaCode實現的、整體思路通過讀取檔案獲取字元出現頻率,通過字元出現頻率可以構建HuffmanTree,每個檔案中出現的字元通過HuffmanTree獲取HuffmanCode,從而將檔案中的字元同過HuffmanTree獲取相應編碼,並寫入壓

編碼實現檔案壓縮和解壓

哈夫曼編碼的概念 哈夫曼編碼是基於哈夫曼樹實現的一種檔案壓縮方式。 哈夫曼樹:一種帶權路徑最短的最優二叉樹,每個葉子結點都有它的權值,離根節點越近,權值越小(根節點權值為0,往下隨深度增加依次加一),樹的帶權路徑等於各個葉子結點的數值與其權值的乘積和。哈夫曼樹如圖: 從圖中我們可以看出

及其操作

ret main turn 分享 nbsp new private pla 運行 1.哈弗曼樹的節點聲明 1 package com.neusoft.Tree; 2 3 public class HuffmanNode { 4 public i

NYOJ 55 懶省事的小明()

pad sans div sam time span border dsm 方案 懶省事的小明 時間限制:3000 ms | 內存限制:65535 KB 難度:3 描寫敘述 小明非常想吃果子,正好果園果子熟了。在果園裏,小明已經將全部的果子

的編碼

哈弗曼 編碼 class strcpy HA span -s 結點 scan 1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #def

(原始碼,具體的細節請查閱相關資料)的構造以及非遞迴遍歷

  寫了一點haffman樹的建立和二叉樹的非遞迴遍歷. 如果編寫程式碼的時候出現了,思維斷點,可以借鑑一下, 避免浪費一些不必要的時間. 我是佔位符我是佔位符我是佔位符我是佔位符我是佔位符我是佔位符我是佔位符我是佔位符我是佔位符我是佔位符我是佔位符我是佔

利用編碼解碼

哈夫曼(Haffman)樹(最優樹) 定義: 給定n個權值作為n個葉子結點,構造一棵二叉樹,若該樹的帶權路徑長度達到最小,稱這樣的二叉樹為最優二叉樹,也稱為哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。 構造過程: 以 1,7,3,4,9,8為例: 第

編碼實現文字壓縮和解壓(C++)

哈弗曼樹:又稱最優二叉樹,是帶權路徑長度最短的樹。 哈夫曼編碼:是一種字首編碼,即同一字符集中任何一個字元的編碼都不是另外一個字元編碼的字首(最左子串)。         在哈弗曼樹中,若用‘0’表示左子樹,‘1’表示右子樹,那麼每當從根遍歷到一個葉子節點時都會形成一個0

基於演算法的檔案壓縮軟體

資料結構課設(一) 作業要求 1、設計並實現一個使用哈夫曼演算法對檔案進行壓縮的工具軟體。 2、通過命令列引數指定操作模式(壓縮/解壓)、原始檔名、目標檔名。 3、壓縮操作將原始檔按位元組讀入並統計位元組頻率,生成位元組的哈夫曼編碼,將編碼樹和用哈夫曼編碼對位元組重新編碼後的結果儲存

】 WOJ2343 圍欄維修

【描述】 農民 John 希望修復圍繞農場的一小段圍欄。他測量了一下,發現需要N (1 <= N <= 20,000) 根木頭,每根都有某一個整數長度 Li (1 <= Li <= 50,000) 單位長度。 他買了一根很長的很長的木頭,正好能夠鋸出他所需要的N根木頭。(

用Huffman實現檔案壓縮與解壓

用Huffman樹實現檔案的壓縮與解壓 我們先來了解一下什麼是Huffman樹? 我們平常所使用的Zip等壓縮工具都是藉助Huffman樹實現的,Huffman是一種特殊的二叉樹,它是一種加權路徑最短的二叉樹, 因此也稱為最優二叉樹。 (下面用一

c++ 資料結構 *** 的應用——壓縮軟體

資料結構的作業,壓縮軟體用的,具體寫的過程中有哪些問題在程式裡說吧。 標頭檔案與常量部分: 利用char的8位,來儲存檔案裡的元素。每次取出檔案中的8位並記錄這八位出現的次數用來進行哈夫曼數的建立。 #include<iostream> #include<

資料結構——實現

利用小堆實現哈夫曼樹 Heap.h #pragma once #include <vector> #include <assert.h> //仿函式 template<class T> struct Less {

的編碼

1.標頭檔案HuffmanTree.h #include <malloc.h> #include <stdio.h> struct TreeNode { char data; int value; TreeNode * leftNode;