本代码已做了详细的注释说明
有疑问可以参考算法第四版(java实现)第五章第五小节
或者,点击查看我的博客原文
测试代码
//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;
}
测试结果
实现代码
// 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