数据压缩(霍夫曼算法) -- C++实现


霍夫曼算法的主要思想是,而是用较少的比特表示出现频率高的字符,用较多的比特表示出现频率低的字符,从而实现数据压缩

实现代码

// Huffman.h

#ifndef Huffman_H
#define Huffman_H

#include "Component.h"
#include "PriorityQueueMinPtr.h"

namespace jay{

	class Huffman
	{
	public:
		Huffman()
		{
			R = 256;
			rootC = nullptr;
			rootE = nullptr;
		}

		~Huffman()
		{
			if (rootC != nullptr)
			{
				Realease(rootC);
			}

			if (rootE!= nullptr)
			{
				Realease(rootE);
			}
		}

		void Realease(HFNode *x)
		{
			if (x->IsLeaf())
			{
				delete x;
				x = nullptr;
				return;
			}

			// 递归释放x的左之树
			Realease(x->m_left);
			// 递归释放x的右子树
			Realease(x->m_right);
			// 释放x自身
			delete x;
			x = nullptr;
		}

		// 压缩
		string Compress(string str,string &strTree)
		{

			if (str.length() == 0)
			{
				return "";
			}

			int* freq = new int[R];
			for (int i=0;i<R;i++)
			{
				freq[i] = 0;
			}
			int iInputLen = str.length();
			// 计算每个字符出现的频率
			for (int i = 0; i < iInputLen; i++)
				freq[str.at(i)]++;

			// 构建霍夫曼查找树
			rootC = BuildTrie(freq);

			// 构建编译表
			// st 表示的是 字符 和 该字符对应的比特字符串 的关系 比如,假设 A是"11",则 st[65] = "11"
			string *st = new string[R]();
			BuildCode(st, rootC, "");

			// 获取霍夫曼单词查找树
			GetTrie(rootC,strTree);

			// 压缩字符
			string strC;

			// 前4字节为原始字符长度
			strC += GetBitString(iInputLen,8);
			strC += " ";

			for (int i = 0; i < iInputLen; i++) 
			{
				// 获取字符对应的比特串
				strC += st[str.at(i)];
				strC += " ";
			}

			return strC;
		}

		// 前序遍历单词树
		void GetTrie(HFNode* x,string &strTree) 
		{
			if (x->IsLeaf()) 
			{
				// 如果是叶子节点则写入"1",并写入该字符对应的4字节二位数
				strTree += "1";
				strTree += GetBitString(x->m_ch,8);
				strTree += " ";
				return;
			}

			// 如果不是叶子节点则写入"0",并递归处理
			strTree += "0";
			GetTrie(x->m_left,strTree);
			GetTrie(x->m_right,strTree);
		}

		// int to bit
		string GetBitString(int d,int r)
		{
			string strb = "";
			for (int i = 0; i < r; i++) 
			{
				// (d >> (r - i -1)) & 1 
				// 第一次获取的是最高位 第二次是次高位,以此类推
				bool bit = ((d >> (r - i - 1)) & 1) == 1;
				if (bit)
				{
					strb += "1";
				}
				else
				{
					strb += "0";
				}
			}

			return strb;
		}

		// 构建编译表
		void BuildCode(string* st, HFNode *x, string s) 
		{
			if (!x->IsLeaf()) 
			{
				// 如果x不是叶子节点,则继续遍历霍夫曼单词查找树,左子树记为"0",右子树记为 "1"
				BuildCode(st, x->m_left,  s + '0');
				BuildCode(st, x->m_right, s + '1');
			}
			else 
			{
				// 如果x为叶子节点,也就是x为输入的字符,说明已经霍夫曼单词查找树找到了一个单词
				// st建立起 字符 与 该字符对应的比特字符串 的关系
				st[x->m_ch] = s;
			}
		}

