霍夫曼编码/译码器的设计
代码见github:HuffmanEncode-DecodeTool(github.com)
使用Qt做了图形化界面,成绩A,欢迎参考
需求分析
赫夫曼编码是用于无损数据压缩和解压缩的算法,使用赫夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本。一个基本功能完备的赫夫曼编码器/译码器的需求有:
1. 数据预处理:能够接收原始数据,并进行必要的预处理,如根据字符出现频率构建赫夫曼树等。
2. 赫夫曼编码生成:基于字符频率,设计并生成优化的赫夫曼编码表。编码器需要根据频率生成每个字符的可变长度编码序列,并确保出现频率高的字符具有更短的编码。
3. 编码器功能:编码器需要能够实现将原始数据转换为赫夫曼编码的功能。它应提供高效的编码算法,能够快速地将原始数据转换为对应的赫夫曼编码。
4. 译码器功能:译码器需要能够将赫夫曼编码还原为原始数据,能够快速地将赫夫曼编码转换为对应的原始数据。
5. 数据压缩比率:赫夫曼编码器/译码器的一个重要需求是实现高压缩比率。编码器应尽量将原始数据压缩为较小的赫夫曼编码,而译码器应能够将这些编码还原为原始数据。
6. 性能:编码器/译码器需要具备高效的算法实现,以保证对大量数据进行快速的编码和解码操作。
概要设计
本程序基于Qt开发,共设计了5个类,其关联关系如图。
其中,DataShare类作为数据共享类,是其余类的实例之间交换数据的载体,将数据保存在内存中,避免反复读取文件。在DataShare类中,使用map<string,string> 的map键值对,来保存字符与其对应的赫夫曼编码的一一对应关系,使用vector<HuffmanTree>的容器来保存赫夫曼树的结点。
在Init类中,生成赫夫曼树的算法如下,时间复杂度为O(n):
- 将给定的n个权值和字符作为n个叶子结点,作为森林中的n棵树。
- 在这些树中选出两个根结点的权值最小的树,作为新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和。
- 从森林中删除选取的两棵树,并将新树加入森林。
- 重复上述步骤2和3,直到森林中只剩下一棵树为止,这棵树即为所求得的赫夫曼树。
在HuffmanCode类中,生成每个字符的赫夫曼编码的算法如下:
- 选定赫夫曼树的一个叶子节点,从其开始,向上回溯至根节点。
- 对于每个叶子节点,根据其所在路径的左分支和右分支的标记(0和1),以及从根节点到该叶子节点的路径,确定该叶子节点的赫夫曼编码。
- 重复步骤1和2,直到所有叶子节点的赫夫曼编码都确定为止。
在HuffmanDecode类中,对编码的字符串译码的算法如下:
- 初始化2个空字符串res和tmp,用于存储译码后的结果和当前查找的编码;初始化1个整数current=0,存储当前字符下标。
- 获得待译码字符串ToDecode。
- 获取当前字符ToDecode[current],添加到tmp中,在存储编码的map中查找是否有编码匹配tmp的字符
- 如果成功匹配,则将查找到的字符添加到res字符串中,并清空tmp;匹配失败则不做操作。
- Current=current+1
- 重复步骤3到5,直到current大于Todecode的长度
- 返回译码后的字符串res。
程序正常使用流程如图
算法使用到的数据结构主要有string字符串,存储待编码字符串,编码后字符串等数据;vector容器,存储赫夫曼树的每个结点;以及map键值对,存储每个字符与其赫夫曼编码之间的对应关系。
-
详细设计:
程序代码
本程序基于Qt开发,由7个.h文件,7个.cpp文件,和1个.ui文件构成。
- //PublicDeclaration.h
- #ifndef PUBLICDECLARATION_H
- #define PUBLICDECLARATION_H
- #include <bits/stdc++.h>
- using namespace std;
- typedef struct DrawNode
- {
- int x,y;
- }TreePoint;
- typedef struct HNode
- {
- string ch;
- double weight; //权值
- int parent; //父节点
- int lc, rc; //左右孩子
- double idx,idy;
- TreePoint pos;
- }*HuffmanTree;
- #endif // PUBLICDECLARATION_H
- //DataShare.h
- #ifndef DATASHARE_H
- #define DATASHARE_H
- #include <bits/stdc++.h>
- #include "PublicDeclaration.h"
- using namespace std;
- class DataShare
- {
- public:
- DataShare();
- void set_Code(map<string,string>);
- const map<string,string> get_Code();
- void set_TreeVector(vector<HuffmanTree> TempTree);
- const vector<HuffmanTree> get_TreeVector();
- private:
- map<string,string> Code;//赫夫曼编码的键值对
- vector<HuffmanTree> Tree;
- };
- #endif // DATASHARE_H
- //InitTree.h
- #ifndef INITTREE_H
- #define INITTREE_H
- #include "PublicDeclaration.h"
- #include <bits/stdc++.h>
- using namespace std;
- class InitTree
- {
- private:
- typedef struct InputDataNode
- {
- string ch;
- double weight;
- }*InNode;
- public:
- InitTree();
- void InitHuffmanTree();
- void ReadFile();
- void WriteFile();
- void CreateHuffTree();
- const vector<HuffmanTree> get_TreeVector();
- private:
- vector<InNode> InData;
- vector<HuffmanTree> Tree;
- };
- #endif // INITTREE_H
- //HuffmanCode.h
- #ifndef HUFFMANCODE_H
- #define HUFFMANCODE_H
- #include "PublicDeclaration.h"
- #include <bits/stdc++.h>
- using namespace std;
- class HuffmanCode
- {
- public:
- HuffmanCode();
- void Encoding();
- void ReadTransFile();
- void CreateHuffmanCode();
- void CodeText();
- void OutPutCodeFile();
- void set_TreeVector(vector<HuffmanTree> TempTree);
- const map<string,string> get_Code();
- const string get_CodeText();
- const string get_ToTransText();
- const string get_HuffCodeText();
- private:
- string TransText;
- string HuffCode;
- vector<HuffmanTree> Tree;
- map<string,string> Code;
- };
- #endif // HUFFMANCODE_H
- //HuffmanDecode.h
- #ifndef HUFFMANDECODE_H
- #define HUFFMANDECODE_H
- #include <bits/stdc++.h>
- using namespace std;
- class HuffmanDecode
- {
- public:
- HuffmanDecode();
- void Decoding();
- void ReadText();
- void DecodeText();
- void OutPutTextfile();
- void set_Code(map<string,string> tempCode);
- const string get_ResText();
- private:
- string ToDecodeText;
- string ResText;
- map<string,string> Code;
- };
- #endif // HUFFMANDECODE_H
- //DrawHuffTree.h
- #ifndef DRAWHUFFTREE_H
- #define DRAWHUFFTREE_H
- #include "PublicDeclaration.h"
- #include <QWidget>
- #include <bits/stdc++.h>
- using namespace std;
- class DrawHuffTree : public QWidget
- {
- Q_OBJECT
- public:
- explicit DrawHuffTree(QWidget *parent = nullptr);
- void set_TreeVector(vector<HuffmanTree> TempTree);
- void set_Code(map<string,string> tempCode);
- signals:
- protected:
- void paintEvent(QPaintEvent *event);
- public slots:
- private:
- vector<HuffmanTree> Tree;
- map<string,string> Code;
- };
- #endif // DRAWHUFFTREE_H
- //mainwindow.h
- #ifndef MAINWINDOW_H
- #define MAINWINDOW_H
- #include <QMainWindow>
- #include "DataShare.h"
- QT_BEGIN_NAMESPACE
- namespace Ui {
- class MainWindow;
- }
- QT_END_NAMESPACE
- class MainWindow : public QMainWindow
- {
- Q_OBJECT
- public:
- MainWindow(QWidget *parent = nullptr);
- ~MainWindow();
- private slots:
- void on_Init_clicked();
- void on_Encode_clicked();
- void on_DecodeButton_clicked();
- void on_ShowTreeButton_clicked();
- void on_Button_Exit_clicked();
- private:
- Ui::MainWindow *ui;
- DataShare data1;
- };
2.说明
主要说明代码中涉及到相关保存文件的设计。
1、保存赫夫曼树的所有节点信息到文件,每行保存字符,权值,父结点,左孩子,右孩子,用空格分隔。
- void InitTree::WriteFile()
- {
- const string FilePath = "E:\\code\\qt\\SDesign\\SDesign\\file\\hfmTree.txt";
- ofstream OutData;
- OutData.open(FilePath, ios::out);
- for (int i = 1; i < (int)Tree.size(); i++)
- {
- //字符,权值,父结点,左孩子,右孩子
- OutData << i << " ";
- if (Tree[i]->ch != "")
- OutData << Tree[i]->ch << " ";
- else
- OutData << "[NULL]" << " ";
- OutData << Tree[i]->weight << " " << Tree[i]->parent << " " << Tree[i]->lc
- << " " << Tree[i]->rc << " " << endl;
- }
- }
2、保存赫夫曼编码后的字符串到文件
- void HuffmanCode::OutPutCodeFile()
- {
- const string FilePath = "E:\\code\\qt\\SDesign\\SDesign\\file\\CodeFile.txt";
- ofstream OutData;
- OutData.open(FilePath, ios::out);
- if (!OutData.is_open())
- {
- QMessageBox::warning(NULL, "Warning", "Fail to Open File!", QMessageBox::Yes);
- return;
- }
- OutData << this->HuffCode << endl;
- OutData.close();
- }
3、保存译码后的字符串到文件
- void HuffmanDecode::OutPutTextfile()
- {
- const string FilePath = "E:\\code\\qt\\SDesign\\SDesign\\file\\Textfile.txt";
- ofstream OutData;
- OutData.open(FilePath, ios::out);
- OutData << ResText << endl;
- OutData.close();
- }
-
运行分析
- 程序运行主界面
- 按下Init(I)初始化,读取文件,生成赫夫曼树,此时程序界面无变化。
3、按下Encode(E),生成赫夫曼编码,显示在“赫夫曼编码”框;读取待编码文件,显示在“原文”框内;并进行编码,显示在“编码后”框内,同时输出到文件
4、按下Decode(D),对赫夫曼编码字符串进行译码,显示在“解码后”框内
5、按下Show(S),打开新窗口,显示赫夫曼树图形和编码信息
6、按下Exit,程序退出
异常操作处理:
- 未进行初始化树操作就进行编码,提示错误信息
- 未进行编码操作就进行译码,提示错误信息
问题思考与改进
- 生成每个字符的赫夫曼编码时,可以尝试从树的根节点开始往下编码,避免逆置字符串操作。
- 可以设计在程序界面输入要编码的字符串,方便操作。
-
实验心得与体会(总结)
经过本次课程设计,我深深体会到了数据结构的独特之处,有了更深入、更实际的理解。
初期构建基本程序框架时比较顺利,然而在编程过程中遇到的问题接踵而至,如怎样在类之间实现数据共享,怎样在图形化界面中绘制赫夫曼树的图形等等,对于在类之间实现数据共享,我首先想到的是全局变量或者静态变量,友元函数等方法,最后想到面向对象编程的思想,能否将要共享的数据抽象为一个类呢?付诸实践后证明了其可行性,解决了这一问题。
在程序设计过程中,我对数据结构这门课程的认识也得到了加深,我明白了数据结构不仅仅是理论上的知识,更还是实际编程应用中的重要工具。