【蓝桥杯】 BASIC-28 Huffman树 ( POJ 3253 Fence Repair )
POJ3253的题目背景其实就是哈夫曼树。
输入格式
输入的第一行包含一个正整数n(n<=100)。
接下来是n个正整数,表示p0, p1, …, pn-1,每个数不超过1000。
输出格式
输出用这些数构造Huffman树的总费用。
样例输入
5
5 3 8 2 9
样例输出
59
【题目分析】
今天做了蓝桥杯官网基础练习里的这道题,题目描述就是Huffman树,大家在数据结构课上都已经学过了。要求输出构造哈夫曼树的总费用,即为所有非叶子结点的权值之和。
之前ACM训练做过POJ 3253 Fence Repair这道题,其实只是修改了一下题目的描述背景,其实问题是一样的。
哈夫曼树的构造过程:
这道题其实用优先队列就能解决了。每次从队列中取出最小的两个元素,将它们的和加入队列,直到队列当中只剩下最后一个元素,它的值就是我们要求的答案。
后来我又尝试建哈夫曼树来做,因为一开始只拿了90分,又发现了一些新的内容,下面会说明。
【优先队列 priority_queue 解法】 100分代码:
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
priority_queue<int,vector<int>,greater<int> > q;//小的优先,注意“>”之间空格
int n,x;
long long ans=0;
cin>>n;
while(n--)
{
cin>>x;
q.push(x);
}
while(q.size()>1)
{
int t1,t2;
t1=q.top();
q.pop();
t2=q.top();
q.pop();
ans+=t1+t2;
q.push(t1+t2);
}
cout<<ans<<endl;
return 0;
}
【构造哈夫曼树的解法】
我们知道,一棵n个叶子结点的哈夫曼树一共有2n-1个结点,输入的叶子结点为1到n号结点,将建树以后得到的第n+1到2n-1号结点权值求和,即为要求的答案。
这里我使在之前数据结构实验哈夫曼编码树的基础上进行修改,但一开始提交的时候发现只拿到了80分,有一组数据输出的结果和正确答案相差了一点点。我对Select函数稍加修改以后,提交拿了90分,但其中显示输出错误的那组样例我在本地测试得到的结果和答案是一样的,让我觉得有点奇怪。经过很长时间的思考并参考了他人的写法,发现是Select函数当中选择结点的时候存在小漏洞,如果样例的建树过程中存在两个或以上的权值相同的结点,程序运行时就有可能出现问题。
然后我采用以下正确的写法,提交100分通过,而且这样也能确保构造出的哈夫曼树具有相同的结构。代码当中给出了注释。
100分代码:
(注释部分为本题不需要用到的操作,以及之前写的存在漏洞的代码)
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <cstring>
#include <algorithm>
#include <map>
using namespace std;
typedef struct
{
int weight;
int parent;
int lchild;
int rchild;
}HTNode,*HuffmanTree;//动态分配数组存储哈夫曼树
typedef char** HuffmanCode;//动态分配数组存储哈夫曼编码表
void Select(HuffmanTree &HT,int num,int &child1,int &child2)//注意Select函数的写法
{
int w1=1999999999,w2=1999999999;
for(int i=1;i<=num;i++)
if(HT[i].parent==0&&w1>HT[i].weight)
{
child1=i;
w1=HT[i].weight;
continue;
}
for(int i=1;i<=num;i++)
if(HT[i].parent==0&&w2>HT[i].weight&&i!=child1)
{
child2=i;
w2=HT[i].weight;
continue;
}
//以上才是正确的Select写法,目的是使构造的哈夫曼树具有相同的结构
//之前做数据结构作业的时候写法如下,有小部分数据可能会出错,在蓝桥杯OJ上只能拿到90分
/*
child1=0;child2=0;
int w1,w2;
for(int i=1;i<=num;i++)
{
if(HT[i].parent==0)
{
if(child1==0||w1>=w2&&w1>=HT[i].weight)
{
child1=i;
w1=HT[i].weight;
continue;
}
if(child2==0||w2>=w1&&w2>=HT[i].weight)
{
child2=i;
w2=HT[i].weight;
continue;
}
}
}
*/
}//在HT[1]-HT[num]中选择parent为0且weight最小的两个结点,其序号分别为s1和s2
void HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int w[],int n)
{
int child1,child2;
if(n<=1) return;
int m=n*2-1;
HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));
for(int i=1;i<=n;i++)
HT[i]={w[i-1],0,0,0};
for(int i=n+1;i<=m;i++)
HT[i]={0,0,0,0};
for(int i=n+1;i<=m;i++)
{
Select(HT,i-1,child1,child2);
HT[child1].parent=i;
HT[child2].parent=i;
HT[i].lchild=child1;
HT[i].rchild=child2;
HT[i].weight=HT[child1].weight+HT[child2].weight;
}//建哈夫曼树
/*
HC=(HuffmanCode)malloc((n+1)*sizeof(char *));
char* cd=(char *)malloc(n*sizeof(char));
cd[n-1]='\0';
for(int i=1;i<=n;i++)
{
int pos=n-1;
for(int c=i,f=HT[i].parent;f!=0;c=f,f=HT[f].parent)
{
if(HT[f].lchild==c)
cd[--pos]='0';
else
cd[--pos]='1';
}
HC[i]=(char *)malloc((n-pos)*sizeof(char));
strcpy(HC[i],&cd[pos]);
}//从叶子到根逆向求出每个字符的哈夫曼编码
*/
}
int main()
{
int size,cnt[105];
cin>>size;
for(int i=0;i<size;i++)
cin>>cnt[i];
sort(cnt,cnt+size);
HuffmanTree HT;//哈夫曼树
HuffmanCode HC;//哈夫曼编码表
HuffmanCoding(HT,HC,cnt,size);//进行哈夫曼编码操作
int ans=0;
for(int i=size+1;i<=2*size-1;i++)
ans+=HT[i].weight;
cout<<ans<<endl;
return 0;
}