		// 构建单词查找树
		HFNode* BuildTrie(int* freq)
		{
			// 所有字符代表的结点入优先队列[以字符出现的频率作为判断,频率最小的值在根部]
			PriorityQueueMinPtr<HFNode*> pq;
			for (int i = 0; i < R; i++)
			{
				if (freq[i] > 0)
					pq.Insert(new HFNode(i, freq[i]));
			}

			// 如果输入的字符串所有字符都一样,此时pq只有一个元素,无法进入下面的循环构造单词查找树
			// 此时,此时添加那么
			if (pq.Size() == 1) 
			{
				pq.Insert(new HFNode('\0', 0));
				// pq.Insert(new Node('\1', 0));

			}

			// 将队列最小的两个结点合并为一个棵树,然后将该树的根节点继续入队列
			// 队列起码还剩下2个元素才能这么处理
			while (pq.Size() > 1) 
			{
				// 出现频率最小的两个结点n1 n2出队列,组成一个新的节点,新节点的频率为 n1+n2,新节点的左右结点分别为n1 n2
				// 然后新节点入队列,继续处理
				HFNode *left  = pq.PopMin();
				HFNode *right = pq.PopMin();
				// 插入一个字符为 '\0' 的parent结点[其字符为字符串的结束符]
				HFNode *parent = new HFNode('\0', left->m_freq + right->m_freq, left, right);
				pq.Insert(parent);
			}

			return pq.PopMin();

		}

		// 解压
		string Expand(string str,string strTree) 
		{
			if (str.length() == 0 || strTree.length() == 0)
			{
				return "";
			}

			string strExpand;

			// 根据压缩时返回的单词查找树的比特串建立单词查找树
			string strTire = strTree;

			rootE = CreateTrie(strTire); 

			// 解码
			int length = GetIntFromStringBit(str.substr(0,8),8);
			str = str.substr(8,str.length()-8);
			for (int i = 0; i < length; i++) 
			{
				HFNode *x = rootE;
				while (!x->IsLeaf()) 
				{
					char c = str.at(0);
					str = str.substr(1,str.length()-1);
					if (c == 49) // c == '1'
						x = x->m_right;
					else         // c == '0'
						x = x->m_left;
				}
				strExpand += x->m_ch;
			}

			return strExpand;
		}

		// 生成单词查找树
		HFNode *CreateTrie(string &strTree) 
		{
			char c = strTree.at(0);
			strTree = strTree.substr(1,strTree.length()-1);
			if (c == 49) // 当前字符为1 代表后面跟着一个8位的字符
			{
				string tmp = strTree.substr(0,8);
				// GetIntFromStringBit获得到的是ascii字符对应的int值
				int iChar = GetIntFromStringBit(tmp,8);
				strTree = strTree.substr(8,strTree.length()-8);
				return new HFNode(iChar, -1, nullptr, nullptr);
			}
			else 
			{
				// 递归处理
				HFNode *left = CreateTrie(strTree);
				HFNode *right = CreateTrie(strTree);
				return new HFNode('\0', -1, left, right);
			}
		}

		int GetIntFromStringBit(string str,int r)
		{
			int d = 0;
			for (int i=0;i<r;i++)
			{
				char c = str.at(i);
				// 如果当前位为1
				if (c == 49)
				{
					d += pow(2.0,r-i-1);
				}
			}
			return d; 
		}

	private:

		// alphabet size of extended ASCII
		int R;
		HFNode *rootC;
		HFNode *rootE;
	};

};

#endif

测试代码

//main.cpp
#include "Component.h"
#include "huffman.h"
using namespace jay;


int main(int argc, char* argv[])
{
	Huffman hf;
	string strTree;
	string strSource = "The best of the BBC, with the latest news and sport headlines, weather, TV & radio highlights and much more from across the whole of BBC Online";
	cout << "原始字符串:" << endl;
	cout << strSource <<endl;
	string strC = hf.Compress(strSource,strTree);
	cout << "压缩后的比特流:" << endl;
	cout << strC <<endl;
	cout << "霍尔曼单词查找树:" << endl;
	cout << strTree <<endl;

	strC.erase(remove(strC.begin(), strC.end(), ' '), strC.end());
	strTree.erase(remove(strTree.begin(), strTree.end(), ' '), strTree.end());

	cout << "解压后的字符串:" <<  endl;
	cout << hf.Expand(strC,strTree) << endl;

	cout << "当前压缩率为:" << strC.length() * 100 /(strSource.length()*8) << "%" <<endl;
}

测试结果

暂无评论

注册用户登录后才能发表或者回复评论,请先登录 注册。