文件压缩【贪心算法】
1.压缩?文件压缩
压缩---->小
2.压缩分类
无损压缩:A---->B---->A
有损压缩:A---->B---->A~
3.为什么?源文件空间太大,压缩变小便于储存
4.如何实现?
源文件--->通过某种方式---->小
ABBBCCCCCDDDDDD--->更短的编码--->重新改写源文件
等长编码
A 00
B 01
C 10
D 11
编码:00010101 10101010 10111111 11111111
问题:D的出现次数比较多,那么是否可以将D的编码11变为一个就可以了
不等长编码:每个字符编码的长度不相等
1.自己设计一个算法--->新的压缩算法
2.借助huffman tree
一、huffman tree
1.路径:从A结点到B结点所经过的分支序列为从A结点到B结点的路径。
2.路径长度:从A结点到B结点所经过的分支个数为从A结点到B结点的路径。
3.从二叉树的根结点到二叉树中所有结点的路径长度之和为该二叉树的路径长度。
4.带权路径长度:叶结点的路径长度与相应权值的成积之和就为该二叉树的带权路径长度WPL。
带权路径长度最小的二叉树为huffman tree(也就是将权值大的结点,使其的路径短一些;经权值小的结点。路径长度可以长一些。这样就保证了权值最小)
例如:
1 3 7 5
16
7(D) 9
4 5(C)
(A)1 (B)3
则可以得到Huffman code:A 100 B 101 C 11 D 0
什么是huffman?
权值的集合{(A)1,(B)3,(D)7,(C)5}---->从下往上来创建:
1.以n个权值创建n支有根节点的二叉树森林
2.堆(优先级队列(默认情况是大堆),所以必须实例化优先级队列为小堆(重新来定制堆的比较规则,通过仿函数来定制))
重复以下步骤,直到二叉树森林中只剩余一棵树:
1.从二叉树森林中获得权值最小的两棵树。
2.以权值最小的两棵树作为某个结点左右子树,新节点权值为左右子树之和。
3.将二叉树重新放回到二叉树森林中。
结论:
文件压缩分为两步:
1.给每个字符重新找更短的编码(等长编码:容易被破解,不常用 不等长编码:1.自己设计 2.huffman)
2.用每个字符新的编码重新改写源文件
设计框架:
1.统计源文件中,每个字符出现的次数。
2.然后构建huffman tree
3.得到huffman code
4.接下来就可以进行压缩了
解压缩:
压缩文件中内容:
保存这个有效字符的行数
.txt
4
A,1
B,3 解压缩需要的信息
C,5
D,7
96 DF FC 00 源文件的压缩数据
编码给出存在的问题:给出的是不等长编码(其中的编码不能是其中一个编码的前缀)
如果用huffmantree 提供的不等长编码,因为路径唯一,而且权值全部存在于叶子节点中,所以不可能出现上述情况。
ABBBCCCCCDDDDDDD
10010110 11011111 11111100 00000000
方法1:取出1,然后和编码进行比较,没有在取出一位进行比较,直到找到为止--->性能太低了
方法2:如果给出huffmantree
拿出来是1那么往根的右边走,下一次去除0,则向左走,知道走到叶子结点便可以得到解码
问题1:怎么才可以拿到这个huffmantree:
1.首先,拿到文件出现的次数,肯定就可以创建出这个Huffman树
问题2:怎么才可以知道压缩文件中读取几行内容
问题3:源文件的后缀又是什么呢:所以在保存源文件的后缀信息(如:.txt等)
解压缩过程:
0.读取需要的信息
1.还原huffmantree
2.通过压缩数据+huffman tree 还原文件
Huffman Tree.h
#pragma once
#include <queue>
//HuffmanTree 的结构体
template<class W>
struct HuffmanTreeNode
{
HuffmanTreeNode(const W& weight = W())
:_pLeft(0)
, _pRight(0)
, _pParent(0)
, _weight(weight)
{}
HuffmanTreeNode<W>* _pLeft;
HuffmanTreeNode<W>*_pRight;
HuffmanTreeNode<W>* _pParent;
W _weight;
};
template<class W>
class Compare
{
public:
bool operator()(HuffmanTreeNode<W>* pLeft, HuffmanTreeNode<W>* pRight)
{
return pLeft->_weight > pRight->_weight;
}
};
//构建HuffmanTree
template<class W>
class HuffmanTree
{
typedef HuffmanTreeNode<W> Node;
typedef Node* PNode;
public:
HuffmanTree(const W* array, size_t size,const W& invalid)
{
std::priority_queue <PNode,std::vector<PNode>,Compare<W>> q;
for (size_t i = 0; i < size; ++i)
{
if (array[i] != invalid)
q.push(new Node(array[i]));
}
while (q.size()>1)
{
PNode pLeft = q.top();
q.pop();
PNode pRight = q.top();
q.pop();
PNode pParent = new Node(pLeft->_weight + pRight->_weight);
pParent->_pLeft = pLeft;
pLeft->_pParent = pParent;
pParent->_pRight = pRight;
pRight->_pParent = pParent;
q.push(pParent);
}
_pRoot = q.top();
}
PNode GetRoot()
{
return _pRoot;
}
~HuffmanTree()
{
Destroy(_pRoot);
}
private:
void Destroy(PNode &pRoot)
{
if (pRoot)
{
Destroy(pRoot->_pLeft);
Destroy(pRoot->_pRight);
delete pRoot;
pRoot = NULL;
}
}
private:
PNode _pRoot;
};
#pragma once
#include <string>
#include <assert.h>
using namespace std;
#include "HuffmanTree.h"
typedef unsigned long long UINT64;
struct CharInfo
{
CharInfo(UINT64 appearCount = 0)
:_appearCount(appearCount)
{}
CharInfo operator+(const CharInfo& info)
{
return CharInfo(_appearCount + info._appearCount);
}
bool operator>(const CharInfo& info)
{
return _appearCount > info._appearCount;
}
bool operator!=(const CharInfo& info)const
{
return _appearCount != info._appearCount;
}
bool operator==(const CharInfo& info)
{
return _appearCount == info._appearCount;
}
unsigned char _ch;
unsigned long long _appearCount;//字符出现的次数
string _strCode;//获得的字符编码
};
class FileCompress
{
public:
FileCompress()
{
for (size_t i = 0; i < 256; ++i)
{
_fileInfo[i]._ch = i;
_fileInfo[i]._appearCount = 0;
}
}
void CompressFile(const string& strFilePath)
{
//1.统计文件中每个字符出现的次数
FILE *fIn = fopen(strFilePath.c_str(), "r");
assert(fIn);
char *pReadBuff = new char[1024];
while (1)
{
size_t readSize = fread(pReadBuff,1,1024, fIn);
if (0 == readSize)
{
break;
}
for (size_t i = 0; i < readSize; ++i)
{
_fileInfo[pReadBuff[i]]._appearCount++;
}
}
//2.根据权值创建huffman树
HuffmanTree<CharInfo> ht(_fileInfo, 256, CharInfo(0));
//3.根据huffman树获取每个字符编码
GenerateHuffmanCode(ht.GetRoot());
FILE* fOut = fopen("1.hzp", "w");//压缩的文件名,以写入的方式打开
assert(fOut);
//4.写解压缩需要的信息
WriteComFileHeadInfo(fOut, strFilePath);
//5.用每个字符的编码来改写文件
fseek(fIn, 0, SEEK_SET);
char ch = 0;
size_t pos = 0;
char *pWriteBuff = new char[1024];
size_t writeSize = 0;
for (;;)
{
size_t readSize = fread(pReadBuff, 1, 1024, fIn);
if (0 == readSize)
break;
for (size_t i = 0; i < readSize; ++i)
{
string& strCode = _fileInfo[pReadBuff[i]]._strCode;
for (size_t j = 0; j < strCode.length(); ++j)
{
ch <<= 1;
if ('1' == strCode[j])
ch |= 1;
++pos;
if (8 == pos)
{
pWriteBuff[writeSize++] = ch;
if (1024 == writeSize)
{
fwrite(pWriteBuff, 1, 1024, fOut);
writeSize = 0;
}
ch = 0;
pos = 0;
}
}
}
}
if (pos < 8)
{
ch <= (8 - pos);
pWriteBuff[writeSize++] = ch;
}
fwrite(pWriteBuff, 1, writeSize, fOut);
fclose(fIn);
fclose(fOut);
delete[] pReadBuff;
delete[] pWriteBuff;
}
//该方法是解压过程
void UNCompressFile(const string& strComFilePath)
{
//1.获取解压缩需要的信息
FILE* fIn = fopen(strComFilePath.c_str(), "r");
assert(fIn);
string strFilePostFix;
GetLine(fIn, strFilePostFix);
//获取有效字符总行数
string strCount;
GetLine(fIn, strCount);
size_t lineCount = atoi(strCount.c_str());
//获取每个字符的次数信息
strCount = "";
for (size_t i = 0; i < lineCount; ++i)
{
GetLine(fIn, strCount);
_fileInfo[strCount[0]]._appearCount = atoi(strCount.c_str() + 2);
strCount = "";
}
//还原huffman tree
HuffmanTree<CharInfo> ht(_fileInfo, 256, CharInfo(0));
//2.读取压缩数据+huffman tree还原源文件
string strFileName("2");
strFileName += strFilePostFix;
FILE* fOut = fopen(strFileName.c_str(), "w");
assert(fOut);
char* pReadBuff = new char[1024];
char* pWriteBuff = new char[1024];
size_t writeSize = 0;
HuffmanTreeNode<CharInfo>*pRoot = ht.GetRoot();
HuffmanTreeNode<CharInfo> * pCur = pRoot;
//源文件大小
size_t fileLen = pRoot->_weight._appearCount;
size_t totalSize = 0;
for (;;)
{
size_t readSize = fread(pReadBuff, 1, 1024, fIn);
if (0 == readSize)
break;
for (size_t i = 0; i < readSize; ++i)
{
char ch = pReadBuff[i];
for (size_t j = 0; j < 8; ++j)
{
if (ch&(1 << (7 - j)))
pCur = pCur->_pRight;
else
pCur = pCur->_pLeft;
if (NULL == pCur->_pLeft&&NULL == pCur->_pRight)
{
pWriteBuff[writeSize++] = pCur->_weight._ch;
if (1024 == writeSize)
{
fwrite(pWriteBuff, 1, 1024, fOut);
writeSize = 0;
}
pCur = pRoot;
totalSize++;
if (totalSize == fileLen)
break;
}
}
}
}
fwrite(pWriteBuff, 1, writeSize, fOut);
fclose(fIn);
fclose(fOut);
delete[] pReadBuff;
delete[] pWriteBuff;
}
private:
void GetLine(FILE* fIn, string& strCode)//得到文件中的有效行数
{
char ch;
while (!feof(fIn))
{
ch = getc(fIn);
if ('\n' == ch)
return;
strCode += ch;
}
}
//该方法是要将编码信息的有效行数写在压缩文件中,用来解压缩的时候创建huffmantree
void WriteComFileHeadInfo(FILE *fOut, const string& strFilePath)
{
string strHeadInfo = GetFilePostFix(strFilePath);//得到后缀信息
strHeadInfo += '\n';
string strCode;
size_t linecount = 0;
char szAppearCount[32];
for (size_t i = 0; i < 256; ++i)
{
if (0 != _fileInfo[i]._appearCount)//不等于0则该位置的信息是有效的
{
linecount++;
strCode += _fileInfo[i]._ch;//写入这个字符(例如:A)
strCode += ',';
_itoa(_fileInfo[i]._appearCount, szAppearCount, 10);//将字符ch出现的次数以十进制形式放在szAppearCount空间中
strCode += szAppearCount;//然后将出现次数放在编码信息中
strCode += '\n';
}
}
_itoa(linecount, szAppearCount, 10);
strHeadInfo += szAppearCount;//将字符编码信息的有效行数放在strCode中
strHeadInfo += '\n';
fwrite(strHeadInfo.c_str(), 1, strHeadInfo.length(), fOut);//将头部的信息写入压缩文件中
fwrite(strCode.c_str(), 1, strCode.length(), fOut);//将编码的信息写入压缩文件中
}
//生成压缩文件的后缀(如:.txt),之后解压缩会用到的
string GetFilePostFix(const string& strFilePath)
{
//111.txt
//F:\work\111.txt
return strFilePath.substr(strFilePath.find_first_of('.'));
//运用库函数从后向前找'.'出现的位置,返回该位置之后;用substr来获取从该位置的字符串
}
//该方法是为了得到huffman编码
void GenerateHuffmanCode(HuffmanTreeNode<CharInfo>* pRoot)
{
if (NULL == pRoot)
return;
GenerateHuffmanCode(pRoot->_pLeft);
GenerateHuffmanCode(pRoot->_pRight);
if(NULL == pRoot->_pLeft && NULL == pRoot->_pRight)
{
HuffmanTreeNode<CharInfo>* pCur = pRoot;
HuffmanTreeNode<CharInfo>* pParent = pRoot->_pParent;
string& strCode = _fileInfo[pCur->_weight._ch]._strCode;
while (pParent)
{
if (pCur == pParent->_pLeft)
strCode += '0';
else
strCode += '1';
pCur = pParent;
pParent = pCur->_pParent;
}
reverse(strCode.begin(), strCode.end());
}
}
private:
CharInfo _fileInfo[256];
};
void TestCompress()
{
FileCompress fc;
//fc.CompressFile("1.txt");
fc.UNCompressFile("1.hzp");
}