7-2 哈夫曼编码

给定一段文字,如果我们统计出字母出现的频率,是可以根据哈夫曼算法给出一套编码,使得用此编码压缩原文可以得到最短的编码总长。然而哈夫曼编码并不是唯一的。例如对字符串"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;
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值