Huffman编码数据压缩算法实现(附MFC界面)

前言

最近老板接了一个国网电力压缩的项目交给我们来做,因为要写一个演示的界面,电脑上又没有装QT,想着自己以后可能也不会写界面这一类的用的不多也就没下,就直接用了Windows的MFC写了一个简单的小压缩程序,这是本人上研究生以来老板给的第一个横向项目,自己也是个小菜鸡到现在什么语言都用的不太熟悉,就这个机会将自己这次写的程序写成一篇文章记录下来,以备后续参考。应用程序中集成了两种无损压缩算法,Huffman编码和LZ77编码,还有两种压缩算法叠加的组合压缩。ps:本人不搞数据压缩,还花了点儿时间研究了下这两种算法,只懂基本的原理 = =!!

Huffman算法的实现

Huffman算法也是一个比较老的算法了,基本流程是先构建Huffman树,再进行Huffman编码,最后将编码结果用二进制的比特流写入二进制文件,不多废话,直接上代码:

这是huffman算法的头文件Huffman.h#pragma once#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <map>using namespace std;
//定义树节点的最大容量
const int MAX_N = 100;
//定义为无穷大
const int INF = 0x7fffffff;//哈夫曼树节点定义
class HNode
{
public:char data;           //节点值double weight;       //权重int parent;          //双亲节点int lchild;          //左孩子节点int rchild;          //右孩子节点
};//哈夫曼编码节点
class HuffCode
{
public:CString code;//数据的编码char huffinfo;//被编码数据
};class HuffMan
{
public:HuffMan() {}~HuffMan() {}/*********************************************************///函数名:CreateForest//返回值类型:void//参数:string filename//功能://    1.读取文件中数据信息//   2.构建单节点二叉树组成的森林/*******************************************************/bool CreateForest(CString filename, bool replace);/*********************************************************///函数名:CreateHuffManTree//返回值类型:void//参数:NULL//功能:利用森林构建哈夫曼树/********************************************************/void CreateHuffManTree();/*********************************************************///函数名:CreateHuffCode//返回值类型:bool -> 判断是否编码成功,成功返回true,否则false//功能:生成哈夫曼编码/********************************************************/bool CreateHuffCode();/*********************************************************///函数名:compress//返回值类型:double//参数:需要压缩文件的路径//功能:压缩文件并输出压缩比例/********************************************************/double compress(CString filepath, CString outpath, CProgressCtrl &pro, CStatic &m_proview);/*********************************************************///函数名:DeCompress//返回值类型:void//参数:需要解压缩文件的路径//功能:解压缩文件并输出/********************************************************/void DeCompress(CString outpath, string codestream, CProgressCtrl &pro, CStatic &m_proview, bool replace);/*********************************************************///函数名:GetInfo//返回值类型:string//参数:需要获取信息的文件的路径//功能:输出目标文件的大小,单位为字节//返回值:从编码的二进制文件中得到源文件的字符统计信息和二进制流,并将二进制流转换为字符串返回/********************************************************/string GetInfo(CString inpath);/*********************************************************///函数名:AcquireLength//返回值类型:long//参数:需要获取长度的文件的路径//功能:输出目标文件的大小,单位为字节/********************************************************/unsigned long long AcquireLength(CString filepath);/*********************************************************///这个函数其实对于huffman编码来说没啥用,可以忽略,是我用来代替文本中重复字符串用的//函数名:Replace//返回值类型:string//参数:需要替换的目标字符串,要替换为的字符串,//功能:输出目标文件的大小,单位为字节//返回值:返回替换后的字符串/********************************************************/string Replace(string str, string rep, string c);public:HNode ht[MAX_N];//哈夫曼树HuffCode hcode[MAX_N];//编码long snum;//总字符数int count = 0;//需要编码的字符总数map<char, double> statistic;//用于存储源文件字符统计信息的字典
};

上述程序中,compress和DeCompress函数中CProgressCtrl &pro, CStatic &m_proview,这两个参数是为了跟MFC进行信息交换的(本来想写个进度条的),事实上也没用到= =,不需要的可以省略。

