给定一段文字,如果我们统计出字母出现的频率,是可以根据哈夫曼算法给出一套编码,使得用此编码压缩原文可以得到最短的编码总长。然而哈夫曼编码并不是唯一的。例如对字符串"aaaxuaxz",容易得到字母 'a'、'x'、'u'、'z' 的出现频率对应为 4、2、1、1。我们可以设计编码 {'a'=0, 'x'=10, 'u'=110, 'z'=111},也可以用另一套 {'a'=1, 'x'=01, 'u'=001, 'z'=000},还可以用 {'a'=0, 'x'=11, 'u'=100, 'z'=101},三套编码都可以把原文压缩到 14 个字节。但是 {'a'=0, 'x'=01, 'u'=011, 'z'=001} 就不是哈夫曼编码,因为用这套编码压缩得到 00001011001001 后,解码的结果不唯一,"aaaxuaxz" 和 "aazuaxax" 都可以对应解码的结果。本题就请你判断任一套编码是否哈夫曼编码。
输入格式:
首先第一行给出一个正整数 N(2≤N≤63),随后第二行给出 N 个不重复的字符及其出现频率,格式如下:
c[1] f[1] c[2] f[2] ... c[N] f[N]
其中c[i]
是集合{'0' - '9', 'a' - 'z', 'A' - 'Z', '_'}中的字符;f[i]
是c[i]
的出现频率,为不超过 1000 的整数。再下一行给出一个正整数 M(≤1000),随后是 M 套待检的编码。每套编码占 N 行,格式为:
c[i] code[i]
其中c[i]
是第i
个字符;code[i]
是不超过63个'0'和'1'的非空字符串。
输出格式:
对每套待检编码,如果是正确的哈夫曼编码,就在一行中输出"Yes",否则输出"No"。
注意:最优编码并不一定通过哈夫曼算法得到。任何能压缩到最优长度的前缀编码都应被判为正确。
输入样例:
7
A 1 B 1 C 1 D 3 E 3 F 6 G 6
4
A 00000
B 00001
C 0001
D 001
E 01
F 10
G 11
A 01010
B 01011
C 0100
D 011
E 10
F 11
G 00
A 000
B 001
C 010
D 011
E 100
F 101
G 110
A 00000
B 00001
C 0001
D 001
E 00
F 10
G 11
输出样例:
Yes
Yes
No
No
代码长度限制:16 KB
时间限制:400 ms
内存限制:64 MB
解题思路:利用哈夫曼树的两个特点,即:
①题目给定的每组的哈夫曼编码对应的哈夫曼树的WPL必须为最小带权路径长度
②题目给定的每组的哈夫曼编码不能有前缀的关系
利用此思路,题目就可以顺利解出来:
①先建立出题目给定的字符及其权所对应的哈夫曼树
②根据该哈夫曼树计算出最小带权路径长度
③对题目给定的每组哈夫曼编码进行判断(根据解题思路)
#include <iostream>
#include <algorithm>
#include <string>
#include <queue>
#include <map>
#include <vector>
using namespace std;
typedef struct TreeNode
{
int Weight;
char Character;
TreeNode* LeftChild, * RightChild;
TreeNode(int value1, char value2, TreeNode* value3, TreeNode* value4)
{
Weight = value1;
Character = value2;
LeftChild = value3;
RightChild = value4;
}
//初始化结构体
}TreeNode;
class Compare
{
public:
bool operator()(TreeNode* TreeNode1, TreeNode* TreeNode2)
{
return (TreeNode1->Weight > TreeNode2->Weight) || (TreeNode1->Weight == TreeNode2->Weight && TreeNode1->Character > TreeNode2->Character);
//TreeNode1->Weight > TreeNode2->Weight:Weight值小的在前面
//TreeNode1->Weight == TreeNode2->Weight:Weight值相同的另行比较:
//TreeNode1->Character > TreeNode2->Character:Character值小的更靠前
}
};
vector<int>Frequency; //频率数组(题目中各字符是按顺序给出的)
TreeNode* Huffman(int Amount)
{
//Amount是题目给定的字符数量
//建立Huffman树,返回树根结点(Weight值最大的)
int Weight;
char Character;
TreeNode* Root = nullptr;
TreeNode* TreeNode1, * TreeNode2;
priority_queue<TreeNode*, vector<TreeNode*>, Compare> Sequence;
for (int i = 0; i < Amount; i++)
{
//森林中“栽种”Amount个树(只有树根节点)
//用nullptr初始化左右子树是为了方便识别出树中哪些结点是叶结点
//因为叶结点度为0,也就意味着该点左右子树都为nullptr
cin >> Character >> Weight;
Frequency.push_back(Weight);
Root = new TreeNode(Weight, Character, nullptr, nullptr);
Sequence.push(Root);
}
while (true)
{
//用树根结点代表树:
//每次循环从优先队列Sequence中选择树根结点Weigh值最小和次小的两棵树
//这里记为TreeNode1和TreeNode2
//新的树(用它的树根结点Root代表它):
//将TreeNode1作为左子树,TreeNode2作为右子树(也可以不是左-右而是右-左)
//将Root加入优先队列Sequence中
//继续执行上述步骤,直到Sequence弹出两个结点时Sequence为空
TreeNode1 = Sequence.top();
Sequence.pop();
TreeNode2 = Sequence.top();
Sequence.pop();
Root = new TreeNode(TreeNode1->Weight + TreeNode2->Weight, '\0', TreeNode1, TreeNode2);
if (Sequence.empty())
{
break;
}
Sequence.push(Root);
}
return Root;
}
int CalculateWPL(TreeNode* Root, int Length)
{
//这里是使用先序遍历来求WPL
//Root是树根结点,Length是经过的边条数
//只要是叶子结点(Root->LeftChild == nullptr && Root->RightChild == nullptr):
//计算WPL:WPL = Root->Weight * Length;return WPL,返回给上一层递归
//上一层递归(都是度非0的结点:WPL = 0)为:WPL = WPL + 叶子结点的WPL
int WPL = 0;
if (Root == nullptr) return WPL;
if (Root->LeftChild == nullptr && Root->RightChild == nullptr)
{
WPL = Root->Weight * Length;
return WPL;
}
WPL = WPL + CalculateWPL(Root->LeftChild, Length + 1);
WPL = WPL + CalculateWPL(Root->RightChild, Length + 1);
return WPL;
}
void HuffmanJudgement(int Size, int WPL)
{
//判断是否为哈夫曼树
//Size是题目给定符号的个数,WPL是CalculateWPL()已经算好的
char Character;
int Summary = 0;
string temp, Code;
multimap<int, string> Mapping;
multimap<int, string>::iterator It, it;
for (int i = 0; i < Size; i++)
{
//利用每个Code的长度来作为Code的索引
//Frequency[i]是因为按顺序给定字符的哈夫曼编码
//Code的长度即为边数,Frequency[i]为Weight值
//计算给定组的哈夫曼编码对应的哈夫曼树的WPL,记为Summary
cin >> Character >> Code;
int Length = Code.size();
Summary = Summary + Length * Frequency[i];
Mapping.insert(pair<int, string>(Length, Code));
}
if (Summary == WPL)
{
//该组哈夫曼编码的WPL等于正确的WPL值
for (It = Mapping.begin(); It != Mapping.end(); It++)
{
//查看是否存在编码是其他编码的前缀
//Code是小串,temp是大串
Code = It->second;
int Length = It->first;
for (it = It, it++; it != Mapping.end(); it++)
{
//it从It的后一个开始
//求temp对应长度为Length的子串(从第一个字符开始)
temp = it->second.substr(0, Length);
if (temp == Code)
{
//Code是temp的前缀
cout << "No" << endl;
return;
}
}
}
//没有发现Code是temp的前缀
cout << "Yes" << endl;
}
else cout << "No" << endl;
return;
}
int main()
{
int Amount, WPL = 0;
TreeNode* Root = nullptr;
cin >> Amount;
Root = Huffman(Amount);
WPL = CalculateWPL(Root, 0);
cin >> Amount;
int Size = Frequency.size();
for (int i = 0; i < Amount; i++)
{
HuffmanJudgement(Size, WPL);
}
return 0;
}