1. 程式人生 > >QT實現哈夫曼壓縮(多執行緒)

QT實現哈夫曼壓縮(多執行緒)

本人菜雞程式設計師一枚,最近剛剛學習的資料結構中的哈夫曼樹,就用QT寫了一個哈夫曼壓縮,話不多說先上步驟,再上程式碼。(如果有更好的想法,歡迎指點)

1.先寫出建最小堆和建哈夫曼樹程式碼(建最小堆的程式碼可以通過STL中的堆代替)
2.寫出壓縮類的程式碼,類中有一個帶有壓縮資訊的陣列,非常重要
3.掃描一遍檔案建立陣列和哈夫曼樹
4.遍歷一遍哈夫曼樹完善程式碼
5.遍歷檔案,通過陣列來進行壓縮

以下壓縮的介面:
在這裡插入圖片描述

heap.h:

#ifndef HEAP_H
#define HEAP_H

#include"huffman.h"
//模板構造堆
template<class T>
class Heap
{
public:
    //建構函式、解構函式
    Heap(int sz = 10);
    Heap(T arr[], int n);
    ~Heap()
    {
        delete[]heap;
    }

    int size()                      //返回堆當前的大小
    {
        return currentsize;
    }
    bool Insert(const T &x);         //插入函式
    bool RemoveMin(T &x);           //刪除函式
    bool IsEmpty()const             //判空
    {
        return (currentsize == 0) ? true : false;
    }

    bool IsFull()                   //判滿
    {
        return (currentsize == maxHeapsize) ? true : false;
    }

    void MakeEmpty()                //置空函式
    {
        currentsize == 0;
    }

private:
    T * heap;                             //陣列
    int currentsize;                     //當前元素個數
    int maxHeapsize;                    //允許最大元素個數
    void siftDown(int start, int m);    //下滑調整
    void siftUp(int start);             //上滑調整
};

//建構函式
template<class T>
Heap<T>::Heap(int sz)
{
    maxHeapsize = sz;
    heap = new T[maxHeapsize];
    currentsize = 0;
}

//通過陣列構造
template<class T>
Heap<T>::Heap(T arr[], int n)
{
    maxHeapsize = n;
    heap = new T[maxHeapsize];
    for (int i = 0; i++; i<n)    //複製陣列元素到堆中
        heap[i] = arr[i];
    currentsize = n;
    int currentPos = (currentsize - 2) / 2;
    while (currentPos >= 0)
    {
        siftDown(currentPos, currentsize - 1);         //從上到下下滑調整
        currentPos--;
    }
}

//插入函式
template<class T>
bool Heap<T>::Insert(const T &x)
{
    if (currentsize == maxHeapsize)
        return false;
    heap[currentsize] = x;        //插入元素
    siftUp(currentsize);        //進行上滑調整
    currentsize++;
    return true;

}
//將最小堆頂部的元素移除
template<class T>
bool Heap<T>::RemoveMin(T &x)
{
    if (!currentsize)
        return false;
    x = heap[0];
    heap[0] = heap[currentsize - 1];    //最後元素補到根節點
    currentsize--;
    siftDown(0, currentsize - 1);      //自上向下調整為堆
    return true;
}

template<class T>                   //下滑函式
void Heap<T>::siftDown(int start, int m)
{
    int i = start;            //開始的節點
    int j = 2 * i + 1;            //開始節點的左子女
    T temp = heap[i];
    while (j <= m)             //檢查是否在最後的位置
    {
        if (j<m && compare2(heap[j],heap[j+1]))
            j++;  //讓j指向兩者中較小者
        if (compare1(temp,heap[j]))               //小則不調整,這裡的compare1、compare2是因為模板巢狀太深寫的有待改進
            break;
        else
        {
            heap[i] = heap[j]; i = j; j = 2 * j + 1;
        }
    }
    heap[i] = temp;
}