这是Huffman算法的主程序文件#include "stdafx.h"               //没写界面这个可以不要
#include "ApplicationDlg.h"       //没写界面这个可以不要
#include "Huffman.h"
#include <bitset>/*********************************************************///函数名:AcquireLength//返回值类型:long//参数:需要获取长度的文件的路径//功能:输出目标文件的大小,单位为字节/********************************************************/
unsigned long long HuffMan::AcquireLength(CString filepath)
{CStdioFile inputFile;unsigned long long inSize;if (inputFile.Open(filepath, CStdioFile::modeRead)){inSize = inputFile.GetLength();}inputFile.Close();return inSize;
}/*********************************************************///这个函数其实对于huffman编码来说没啥用,可以忽略,是我用来代替文本中重复字符串用的//函数名:Replace//返回值类型:string//参数:需要替换的目标字符串,要替换为的字符串,//功能:输出目标文件的大小,单位为字节//返回值:返回替换后的字符串/********************************************************/
string HuffMan::Replace(string str, string rep, string c)
{int pos;pos = str.find(rep);查找指定的串while (pos != -1){str.replace(pos, rep.length(), c);用新的串替换掉指定的串pos = str.find(rep);//继续查找指定的串,直到所有的都找到为止}return str;
}
/*********************************************************/
//函数名:CreateForest
//返回值类型:void
//参数:string filename
//返回:创建成功返回True,失败返回False
//功能:
//  1.读取文件中数据信息
//  2.构建单节点二叉树组成的森林
/*******************************************************/
bool HuffMan::CreateForest(CString filename, bool replace = FALSE)
{int i = 0;fstream readfile;ULONGLONG inSize = AcquireLength(filename);string strALL;  //存储从文件中读到的字符char c, b;long pcount = 0;readfile.open(filename, ios::in);if (!readfile.is_open()) {cout << "Could not find the file\n";cout << "Program terminating\n";exit(EXIT_FAILURE);}//下面这个if语句是为了替换源文件中的一些字符串并将新内容从新输入到一个文件中, 文件名为replace.txtif (replace){while (!readfile.eof()){if (readfile.good()){c = readfile.get();if (c == '6'){b = readfile.get();if (b == '8'){strALL += '#';}else{readfile.seekg(-1, ios::cur);strALL += c;}}else{strALL += c;}}}CString root = filename.Left(filename.ReverseFind('\\') + 1);CString file_replace = root + TEXT("replace.txt");ofstream repfile;repfile.open(file_replace, ios::out);repfile << strALL;repfile.close();}else{while (!readfile.eof()){if (readfile.good()){c = readfile.get(); //每个字符都读,包括回车键‘\n’strALL += c;}}}readfile.close();pcount = strALL.length();snum = pcount - 1;//读取文件中各个字符出现的频次,单个字符的ASCII码值不会超过127,所以使用一个大小为127的数组进行统计double *numbers = new double[127]();for (long i = 0; i < snum; i++){numbers[strALL[i]]++;}//构造霍夫曼树的节点,并将字符出现的频次信息写入一个字典中待后续使用for (int j = 0; j < 127; j++){if (numbers[j] != 0){ht[count].data = (char) j;ht[count].weight = numbers[j];statistic[ht[count].data] = ht[count].weight;count++;}}delete[] numbers;return true;
}/*********************************************************/
//函数名:CreateHuffManTree
//返回值类型:void
//参数:NULL
//功能:利用森林构建哈夫曼树
/********************************************************/
void HuffMan::CreateHuffManTree()
{//左右孩子int lnode, rnode;//最小的两个频率值double min1, min2;//先将各个待编码字符的左右孩子节点的值初始化为-1,作为后续判断叶子节点的标志for (int i = 0; i < count; i++){ht[i].parent = ht[i].lchild = ht[i].rchild = -1;}//end for -> init result//根据单独的节点构造出一颗完整的霍夫曼树,整棵树的节点数量为2 * count - 1for (int j = count; j < 2 * count - 1; j++){//每次重新搜寻最小权值的时候都将最小值和次小值初始化min1 = min2 = INF;lnode = rnode = -1;//遍历寻找最小的两个频率值min1和min2for (int k = 0; k < j; k++){//逐一排查不存在双亲的节点if (ht[k].parent == -1){//每次寻找最小值节点时先除去含有双亲的节点if (ht[k].weight < min1)//小于最小{min2 = min1;//最小赋值给次小rnode = lnode;//同理下标min1 = ht[k].weight;//新的最小频率值lnode = k;//其下标}//end if -> less//不小于最小,小于次小else if (ht[k].weight < min2){min2 = ht[k].weight;rnode = k;}//end else if -> only bigger than min1//no else }//end if -> search}ht[j].weight = ht[lnode].weight + ht[rnode].weight;//双亲结点权重ht[j].lchild = lnode;//双亲节点左孩子ht[j].rchild = rnode;ht[lnode].parent = j;//原最小频率值所在节点的双亲节点赋值为当前节点jht[rnode].parent = j;ht[j].parent = -1;//双亲节点参与比较,赋值为-1}
}/*********************************************************/
//函数名:CreateHuffCode
//返回值类型:bool -> 判断是否编码成功,成功返回true,否则false
//功能:根据构造好的霍夫曼树生成哈夫曼编码
/********************************************************/
bool HuffMan::CreateHuffCode()
{int f, c;HuffCode hc;for (int i = 0; i < count; i++)//作为hcode下标{//自下往上搜寻,直至找到parent为-1的节点即是根节点hc.huffinfo = ht[i].data;c = i;//下标->左节点f = ht[i].parent;while (f != -1)//未到根节点{if (ht[f].lchild == c)//找到左节点{hc.code = _T("0") + hc.code;//左边较小赋值为0}//end if -> left nodeelse{hc.code = _T("1") + hc.code;//右边赋值为1}//end else -> right nodec = f;//替换为上一层节点f = ht[f].parent;//上一层的双亲节点}//end whilehcode[i] = hc;//赋值当前编码hc.code = "";//清空code内容,进行下一次访问}//end forreturn true;
}/*********************************************************/
//函数名:compress
//返回值类型:double
//参数:需要压缩文件的路径,压缩文件的输出路径
//返回值:压缩比例
//功能:对源文件进行压缩并输出
/********************************************************/
double HuffMan::compress(CString filepath, CString outpath, CProgressCtrl &pro, CStatic &m_proview)
{ifstream inFile;inFile.open(filepath);if (!inFile.is_open()) {cout << "Could not find the file\n";cout << "Program terminating\n";exit(EXIT_FAILURE);}ofstream outFile;outFile.open(outpath, ios::out | ios::binary);if (!outFile.is_open()) {cout << "Could not find the file\n";cout << "Program terminating\n";exit(EXIT_FAILURE);}//将字符的统计信息写入压缩文件,字符总数+各个字符出现的频次,以供后续解压使用outFile << snum;outFile << ":";for (auto iter : statistic){outFile << iter.first << "~" << iter.second << " ";}//统计信息结束标志outFile << "*@#";//开始写入压缩内容,每8位一个字节写入文件,末尾不够8位的话补0char c;int pos = 0;long number = 0;char value = 0;double progress = 0;CString code, str;double step = 100 / snum;inFile >> noskipws;  //读字符的时候不跳过换行符inFile >> c;number++;while (!inFile.eof()){if (inFile.good()){ for (int j = 0; j < count; j++){if (c == hcode[j].huffinfo){code = hcode[j].code;   //找到字符对应的编码break;}}for (int i = 0; i < code.GetLength(); i++){value = value << 1;if (code[i] == '1'){value |= 1;   //这个应该好理解, 00000000 | 00000001 = 00000001,相当于最低位赋值1}if (++pos == 8){outFile << value;  // 满8位就将其写入文件value = 0;pos = 0;}}inFile >> c;}}//末尾不够8位补0if (pos){value = value << (8 - pos);outFile << value;}inFile.close();outFile.close();ULONGLONG originLen = AcquireLength(filepath);ULONGLONG compressLen = AcquireLength(outpath);double ratio = double(compressLen) * 100 / double(originLen);return ratio;
}/*********************************************************///函数名:GetInfo//返回值类型:string//参数:需要获取信息的文件的路径//功能:输出目标文件的大小,单位为字节//返回值:从编码的二进制文件中得到源文件的字符统计信息和二进制流,并将二进制流转换为字符串返回/********************************************************/
string HuffMan::GetInfo(CString inpath)
{//先将待解压文件中的字符统计信息和编码内容读取出来,分别用strline和codestream来表示char c, o, p, q;bitset<8> a;string strline, codestream;  //strline存储二进制文件中的字符及对应权值,codestream存储01串fstream infile_b(inpath, ios::in | ios::binary);while (true)         //从二进制文件中获取字符及对应权值{if (infile_b.peek() == EOF)break;infile_b.read(&o, 1);if (o == '*'){infile_b.read(&p, 1);infile_b.read(&q, 1);if (p == '@'&&q == '#')break;else infile_b.seekg(-2, ios::cur);}strline += o;}while (true)       //读取字符转化为01串{if (infile_b.peek() == EOF)break;infile_b.read(&c, 1);a = c;   //字符赋值给bitset<8>可直接转化为二进制codestream += a.to_string();}//根据字符统计信息构造霍夫曼树,然后逐字符读取编码内容根据霍夫曼树进行解码char key;double b;long t;double snumcopy;string tmp, num;map<char, double> data_b;while (!strline.empty())   //读取其中存储的字符以及出现的频次,由此生成哈弗曼树{if (snum == 0){//提取文件中字符的总数t = strline.find(':');num = strline.substr(0, t);strline.erase(0, t + 1);snum = long(stoi(num, nullptr, 10));snumcopy = double(snum);}//提取各个字符的数量并将其存入字典key = strline.at(0);strline.erase(0, 2);t = strline.find(' ');tmp = strline.substr(0, t);strline.erase(0, t + 1);b = double(stoi(tmp, nullptr, 10));pair<char, double> item(key, b);data_b.insert(item);}map<char, double>::iterator it;count = 0;for (it = data_b.begin(); it != data_b.end(); it++){ht[count].data = (*it).first;ht[count].weight = (*it).second;count++;}CreateHuffManTree();//此处生成霍夫曼树infile_b.close();return codestream;
}
/*********************************************************/
//函数名:DeCompress
//返回值类型:void
//参数:需要解压缩文件的路径
//功能:解压缩文件并输出,输出文件为.txt文件
/********************************************************/
void HuffMan::DeCompress(CString outpath, string codestream, CProgressCtrl &pro, CStatic &m_proview, bool replace = FALSE)
{fstream outfile_t(outpath, ios::out);CString str;long number = 0;double snumcopy;double progress = 0;snumcopy = double(snum);double step = 100 / snumcopy;//从根节点开始,根据压缩内容,遇到0向左,遇到1向右,直至读取到叶子节点,将叶子节点代表的字符写入txt文件HNode root = ht[2 * count - 2];for (auto i : codestream)  //用01串遍历哈弗曼树{if (i == '0')  //遇0,走左root = ht[root.lchild];else  //遇1,走右root = ht[root.rchild];if (root.lchild == -1 && root.rchild == -1)  //遇叶子节点,提取字符并存储{if (replace){number++;if (root.data == '#'){outfile_t << "68";}else{outfile_t << root.data;}root = ht[2 * count - 2];snum -= 1;}else{number++;outfile_t << root.data;/*pro.SetPos(number);progress += step;str.Format(L"%.2f", progress);str = str + TEXT("%");m_proview.SetWindowTextW(str);*/root = ht[2 * count - 2];snum -= 1;}       }if (snum == 0){break;}}outfile_t.close();
}

关于使用C++遍历某个文件夹下所有文件的方法

这里仅记录关键代码,下述代码可以封装进函数里,前提是文件夹中只含有文件而不含有子文件夹,包含子文件夹遍历的代码多加一层判断写个递归就好了

bool cmp(std::string const &arg_a, std::string const &arg_b)
{return arg_a.size() < arg_b.size() || (arg_a.size() == arg_b.size() && arg_a < arg_b);
}long hFile = 0;
string path = "E:\\胡浩星\\工作项目\\电力数据压缩\\霍夫曼编码实验\\[450, 500)";
vector<string> files;
vector<string>::iterator it;
struct _finddata_t fileinfo;
string p;
if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
{do {files.push_back(p.assign(path).append("\\").append(fileinfo.name));} while (_findnext(hFile, &fileinfo) == 0); //寻找下一个,成功返回0,否则-1_findclose(hFile);
}files.erase(files.begin(), files.begin() + 2);   //这里一般来讲返回的vector中前两个元素是文件夹当前目录,与文件夹所在目录,程序中用不到就剔除了
sort(files.begin(), files.end(), cmp);  //很多需要根据文件名的数字顺序进行排序,所以需要这一句,比如图片如果是按照1, 2, 3, 4, 5.....这样命名的话就需要这步操作

最终的效果图:

第一次写博客,如果中间有什么东西写错了误导了大家请见谅,欢迎大家批评指正。

记录一个小型的数据压缩项目相关推荐

  1. 记录一个超分辨率算法项目,来自B站

    ailab/Real-CUGAN at main · bilibili/ailab · GitHub 效果如下所示: 这是其中需要修改参数的一段代码: #超分倍率 scale=3#参数路径,可更换 m ...

  2. 一个小型 BI 项目的总结

    最近在做一个小型 BI 项目,项目的工期很紧,现在项目一期已经接近尾声,趁这个机会做个项目总结. 项目背景: 该项目的需求方是一家大型的跨国销售类企业, 在世界各地都有销售网点, 每个销售网点会将当日 ...

  3. [carla入门教程]-6 小项目:基于carla-ros-bridge构建一个小型比赛赛道

    本专栏教程将记录从安装carla到调用carla的pythonAPI进行车辆操控并采集数据的全流程,带领大家从安装carla开始,到最终能够熟练使用carla仿真环境进行传感器数据采集和车辆控制. 第 ...

  4. pythonhelloworld项目,10分钟搭建一个小型网页(python django)(hello world!)

    10分钟搭建一个小型网页(python django)(hello world!) 1.安装django pip install django 安装成功后,在Scripts目录下存在django-ad ...

  5. 复习Java第一个项目学生信息管理系统 04(权限管理和动态挂菜单功能) python简单爬数据实例Java面试题三次握手和四次挥手生活【记录一个咸鱼大学生三个月的奋进生活】016

    记录一个咸鱼大学生三个月的奋进生活016 复习Java(学生信息管理系统04权限管理和动态挂菜单功能) 改写MainFrame的构造方法 新增LoginFrame的验证登录是否成功的代码 新增Logi ...

  6. 学习C++:C++进阶(三)CMake基础篇---用一个小型项目了解CMake及环境构建

    V1.1 于2022年7月15日第二次修改:添加了比较多的解释图,解读了各类库的CMakelist.txt文件 目录 第一部分 基础篇(Basics) 1.0 本部分主要学什么(Intro) 1.1 ...

  7. 复习JavaWeb的小项目书籍信息的增删改查分页功能实现Java面试题Session和Cookie的基础概念生活【记录一个咸鱼大学生三个月的奋进生活】034

    记录一个咸鱼大学生三个月的奋进生活034 JavaWeb的增删改查分页功能实现 前期准备工作(数据库连接类和实体类) 数据库建立 数据库连接类(DBManager) 书籍信息的实体类(Book) 操作 ...

  8. 复习Java第二个项目仿QQ聊天系统 01(界面部分) Java面试题Redis的过期策略和内存淘汰策略生活【记录一个咸鱼大学生三个月的奋进生活】023

    记录一个咸鱼大学生三个月的奋进生活023 复习Java(仿QQ聊天系统01界面部分) 设置背景(ImgPanel)类 登录界面(LoginFrame)类 注册界面(RegisterFrame)类 好友 ...

  9. 复习Java第二个项目仿QQ聊天系统 03(两种通信类、登录以及注册功能完善) Java面试题并发编程相关知识生活【记录一个咸鱼大学生三个月的奋进生活】025

    记录一个咸鱼大学生三个月的奋进生活025 复习Java(仿QQ聊天系统03两种通信类.登录以及注册功能完善) TcpSocket类(与服务器进行通信) Server类(服务器类) TcpMessage ...

最新文章

  1. Java语言学习思维导图
  2. dell r720服务器raid5安装centos6.5系统
  3. 51CTO博客移动化意味着什么?IT博主可以搞事情了!
  4. 2021巨量引擎UGC互动营销白皮书
  5. jsmin php,使用JSMin.php缩小Javascript
  6. 查询blob字段_一次注解开发实战-我使用注解对微服务的跨库查询做了封装
  7. 剑指offer--3
  8. java面试要点005---git和svn的区别
  9. 谷歌在招什么样的人?
  10. 拓端tecdat|R语言特征选择——逐步回归
  11. LoadRunner11下载以及详细破解说明
  12. Python Pytest自动化测试 获取测试用例执行结果
  13. [百晓生]-鼠标右键新建添加RTF文档
  14. UIImageJPEGRepresentation 使用中存在的问题
  15. 字符串intern()方法详解
  16. 红色建筑装饰公司营销型网站织梦模板
  17. 4x root 红米_红米手机4x如何获取root权限?
  18. 广州市长温国辉:用“加减乘除法”发展民营经济
  19. 什么是BGP线路?什么是BGP机房?
  20. [回溯]leetcode1219:黄金矿工(medium)

热门文章

  1. 史蒂芬·柯维《高效能人士的七个习惯》读书笔记
  2. 计算机与数学交融的教学设计,信息技术与小学数学学科的整合 小学数学教案...
  3. 斯坦福AI2021报告出炉!详解七大热点,论文引用中国首超美国
  4. Ubuntu下Logi MX Ergo自定义按键
  5. logi option闪退_什么是LogiOptions.exe(UNICODE),是进程安全吗? | MOS86
  6. 金仓数据库 KingbaseES 与 Oracle 的兼容性说明(4. SQL)
  7. STM32 USB AUDIO 基础篇①——通过STM32CubeMX生成USB Speaker音频播放Demo(史上最简单)
  8. 华中科技大学计算机学院郑强教授简历,华中科大教授声讨后勤被处分 郑强个人简介介绍...
  9. 千锋Flask学习笔记
  10. 转载 RINEX 中观测值的类型