9.8.1哈夫曼树

带权路径长度最小的树被称为哈夫曼树,也称最优二叉树

9.8.2哈夫曼编码

前缀编码,哈夫曼编码可以不产生混淆,让解码可以顺利进行

前四道练习题

这四道题目都是练习哈夫曼编码的,主要就是建立哈夫曼树
问题 A: 算法6-12:自底向上的赫夫曼编码
问题描述:

  • 输入
输入的第一行包含一个正整数n,表示共有n个字符需要编码。其中n不超过100。
第二行中有n个用空格隔开的正整数,分别表示n个字符的权值。
  • 输出
共n行,每行一个字符串,表示对应字符的赫夫曼编码。
  • 样例输入
8
5 29 7 8 14 23 3 11
  • 样例输出
0110
10
1110
1111
110
00
0111
010

提示:赫夫曼树又名最优二叉树,它是一类带权路径长度最小的二叉树。通过构造赫夫曼树,我们可以得到赫夫曼编码,从而使得通信能够得到更高的效率。在本题中,构造赫夫曼树的过程使用了从叶子到根的逆向顺序,另外,还有一种从根出发直到叶子的赫夫曼编码构造算法,这将在下一题中进行讨论。

#define _CRT_SECURE_NO_WARNINGS 1
#include <cstdio>
#include <climits>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 110;
struct HuffNode
{
	int w, parent, lchild, rchild;//使用int指向树节点
}Node[maxn * 2];
void SearchMin(int& a, int& b, int n)
{
	int min = INT_MAX;
	for (int i = 0; i < n; ++i)
	{
		if (Node[i].parent == 0 && Node[i].w < min)
		{
			min = Node[i].w;
			a = i;
		}
	}
	min = INT_MAX;
	for (int i = 0; i < n; ++i)
	{
		if (Node[i].parent == 0 && Node[i].w < min && i != a)
		{
			min = Node[i].w;
			b = i;
		}
	}
	if (a > b)
	{
		swap(a, b);
	}
}
void HuffmanCode(int n, int* w, char**& ans)//n是元素个数,w是各个节点的权值,ans是
{
	for (int i = 0; i < n; ++i)
	{
		Node[i].parent = Node[i].lchild = Node[i].rchild = 0;
		Node[i].w = w[i];
	}//新建n个节点,
	for (int i = n; i < 2 * n - 1; ++i)
	{
		int a, b;
		SearchMin(a, b, i);
		Node[i].lchild = a;
		Node[i].rchild = b;
		Node[i].w = Node[a].w + Node[b].w;
		Node[a].parent = Node[b].parent = i;
	}//找到最小的两个树
	int c, f, index;
	char temp[n];
	ans = new char* [n];//编码
	for (int i = 0; i < n; ++i)
	{
		c = i;
		index = n - 1;
		temp[index] = 0;
		while (Node[c].parent != 0)
		{
			f = Node[c].parent;
			if (Node[f].lchild == c)
				temp[--index] = '0';
			else
				temp[--index] = '1';
			c = f;
		}
		ans[i] = new char[n - index];
		strcpy(ans[i], temp + index);
	}
}
int main()
{
	int n, w[maxn];
	char** ans;
	while (~scanf("%d", &n))
	{
		for (int i = 0; i < n; ++i)
		{
			scanf("%d", &w[i]);
		}
		HuffmanCode(n, w, ans);
		for (int i = 0; i < n; ++i)
		{
			printf("%s\n", ans[i]);
		}
	}
	delete ans;
	return 0;
}

问题 B: 算法6-13:自顶向下的赫夫曼编码
问题描述:

  • 输入
输入的第一行包含一个正整数n,表示共有n个字符需要编码。其中n不超过100。
第二行中有n个用空格隔开的正整数,分别表示n个字符的权值。
  • 输出
共n行,每行一个字符串,表示对应字符的赫夫曼编码。
  • 样例输入
8
5 29 7 8 14 23 3 11
  • 样例输出