template<class T>
void Heap<T>::siftUp(int start)     //上調函式
{
    int j = start, i = (j - 1) / 2;      //j為子女,i為父節點
    T temp = heap[j];
    while (j>0)
    {
        if (compare1(heap[i],temp))     
            break;
        else
        {
            heap[j] = heap[i];
            j = i;
            i = (i - 1) / 2;
        }
    }
    heap[j] = temp;
}
#endif // HEAP_H

------------------------------------------------------------------------------------------
huffman.h

#ifndef HUFFMAN_H
#define HUFFMAN_H

#include"heap.h"
#include<QStack>
#include<QString>

template<class T>
struct HuffmanNode      //樹節點的定義
{
    T data;             //節點的資料
    HuffmanNode<T> *leftChild, *rightChild, *parent;        //左右子女和 父節點指標
    HuffmanNode() :leftChild(NULL), rightChild(NULL), parent(NULL) {}
    HuffmanNode(T elem, HuffmanNode<T> *left = NULL, HuffmanNode<T> *right = NULL)
    {
        this->data = elem;
        this->leftChild = left;
        this->rightChild = right;
    }

    bool operator<=(HuffmanNode<T> &R)	//由於巢狀太深,此過載函式沒有起作用
    {
        return data <= R.data;
    }

    bool operator >(HuffmanNode<T> &R)
    {
        return data>R.data;
    }

    bool operator<=(HuffmanNode<T> *R)
    {
        return data<=R->data;
    }

    bool operator >(HuffmanNode<T> *R)
    {
        return data>R->data;
    }
};

//比較函式,有待修改改
template<class T>
bool compare1(HuffmanNode<T> *x, HuffmanNode<T> * y)
{
    if (x->data <= y->data)
        return true;
    else
        return false;
}

template<class T>
bool compare2(HuffmanNode<T> *x, HuffmanNode<T> * y)
{
    if (x->data>y->data)
        return true;
    else
        return false;
}

//哈夫曼樹
template<class T>
class HuffmanTree
{
public:
    //建構函式根據陣列創造樹
    HuffmanTree(T w[], int n);
    ~HuffmanTree();//解構函式

    HuffmanNode<T> *getRoot()       //獲取根節點
    {
        return root;
    }

protected:
    HuffmanNode<T> *root;          //根節點
    //void deleteTree(HuffmanNode<T> *t);    //刪除子樹
    void mergeTree(HuffmanNode<T> &ht1, HuffmanNode<T> &ht2, HuffmanNode<T> *&parent);                   //融合函式
};

template<class T>
HuffmanTree<T>::HuffmanTree(T w[], int n)       //通過陣列構建哈夫曼樹
{

    Heap<HuffmanNode<T> *> hp(256);    //使用最小堆放森林
    for (int i = 0; i<n; i++)
    {
        HuffmanNode<T> *work=new HuffmanNode<T>();
        work->data = w[i];
        work->leftChild = NULL;
        work->rightChild = NULL;
        work->parent = NULL;
        hp.Insert(work);
    }
    HuffmanNode<T> *parent = NULL;

    while (hp.size()>1)
    {
        HuffmanNode<T> *first, *second;
        //HuffmanNode<T> *parent = NULL;
        if (hp.RemoveMin(first) && hp.RemoveMin(second))
        {
            this->mergeTree(*first,*second, parent);
            hp.Insert(parent);
        }
    }
    hp.RemoveMin(root);

}

//解構函式(不能使用遞迴的函式,因為樹非常高,所以採用非遞迴方法)
template<class T>
HuffmanTree<T>::~HuffmanTree()
{
    QStack<HuffmanNode<T> *>Q1;
    QStack<HuffmanNode<T> *>Q2;
    HuffmanNode<T> *p = NULL;
    Q1.push(root);
    while (!Q1.empty())
    {
        p = Q1.pop();
        Q2.push(p);
        if (p->rightChild != NULL)
            Q1.push(p->rightChild);
        if (p->leftChild != NULL)
            Q1.push(p->leftChild);
    }

    while (!Q2.empty())
    {
        p = Q2.pop();
        delete p;
    }
}


