DS二叉树——Huffman编码与解码
哈夫曼树简介
给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
- 根据数据出现的频率,使用不同长度的编码
一段文字由x个字符组成,每个字符的出现频率不同,比如a出现114514次,而c只出现了810次,如果对a使用短长度的编码(比如01),对b使用较长的编码(比如01011),相比于使用同样长度的编码(00001 与 01011),可使编码效率变高
题目
- 给定n个字符及其对应的权值,构造Huffman树,并进行huffman编码和译(解)码。
- 构造Huffman树时,要求左子树根的权值小于、等于右子树根的权值。
- 进行Huffman编码时,假定Huffman树的左分支上编码为‘0’,右分支上编码为‘1’。
输入
第一行测试次数
第2行:第一组测试数据的字符个数n,后跟n个字符
第3行:第一组测试数据的字符权重 待编码的字符串s1 编码串s2输出
第一行~第n行,第一组测试数据各字符编码值
第n+1行,串s1的编码值
第n+2行,串s2的解码值,若解码不成功,输出error!
样例输入
2 5 A B C D E
15 4 4 3 2
ABDEC
00000101100
4 A B C D
7 5 2 4
ABAD
1110110
样例输出
A :1
B :010
C :011
D :001
E :000
1010001000011
error!
A :0
B :10
C :110
D :111
0100111
DAC
哈夫曼树的构造规则
共有 n 个不同的字符需要编码,编码规则如下:
- 每次合并,选择没有被选择的,两个权值最小的根节点:x1, x2,将他们的合并
- 合并之后的根节点的权值是 x1的权值 + x2的权值
- 合并之后的根节点仍然可以作为新的根节点被选择
- 需要合并 n-1 次,最后剩下一个根,就是哈夫曼树的根
使用顺序结构(数组)构造哈夫曼树
初始状态:
第一次合并,选择 2,3 合并
第二次合并,选择 4, 4
第三次合并,选择 5, 8
第四次合并,选择 15, 13
哈夫曼树结构实现与创建:
#define maxlen 100
// 哈夫曼树节点类
class hft_node
{
public:
hft_node();
int weight;
int parent;
int lchild;
int rchild;
char ch; // 该节点代表的字符
string code; // 该节点字符对应的编码
};
hft_node::hft_node()
{
this->lchild = -1;
this->rchild = -1;
this->parent = -1;
}
// 哈夫曼树类
class hft
{
public:
hft();
int n; // 需要编码的字符个数
hft_node nodes[maxlen]; // 节点
int visited[maxlen]; // 选择标识数组
// 找两个未被选择的最小权值节点
void min2(int &index1, int &m1, int &index2, int &m2, int range);
void encode(string s); // 获得字符的01编码
void decode(string s); // 对01编码解码获得字符串
};
创建哈夫曼树
- 找两个最小权值且未被选择的节点:函数实现
void hft::min2(int &index1, int &m1, int &index2, int &m2, int range)
{
int i;
int min = 114514;
int min_index = 0;
// min
for(i=0; i<range; i++)
{
if(visited[i]==0 && min>nodes[i].weight)
{
min = nodes[i].weight;
min_index = i;
}
}
visited[min_index] = 1;
m1 = min;
index1 = min_index;
// sub min
min = 114514;
min_index = 0;
for(i=0; i<range; i++)
{
if(visited[i]==0 && min>nodes[i].weight)
{
min = nodes[i].weight;
min_index = i;
}
}
visited[min_index] = 1;
m2 = min;
index2 = min_index;
}
- 两两合并节点
// build hfm tree
for(i=n; i<2*n-1; i++)
{
int idx1, m1, idx2, m2;
min2(idx1, m1, idx2, m2, i);
nodes[i].weight = m1 + m2;
nodes[i].lchild = idx1;
nodes[i].rchild = idx2;
nodes[idx1].parent = i;
nodes[idx2].parent = i;
}
哈夫曼树编码
// hft coding
#define push push_back
#define pop pop_back
#define top back
for(i=0; i<n; i++) // 对每个叶子,下标: 0~n-1 进行编码
{
deque<char> s;
int x = i;
while(1)
{
int pa = nodes[x].parent;
if(pa == -1)
{
break;
}
if(nodes[pa].lchild == x)
{
s.push('0');
}
else
{
s.push('1');
}
x = pa;
}
// 因为是由叶子向根回溯,需要逆序输出才是真正的编码结果
while(!s.empty())
{
nodes[i].code += s.top();
s.pop();
}
}// end of hft coding
对给定的字符串,得到它的01编码
void hft::encode(string s)
{
for(int i=0; i<s.length(); i++)
{
char c = s[i];
for(int j=0; j<n; j++)
{
if(c == nodes[j].ch)
{
cout<<nodes[j].code;
break;
}
}
}
cout<<endl;
}
给定一串01串,得到解码后的字符串
void hft::decode(string s)
{
deque<char> q; // 队列保存解码结果
int x = 2*n-2; // 从根节点出发
for(int i=0; i<s.length(); i++)
{
if(s[i] == '0')
{
x = nodes[x].lchild;
}
else
{
x = nodes[x].rchild;
}
// 如果是叶子,解码完成
if(nodes[x].lchild==-1 && nodes[x].rchild==-1)
{
q.push_back(nodes[x].ch);
x = 2*n-2; // 下一次循环再次从根出发
}
}
// 如果读取01串之后,没有到达叶子节点,出错
if(n<=x && x<2*n-2)
{
cout<<"error!"<<endl;
}
else
{
while(!q.empty())
{
cout<<q.front();
q.pop_front();
}
cout<<endl;
}
}
完整代码
#include <iostream>
#include <string>
#include <deque>
using namespace std;
#define maxlen 100
class hft_node
{
public:
hft_node();
int weight;
int parent;
int lchild;
int rchild;
char ch;
string code;
};
hft_node::hft_node()
{
this->lchild = -1;
this->rchild = -1;
this->parent = -1;
}
class hft
{
public:
hft();
int n;
hft_node nodes[maxlen];
int visited[maxlen];
void min2(int &index1, int &m1, int &index2, int &m2, int range);
void encode(string s);
void decode(string s);
};
void hft::min2(int &index1, int &m1, int &index2, int &m2, int range)
{
int i;
int min = 114514;
int min_index = 0;
// min
for(i=0; i<range; i++)
{
if(visited[i]==0 && min>nodes[i].weight)
{
min = nodes[i].weight;
min_index = i;
}
}
visited[min_index] = 1;
m1 = min;
index1 = min_index;
// sub min
min = 114514;
min_index = 0;
for(i=0; i<range; i++)
{
if(visited[i]==0 && min>nodes[i].weight)
{
min = nodes[i].weight;
min_index = i;
}
}
visited[min_index] = 1;
m2 = min;
index2 = min_index;
}
hft::hft()
{
cin>>n;
int i;
for(i=0; i<n; i++)
{
cin>>nodes[i].ch;
}
for(i=0; i<n; i++)
{
cin>>nodes[i].weight;
}
for(i=0; i<2*n; i++)
{
visited[i] = 0;
}
// build hfm tree
for(i=n; i<2*n-1; i++)
{
int idx1, m1, idx2, m2;
min2(idx1, m1, idx2, m2, i);
nodes[i].weight = m1 + m2;
nodes[i].lchild = idx1;
nodes[i].rchild = idx2;
nodes[idx1].parent = i;
nodes[idx2].parent = i;
}
// hft coding
#define push push_back
#define pop pop_back
#define top back
for(i=0; i<n; i++)
{
deque<char> s;
int x = i;
while(1)
{
int pa = nodes[x].parent;
if(pa == -1)
{
break;
}
if(nodes[pa].lchild == x)
{
s.push('0');
}
else
{
s.push('1');
}
x = pa;
}
while(!s.empty())
{
nodes[i].code += s.top();
s.pop();
}
}// end of hft coding
// hft display
for(i=0; i<n; i++)
{
cout<<nodes[i].ch<<" :"<<nodes[i].code<<endl;
}
}
void hft::encode(string s)
{
for(int i=0; i<s.length(); i++)
{
char c = s[i];
for(int j=0; j<n; j++)
{
if(c == nodes[j].ch)
{
cout<<nodes[j].code;
break;
}
}
}
cout<<endl;
}
void hft::decode(string s)
{
deque<char> q;
int x = 2*n-2;
for(int i=0; i<s.length(); i++)
{
if(s[i] == '0')
{
x = nodes[x].lchild;
}
else
{
x = nodes[x].rchild;
}
if(nodes[x].lchild==-1 && nodes[x].rchild==-1)
{
q.push_back(nodes[x].ch);
x = 2*n-2;
}
}
if(n<=x && x<2*n-2)
{
cout<<"error!"<<endl;
}
else
{
while(!q.empty())
{
cout<<q.front();
q.pop_front();
}
cout<<endl;
}
}
int main()
{
int n;
cin>>n;
while(n--)
{
hft t;
string s;
cin>>s;
t.encode(s);
cin>>s;
t.decode(s);
}
return 0;
}