文件压缩(Huaffman树的概念及其实现)

什么是压缩

想办法让源文件变得更小并能还原。

为什么要进行文件压缩

  1. 文件太大,节省空间
  2. 提高数据再网络上传输的效率
  3. 对数据有保护作用—加密

文件压缩的分类

  1. 无损压缩
    • 源文件被压缩后,通过解压缩能够还原成和源文件完全相同的格式
  2. 有损压缩
    • 解压缩之后不能将其还原成与源文件完全相同的格式–解压缩之后的文件再识别其内容时基本没有影响

GZIP压缩

LZ77变形:

原理将重复出现得语句用尽可能短得标记来替换
字符串的压缩
LZ77可以消除文件中重复出现的语句,但还存在字节方面的重复

基于Huffman编码得压缩

基于字节的压缩
1个字节—>8个bit位,如果对于每个字节如果能够找到一个更短的编码,来重新改写原3数据,可以起到压缩的目的

Huffman树的概念

从二叉树的根结点到二叉树中所有叶结点的路径长度与相应权值的乘积之和为该二叉树的带权路径长度WPL。
把带权路径最小的二叉树称为Huffman树

Huffman树的概念

用户提供一组权值信息

  1. 以每个权值为结点创建N棵二叉树的森林
  2. 如果二叉树森林中有超过两个树,进行以下操作
    • 从二叉树森林中取出根结点权值最小的两棵二叉树
    • 以该两棵二叉树作为某个结点的左右子树创建一颗新的二叉树,新二叉树中的权值为其左右子树权值之和
    • 将新创建的二叉树插入到森林中

创建好之后的二叉树森林中的每棵树可以采用堆(priority_queue)保存

Huffman树的创建

Huffman树的结点定义

struct HuffManTreeNode
{HuffManTreeNode(const W& weight = W()):_pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _weight(weight){}HuffManTreeNode<W>* _pLeft;HuffManTreeNode<W>* _pRight;HuffManTreeNode<W>* _pParent;//加上双亲指针域W _weight; //结点的权值
};

Huffman树的类

template<class W>
class HuffManTree
{typedef HuffManTreeNode<W> Node;
public:HuffManTree():_pRoot(nullptr){}HuffManTree(const vector<W>& vWeight, const W& invaild){//进行建树操作CreatHuffManTree(vWeight,invaild);}~HuffManTree(){_DestroyTree(_pRoot);}Node* GetRoot(){return _pRoot;}void CreatHuffManTree(const vector<W>& vWeight,const W& invaild){//1.构建森林//底层为优先级队列priority_queue<Node*,vector<Node*>,Less<W>> q;for (auto e : vWeight){//默认插入的数据没有重复if (e == invaild)//如果是无效的权值,就不放了continue;q.push(new Node(e));//创建新的结点,树就有了}//2.看森林里面有没有超过两个树while (q.size() > 1){//取根结点权值最小的两颗树Node* pLeft = q.top();q.pop();Node* pRight = q.top();q.pop();// 以该两棵二叉树作为某个结点的左右子树创建一颗新的二叉树,//新二叉树中的权值为其左右子树权值之和Node* pParent = new Node(pLeft->_weight + pRight->_weight);pParent->_pLeft = pLeft;pParent->_pRight = pRight;//将新数插入到Huffman树中pLeft->_pParent = pParent; //更新双亲pRight->_pParent = pParent;q.push(pParent);}//3.森林里只有一棵树了,就是哈夫曼树_pRoot = q.top();}private://销毁二叉树,只能用后序遍历的方式进行销毁void _DestroyTree(Node*& pRoot){if (pRoot){_DestroyTree(pRoot->_pLeft);_DestroyTree(pRoot->_pRight);delete pRoot;//要改指针本身,要么传二级指针,要么一级指针的引用pRoot = nullptr;}}
private:Node * _pRoot;
};

基于Huffman编码的压缩和解压缩

压缩