0110
10
1110
1111
110
00
0111
010
#include <cstdio>
#include <climits>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn=110;
struct HuffNode
{
	int w,parent,lchild,rchild;
}Node[maxn*2];
void SearchMin(int &a,int &b,int n)
{
	int min=INT_MAX;
	for(int i=1;i<=n;++i)
	{
		if(Node[i].parent==0&&Node[i].w<min)
		{
			min=Node[i].w;
			a=i;
		}
	}
	min=INT_MAX;
	for(int i=1;i<=n;++i)
	{
		if(Node[i].parent==0&&Node[i].w<min&&i!=a)
		{
			min=Node[i].w;
			b=i;
		}
	}
	if(a>b)
	{
		swap(a,b);
	}
}
void HuffmanCode(int n,int * w,char * * &ans)
{
	int m=2*n-1;
	for(int i=1;i<=n;++i)
	{
		Node[i].parent=Node[i].lchild=Node[i].rchild=0;
		Node[i].w=w[i];
	}
	for(int i=n+1;i<=m;++i)
	{
		int a,b;
		SearchMin(a,b,i-1);
		Node[i].lchild=a;
		Node[i].rchild=b;
		Node[i].w=Node[a].w+Node[b].w;
		Node[i].parent=0;
		Node[a].parent=Node[b].parent=i;
	}
	int c,f,index=0;
	char temp[n];
	ans=new char * [n+1];
	for(int i=1;i<=m;++i)
		Node[i].w=0;
	while(m)
	{
		if(Node[m].w==0)
		{
			Node[m].w=1;
			if(Node[m].lchild!=0)
			{
				m=Node[m].lchild;
				temp[index++]='0';
			}
			else if(Node[m].rchild==0)
			{
				ans[m]=new char[index+1];
				temp[index]='\0';
				strcpy(ans[m],temp);
			}
		}
		else if(Node[m].w==1)
		{
			Node[m].w=2;
			if(Node[m].rchild!=0)
			{
				m=Node[m].rchild;
				temp[index++]='1';
			}
		}
		else
		{
			Node[m].w=0;
			m=Node[m].parent;
			--index;
		}
	}
}
int main()
{
	int n,w[maxn];
	char * * ans;
	while(~scanf("%d",&n))
	{
		for(int i=1;i<=n;++i)
		{
			scanf("%d",&w[i]);
		}
		HuffmanCode(n,w,ans);
		for(int i=1;i<=n;++i)
		{
			printf("%s\n",ans[i]);
		}
	}
	delete ans;
    return 0;
}

问题 C: 哈夫曼树
问题描述:哈夫曼树,第一行输入一个数n,表示叶结点的个数。需要用这些叶结点生成哈夫曼树,根据哈夫曼树的概念,这些结点有权值,即weight,题目需要输出所有结点的值与权值的乘积之和。

  • 输入
输入有多组数据。
每组第一行输入一个数n,接着输入n个叶节点(叶节点权值不超过100,2<=n<=1000)。
  • 输出
输出权值。
  • 样例输入
2
2 8 
3
5 11 30
  • 样例输出
10
62

这题也可以用优先队列实现

#include <cstdio>
#include <climits>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn=1100;
struct HuffNode
{
	int w,parent,lchild,rchild,flag;
}Node[maxn*2];
void SearchMin(int &a,int &b,int n)
{
	int min=INT_MAX;
	for(int i=1;i<=n;++i)
	{
		if(Node[i].parent==0&&Node[i].w<min)
		{
			min=Node[i].w;
			a=i;
		}
	}
	min=INT_MAX;
	for(int i=1;i<=n;++i)
	{
		if(Node[i].parent==0&&Node[i].w<min&&i!=a)
		{
			min=Node[i].w;
			b=i;
		}
	}
	if(a>b)
	{
		swap(a,b);
	}
}
int HuffmanCode(int n,int * w)
{
	int m=2*n-1;
	for(int i=1;i<=n;++i)
	{
		Node[i].parent=Node[i].lchild=Node[i].rchild=0;
		Node[i].w=w[i];
	}
	for(int i=n+1;i<=m;++i)
	{
		int a,b;
		SearchMin(a,b,i-1);
		Node[i].lchild=a;
		Node[i].rchild=b;
		Node[i].w=Node[a].w+Node[b].w;
		Node[i].parent=0;
		Node[a].parent=Node[b].parent=i;
	}
	int ans=0,index=0;
	for(int i=1;i<=m;++i)
		Node[i].flag=0;
	while(m)
	{
		if(Node[m].flag==0)
		{
			Node[m].flag=1;
			if(Node[m].lchild!=0)
			{
				m=Node[m].lchild;
				++index;
			}
			else if(Node[m].rchild==0)
			{
				ans+=Node[m].w*index;
			}
		}
		else if(Node[m].flag==1)
		{
			Node[m].flag=2;
			if(Node[m].rchild!=0)
			{
				m=Node[m].rchild;
				++index;
			}
		}
		else
		{
			Node[m].flag=0;
			m=Node[m].parent;
			--index;
		}
	}
	return ans;
}
int main()
{
	int n,w[maxn];
	while(~scanf("%d",&n))
	{
		for(int i=1;i<=n;++i)
		{
			scanf("%d",&w[i]);
		}
		int ans=HuffmanCode(n,w);
		printf("%d\n",ans);
	}
    return 0;
}

