这里的Huffman编码就是离散数学中学的最优二叉树,现在我们用C++来实现它。
首先考虑实现它使用的数据结构,当然这里肯定使用二叉树。其次就是存储方式,对于树一般情况是用间接存储也就是链表。但是二叉链表不方便寻找双亲节点,这里使用向量更好,对存储空间的利用率也较高。
建立数据结构:
Huffman树中每个节点看成数组中的一个元素,它的属性有:权(weight),字符(data) 。为了方便寻找双亲和孩子节点加入三个域parent ,lch ,rch。分别存储双亲和左右孩子节点在数组中位置下标。
然后再建立个类来操作Huffman树。
#include <string>
using std::string;
class HuffmanTree
{
public:
HuffmanTree(int,int[]);
void BuildTree();
void SetCode();
void Display();
string Send(char*);
string Get(string);
private:
void Select(int,int&,int&);
private:
class Node
{
friend class HuffmanTree;
int weight;
char data;
string code;
int parent,lchild,rchild;
}*nodes;
int codenum;
int nodenum;
};
这里,我在节点中又加了个code属性,这个是每个字符的最后编码。codenum是叶子节点数,也就是需要编码的字符节点数。nodenum是总节点数。要让外面那个类能操作Node类,则需要声明为它的友元函数。
Huffman数建立的过程:
1. 根据给定的n个权值构成n棵二叉树。
2. 从所有的二叉树中选取两个权值最小节点的构成一个新的树,这个树的权值是两个节点权值之和。
3. 重复2,直到只剩一棵树为止。
下面是实现这个类的代码:
#include "Huffman.h"
#include <iostream>
#include <string>
using namespace std;
HuffmanTree::HuffmanTree(int num,int w[]):codenum(num)
{
nodenum=2*codenum-1;
nodes=new Node[nodenum];
for(int i=0;i<codenum;i++)
{
nodes[i].weight=w[i];
nodes[i].data='a'+i;
nodes[i].parent=nodes[i].lchild=nodes[i].rchild=-1;
nodes[i].code="";
}
for(int i=codenum;i<nodenum;i++)
{
nodes[i].weight=0;
nodes[i].data=0;
nodes[i].parent=nodes[i].lchild=nodes[i].rchild=-1;
}
}
void HuffmanTree::BuildTree()
{
for(int i=codenum;i<nodenum;i++)
{
int s1=0,s2=0;
Select(i,s1,s2);
nodes[s1].parent=nodes[s2].parent=i;
nodes[i].lchild=s1;
nodes[i].rchild=s2;
nodes[i].weight=nodes[s1].weight+nodes[s2].weight;
}
}
void HuffmanTree::Select(int size,int& s1,int& s2)
{
int temp=0;
for(int i=0;i<size;i++)
{
if(nodes[i].parent!=-1)
continue;
if(temp==0)
{
s2=i;
temp++;
continue;
}
if(temp==1)
{
if(nodes[i].weight>nodes[s2].weight)
{
s1=s2;
s2=i;
}
else
s1=i;
temp++;
continue;
}
if(nodes[i].weight<nodes[s2].weight)
{
if(nodes[i].weight<nodes[s1].weight)
{
s2=s1;
s1=i;
}
else
s2=i;
}
}
}
void HuffmanTree::SetCode()
{
for(int i=0;i<codenum;i++)
{
int j=i;
int p=nodes[j].parent;
while(p!=-1)
{
if(nodes[p].lchild==j)
nodes[i].code="0"+nodes[i].code;
else
nodes[i].code="1"+nodes[i].code;
j=p;
p=nodes[j].parent;
}
}
}
void HuffmanTree::Display()
{
for(int i=0;i<codenum;i++)
cout<<nodes[i].data<<": weight = "<<nodes[i].weight<<": "<<nodes[i].code<<endl;
}
string HuffmanTree::Send(char* old)
{
string newcode="";
for(;*old;old++)
{
for(int i=0;i<codenum;i++)
{
if(nodes[i].data==*old)
{
newcode+=nodes[i].code;
break;
}
}
}
return newcode;
}
string HuffmanTree::Get(string old)
{
string newcode="";
string::size_type index=0;
int treeindex=nodenum-1;
while(index<=old.size())
{
if(nodes[treeindex].lchild==-1)
{
newcode+=nodes[treeindex].data;
treeindex=nodenum-1;
}
else
{
if(old[index]=='0')
treeindex=nodes[treeindex].lchild;
else
treeindex=nodes[treeindex].rchild;
index++;
}
}
return newcode;
}
构造函数:用待编码字符数和每个字符的权值来初始化。
根据节点个数在堆区建立数组。
节点分两种叶子节点(为编码的字符)和非叶子节点,所以分开初始化。
Build 函数:这个是根据Huffman树规则建立二叉树。
Select 函数:从节点数组0到size的范围内选取两个权值最小的节点,下标放在s1,s2中。
SetCode 函数:求出每个字符的编码。
Display 函数:显示编码。
Send 函数:给定一个字符串,求出它的编码。
Get 函数:从得到的0、1串中翻译出它原编码。
编码的过程就是从树叶到树根的路径,而反编码则是从树根到树叶的路径。
最后给出一个测试代码:
#include <iostream>
#include <string>
#include "Huffman.h"
using namespace std;
int main()
{
int w[]={5,29,7,8,14,23,3,11,12,9,20,17,13,57,32};
HuffmanTree* t=new HuffmanTree(15,w);
t->BuildTree();
t->SetCode();
t->Display();
char s[]="aefbcgbam";
string p=t->Send(s);
cout<<p<<endl;
cout<<t->Get(p)<<endl;
}
最后说一点,操作字符串在C++中,使用标准库中的string类比自己来拨弄指针方便多了,大家以后多试着用下,可以发现它的很多好处。