//融合函式
template<class T>
void HuffmanTree<T>::mergeTree(HuffmanNode<T> &ht1, HuffmanNode<T> &ht2, HuffmanNode<T> *&parent)
{
    parent = new HuffmanNode<T>( ht1.data + ht2.data);
    parent->leftChild = &ht1;
    parent->rightChild = &ht2;
    ht1.parent = parent;
    ht2.parent = parent;
}

#endif // HUFFMAN_H

下面是重中之中了,是實現了壓縮和解壓的主要演算法
這個壓縮演算法我讓它繼承執行緒類,目的是多執行緒操作,如果不開執行緒,就一個主執行緒工作會使得壓縮大檔案時主介面呈現出假死狀態非常不好
compression.h

#ifndef COMPRESSION_H
#define COMPRESSION_H

#include"huffman.h"
#include<QString>
#include<stdio.h>
#include<QObject>
#include<QThread>
#include<QString>
#include<QTime>

//一個小資料,儲存著每個字元的資訊
struct  Info
{
    unsigned char  ch;                    //'a'
    quint64        count;                       //出現的頻度 23
    QString      str;                      //“10010”
    quint16      len;                 //長度為5  , UIN T  temp<<=len ;

    Info(long count = 0)                      //建構函式
    {
        ch = 0;
        this->count = count;
        len = 0;
        //value = 0;
    }

    ~Info()                                 //解構函式
    {
        ch = 0;
        count = 0;
        len = 0;
        str.clear();
    }

    bool operator<=(Info R)        //運算子過載
    {
        return count <= R.count;
    }

    bool operator >(Info R)
    {
        return count>R.count;
    }

    Info  operator+(Info &R)
    {
        long temp = this->count + R.count;
        return Info(temp);
    }

    bool operator<=(Info *R)        //運算子過載
    {
        return count <= R->count;
    }

    bool operator >(Info *R)
    {
        return count>R->count;
    }

    Info  operator+(Info *R)
    {
        long temp = this->count + R->count;
        return Info(temp);
    }
};

enum programe
{
    coding,
    decoding
};

//壓縮類
class Compression:public QThread
{
      Q_OBJECT
public:
   explicit   Compression(QObject *parent = 0);
    ~Compression();
    bool CreatInfo(QString path);                               //得到基本資訊建立樹
    void Coding(QString path);                                 //壓縮函式
    void Decoding(QString path);                               //解壓函式
    void setCode(HuffmanNode<Info> *node);
    void clear();

    int sum;                //位元組的總個數
    int currentcount;       //用於判斷位元組數是否達到總位元組數的100分之一
    int percentage;         //位元組100分之一
    programe condition;     //程序的狀態,是壓縮還是解壓
    QString MYpath;         //檔案路徑
    bool isoK;

signals:
    void mysigals();		//自定義訊號用於進度條
protected:
    void run();
private:
    Info My_Infos[256];		//儲存ASCii碼的轉化而成的陣列
    Info *bInfo;			//建樹時需要傳的資訊
    int size;              //檔案中字元型別的個數
    HuffmanTree<Info> *my_Tree;
    QTime time;         //測試時間
};

#endif // COMPRESSION_H

compression.cpp

#include "compression.h"
#include<QStack>
#include<QString>
#include <QFile>
#include <QDataStream>
#include<QMessageBox>
#include<QDebug>
#include <stdio.h>
#include <stdlib.h>
#include<QTime>
#include<QByteArray>

//建構函式
Compression::Compression(QObject *parent):QThread(parent)
{
    bInfo = NULL;
    my_Tree = NULL;
    size = 0;
    sum = 0;
    currentcount=0;
    percentage=0;
}