  1. 统计源文件中每个字节出现的次数
  2. 用统计的结果创建Huffman树
    • 什么是Huffman树:带权路径长度最短的一颗二叉树----权值越大,越靠近根结点
    • Huffman树创建规则:
      • 用所有的权值创建只有根结点的二叉树森林
      • 从二叉树森林中取出根结点权值最小的两棵二叉树
      • 以该两棵二叉树作为某个结点的左右子树创建一颗新的二叉树,新二叉树中的权值为其左右子树权值之和
      • 将新创建的二叉树插入到森林中
  3. 取权值最小,用优先级队列也就是堆
  4. 从Huffman树种获取字节的编码
void GenerateHuffmanCode(pRoot)
{if(nullptr == pRoot )return ;GenerateHuffmanCode(pRoot->left);GenerateHuffmanCode(pRoot->right);//是叶子结点获取编码if(nullptr == pRoot->left&& nullptr == pRoot->right){Node* pParent =pRoot->parent;Node* pCur = pRoot;while(pParent){if(pCur == pParent->left)str+='0';elsestr+='1';}}
}

在这里插入图片描述

压缩文件实现

我们将文件中出现的字符信息保存在一个大小为256的数组中

//构造函数
FileCompressHuff::FileCompressHuff()
{_fileInfo.resize(256);for (int i = 0; i < 256; ++i){_fileInfo[i]._ch = i;_fileInfo[i]._count = 0;}
}

每一个数组的元素都是一个结构体类型,里面要保存该字符的信息,如:编码,出现的次数
而且要支持字符之间的一些运算

struct CharInfo
{unsigned char _ch;		//具体的字符size_t _count; //字符出现的次数std::string _strCode;//字符编码CharInfo(size_t count = 0):_count(count){}CharInfo operator+(const CharInfo& c){//返回对象,无名对象return CharInfo(_count + c._count);}bool operator>(const CharInfo& c){return _count > c._count;}//比较次数bool operator==(const CharInfo& c){return _count == c._count;}
};

最后再按照上面的四步来完成压缩的任务

void FileCompressHuff::CompressFile(const string& path)
{//1.统计源文件中每个字符出现的次数FILE* fIn = fopen(path.c_str(),"rb");if (nullptr == fIn){assert(false);return;}unsigned char * pReadBuff = new unsigned char[1024];int rdSize = 0;while (true){//读文件rdSize = fread(pReadBuff, 1, 1024, fIn);	if (0 == rdSize)break;//统计for (int i = 0; i < rdSize; ++i){_fileInfo[pReadBuff[i]]._count++;}}//2.以这些出现的次数为权值创建哈夫曼树HuffManTree<CharInfo> t(_fileInfo,CharInfo());//3.获取每个字符的编码GenerateHuffManCode(t.GetRoot());//4.获取到的字符编码重新改写FILE* fOut = fopen("2.txt", "wb");  //打开一个文件保存压缩后的结果if (nullptr == fOut){assert(false);return;}//将来要保存文件的后缀,所以压缩时要读取压缩格式文件头部信息WriteHead(fOut, path);fseek(fIn,0,SEEK_SET); //把文件指针再次放到文件的起始位置char ch = 0;	//存放字符编码int bitcount = 0; //计算放了多少个比特位while (true){rdSize = fread(pReadBuff, 1, 1024,fIn);if (0 == rdSize)  //文件读取结束break;//根据字节的编码对读取到的内容进行重写for (size_t i = 0; i < rdSize; ++i){//拿到编码string strCode = _fileInfo [pReadBuff[i]]._strCode;//A "100"for (size_t j = 0; j < strCode.size(); ++j){ch <<= 1; //每放一个往左一位,把下一个编码往里放//存放的时候,一个一个比特位来放if ('1' == strCode[j])ch |= 1;bitcount++;if (8 == bitcount) //说明ch满了,把这个字节写到文件中去{//fputc 写单个字节的函数fputc(ch, fOut);//写完后,都清零bitcount = 0;ch = 0;}}}}//最后一次ch中可能不够8个比特位if (bitcount < 8){ch <<= 8 - bitcount; //左移剩余的位数//不够的位肯定要写到文件中fputc(ch, fOut);}delete[]pReadBuff;fclose(fIn);fclose(fOut);
}

获取字符编码

//计算字符编码
void FileCompressHuff::GenerateHuffManCode(HuffManTreeNode<CharInfo>* pRoot)
{if (nullptr == pRoot)return;GenerateHuffManCode(pRoot->_pLeft);GenerateHuffManCode(pRoot->_pRight);//找到叶子结点if (nullptr == pRoot->_pLeft&&pRoot->_pRight == nullptr){string& strCode = _fileInfo[pRoot->_weight._ch]._strCode;HuffManTreeNode<CharInfo>*  pCur = pRoot;HuffManTreeNode<CharInfo>*  pParent = pCur->_pParent;while (pParent){if (pCur == pParent->_pLeft)	//左为0{strCode += '0';}else							//右为1{strCode += '1';}pCur = pParent;pParent = pCur->_pParent;}//字符编码从叶子结点开始获取是个反的//我们需要把它翻转一下reverse(strCode.begin(), strCode.end());//_fileInfo[pRoot->_weight._ch]._strCode = strCode;}
}

获取文件后缀

//获取文件的后缀
//2.txt
//F:\123\2.txt
string FileCompressHuff:: GetFilePostFix(const string& filename)
{//substr截取文件,第二个参数没有给的话默认截取到末尾return  filename.substr(filename.rfind('.'));
}

压缩文件的格式

压缩文件中只保存压缩之后的数据可以吗?
答案是不行的,因为在解压缩时,没有办法进行解压缩。比如:10111011 00101001 11000111 01011,只有压缩数据是没办法进行解压缩的,因此压缩文件中除了要保存压缩数据,还必须保存解压缩需要用到的信息:

