作业11——贪心解决最优前缀码问题

1.问题

给定字符集 C = { x 1 , x 2 , x 3 , . . . , x n } C=\{x_1,x_2,x_3,...,x_n\} C={x1,x2,x3,...,xn}和每个字符的频率 f ( x i ) {f(x_i)} f(xi) ,求关于 C 的一个最优前缀码
前缀码:是在有效字符前加的通用型代码。任何一个字符的编码都不能是其他字符编码的前缀,此即前缀码特性。具有前缀码特性的编码即为前缀码(名字有歧义)。
最优前缀码:对于编码字符集C,使平均码长达到最小的前缀码编码方案。
(前缀码和最优前缀码定义来源于百度百科)

2.解析

思路:贪心构造最优前缀码
具体实现:构造最小堆,先后提取堆顶的两个节点,构造一个新节点,节点的值是之前两个节点值之和,新节点的左儿子是第一个节点,右儿子是第二个节点,再将原来的堆顶两个节点弹出堆,将新节点加入,如此直到最小堆的大小为1,即哈夫曼森林变成哈夫曼树,最后输出对应编码即可。
例子:
有C={A,B,C,D,E,F,G},对应频率为f(x)={3,6,7,4,2,20,1}
作业11——贪心解决最优前缀码问题_第1张图片
由图中易得:A:1010 B:110 C:100 D:111 E:10110 F:0 G:10111

3.设计

class node					//节点
{
public:
	node* left, * right;	//左右儿子
	int val, ID;			//频率和对应字符
	string code;			//编码
	node(int val, int ID)	//构造函数1
	{
		this->ID = ID;
		this->val = val;
		this->left = this->right = nullptr;
		this->code = "";
	}
	node(int val, node* L, node* R)	//构造函数2
	{
		this->val = val;
		this->left = L;
		this->right = R;
		code = "";
	}
	bool morethan(const node* p) const	//定义大于规则
	{
		return this->val > p->val;
	}
};
class nodeCompare						//优先队列的指针排序规则
{
public:
	nodeCompare() {}
	bool operator () (const node* p1, const node* p2) const
	{
		return p1->morethan(p2);
	}
};
priority_queue<node*,vector<node*>,nodeCompare>que;//优先队列
void TravelTree(node* root) 
{
	if (root) 
	{
		if (root->left)root->left->code = root->code + "1";//左叶子节点的编码修改
		if (root->right)root->right->code = root->code + "0";//右叶子节点的编码修改
		if (!root->left && !root->right)printf("%d %s\n", root->ID, root->code.c_str());//叶子节点输出编码
		TravelTree(root->left);
		TravelTree(root->right);
	}
}
node* createHuffmanTree(int fqy[], int n)//创建哈夫曼树
{
	for (int i = 1; i <= n; i++) 
	{
		node* tmp = new node(fqy[i - 1], i);
		que.push(tmp);
	}
	while (que.size() != 1)				//贪心构造哈夫曼森林,最后形成哈夫曼树
	{
		node* L = que.top();
		que.pop();
		node* R = que.top();
		que.pop();
		node* newNode = new node(L->val + R->val, L, R);
		que.push(newNode);
	}
	TravelTree(que.top());
	return nullptr;
}

4.分析

构建最小堆O(nlogn),遍历整个频率序列O(n),插入最小堆O(logn),删除堆顶元素O(logn),构造哈夫曼编码O(n),所以总的时间复杂度为O(nlogn)

哈夫曼算法的正确性证明,实质上也就是证明一棵最优树,最小的两个缩成一个,同时删除最小的两个,得到的新树还是最优树
证明:采用反证法
首先假定 T 是一颗最优树,T这棵树的权值记为W(T)。最小权值的两个A,B一定在最下层,对应的路径权值分别是 Wa, Wb。
倘若删除A和B,A和B的父节C成为一个叶子节点。此时新树为 T1,W(T) = W(T1)+Wa + Wb
倘若T1不是最优树,则必定有最优树 T11。因为T11是最优树,所以必定有W(T11)<= W(T1) ,
此时若把T11中的C展开成A和B,形成新树 T111 ,W(T111)= W(T11)+ Wa + Wb
上述四个关系是矛盾,所以,T1 一定是最优树得证。(证明转载自大佬)

源码

Github

你可能感兴趣的:(算法课程)