带权路径长度
给定N个权值作为N个叶子结点,构造哈夫曼树,求其带权路径长度
输入
输入由多组数据组成。
每组数据分成两行。第一行仅一个整数n(2<=n<=100000)。第二行有n个空格分开的权值,值范围在[1,1000000000]之间。
输出
对于每组测试数据,输出一行,即其对应哈夫曼树的带权路径长度对1000000007取模。
样例输入
4 7 5 2 4 8 5 29 7 8 14 23 3 11
样例输出
35 271
二、分析与设计实现
根据实际情况可以包含下面几部分:
(1) 本题可以考虑用传统方法构造哈夫曼树。但是缺点很明显,数据如果过大会时间超限,递归调用也可能产生栈溢出(内存超限)采用了另一种数据结构——优先队列解决。不但代码简洁而且效率高,内存花销小(很重要的原因是每一次循环都合并在WPL(带权路径长度)中了)。
(2) 具体的数据结构和算法描述:
方法一(传统的哈夫曼树构造,非完整程序):
//---------哈夫曼树的存储表示(本题中附加一个len表示结点的层次)
typedef struct
{
long long int weight; //结点的权值
int parent; //结点的双亲,左孩子,右孩子的下标
int lchild;
int rchild;
int len;
}HTNode,*HuffmanTree; //动态分配数组存储哈夫曼树
void Select(HTNode &HT,int n,int &s1,int &s2) //s1记录最小结点下标,s2记录次小结点下标
{
long long int min;long long int min2;
for(int i=1;i<=n;i++)
{
if(HT[i].parent!=0)
continue;
else
{
min=HT[i].weight;
s1=i;
break;
}
}
for(int i=1;i<=n;i++)
{
if(HT[i].parent!=0)
continue;
else
{
if(HT[i].weight<min)
{
s1=i;
min=HT[i].weight;
}
}
}//以上两个循环找出权值最小的结点
int flag=1;
for(int i=1;i<=n;i++)
{
if(HT[i].parent!=0)
continue;
else if(HT[i].weight==min&&i!=s1)//当s1的权值等于s2时,此时s2也是最小的
{
min2=HT[i].weight;
s2=i;
flag=0;//不用后续再遍历了
break;
}
else
{
min2=HT[i].weight;//此时s2的权值比s1大,但不一定是比s1权值大的所有结点中最小的,所以还需要一次遍历
}
}
if(flag==1)
{
for(int i=1;i<=n;i++)
{
if(HT[i].parent!=0)
continue;
else if(HT[i].weight<min2&&HT[i].weight>min)
{
min2=HT[i].weight;
s2=i;
}
}
}
}
void CreateHuffmanTree(HuffmanTree &HT,int n)//构造哈夫曼树
{
if(n<=1)
return ;
int m=2*n-1;
HT=new HTNode[m+1];
//0号单元未用,所以需要动态分配m+1个单元,HT[m]表示根节点(知道这个很重要)
for(i=1;i<=m;i++)
{HT[i].parent=0;HT[i].lchild=HT[i].rchild=0;}
//将1~号单元中双亲,左孩子,右孩子的下标都初始化为0
for(i=1;i<=n;i++)
cin>>HT[i].weight;
/*-------------------------初始化结束,下面开始创建哈夫曼树------------------------*/
for(i=n+1;i<=m;i++)
{
//通过n-1次的选择,删除,合并来创建哈夫曼树
Select(HT,i-1,s1,s2);
//在HT[k](1<=k<=i-1)中选择两个其双亲域为0且权值最小的节点,并返回它们在HT中的序号s1和s2
HT[s1].parent=I;HT[s2].parent=i;
//得到新结点i,从森林中删除s1,s2,将s1和s2的双亲域由0改为i
HT[i].lchild=s1;HT[i].rchild=s2;
HT[i].weight=HT[s1].weight+HT[s2].weight;
}
}
最后由于哈夫曼树是一种二叉树,因此可以利用递归调用从根结点HT[m]通过lchild,rchild遍历后逐个访问各结点,并逐层改变结点中的len(逐层+1),最后再写一个函数从根结点递归调用,WPL(带权路径长度)就是各结点权值* len的累加和了,后续代码实现不搬辽。。。
方法二:优先队列
#include<bits/stdc++.h>//万用头文件,包含C++的所有头文件
using namespace std;
int main()
{
long long int d[100000];int n;
while(cin >> n)
{
long long int sum,a,b,x;sum=0;
priority_queue< long long int,vector<long long int>,greater<long long int> >q;//q是优先队列名,此处不说明定义格式。
for(int i=1;i<=n;i++)
{
cin>>d[i];
q.push(d[i]);
}
for(int i=2;i<=n;i++)
{
a=q.top();
q.pop();
b=q.top();
q.pop();
sum=sum+a+b;
x=a+b;
q.push(x);
}
cout<<sum%1000000007<<endl;
}
}
}
下面给出部分优先队列的常用函数提供参考(想进一步了解优先队列就自行百度啦):
q.size();//返回q里元素个数
q.empty();//返回q是否为空,空则返回1,否则返回0
q.pop();//删掉q的第一个元素
q.push(k);//在q的末尾插入k
q.top();//返回q的第一个元素
四、调试与测试数据 (学校OJ上测评)
Time:186 ms
Memory:4416 kb
不得不感叹C++相比C挺耗内存的。。。
最后的最后,欢迎在下方大家评论,大伙们互关互关哦!!!!