  1. 源文件的后缀
  2. 字符次数对的总行数
  3. 字符以及字符出现次数(为简单期间,每个字符放置一行
  4. 压缩数据
//压缩文件格式
void FileCompressHuff::WriteHead(FILE* fOut, const string& filename)
{assert(fOut);//写文件的后缀string strHead;strHead += GetFilePostFix(filename);strHead += '\n'; //后缀与后面的内容之间用\n隔开//写行数size_t lineCount = 0;string strChCount;char szValue[32] = { 0 };	//缓冲区放入字符次数for (int i = 0; i < 256; ++i){CharInfo& charInfo = _fileInfo[i];if (charInfo._count){lineCount++;	//行数strChCount += charInfo._ch; //字符strChCount += ':';	//字符与字符次数之间用冒号隔开_itoa(charInfo._count, szValue, 10);strChCount += szValue;		//字符次数strChCount += '\n';	//末尾加\n}}_itoa(lineCount, szValue, 10);//接受字符的行数strHead += szValue;	//字符的行树strHead += '\n';	//换行隔开信息strHead += strChCount; //字符的种类个数//写信息fwrite(strHead.c_str(),1,strHead.size(),fOut);
}

解压缩

  1. 从压缩文件中获取源文件的后缀
  2. 从压缩文件中获取字符次数的总行数
  3. 获取每个字符出现的次数
  4. 重建huffman树
  5. 依靠Huffman树解压缩解压缩

要解压缩文件我们先要读取压缩文件的格式信息
//读取压缩文件的信息

void FileCompressHuff::ReadLine(FILE* fIn, string& strInfo)
{assert(fIn);//读取一行的字符while (!feof(fIn))  //只要文件指针没有到文件末尾就读取{//读取一个字符char ch = fgetc(fIn);if (ch == '\n')break;//有效的字符就拼接上去strInfo += ch;}
}

按照上面的步骤完成解压缩

void FileCompressHuff::UNComPressFile(const std::string& path)
{FILE* fIn = fopen(path.c_str(), "rb");if (nullptr == fIn){assert(false);return;}//读取文件的后缀string strFilePostFix;ReadLine(fIn,strFilePostFix);//读取字符信息的总行数string strCount;ReadLine(fIn, strCount);int lineCount = atoi(strCount.c_str()); //总的行数//读取字符的信息for (int i = 0; i < lineCount; ++i){string strchCount;ReadLine(fIn, strchCount); //读每一行的字符信息//如果读取到的是\nif (strchCount.empty()){strchCount += '\n'; //将\n写入读取ReadLine(fIn, strchCount); //多读一行,将\n的次数和冒号读取}//A:100_fileInfo[(unsigned char)strchCount[0]]._count = atoi(strchCount.c_str() + 2);//跳过前两个字符,因为前两个是A:}//还原Huffman树HuffManTree<CharInfo> t;  //创建Huffamn树的对象t.CreatHuffManTree(_fileInfo, CharInfo(0)); //还原Huffman树FILE* fOut = fopen("3.txt","wb");assert(fOut);//解压缩char* pReadBuff = new char[1024];	//创建缓冲区char ch = 0;HuffManTreeNode<CharInfo>* pCur = t.GetRoot();size_t fileSize = pCur->_weight._count; //文件总的大小就是根结点的权值的次数size_t uncount = 0;//表示解压缩了多少个while (true){size_t rdSize = fread(pReadBuff,1,1024,fIn); //读数据if (0 == rdSize)break;//一个个字节进行解压缩for (size_t i = 0; i < rdSize; ++i){//只需要将一个字节中的8个比特位单独处理ch = pReadBuff[i];for (int pos = 0; pos < 8; ++pos){//增加一次判断if (nullptr == pCur->_pLeft && nullptr == pCur->_pRight){//uncount++; //每次解压缩一个,就++一下fputc(pCur->_weight._ch, fOut);if (uncount == fileSize)break;//叶子结点,解压出一个字符,写入文件			pCur = t.GetRoot(); //把pCur放到树根的位置上继续}if (ch & 0x80)//如果该位上的数字为1pCur = pCur->_pRight;elsepCur = pCur->_pLeft;ch <<= 1; //与完后往左移动一位if (nullptr == pCur->_pLeft && nullptr == pCur->_pRight){uncount++; //每次解压缩一个,就++一下fputc(pCur->_weight._ch, fOut);if (uncount == fileSize)break;//叶子结点,解压出一个字符,写入文件			pCur = t.GetRoot(); //把pCur放到树根的位置上继续}}//for循环完后再读取下一个字节}}delete[]pReadBuff;fclose(fIn);fclose(fOut);
}

出现的问题

1. 加中文

问题1

压缩时统计源文件中每个字符出现的次数时,出现崩溃,因为数组越界,因为char[-128.127];
修改:将char改成unsigned char;

问题2

获取编码时崩溃,将CharInfo结构体中的char改为unsigned char

问题3

解压缩函数读取字符信息时崩溃,strchCount要作为数组下标必须为整数,将其强转为unsigned char

2.多行信息

问题1

解压缩函数中的解压缩时崩溃,因为pCur为空了,因为读取文件时,读取空格就直接什么都没有读到,所以遇到空格时应该多读一行,在解压缩读取文件时处理\n

问题2

文件量增大时,只能解压缩一部分,将所有的文件打开和写入方式转变为以二进制形式打开和写入,并在解压缩时再增加一次判断

完整代码:

要改进的方面

  1. 保证压缩的正确性
  2. 压缩比率
  3. 是不是每次压缩之后变小?有没有可能压缩变大?
  4. 文本文件,视频,音频,图片,都可以压缩?
  5. 其它改进的方式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383091.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

详解STL中的空间配置器(SGI版本)

空间配置器 1.什么是空间配置器 为各个容器高效的管理空间(空间的申请与回收)的 2.为什么需要空间配置器 各种容器----->可以存放元素---->底层需要空间 new 申请空间 operator new ---->malloc调用构造函数------完成对象的构造 动态内存管理总结 前面的容器…

大四阶段的社会实践的主要目的是_疫情当前,大三大四的学生“很惨”?大一大二的学生也别松懈...

大四毕业生不容易这次疫情对于高校学生而言&#xff0c;可以说是各有各的难处&#xff0c;“这届毕业生很惨”更是屡上热搜。不可否认&#xff0c;大四毕业生确实很不容易&#xff0c;论文答辩、毕业、求职就业等都受到了影响&#xff0c;虽然有困难&#xff0c;但各方都在积极…

【剑指offer】_19 滑动窗口中的最大值

题目描述 给定一个数组和滑动窗口的大小&#xff0c;找出所有滑动窗口里数值的最大值。例如&#xff0c;如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3&#xff0c;那么一共存在6个滑动窗口&#xff0c;他们的最大值分别为{4,4,6,6,6,5}&#xff1b; 针对数组{2,3,4,2,6,2,…

android 文字反转_多文字共享信息系统

欧阳贵林 www.HeZi.net首发表于2016年03月23日“ 处在信息时代的开端&#xff0c;信息技术不应有特殊的文字性&#xff0c;需要创建多文字共享信息系统&#xff0c;给各国文字一个公平的参与信息与科技创新发展的平台。这是世界的事&#xff0c;更是中国事。”01人类语言语言文…

LeetCode【1--两数之和】 LeetCode【2--两数相加】

两数之和 题目描述 给定一个整数数组 nums 和一个目标值 target&#xff0c;请你在该数组中找出和为目标值的那 两个 整数&#xff0c;并返回他们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;你不能重复利用这个数组中同样的元素。 解题思路 直接两…

input数字开头不能为0_李商隐为初恋写诗,每句以数字开头,最后10字一直被仿从未被超越...

上学时&#xff0c;每次写作文&#xff0c;老师总爱在耳边念叨&#xff1a;“你的作文得让阅卷老师看得懂&#xff0c;不然不可能给你高分的&#xff01;”每次听到话&#xff0c;笔者总是用李商隐的诗来和他斗嘴。是的&#xff0c;李商隐的诗作常常是让人读不懂的&#xff0c;…

lsass.exe 当试图更新密码时_“驱动人生”下载器木马再度更新-你应该注意什么?...

360安全大脑监测到通过"驱动人生"供应链攻击传播的挖矿木马在1月30日下午4时左右再次更新。此次更新中&#xff0c;木马在此前抓取系统帐户密码的基础上增加了抓取密码hash值的功能&#xff0c;并试图通过pass the hash攻击进行横向渗透&#xff0c;使得该木马的传播…

LeetCode【3--无重复的最长字串】 LeetCode【4--有序数组中的中位数】

无重复的最长字串 题目描述 给定一个字符串&#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 解题思路 看到这道题&#xff0c;其实就两个步骤&#xff0c;遍历字符串&#xff0c;记录当前字符有没有重复。 重复一般解决就是哈希&#xff0c;这里用个数组表示…

hwt字体转换ttf_五分钟教你弄懂了字体反爬是个啥

今天的文章内容主要是关于字体反爬。目前已知的几个字体反爬的网站是猫眼&#xff0c;汽车之家&#xff0c;天眼查&#xff0c;起点中文网等等。以前也看过这方面的文章&#xff0c;今天跟个老哥在交流的时候&#xff0c;终于实操了一把&#xff0c;弄懂了字体反爬是个啥玩意。…

LeetCode【5--最长的回文子串】 LeetCode【6--Z字形变换】

最长的回文子串 题目描述 给定一个字符串 s&#xff0c;找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。 解题思路 可以跟无重复的最长子串一样&#xff0c;用一个滑动窗口&#xff0c;只不过这个窗口的右边界往右&#xff0c;左边界每回要从右边界的下标往左…

androidstudio 日历视图怎么显示农历_中秋国庆旅游攻略怎么做?用这个便签软件很简单...

九月已经到来&#xff0c;中秋节和国庆节距离我们也不远了&#xff0c;今年的中秋和国庆节重叠了有足足八天的假期。不少人都想趁着这个小长假出门旅游&#xff0c;要想保证旅游质量&#xff0c;那么就要做好攻略。中秋国庆旅游攻略怎么做&#xff1f;要想做好一份中秋国庆旅游…

c++ select函数_PySpark 操作函数一览

PySpark 操作函数一览Created: Sep 14, 2020 10:28 AM Tags: Big Data, PySpark, Python, SparkPyspark.sql.functionsfrom pyspark.sql import functions as F函数使用说明基本数学函数类abssin、cos、tan、asin、acos 、atan、sinh、cosh、tanhceil、round、floorexp、log、l…

LeetCode【7--整数反转】 LeetCode【8--字符串转整数】

整数反转 题目描述 给出一个 32 位的有符号整数&#xff0c;你需要将这个整数中每位上的数字进行反转。 解题思路 x%10 取一位&#xff0c;x/10下一位&#xff0c;注意越界&#xff0c; 代码实现 class Solution { public:int reverse(int x) {int sum 0;while(x){if(s…

word2003如何设置护眼模式_ERP系统上线,如何设置采购收货的模式,提升企业的采购效率...

如何合理的规划采购计划上次去拜访一个朋友&#xff0c;他们说公司既然出现没有下达采购订单&#xff0c;供应商也有送货过来的事情&#xff0c;对于公司来说&#xff0c;这个是非常严重的问题。若用了ERP系统之后&#xff0c;如何避免类似的事情发生&#xff0c;今天我们来分享…

LeetCode【9-- 回文数】LeetCode【10 --正则表达式的匹配】

回文数 题目描述 判断一个整数是否是回文数。回文数是指正序&#xff08;从左向右&#xff09;和倒序&#xff08;从右向左&#xff09;读都是一样的整数。 解题思路 判断该数的逆序数是不是和原数相同 代码实现 class Solution { public:bool isPalindrome(int x) {if(…

LeetCode【11--盛水最多的容器】LeetCode【12 -- 整数转罗马数字】

盛水最多的容器 题目描述 给定 n 个非负整数 a1&#xff0c;a2&#xff0c;…&#xff0c;an&#xff0c;每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线&#xff0c;垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线&#xff0c;使得它们与 x 轴共…

LeetCode【13--罗马数字转整数】LeetCode【14--最长的公共前缀】

罗马数字转整数 题目描述 罗马数字包含以下七种字符: I&#xff0c; V&#xff0c; X&#xff0c; L&#xff0c;C&#xff0c;D 和 M。 例如&#xff0c; 罗马数字 2 写做 II &#xff0c;即为两个并列的 1。12 写做 XII &#xff0c;即为 X II 。 27 写做 XXVII, 即为 XX…

文件压缩(基于LZ77的压缩)

LZ77压缩原理 初始LZ77 LZ77是基于字节的通用压缩算法&#xff0c;它的原理就是将源文件中的重复字节(即在前文中出现的重复字节)使用(offset&#xff0c;length&#xff0c;nextchar)的三元组进行替换 这里的 长度–offset&#xff0c;距离—length&#xff0c;先行缓冲匹配…

好中的图像处理方面的期刊_约会中,注意这四个方面,帮助你把握好自己的真爱...

两个人想要拥有一段美好的感情&#xff0c;那么男生就要掌握好一些技巧去追求对方&#xff0c;在追求的过程中&#xff0c;两个人的约会也非常重要&#xff0c;毕竟只有约会过程中&#xff0c;女孩子才能够看到你光鲜亮丽的一面&#xff0c;才能够慢慢的接受你&#xff0c;如果…

kafka consumer配置拉取速度慢_Kafka消费者的使用和原理

这周我们学习下消费者&#xff0c;仍然还是先从一个消费者的Hello World学起&#xff1a;public class Consumer { public static void main(String[] args) { // 1. 配置参数 Properties properties new Properties(); properties.put("key.des…