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<rang