//解構函式
Compression::~Compression()
{
    if(bInfo!=NULL)
        delete[]bInfo;
    if(my_Tree!=NULL)
        delete my_Tree;
    size = 0;
    sum = 0;
}

bool Compression::CreatInfo(QString path)
{


  //  time.start();     //時間函式用於測試壓縮時間
    this->clear();      //清空內容
    for (int i = 0; i<256; i++)
    {
        My_Infos[i].ch = (unsigned char)i;			//將字元賦上初值
    }
    quint8 ch=0;

    QFile openfile(path);           //讀取二進位制檔案
    if(!openfile.open(QIODevice::ReadOnly))
    {

        qDebug()<<QString("開啟失敗");
        isoK=false;
        return false;
    }


    QByteArray a;
    while(!openfile.atEnd())
    {
        a=openfile.read(1024);		//採取的時1kb一讀,當然也可以一行一讀
        for(int i=0;i<a.size();i++)
        {
            ch=a[i];
            if (My_Infos[ch].count==0)//如果第一次讀到這個字元
            {
                size++;
            }
            My_Infos[ch].count++;  //字元型別數增加
            sum++; //次數增加。記錄檔案的位元組數,其中檔案的位元組數在檔案的屬性中可以看到
        }

    }

    openfile.close();
    bInfo = new Info[size];			//儲存那些出現次數不為0的字元
    for (int i = 0, j = 0; i<256; i++)
    {
        if (My_Infos[i].count != 0)
        {
            bInfo[j].ch = My_Infos[i].ch;
            bInfo[j].count = My_Infos[i].count;
            j++;
        }
    }

    percentage=sum/100;			//檔案位元組數的100分之一,用於進度條,在下面會有介紹
    my_Tree = new HuffmanTree<Info>(bInfo, size); //通過陣列建立樹
    this->setCode(my_Tree->getRoot());//完善陣列中的資訊,也就是獲得編碼
    return true;
}

//壓縮,有待優化
void Compression::Coding(QString path)
{
    QFile openfile(path);   //讀檔案
    if(!openfile.open(QIODevice::ReadOnly))
    {
       isoK=false;
        return ;
    }

    quint8 ch = 0;

    path=path+QString(".Nzip");     //寫檔案
    QFile savefile(path);
    if(!savefile.open(QIODevice::WriteOnly))
    {
        qDebug()<<"開啟失敗";
        isoK=false;
        return ;
    }
    QDataStream fout(&savefile);

    //將配置檔案寫進去,這個配置檔案時用於解壓的時候使用
    fout<<sum;
    fout<<size;
    for(int i=0;i<size;i++)
    {
        fout<<bInfo[i].ch<<bInfo[i].count;
    }

    //進行壓縮
    quint64  temp = 0;  //儲存的單位,就是每次讀入64位後就儲存一次
    int pos = 0;

    QByteArray a;
    while(!openfile.atEnd())
    {
        a=openfile.read(1024);		//同上1kb一讀
        for(int i=0;i<a.size();i++)
        {
            ch=a[i];
            currentcount++;							//當前讀的位元組數
            QString &strRecord = My_Infos[ch].str;		//採取引用速度更快
            quint16 &len= My_Infos[ch].len;
            for(int i=0;i<len;i++)
            {
                temp<<=1;
                if(strRecord[i]=='1')								//當讀到1時就將此位變為1
                {
                    temp|=1;
                }

                if(++pos==64)
                {
                    fout<<temp;
                    pos = 0;
                    temp = 0;
                }
            }

            if(currentcount==percentage)			//當讀到檔案位元組數的100分之一時,發射訊號,實現進度條的數值改變
            {
                emit mysigals();
                currentcount=0;
            }

        }

    }

    if (pos)    //最後的編碼不滿足64位,填充0
    {
        temp = temp << (64- pos);
        fout<<temp;
    }

    openfile.close();
    savefile.close();
    qDebug()<<"11111111111";
  //  qDebug()<<time.elapsed()/1000<<"s";
}

