文件压缩---huffman算法
来源:互联网 发布:大智慧软件 编辑:程序博客网 时间:2024/06/05 04:59
万年历online项目链接点击这里
本篇文章主要包含4个方面:
1.哈夫曼算法实现压缩的原理
2.具体压缩及解压过程思路阐述
3.项目中遇到的问题
4.项目扩展
一、原理简述:
huffman算法实现文件压缩的主要原理是通过huffman编码来重新表示字符,使得出现频率高的字符编码短,出现少的字符编码长。当用编码表示原文件时,总体的bit位时相对减少的。但当大部分字符出现的频率都差不多时,huffman压缩的压缩效率会很低。
二、具体压缩及解压思路:
压缩:
1. 统计字符出现的次数
由于所有的文件在电脑中都是以二进制的形式存储的,打开文本图片音乐视频的工具就是一种解码的过程,打开不同的文件则依据的是不同的解码规则。例如,图片是由一个个像素构成的,像素呢又是由二进制组成,所以同一样可以使用ASCII码保存信息。
我们使用一个容量为256个元素的数组来统计字符出现的次数,通过结构体将次数、字符、huffman编码相对应。
2.构建哈夫曼树
采用哈夫曼算法,将一组集合中权值最小的两棵树拿出来,以他们的权值之和作为父节点插入到这个集合中,不断重复,直到集合中只有一棵树。这棵树就是为哈夫曼树,再次我们采用最小堆来寻找这两个最小的数。在此我们以字符出现次数作为权值构建哈夫曼树,这样一来出现次数越多的字符就越接近根结点。
3.得到哈夫曼编码
有了哈夫曼树,我们通过从根结点出发走到各个叶子节点,向左走为0,向右走为1,就得到每个字符的哈夫曼编码。因此我们如果有哈夫曼树就可以将哈夫曼编码翻译为具体字符。
4.压缩
先写入配置信息,由于在解压时是没有原文件的,因为我们的压缩文件里存储的是哈夫曼编码,要接压必须先构建哈夫曼树,从而才能将哈夫曼编码通过发福曼树解释为字符。哈夫曼树是根据字符及次数构建的,所以我们存入字符和对应次数即可。
重新读取源文件,逐个将字符转换为对应的哈夫曼编码存放到压缩文件中。
解压缩(原理和压缩相似):
1.打开压缩文件并读取配置信息;
2.根据配置信息,建立哈夫曼树;
3.解压缩
在压缩文件中逐个字符读取,解析该字符的每一位,才用贪心法,只要遇到一个叶子节点就还原对应的字符,并将该字符存放到解压缩的文件里面。此时应注意,压缩文件的最后几位可能是我们补上去的,通过哈夫曼树性质可知,根结点的权值就是所有字符出现的总次数,我们可以借总次数来控制解压。
三、在项目中遇到的问题
1.解压时解压不完全
当用文本方式读取压缩文件时,由于是以哈夫曼编码(二进制数)存储,用EOF(宏,定义为-1)判断文件结尾时有可能提前遇到文件结束标志,因为二进制文件中-1是可以实现的。所以应采用二进制形式打开并使用 feof() 函数判断文件结束。
如果以文本方式读取,要把 ‘\r’(回车)、‘\n’(换行)两个字符转换为一个字符\n,而二进制形式则不需要处理。
2.二次压缩效率低
因为压缩之后,配置信息中字符出现的次数都相差不大,体现不出来哈夫曼的特性,所以再压缩的话效率会非常底。
3.汉字压缩时出现问题
由于汉字是用多个字符表示的,这些字符的范围是0-255,所以应该用 unsigned char 声明读取的字符。
四、项目扩展
实现了对文件夹的压缩,对文件夹实际上就是对文件夹中的内容进行压缩,所以找到一个子文件后就一直向里找,直到对找到的文件进行压缩。
MyHeap.h
#pragma once#include<iostream>#include<assert.h>#include<vector>using namespace std;//堆排序//仿函数 定义排列的方法,实现排列代码的复用template<class T>struct UpOrder{ bool operator()(const T i, const T j) { return i < j; }};template<class T>struct DownOrder{ bool operator()(const T i, const T j) { return i > j; }};//堆排列,默认升序排列方法,即排列成小堆template<class T, class Compare = DownOrder<T>>class Heap{public: Heap() {} Heap(T*arr, int size) { //将数组中的顺序插入顺序表中 _a.reserve(size); for (int i = 0; i < size; i++) { _a.push_back(arr[i]); } //建堆 for (int i = (size - 2) / 2; i >= 0; i--) { AdJustDown(i, size); } } void Sort(T* arr, int size) { assert(arr); int i = (size - 2) >> 1;//找到倒数第一个非叶节点 for (; i >= 0; i--)//从倒数第一个非叶节点开始往上排序 { AdJustDown(i, size); } } void Push(const T &a) { _a.push_back(a); AdJustUp(_a.size()-1); } size_t Size() { return _a.size(); } void Printf() { for (size_t i = 0; i < _a.size(); i++) { cout << _a[i] << " "; } cout << endl; } const T& Top() { return _a[0]; } void Pop()//将堆顶元素和最后一个元素交换,删除最后一个,再调整顺序 { assert(!_a.empty()); swap(_a[0], _a[_a.size() - 1]); _a.pop_back(); if (_a.size() > 1) { AdJustDown(0, _a.size()); } }protected: void AdJustDown(int root, int size) { assert(!_a.empty()); int parent = root; //用parent指针接收要排序的根结点 int child = parent * 2 + 1; //child表示该parent的左孩子结点 while (child < size) { //如果右孩子存在并且左孩子的值大于右孩子,则让child指针指向右孩子 if (((child + 1) < size) && Compare()(_a[child], _a[child + 1])) { child++; } if (Compare()(_a[parent], _a[child])) //若parent大则交换父子结点的值 { swap(_a[parent], _a[child]); parent = child; //继续往下比较 child = 2 * parent + 1; } else { break; } } } void AdJustUp(int child) { assert(!_a.empty()); while (child>0) { int parent = (child-1)>>1;//找到倒数第一个非叶节点 if (Compare()(_a[parent], _a[child])) { swap(_a[parent], _a[child]); child = parent; } else { break; } } }private: vector<T> _a;};
FileCompress.h
#pragma once #include<string> #include<algorithm> //算法头文件,可直接使用reverse函数 #include<io.h> #include<direct.h> #include "MyHufmanTree.h" typedef long long LongType; //定义结构体存放字符信息 struct FileInfo { FileInfo(LongType appearCount = 0) :_appearCount(appearCount) {} FileInfo operator+(const FileInfo &info)const { return FileInfo(_appearCount + info._appearCount); } bool operator != (const FileInfo &info)const { return _appearCount != info._appearCount; } bool operator == (const FileInfo &info)const { return _appearCount == info._appearCount; } bool operator<(const FileInfo &info)const { return _appearCount < info._appearCount; } bool operator>(const FileInfo &info)const { return _appearCount > info._appearCount; } unsigned char _ch; //字符 LongType _appearCount;//字符出现的次数 string _strCode; //字符对应的哈夫曼编码,定义为字符串 }; class HuffCompressFile { public: struct _HuffmanInfo { unsigned char _ch; LongType _count; }; const string Compressfile(string filename) //传入要压缩的文件名,返回已压缩的文件名 { vector<string>file; string path = filename.c_str();//c_str()函数返回一个指向该字符串的指针常量 getFiles(path, file);//把各级文件夹信息保存到file数组中 if (file.empty())//如果为空,则表示是一个文件,直接压缩 { return _Compress(filename); } else //文件夹 { //首先创建一个新的文件夹 string newpath = path; //新的压缩后文件夹的路径名 newpath += ".huf"; _mkdir(newpath.c_str()); for (int i = 0; i < (int)file.size(); i++) { _Compress(file[i], newpath); } return newpath; //返回新建的压缩文件夹的名字 } } const string _UnCompressfile(string filename) { vector<string>file; string path = filename.c_str();//c_str()函数返回一个指向该字符串的指针常量 getFiles(path, file);//把各级文件夹信息保存到file数组中 if (file.empty())//如果为空,则表示是一个文件进行压缩 { return _UnCompress(filename); } else //文件夹 { //首先创建一个新的文件夹 string newpath = filename; //新文件夹 for (int i = (int)filename.size() - 1; i >= 0; i--) { if (filename[i] == '.') { newpath.resize(i); break; } } newpath += ".uhuf"; _mkdir(newpath.c_str()); //创建一个新的解压缩文件 for (int i = 0; i < (int)file.size(); i++) { _UnCompress(file[i], newpath); } return newpath; //返回新建的解压缩文件夹的名字 } } protected: //初始化所有字符 void HuffFileCompress() { for (size_t index = 0; index < 256; ++index) { _fileInfo[index]._ch = index; _fileInfo[index]._appearCount = 0; } } const string _Compress(const string filename, const string path = string())// { HuffFileCompress();//初始化结点 //1.获取源文件中每个字符出现的次数 FILE* fp = fopen(filename.c_str(), "rb"); assert(fp); unsigned char ch = fgetc(fp); assert(ch); while (!feof(fp)) { _fileInfo[ch]._appearCount++; ch = fgetc(fp); } //2.根据字符出现的次数构建哈夫曼树,树中得体现出字符和次数 FileInfo invalid; invalid._appearCount = 0;//出现0次的字符不用来创建哈夫曼树 HuffmanTree<FileInfo> hf(_fileInfo, 256, invalid); //3.通过哈夫曼树获得每个字符的哈夫曼编码,遍历到叶子结点即可得到该结点哈夫曼编码 //由于需要从树顶向根部遍历,所以需要parent指针,即树节点是三岔链 HuffmanStrCode(hf.Top()); //4.压缩(遍历文档,字符转换为strcode) 压缩文件后缀定为.huf FILE* fIn = NULL; string CompressFileName = filename; CompressFileName += ".huf"; if (path.empty()) //为空表示是单个文件 { //第二次fOut打开 filename.huf fIn = fopen(CompressFileName.c_str(), "wb"); assert(fIn); } else //不为空表示是文件夹 { //得到要创建的路径 string FileName; //得到文件名 int i = filename.size() - 1; for (; i >= 0; i--) { if (filename[i] == '\\') break; FileName += filename[i]; } reverse(FileName.begin(), FileName.end()); string newpath = path; newpath += '\\'; newpath += FileName; newpath += ".huf"; fIn = fopen(newpath.c_str(), "wb"); //打开压缩文件 assert(fIn); } //写配置信息(字符出现的次数),解压时根据配置信息方能正确解压 _HuffmanInfo info; for (size_t i = 0; i < 256; ++i) { if (_fileInfo[i]._appearCount)//出现过的以结构体的形式直接写进去 { info._ch = _fileInfo[i]._ch; info._count = _fileInfo[i]._appearCount; size_t size = fwrite(&info, sizeof(_HuffmanInfo), 1, fIn); assert(size = sizeof(_HuffmanInfo)); } } //如何知道写了多少个info进去? 区分配置信息和压缩信息 info._count = 0;//已写的info count肯定不为0,用来隔离配置信息和压缩信息 fwrite(&info, sizeof(_HuffmanInfo), 1, fIn); unsigned char value = 0; int count = 0; fseek(fp, 0, SEEK_SET); //fp指向文件的开始 ch = fgetc(fp); //逐个字符读取 assert(ch); while (!feof(fp)) { string &code = _fileInfo[ch]._strCode; for (size_t i = 0; i < code.size(); i++) { value <<= 1; if (code[i] == '1') { value |= 1; } else { value |= 0; } ++count; if (count == 8)//满8位写入一次 { fputc(value, fIn); value = 0; count = 0; } } ch = fgetc(fp); } if (count != 0) // { value <<= (8 - count); fputc(value, fIn); } fclose(fIn); fclose(fp); return CompressFileName; } const string _UnCompress(const string filename,const string path = string()) { //得到解压缩之后的文件的名字 string name; name = filename; int i = 0; string posfix; for (i = (int)filename.size() - 1; i >= 0; --i) //找到后缀出现的位置 { posfix.push_back(filename[i]); if (filename[i] == '.') break; } reverse(posfix.begin(), posfix.end());//让posfix保存要解压文件的后缀 if (posfix != ".huf") //如果要解压的不是huffman压缩的则不能解压 { return string(); } //1.改变文件后缀 name.resize(i); string UnCompressFileName = name; //得到压缩文件名 UnCompressFileName += ".unhuf"; FILE *fInput = fopen(filename.c_str(), "rb"); assert(fInput); FILE *fOut = NULL; //解压缩文件 if (path.empty()) //如果为空,表示是单个文件解压 { //打开解压缩文件 fOut = fopen(UnCompressFileName.c_str(), "wb"); if (fOut == NULL) { fclose(fInput); exit(EXIT_FAILURE); } } else //文件夹进行解压缩 { string FileName; //先得到压缩的文件名 for (int i = (int)name.size() - 1; i >= 0; i--) { if (name[i] == '\\') { break; } FileName += name[i]; } reverse(FileName.begin(), FileName.end()); string newpath = path; newpath += "\\"; newpath += FileName; newpath += ".uhuf"; //打开解压缩文件 fOut = fopen(newpath.c_str(), "wb"); if (fOut == NULL) { fclose(fInput); exit(EXIT_FAILURE); } } //2.重建哈夫曼树(根结点是所有叶子结点的和) //先读取字符次数信息,再重建哈弗曼树 FileInfo UnComInfo[256]; _HuffmanInfo info; while (1) { size_t size = fread(&info, sizeof(_HuffmanInfo), 1, fInput); assert(size = sizeof(_HuffmanInfo)); if (info._count > 0) { UnComInfo[(unsigned char)info._ch]._ch = info._ch;//可能出现汉字,所以用unsigned char UnComInfo[(unsigned char)info._ch]._appearCount = info._count; } else { break; } } FileInfo invalid; invalid._appearCount = 0;//出现0次的字符不用来创建哈夫曼树 HuffmanTree<FileInfo> tree(UnComInfo, 256, invalid); HuffmanTreeNode<FileInfo>* root = tree.Top(); LongType charCount = root->_w._appearCount; //所有结点出现的次数,即有效字符出现的个数 //3.解压缩 FILE* fIn = fopen(UnCompressFileName.c_str(), "wb"); assert(fIn); //获取8位后和1000 0000 按位与 unsigned char value = fgetc(fInput); //每次读取一个字节,一个字节八个位 HuffmanTreeNode<FileInfo>*cur = root; while (!feof(fIn)) { for (int tmp = 7; tmp >= 0; --tmp) //获取压缩文件存放的二进制编码 { if (value &(1 << tmp)) //逐个位判断为1或0,从而决定叶节点寻找的方向 cur = cur->_right; else cur = cur->_left; if (cur->_left == NULL && cur->_right == NULL) { fputc(cur->_w._ch, fIn); cur = root; if (--charCount == 0)//有效字符已经读完 { goto end; } } } value = fgetc(fInput); } end: fclose(fIn); fclose(fInput); return UnCompressFileName; } void HuffmanStrCode(HuffmanTreeNode<FileInfo>*head) { if (head) { HuffmanStrCode(head->_left); HuffmanStrCode(head->_right); if (head->_left == NULL && head->_right == NULL) //找到叶子结点 { //从叶子结点到根结点遍历 HuffmanTreeNode<FileInfo> *cur = head; HuffmanTreeNode<FileInfo> *parent = head->_parent; string& code = _fileInfo[head->_w._ch]._strCode; while (parent) { if (parent->_left == cur) code.push_back('0'); else code.push_back('1'); cur = parent; parent = parent->_parent; } reverse(code.begin(), code.end()); } } } void getFiles(string path, vector<string>& files) { //文件句柄 long hFile = 0; //文件信息 struct _finddata_t fileinfo;//该结构体是用来存储文件的各种信息 string p; //string &assign(const char *s);用c类型字符串s赋值,append 指追加 if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1) { do { //如果是目录,迭代之 //如果不是,加入列表 if ((fileinfo.attrib & _A_SUBDIR))//判断当前文件夹是否为一个文件夹 { if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)//不为空就向里递归 getFiles(p.assign(path).append("\\").append(fileinfo.name), files); } else { files.push_back(p.assign(path).append("\\").append(fileinfo.name));// } }while (_findnext(hFile, &fileinfo) == 0);//查找成功 _findclose(hFile); } } private: FileInfo _fileInfo[256]; };
MyHufmanTree.h
#pragma once#include"MyHeap.h" template <class W>struct HuffmanTreeNode{ HuffmanTreeNode<W>* _left; HuffmanTreeNode<W>* _right; HuffmanTreeNode<W>* _parent; W _w; HuffmanTreeNode<W>(const W&w) : _left(NULL) , _right(NULL) , _parent(NULL) , _w(w) {}};template<class W>class HuffmanTree{ typedef HuffmanTreeNode<W> Node;public: HuffmanTree() :_root(NULL) {} ~HuffmanTree() { Destory(_root); } HuffmanTree(W*a, size_t n, const W&invalid)//定义非法值,遇到不创建 { struct Compare { bool operator()(const Node*left, const Node* right) { return left->_w > right->_w; } }; Heap<Node*> minHeap; for (size_t i = 0; i < n; ++i) { if (a[i] != invalid) { minHeap.Push(new Node(a[i])); } } while (minHeap.Size() > 1) { //取两个权值最小的节点 Node* left = minHeap.Top(); minHeap.Pop(); Node*right = minHeap.Top(); minHeap.Pop(); Node*parent = new Node(left->_w + right->_w); parent->_left = left; parent->_right = right; left->_parent = parent; right->_parent = parent; minHeap.Push(parent); } _root = minHeap.Top(); } void Destory(Node* head) { if (head) { Destory(head->_left); Destory(head->_right); delete head; head = NULL; } } void Printf() { _printf(_root); cout << endl; } Node* Top() { return _root; }private: Node* _root;};
test.cpp
#include "MyHeap.h"#include "MyHufmanTree.h"#include "FileCompress.h"void testCompress(){ HuffCompressFile hf; string str; str = hf.Compressfile("1"); string out; out = hf._UnCompressfile("1.huf");} int main(){ testCompress(); system("pause"); return 0;}
- huffman算法---文件压缩
- 文件压缩---huffman算法
- Huffman 编码压缩算法
- huffman编码压缩算法
- Huffman 编码压缩算法
- Huffman 编码压缩算法
- Huffman 编码压缩算法
- Huffman 编码压缩算法
- Huffman 编码压缩算法
- Huffman 编码压缩算法
- Huffman 编码压缩算法
- Huffman 编码压缩算法
- Huffman 编码压缩算法
- Huffman 编码压缩算法
- Huffman 编码压缩算法
- huffman编码压缩算法
- Huffman 编码压缩算法
- huffman 文件 压缩 解压缩
- 学习笔记-Android AIDL传递对象数据
- js 鼠标右击事件
- 汉诺塔问题
- 统计学习方法笔记:决策树
- Hibernate Tools for Eclipse安装与使用
- 文件压缩---huffman算法
- spring_让 spring自动扫描和管理bean
- 练习三(第二周)
- PAT乙级1067. 试密码(20)
- Python:读取命令行参数
- 纯 div 固定在 页面底部, 不随滚动条 滚动
- 操作系统基本知识概述
- (CSU
- session的生命周期