huffman树-------文件压缩

19人阅读 评论(1) 收藏 举报
分类:
哈夫曼树(霍夫曼树)又称为最优树.
1、路径和路径长度
在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。
2、结点的权及带权路径长度
若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积
3、树的带权路径长度
树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL。


有了上面的概念,那么下面再给出huffman树的概念:

           假设有n个权值{w1,w2,w3........,wn},试构造一棵有n个叶子结点的二叉树,每个叶子结点带权为wi,则其中带权路径长度WPL最小的二叉树称作最优二叉树或赫夫曼树。

那么下面是构造一棵赫夫曼树的方法:

假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,则哈夫曼树的构造规则为:
(1) 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);
(2) 在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;
(3)从森林中删除选取的两棵树,并将新树加入森林;
(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。

下面是huffman.h的实现代码:

#pragma once
#include<queue>
template<typename T>struct Nod{
	T _w;
	struct Nod *_left;
	struct Nod *_right;
	Nod(T const& w) :_w(w), _left(NULL), _right(NULL){}
};
template<typename T> class Huffman{
public:
	typedef Nod<T> Node;
	template<typename Nod>struct Greater{        //仿函数,比较结点里面的值域的大小
		bool operator()(Nod const& l, Nod const& r)
		{
			return l->_w > r->_w;
		}
	};
	Huffman() = default;//默认构造函数
	Huffman(T* w,size_t N,T invalue)//传一个数组名和数组的长度进来
	{
		//priority_queue<Node> pq0;//优先级队列默认大根堆   less的意思是孩子小于双亲
		priority_queue<Node*, vector<Node*>, Greater<Node*> > pq;//小根堆,greater的意思是孩子大于双亲
		for (size_t i = 0; i < N; ++i){
			if (w[i] != invalue)
				pq.push(new Node(w[i]));//建立小根堆,压进去是Node*
		}
		_root = CreatTree(pq);
	}
	Node* GetRoot()
	{
		return _root;
	}
private:
	Node *_root;
	Huffman(Huffman<T> const&);//防拷贝
	Huffman<T>& operator=(Huffman<T> const&);//防复制
	Node* CreatTree(priority_queue<Node*, vector<Node*>, Greater<Node*>> &pq)
	{
		while (pq.size() > 1){
			Node *left = pq.top(); pq.pop();
			Node *right = pq.top(); pq.pop();
			Node *parent = new Node(left->_w + right->_w); pq.push(parent);

			parent->_left = left;parent->_right = right;
		}
		return pq.top();
	}
};


---------------------------------------------我是分界线-------------------------------------------------------------------------


     构建完了huffman树,开始进行文件压缩。文件压缩可以分为五个步骤:

  • 1. 统计字符出现的次数
  • 2. 构建HuffmanTree
  • 3. 生成哈夫曼编码 (Huffman Code)
  • 4. 压缩 (compress)
  • 5. 解压缩 (uncompress)


 下面是文件具体的压缩过程:

Input.txt 
内容:aaaabbbcccd

1. 统计字母出现的次数:

a —— 4 
b —— 3 
c —— 2 
d —— 1

2. 构建huffmanTree:

这里写图片描述

3. 生成huffman code(编码):

a -> 0 (* 出现次数多的字符(数值大)–路径短–编码短) 
b -> 11 
c -> 101 
d -> 100

4. 压缩(按位存储):

文件 :Input.txt.huffman 
内容 : 00001111 11101101 100(00000) —-按位存储 
第三个字节后面的五个0是补位 
(原文件占用10个字节,压缩后占用3个字节)

5. huffman解压缩:

还原文件:Input.txt.unhuffman

文件压缩的完整实现代码:

包括huffman.h,compressfile.h和test.cpp。

huffman.h:(如上代码)

compressfile.h

#pragma once
#include"huffman.h"
#include<string>
class FileCompress{
public:
	struct _charinfos{//内置类型
		char _ch;
		size_t _count;
		std::string _code;
		bool operator!=(_charinfos const& charin)//如果结构体中_count不相等,那么结构体不相等
		{
			return _count != charin._count;
		}
		bool operator>(_charinfos const& charin)
		{
			return _count > charin._count;
		}
		_charinfos operator+(_charinfos const& charin)
		{
			_charinfos tmp;
			tmp._count = _count + charin._count;
			return tmp;
		}
	};
	typedef Nod<_charinfos> Node;
	struct _tmpinfos{
		char _ch;
		size_t _count;
	};
	FileCompress()//默认构造函数
	{
		for (size_t i = 0; i < 256; ++i){
			charinf[i]._ch = i;
			charinf[i]._count = 0;
		}
	}
	void Compress(const char* file)//huffman.txt
	{
		//1.打开一个文件,对其字符进行统计出现的频率
		FILE *fsrc = fopen(file, "rb");
		char ch = fgetc(fsrc);
		while (!feof(fsrc)){
			++charinf[(unsigned char)ch]._count;
			ch = fgetc(fsrc);
		}
		//2.构建一棵赫夫曼树并得到huaffman code
		_charinfos invalue; invalue._count = 0;
		Huffman<_charinfos> h(charinf, 256, invalue);//数组向指针的转化
		auto root = h.GetRoot(),cur = root; string str;
		Traverse(cur, str);//遍历赫夫曼树,得到huffman code
		//3.压缩文件,把1.中统计的字符放到压缩文件中去
		string filedst(file);
		filedst += ".huffman";
		FILE *fdst = fopen(filedst.c_str(), "wb");//打开目标文件
		_tmpinfos tmpinf;
		for (size_t i = 0; i < 256; ++i){//为了尽可能方便且少的将信息压入目标文件
			if (charinf[i]._count){
				tmpinf._ch = i;tmpinf._count = charinf[i]._count;
				fwrite(&tmpinf, sizeof(_tmpinfos), 1, fdst);//将有用的信息写入目标文件
			}
		}tmpinf._count = 0;
		fwrite(&tmpinf, sizeof(_tmpinfos), 1, fdst);//多写入一个信息,用作信息结束标志

		rewind(fsrc);//将stream指向的流的文件定位符设置在文件的开始位置,等价于fseek(fsrc,0,SEEK_SET)
		char ch1 = fgetc(fsrc), ch2 = 0; size_t pos = 0;
		while (!feof(fsrc)){
				string code = charinf[(unsigned char)ch1]._code;
				for (size_t j = 0; j < code.size(); ++j){
					ch2 |= code[j] - '0' << pos++;
					if (pos == 8){
						//int test55 = ch2; cout << test55 << " ";//验证信息,查看到底压入了什么数据
						fputc(ch2, fdst);//向目标文件中写入一个每个位都被重置了的字符
						ch2 = 0; pos = 0;
					}
				}
				ch1 = fgetc(fsrc);
		}
		//还要考虑当原文件读到最后一字符个时,ch2的8个位没有被全部重置的情况
		if (pos){ /*int test55 = ch2; cout << test55 << " ";*/
			fputc(ch2, fdst);
		}
		fclose(fsrc);
		fclose(fdst);
	}
	void Uncompress(const char* file)//huffmanhzq.txt
	{
		//4.根据压缩文件重构赫夫曼树
		FILE *fout = fopen(file, "rb");
		_tmpinfos tmpinf;
		fread(&tmpinf, sizeof(_tmpinfos), 1, fout);
		while (tmpinf._count){//根据压缩文件,对其字符进行统计出现的频率
			charinf[(unsigned char)tmpinf._ch]._ch = tmpinf._ch;
			charinf[(unsigned char)tmpinf._ch]._count = tmpinf._count;
			fread(&tmpinf, sizeof(_tmpinfos), 1, fout);
		}
		_charinfos invalue; invalue._count = 0;
		Huffman<_charinfos> h(charinf, 256, invalue);//重构赫夫曼树
		//5.解压缩文件(root->_w._count记录了原文件中所有字符出现的次数)
		string filedst(file);
		filedst.erase(filedst.rfind('.')); filedst += ".unhuffman";
		FILE *fin = fopen(filedst.c_str(), "wb");//以写的方式打开文件
		auto root = h.GetRoot(), cur = root;
		char ch = fgetc(fout); size_t n = root->_w._count;
		while (!feof(fout)){
			//int test55 = ch; cout << test55 << " ";//验证信息,查看到底拿到了什么数据
			for (size_t i = 0; i < 8; ++i){
				if (ch & 1 << i){ cur = cur->_right; }
				else{ cur = cur->_left; }

				if (!cur->_left && !cur->_right){
					fputc(cur->_w._ch, fin);
					--n;//因为n记录了原文件所有字符出现的次数,所以当n==0时表示已经翻译完
					cur = root;
				}
				if (!n)break;
			}
			ch = fgetc(fout);
		}
		fclose(fout);
		fclose(fin);
	}
private:
	_charinfos charinf[256];
	void Traverse(Node* const& root, string str)//遍历赫夫曼树,得到huffman code
	{
		if (!root->_left && !root->_right){//走到叶子结点将huffman code信息写入并返回
			charinf[(unsigned char)root->_w._ch]._code = str;
			return;
		}
		Traverse(root->_left, str + '0');
		Traverse(root->_right, str + '1');
	}
};
test.cpp:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include<CoreWindow.h>
#include"FileCompress.h"

void test_compressfile()
{
	FileCompress fc;
	fc.Compress("input.txt");      //从input.txt到input.txt.huffman
	fc.Uncompress("input.txt.huffman"); //从input.txt.huffman到input.txt.unhuffman

	//FileCompress fc;
	//fc.Compress("hehe.jpg");      //从hehe.jpg到hehe.jpg.huffman
	//fc.Uncompress("hehe.jpg.huffman");//从hehe.jpg.huffman到hehe.jpg.unhuffman
}
int main()
{
	test_compressfile();
	system("pause");
	return 0;
}


最后讲一下在进行文件压缩的时候可能会遇到的问题:(ps:别人总结的很好,下面是摘抄笔记)

1.压缩带中文的文件,程序就会崩溃。

最后发现数组越界的问题. \ 
因为char它的范围是-128~127,程序中使用char类型为数组下标(0~127),所以字符没有问题,但是汉字是占两个字节的,所以会出现越界的问题,解决的方法就是char类型强转为unsigned char,它的范围为0~255.

2.解压缩文件生成后会丢失很多内容.

1. 文件的打开方式.
   这里打开文件一定要用二进制形式,"wb","rb".因为二进制打开和文本打开其实是有区别的。
   1.文本方式打开:会对‘\n’进行特殊处理,那如果这个字符本身就是'\n'.这就会出现问题。
   2.二进制方式打开:不进行任何处理,是什么就是什么。
2. 文件结束符的问题。
   刚开始用的文件结束标志是EOF在ASII中它的编码是-1,随后我们用了 二进制方式打开文件,而二进制文件是会出现-1的,所以提前结束了,所以我们用二进制读取并且不能用EOF来判断结束。
   C中有一个函数叫做feof(),它可以检查流上文件的结束符,如果文件结束,则返回非0值,否则返回0

下面引用百科的解释: 
feof(fp)有两个返回值:如果遇到文件结束,函数feof(fp)的值为非零值,否则为0EOF是文本文件结束的标志。在文本文件中,数据是以字符的ASCⅡ代码值的形式存放,普通字符的ASCⅡ代码的范围是32127(十进制),EOF16进制代码为0x1A(十进制为26),因此可以用EOF作为文件结束标志。[1]

当把数据以二进制形式存放到文件中时,就会有-1值的出现,因此不能采用EOF作为二进制文件的结束标志。为解决这一个问题,ASCI C提供一个feof函数,用来判断文件是否结束。feof函数既可用以判断二进制文件又可用以判断文本文件。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
查看评论

【小项目】用Huffman树实现文件压缩并解压

一、前言         如果你学习数据结构,就一定会学到Huffman树,而Huffman编码实际上上就是zip压缩的核心部分,所以,如果已经学习了Huffman树,为何不尝试写一个压缩程序出来呢?...
  • pointer_y
  • pointer_y
  • 2016-11-09 20:55:23
  • 4755

Huffman编码文件压缩 - Huffman树的建立与编码

【问题描述】 编写一程序采用Huffman编码对一个正文文件进行压缩。具体压缩方法如下: 1.    对正文文件中字符(换行字符'\'除外,不统计)按出现次数(即频率)进行统计 2.    依据字符频...
  • Jason_Ranger
  • Jason_Ranger
  • 2016-05-20 11:09:26
  • 1951

基于Huffman树的文件压缩原理及C语言实现(二)

在上文基于Huffman树的文件压缩原理及C语言实现(一)中已经介绍并实现了如何建立Huffman树,得到Huffman编码,这篇我们将会接着说。如何通过Huffman树,实现文件压缩。实现思路任何文...
  • sinat_26106275
  • sinat_26106275
  • 2015-11-24 19:41:25
  • 1882

Huffman的应用之文件压缩与解压缩

文件压缩与解压缩>      最近这段时间一直在学习树的这种数据结构,也接触到了Huffman树以及了解了什仫是Huffman编码,而我们常用的zip压缩也是利用的Huffman编码的特性,那仫是不是...
  • qq_34328833
  • qq_34328833
  • 2016-10-30 12:02:28
  • 2664

用哈夫曼编码实现文件压缩

  • 2010年07月10日 16:27
  • 65KB
  • 下载

c++实现的文件压缩(Huffman)

  • 2017年09月14日 12:15
  • 4KB
  • 下载

Huffman 压缩解压缩java实现

本文介绍了利用Huffman编码对文件进行压缩和解压缩的过程。其中使用了java做为编程语言。为大家提供参考。本文只实现了对文本文件进行压缩和解压缩,对二进制文件压缩解压缩留待大家研究。完整工程留待以...
  • u010485034
  • u010485034
  • 2014-06-14 16:14:16
  • 1938

huffman算法实现文件的压缩与解压

本文采用哈夫曼编码的方式进行文件的压缩和解压缩,主要原理是通过huffman编码来表示字符,出现次数多的编码短,出现次数少的编码长,这样整体而言,所需的总的bit位是减少的。但是当大部分字符出现的频率...
  • qq_29503203
  • qq_29503203
  • 2016-11-05 11:04:28
  • 2746

基于Huffman树的文件压缩原理及C语言实现(一)

什么是Huffman树? 设二叉树具有n个带权值的叶子结点,从根结点到各个叶子结点的路径长度与对应叶子结点权值的乘积之和叫做二叉树的“带权”路径长度。 什么是最优二叉树? 对于一组带有确定权...
  • sinat_26106275
  • sinat_26106275
  • 2015-11-16 16:20:18
  • 2282

Huffman编码实现压缩、解压文件

Huffman编码:根据词频构建Huffman树,实现对文本的前缀编码。 1、统计文本中每个字符出现的次数,放入优先队列中,构建一棵空的二叉树; 2、取出频率最小的两个字符a、b,字符a、b的频率...
  • niuxiunan
  • niuxiunan
  • 2015-11-14 01:15:05
  • 2855
    个人资料
    等级:
    访问量: 189
    积分: 134
    排名: 116万+
    文章分类
    文章存档
    最新评论