//解壓
void Compression::Decoding(QString path)
{

    this->clear();

    QFile openfile(path);   //讀檔案
    if(!openfile.open(QIODevice::ReadOnly))
    {
        QMessageBox::information(NULL,QString("失敗"),QString("檔案開啟失敗"));
        qDebug()<<QString("開啟失敗");
        isoK=false;
        return ;
    }
    QDataStream  fin(&openfile);

    //讀取配置檔案
    fin>>sum;
    fin>>size;
    percentage=sum/100;
    bInfo = new Info[size];

    for(int i=0;i<size;i++)
    {
       fin>>bInfo[i].ch>>bInfo[i].count;
    }


    my_Tree = new HuffmanTree<Info>(bInfo, size); //重新建立樹
    HuffmanNode<Info>  *p = my_Tree->getRoot();	//獲得樹的根節點
    path=path.remove(QString(".Nzip"));			//將尾綴去掉,獲得原來檔案
    //path="myfile.txt";
    QFile savefile(path);
    if(!savefile.open(QIODevice::WriteOnly))
    {
        QMessageBox::information(NULL,QString("失敗"),QString("檔案開啟失敗"));
        qDebug()<<QString("開啟失敗");
    }
    QDataStream fout(&savefile);

    unsigned char ch = 0;
    quint64 a=0;
    quint64 temp=0;
    int count = 0;          //字元數量的記錄
    while (sum>count)
    {

       fin>>a;
        for (int i=0;i<64;i++)
        {

            temp = a;
            temp >>= 63;
            if (temp==1)
                p = p->rightChild;  //向右搜尋;
            else
                p = p->leftChild;	//向左搜尋;
            if (p->leftChild == NULL && p->rightChild == NULL)	//讀取到也葉子節點時
            {
                // 將其中的資訊寫到檔案中 ;
                // *P = 根節點 ;重新 令p為根節點
                ch = p->data.ch;
                p = my_Tree->getRoot();
                fout<<ch;
                count++;		//字元數++
                currentcount++;
                if(currentcount==percentage)		
                {
                    emit mysigals();
                    currentcount=0;
                }
                if (count == sum)		//單獨讀的位元組數等於總數時,就跳出
                    break;
            }
            a<<=1;
        }
    }

    openfile.close();
    savefile.close();
    qDebug()<<"11111111111";

}

//設定字元,採取了非遞迴的方法,時一種非遞迴的前序遍歷
void Compression::setCode(HuffmanNode<Info> *node)
{

    QStack<HuffmanNode<Info> *>s;
    QStack<QString> s1;
    s.push(node);
    QString str;
    s1.push(str);
    HuffmanNode<Info> *p = NULL;
    while (!s.empty())
    {
        p = s.pop();
        QString temp = s1.pop();
        if (p->leftChild == NULL && p->rightChild == NULL) //當讀到葉子節點時
        {
            QString a = temp;
            My_Infos[p->data.ch].str = a;
            My_Infos[p->data.ch].len = a.size();
        }

        if (p->rightChild != NULL)
        {
            s.push(p->rightChild);
            QString a = temp + "1";	//向右加1
            s1.push(a);
        }

        if (p->leftChild != NULL)
        {
            s.push(p->leftChild);
            QString a = temp + "0";//向左加0
            s1.push(a);
        }
    }
}

//清空
void Compression::clear()
{
    for(int i=0;i<256;i++)
    {
        My_Infos[i].ch=0;
        My_Infos[i].count=0;
        My_Infos[i].len=0;
        My_Infos[i].str.clear();
    }


    if(bInfo!=NULL)
    {
        delete[]bInfo;
        bInfo=NULL;
    }

    if(my_Tree!=NULL)
    {
        delete my_Tree;
        my_Tree=NULL;
    }
    size = 0;
    sum = 0;
    currentcount=0;
    percentage=0;
}

