哈夫曼树应用——文件压缩
来源:互联网 发布:mt4软件使用视频教程 编辑:程序博客网 时间:2024/05/20 05:58
1.哈夫曼树的简介:
哈夫曼树(Huffman tree),又名最优树,指给定n个权值作为n的叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。
2.哈夫曼树的构造方式如下:
比如说有一组数,1,4,3,5,2,6,7,根据哈夫曼树的定义,首先我们找到这些树中最小的2个数据,当做叶子节点,用他们两个来构造其父亲节点,父亲节点的值即为2个叶子节点的权值之和,再将父亲放回原来的数据中,挑选出2个最小的来继续构建,依次类推,直到所有的数据都被拿出来我们构造他如下图所示:
每次都从数据中拿出2个最小的数据出来,但是要怎样才能拿出来最小的2个数据呢?这就要用到我之前介绍的堆,堆有最小堆 和最大堆,这里的哈夫曼树要运用最小堆的形式,把这些数据建立小堆,每次从中拿一个最小的,拿2次,构建哈夫曼树。
堆的实现代码前边我已经解释过,这里再看一遍实现的代码:
#pragma once#include <iostream>#include <vector>#include <cassert>using namespace std;template<typename T>struct Max{ bool operator()(const T& x1, const T& x2) { return x1 > x2; }};template<typename T>struct Min{ bool operator()(const T& x1, const T& x2) { return x1 < x2; }};template <typename T,typename Compare=Min<T>>class Heap{public: Heap() {} Heap(T* a, size_t sz) { for (int i = 0; i < sz; i++) { _a.push_back(a[i]); } //从最后一个叶节点的父节点建堆 for (int i = (sz - 2) / 2; i >= 0; --i) { _AdjustDown(i); } } void Push(const T& x) { _a.push_back(x); _AdjustUp(_a.size()-1); } void Pop() { assert(_a.size()); swap(_a[0], _a[_a.size() - 1]); _a.pop_back(); _AdjustDown(0); } const T& Top() { return _a[0]; } size_t Size() { return _a.size(); } bool Empty() { return _a.empty(); }protected: void _AdjustDown(int parent) { int child = parent * 2 + 1; int size = _a.size(); Compare com; while (child < size) { if (child + 1 < size&&com(_a[child + 1], _a[child])) { ++child; } if (com(_a[child], _a[parent])) { swap(_a[child], _a[parent]); parent = child; child = parent * 2 + 1; } else { break; } } } void _AdjustUp(int child) { int parent = (child - 1) / 2; Compare com; while (child >= 0) {//只考虑父亲与孩子的大小,不考虑兄弟之间大小 if (com(_a[child], _a[parent])) { std::swap(_a[child],_a[parent]); child = parent; parent =(child - 1) / 2; } else { break; } } }protected: vector<T> _a;};
接下来是生成哈夫曼树的代码,运用到了堆的问题
#pragma once#include <iostream>#include "Heap.h"using namespace std;template<typename T>struct HuffmanTreeNode{ T _weight; HuffmanTreeNode* _parent; HuffmanTreeNode* _left; HuffmanTreeNode* _right; HuffmanTreeNode(const T& weight) :_weight(weight) , _parent(NULL) , _left(NULL) , _right(NULL) {}};template<typename T>class HuffmanTree{ typedef HuffmanTreeNode<T> Node;public: HuffmanTree() :_root(NULL) {} HuffmanTree(T* a, size_t size, T& invalid) { //仿函数 struct Min { bool operator()( Node* t1, Node* t2) { return (t1->_weight)< (t2->_weight); } }; //建小堆 Heap<Node*, Min> min_heap; for (size_t i = 0; i < size; i++) { if (a[i] != invalid) { Node* newNode = new Node(a[i]); min_heap.Push(newNode); } } if (min_heap.Size() == 1) { _root = min_heap.Top(); } while (min_heap.Size()>1) { Node* left = min_heap.Top(); min_heap.Pop(); Node* right = min_heap.Top(); min_heap.Pop(); Node* parent = new Node(left->_weight + right->_weight); parent->_left = left; parent->_right = right; left->_parent = parent; right->_parent = parent; min_heap.Push(parent); _root = parent; } } Node* GetRoot() { return _root; } ~HuffmanTree() { assert(_root); _HuffmanTreeDestory(_root); _root = NULL; }protected: void _HuffmanTreeDestory(Node* root) { if (root != NULL) { _HuffmanTreeDestory(root->_left); _HuffmanTreeDestory(root->_right); delete root; root = NULL; } } Node* _root;};
3.接下来我要开始给大家解析一下文件压缩是怎么回事。
文件压缩就是想要让文件变得小一点,节省存储空间,用哈夫曼树实现文件压缩的原理就是让其所占得内存变小,但怎样让它的存储空间变小呢?举例说明,比如存一个文件aaaabbbccd
1>首先统计每个字符出现的个数,然后用这些字符出现的个数来构造哈夫曼树,构造出来的哈夫曼树我们可以看到,每个叶子节点都是字符出现的次数,看图更好的说明一下:
2>一个字符占一个字节的存储空间,要想办法来减少它所占用的内存,我们可以用哈夫曼编码来实现,就是说从根结点开始,向左走我们规定为0,向右走规定为1,每条路径都是不是1就是0,直到走到根节点,形成字符对应的哈夫曼编码,如图:
3>实现对文件的压缩设置,我们将每个字符都生成哈夫曼编码之后,每一个码都只是占用内存的1位,我们将每8位为一组,写入到压缩文件中,最后不够8位的要进行移位操作,这样才能保证所有的编码是连续的。
例子中的编码为:
最后编码生了6位,需要左移2位才能保证解压缩的时候不会出现问题。
4>需要生成配置文件,来保存每个字符,以及每个字符出现的次数
5>要知道文件中的字符最多只有256种,所以我们可以定义一个数组来存储这些字符,同样这个数组不能使普通数组,应该是一个结构体数组,每个数组元素应该包含字符,字符出现的个数,以及字符对应的编码,定义如下:
//字符信息typedef long long LongType;struct CharInfo{ unsigned char _ch;//字符 LongType _count;//字符在文件中出现的个数 string _code;//哈夫曼编码 CharInfo(LongType count=0) :_ch(0) ,_count(count) {} CharInfo operator + (const CharInfo& info) { return CharInfo(_count + info._count); } bool operator < (const CharInfo& info) { return _count < info._count; } bool operator >(const CharInfo& info) { return _count>info._count; } bool operator !=(const CharInfo& info) { return _count != info._count; } bool operator ==(const CharInfo& info) { return _count == info._count; }};
6>实现解压缩就是压缩的逆过程,从配置文件中获取字符出现的次数,重建哈夫曼树,从压缩文件的第一个字符开始,比较其二进制的每一位,是0的话,从根结点开始,向左走一步,是1的话,从根结点向右走一步,比较8次还没有找到叶子节点,就接着从压缩文件中获取字符,直到找到叶子节点为止,将叶子节点的字符信息写到解压缩文件中
接下来就是完整的代码了
class FileCompress{ typedef HuffmanTreeNode<CharInfo> Node;public: FileCompress() { for (int i = 0; i < 256; i++) { _Info[i]._ch = i; } } string Compress(const char* filename) { //1.统计字符出现的次数 assert(filename); //二进制方式读 FILE* fout = fopen(filename,"rb"); assert(fout); //读取文件中的字符 int ch = fgetc(fout); while (ch != EOF) { _Info[ch]._count++; ch = fgetc(fout); } //2.生成Huffman树 CharInfo invalid; HuffmanTree<CharInfo> Tree(_Info, 256,invalid); //3.生成Huffman编码 string code; MakeHuffmancode(Tree.GetRoot(),code); Node* ROOT = Tree.GetRoot(); cout << "压缩" << ROOT->_weight._count<< "个字符" << endl; //4.压缩 string CompressFilename = filename; CompressFilename.append(".Compress"); FILE* fin = fopen(CompressFilename.c_str(), "wb");//二进制方式写入 fseek(fout,0,SEEK_SET); ch = fgetc(fout); unsigned char value = 0; int pos = 0; while (ch != EOF) { //code中只有0和1 code = _Info[ch]._code; for (size_t i = 0; i < code.size(); i++) { if (code[i] == '1') { value <<= 1; value |= 1; } else { value <<= 1; value |= 0; } pos++; if (pos == 8) { fputc(value,fin); pos = 0; value = 0; } } ch = fgetc(fout); } if (pos != 8) { value <<= (8 - pos); fputc(value, fin); } cout << "压缩文件的名字:" << filename << endl; fclose(fout); fclose(fin); //5.创建配置文件,其中存放的树字符以及字符出现的个数 string ConfigFilename = filename; ConfigFilename.append(".config"); FILE* finConfig = fopen(ConfigFilename.c_str(), "wb"); string Line; char Buff[128]; for (int i = 0; i < 256; i++) { if (_Info[i]._count != 0) { fputc(_Info[i]._ch, finConfig); Line += ','; _itoa((int)_Info[i]._count, Buff, 10); Line += Buff; Line += '\n'; fputs(Line.c_str(), finConfig); Line.clear(); } } fclose(finConfig); return CompressFilename; } //6.解压缩 string UnCompress(const char* filename) { assert(filename); //打开配置文件 string name = filename; size_t _index = name.rfind('.'); string Configfilename = name.substr(0, _index); Configfilename += ".config"; FILE* foConfigname = fopen(Configfilename.c_str(), "rb"); cout << "解压缩文件的名字:" << name << endl; //重建_Info的哈希表 string Line; while (ReadLine(foConfigname, Line)) { unsigned char ch = Line[0]; _Info[ch]._ch = Line[0]; LongType count = atoi(Line.substr(2).c_str()); _Info[ch]._count = count; Line.clear(); } //解压缩文件名 string Compressfilename = filename; size_t index = Compressfilename.rfind('.'); string UnCompressFilename = Compressfilename.substr(0,index); UnCompressFilename.append(".UnCompress"); //生成Huffman树 CharInfo invalid; HuffmanTree<CharInfo> Tree(_Info, 256, invalid); //打开要被解压缩的文件 FILE* fout = fopen(filename,"rb"); //创建解压缩文件并写入 FILE* fin = fopen(UnCompressFilename.c_str(),"wb"); Node* root = Tree.GetRoot(); Node* cur = root; LongType count = root->_weight._count; cout << "解压缩" << count << "个字符" << endl; //文件中只有一种字符时,没有哈夫曼编码,要特殊处理 /*if (cur->_left == NULL&&cur->_right == NULL) { while (count--) { fputc(root->_weight._ch,fin); } return UnCompressFilename; }*/ //文件中只有一种字符的情况 unsigned Readch = fgetc(fout); if (Readch == 0) { while (count--) { fputc(root->_weight._ch, fin); } return UnCompressFilename; } int pos = 7; while (Readch != EOF) { if (pos >= 0) { if (((Readch >> pos) & 1) == 1) { cur = cur->_right; } else { cur = cur->_left; } --pos; if (cur->_left == NULL&&cur->_right == NULL) { fputc(cur->_weight._ch, fin); cur = root; //找到一个叶子节点,解压缩的字符个数减少一个 --count; } if (count == 0) { break; } } else { pos = 7; Readch = fgetc(fout); } } return UnCompressFilename; }protected: void MakeHuffmancode(Node* root, string code) { if (root == NULL) return; else if (root->_left == NULL&&root->_right == NULL) { _Info[(root->_weight)._ch]._code = code; } else { MakeHuffmancode(root->_left, code + '0');//向左走编码加0 MakeHuffmancode(root->_right, code + '1');//向右走编码加1 } } //读取一行字符 bool ReadLine(FILE* filename, string& Line) { int ch = 0; while ((ch=fgetc(filename))!= EOF) { if (ch == '\n'&&Line.size() != 0) { return true; } Line += ch; } return false; }protected: CharInfo _Info[256];};void Test(){ //压缩 FileCompress f1; int begin = GetTickCount(); string Cf1 = f1.Compress("haha.txt"); //string Cf1 = f1.Compress("photo.jpg"); //string Cf1 = f1.Compress("Music.m4a"); int end = GetTickCount(); cout << "压缩时间为:" << end - begin << endl; //解压缩 FileCompress f2; int begin1 = GetTickCount(); string UnCf1 = f2.UnCompress("haha.txt.Compress"); //string UnCf1 = f2.UnCompress("photo.jpg.Compress"); //string UnCf1 = f2.UnCompress("Music.m4a.Compress"); int end1 = GetTickCount(); cout << "解压缩时间为:" << end1 - begin1 << endl;}
结果为:
在文件中生成了对应的压缩文件,配置文件,以及解压缩文件,
解压缩文件与源文件大小相同,压缩文件较源文件小,同样可以进行图片,视频,音频的测试,在这里我就不一一做测试了。
其中用到了许多关于文件的知识还有string的知识,substr()是字符串复制函数,可以指定复制字符的个数,c_str()返回指向字符串的指针,还用了其他一些函数,文件压缩综合了许多知识,要好好理解哈夫曼树以及文件压缩的实现思路,体改自己的融会贯通能力。
- 哈夫曼树应用——文件压缩
- huffman树的应用——文件压缩
- 【项目】哈夫曼树的应用:文件压缩
- GZIP文件压缩的应用
- Linux——文件与文件系统的压缩和打包(更重要的应用是备份)
- 图像压缩_文件压缩zlib的应用
- java应用压缩、解压zip文件
- 哈夫曼编码应用之实现文件压缩
- 哈夫曼树实现文件压缩
- 文件压缩总结-哈夫曼树
- Huffman编码——文件压缩项目
- linux命令—文件压缩解压缩
- js文件上传—图片压缩
- 哈夫曼树应用拓展:压缩文件夹
- web应用性能优化--采用gzip静态压缩+动态压缩方式压缩js、css文件
- iOS之网络—— JSON解析、XML解析、文件下载、文件的压缩和解压缩
- JAVA文件工具类之——文件压缩
- dos.bat 批处理文件的应用——批量压缩
- 用Flex实现的百分比布局
- HTML5新特性-多线程(Worker SharedWorker)
- zoj 3954 思维+ 码力 training 2
- 欢迎使用CSDN-markdown编辑器
- Physically Based Shading and Image Based Lighting 9
- 哈夫曼树应用——文件压缩
- hdu2617-happy2009
- 在屏幕上打印菱形
- JVM调优总结(九)-新一代的垃圾回收算法
- 第一个页面
- 高精度除以单精度 和 高精度对单精度取模
- Android.mk
- 题目1095:2的幂次方
- Mac 解压Android NDK.bin文件