本人菜鸡程序员一枚,最近刚刚学习的数据结构中的哈夫曼树,就用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
#include
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); //删除子树
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 *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
#include
#include
#include
#include
#include
//一个小数据,储存着每个字符的信息
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
#include
#include
#include
#include
#include
#include
#include
#include
#include
//构造函数
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()<
}
//解压
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
#include"compression.h"
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
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分钟多,应该还行吧。
感谢您观看到这里,如果有什么可以优化的地方,欢迎指点。
源代码传送门