//開執行緒
void Compression::run()
{
    isoK=true;
    if(condition==coding)
    {
      if(this->CreatInfo(MYpath))
      {
          this->Coding(MYpath);
      }

    }
    else if(condition==decoding)
        this->Decoding(MYpath);
}

下面是視窗的函數了
mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include"compression.h"
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QMimeData>
#include <QList>
#include <QDrag>
namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
    void display();
protected:
//重新寫了拖拽事件,實現了可以拖檔案到介面上,方便很多
    void dragEnterEvent(QDragEnterEvent *event);
    void dropEvent(QDropEvent *event);


private slots:
    void on_openButton_clicked();		//開啟檔案的按鈕

    void on_codeButton_clicked();		//壓縮的按鈕

    void on_decodeButton_2_clicked();		//解壓的按鈕

private:
    Ui::MainWindow *ui;
    Compression *process;				//執行緒解壓
    QString path;								//記錄檔案的路徑
    int z;										//記錄當前進度

};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<QFileDialog>
#include<QMessageBox>
#include<QString>
#include<QDebug>
#include<QTime>
#include<QObject>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    process=NULL;
    this->setAcceptDrops(true);
    ui->progressBar->setMinimum(0);
    ui->progressBar->setMaximum(100);
    ui->progressBar->setValue(0);
    z=0;
    if(process==NULL)
        process=new Compression();
    //接受訊號用於進度條
     connect(process,&Compression::mysigals,this,&MainWindow::display);

}

MainWindow::~MainWindow()
{
    delete ui;
    process->quit();
    if(process!=NULL)
        delete process;

}

//當用戶拖動檔案到視窗部件上時候,就會觸發dragEnterEvent事件
void MainWindow::dragEnterEvent(QDragEnterEvent *event)
{
    //如果為檔案,則支援拖放
    if (event->mimeData()->hasFormat("text/uri-list"))
        event->acceptProposedAction();
}

//當用戶放下這個檔案後,就會觸發dropEvent事件
void MainWindow::dropEvent(QDropEvent *event)
{
    //注意:這裡如果有多檔案存在,意思是使用者一下子拖動了多個檔案,而不是拖動一個目錄
    //如果想讀取整個目錄,則在不同的操作平臺下,自己編寫函式實現讀取整個目錄檔名
    QList<QUrl> urls = event->mimeData()->urls();
    if(urls.isEmpty())
        return;

    //往文字框中追加檔名
    foreach(QUrl url, urls) {
        QString file_name = url.toLocalFile();
        ui->lineEdit->setText(file_name);
        path=file_name;
    }
}

//進度條
void MainWindow::display()
{
    z++;
    ui->progressBar->setValue(z);
}

//開啟檔案
void MainWindow::on_openButton_clicked()
{
    path=QFileDialog::getOpenFileName(this,QString("選擇檔案"),QString("../.."),"Images (*.png *.xpm *.jpg);;"
                                                                            "Text files (*.txt);;XML files (*.xml);;"
                                      " (*.*);;");

    ui->lineEdit->setText(path);
}

//壓縮
void MainWindow::on_codeButton_clicked()
{
    ui->progressBar->setValue(0);
    z=0;
    process->MYpath=path;
    process->condition=coding;

    process->start();

    qDebug()<<"開始壓縮";

}
//解壓
void MainWindow::on_decodeButton_2_clicked()
{
    ui->progressBar->setValue(0);
    z=0;
    process->condition=decoding;
    process->MYpath=path;

    process->start();
    qDebug()<<"開始解壓";

}

壓縮完成:
在這裡插入圖片描述

壓縮的結果:
:在這裡插入圖片描述

至於壓縮的時間大概是100M的需要30S多,750M的需要4分鐘多,應該還行吧。
感謝您觀看到這裡,如果有什麼可以優化的地方,歡迎指點。