问题 D: Haffman编码
问题描述:哈弗曼编码大家一定很熟悉吧(不熟悉也没关系,自己查去。。。)。现在给你一串字符以及它们所对应的权值,让你构造哈弗曼树,从而确定每个字符的哈弗曼编码。当然,这里有一些小规定:
1.规定哈弗曼树的左子树编码为0,右子树编码为1;
2.若两个字符权值相同,则ASCII码值小的字符为左孩子,大的为右孩子;
3.创建的新节点所代表的字符与它的做孩子的字符相同;
4.所有字符为ASCII码表上32-96之间的字符(即“ ”到“`”之间的字符)。

  • 输入
输入包含多组数据(不超过100组)
每组数据第一行一个整数n,表示字符个数。接下来n行,每行有一个字符ch和一个整数weight,表示字符ch所对应的权值,中间用空格隔开。
输入数据保证每组测试数据的字符不会重复。
  • 输出
对于每组测试数据,按照输入顺序输出相应的字符以及它们的哈弗曼编码结果,具体格式见样例。
  • 样例输入
3
a 10
b 5
c 8
4
a 1
b 1
c 1
d 1
  • 样例输出
a:0
b:10
c:11
a:00
b:01
c:10
d:11
#include <cstdio>
#include <climits>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn=110;
struct HuffNode
{
	int w,parent,lchild,rchild;
	char s;
}Node[maxn*2];
void SearchMin(int &a,int &b,int n)
{
	int min=INT_MAX;
	for(int i=1;i<=n;++i)
	{
		if(Node[i].parent==0&&Node[i].w<min)
		{
			min=Node[i].w;
			a=i;
		}
	}
	min=INT_MAX;
	for(int i=1;i<=n;++i)
	{
		if(Node[i].parent==0&&Node[i].w<min&&i!=a)
		{
			min=Node[i].w;
			b=i;
		}
	}
	if(a>b)
	{
		swap(a,b);
	}
	if(Node[a].w==Node[b].w&&Node[a].s>Node[b].s)
	{
		swap(a,b);
	}
}
void HuffmanCode(int n,char * s,int * w,char * * &ans)
{
	int m=2*n-1;
	for(int i=1;i<=n;++i)
	{
		Node[i].parent=Node[i].lchild=Node[i].rchild=0;
		Node[i].w=w[i];
		Node[i].s=s[i];
	}
	for(int i=n+1;i<=m;++i)
	{
		int a,b;
		SearchMin(a,b,i-1);
		Node[i].lchild=a;
		Node[i].rchild=b;
		Node[i].w=Node[a].w+Node[b].w;
		Node[i].parent=0;
		Node[i].s=Node[a].s;
		Node[a].parent=Node[b].parent=i;
	}
	int c,f,index=0;
	char temp[n];
	ans=new char * [n+1];
	for(int i=1;i<=m;++i)
		Node[i].w=0;
	while(m)
	{
		if(Node[m].w==0)
		{
			Node[m].w=1;
			if(Node[m].lchild!=0)
			{
				m=Node[m].lchild;
				temp[index++]='0';
			}
			else if(Node[m].rchild==0)
			{
				ans[m]=new char[index+1];
				temp[index]='\0';
				strcpy(ans[m],temp);
			}
		}
		else if(Node[m].w==1)
		{
			Node[m].w=2;
			if(Node[m].rchild!=0)
			{
				m=Node[m].rchild;
				temp[index++]='1';
			}
		}
		else
		{
			Node[m].w=0;
			m=Node[m].parent;
			--index;
		}
	}
}
int main()
{
	int n,w[maxn];
	char s[maxn];
	char * * ans;
	while(~scanf("%d",&n))
	{
		for(int i=1;i<=n;++i)
		{
			getchar();
			scanf("%c %d",&s[i],&w[i]);
		}
		HuffmanCode(n,s,w,ans);
		for(int i=1;i<=n;++i)
		{
			printf("%c:%s\n",s[i],ans[i]);
		}
	}
	delete ans;
    return 0;
}