诸位既然点开了本帖,相信对此问题已有初步了解,哈夫曼树的原理不再赘述,我们开门见山,直入主题。
一、概要设计
问题拆解:设计一个基于哈夫曼编码的解压缩软件,这个问题我认为可以分解为以下几个子问题:
- 读取传入文件,进行字符权重统计
- 将出现的字符放入哈夫曼树结点,构建哈夫曼树,获取哈夫曼编码
- 将编码相关信息写入压缩后的文件,再将传入文件的每个字符按照哈夫曼编码转换,每8个二进制位作为一个字节传入压缩后的文件
- 解压部分:将传入的已压缩文件进行文件流读取,获取编码信息进行还原
根据这几个子问题的思路顺序,我们逐个击破,寻找解决方案:
- 读取传入文件,需要用到文件流。这里推荐使用Qt自带的
QFile
文件流配以QDatastram
辅助。因为,C++的文件流fstream
无法识别中文路径(一说起这个,就想起Debug时的辛酸)。权重统计我用的是map
记录:map<unsigned char ,int>Weightmap
。 - 哈夫曼结点的构造,和二叉树没有什么区别,只是在封装的
struct
里面加了unsigned char
型的字符,int
型的权重,string
型的哈夫曼编码,和是否是叶子结点的bool
型标记(哈夫曼树叶子结点才是我们需要的编码)。将前面我们获取到的字符和权重加进去。
我们将建立好的结点放入到一个vector
内(理论上什么容器都可以),进入循环:根据哈夫曼结点的权重比较进行排序(直接调用sort
),每次将连个最小的结点权重取出,相加得到权重和,以权重和建立一个新的结点,新结点的左右孩子结点就是这两个结点,将新结点加入到vector
中,那两个结点删除。循环结束条件为vector
内只剩下一个结点。这个结点便是哈夫曼树的根结点,保存一根结点足矣。
我们从根结点出发遍历,左子树的string+“0”
,右子树的string+“1”
。并将unsigned char
型的字符与这个string
用map<unsigned char,string> PasswordMap
记录。 - 传入辅助信息阶段:我们传入的信息有,
PasswordMap.size()
,循环传入PasswordMap->first
(字符),PasswordMap->second.size()
(记录编码长度),PasswordMap->second
(这个字符串有可能超过8位,每个字节不足8位的部分补0)直到完全传入。另外一个方法是将所有字符与字符权重传入,在解压缩时再次构建哈夫曼树,两种思路我觉得都可以。这里我采用的第一种方法。
传入哈夫曼编码阶段:遍历先前在读取文件时获取的字符串,将字符串的每一位转化为哈夫曼编码,用另一个字符串储存。这里不妨称这个字符串为二进制字符串,这个二进制字符串按8分割到最后可能会有不足8位的部分,对其进行补0操作。 最后再传入一个字节,记录补0数。 - 解压部分:对应上部分的操作进行对应读取,主要思路是将压缩时传入的辅助信息提取,建立一个新的
Map
映射,根据这个映射,将原文件还原。
二:设计效果展示:
压缩前:
压缩中:
压缩后:
解压中:
解压后:
三、源代码
前面分析得很清晰,不过写代码的过程异常艰辛,充满了Debug时的汗水和泪水。
完整代码与封装好的可执行文件下载链接:https://gitee.com/sherlocknovitch/Qt_Compression
(可执行文件有58M,是因为封装了Qt的核心控件,去掉这些估计只有几百Kb的大小)
上代码:
1°MainWindow.h
先从窗口头文件看起,这块没有什么实质性的内容,只有几个槽函数(3个按钮的Click
槽函数加一个让QprogressBar
动起来的槽函数和一个出现文件打开错误情况时让QLineEdit
清空的槽函数)
私有成员中QStirng path
的作用是读取文件路径,作为压缩函数和解压缩函数的参数。
Compression* com
是一个类指针,这个类是压缩类,在后面会提及。
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include"compression.h"
#include<QFileDialog>
#include<QMenu>
#include<QAction>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
void clear();
private slots:
void on_pushButton_open_clicked();
void on_pushButton_compression_clicked();
void on_pushButton_decompression_clicked();
void myslot(double per);
private:
Ui::MainWindow *ui;
Compression* com;
QString path;
};
#endif // MAINWINDOW_H
2°MainWindow.cpp
这一块主要含义见注释即可,QProgressBar
是Qt的一个控件,可以理解为进度条
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :