帶權路徑長度
給定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挺耗內存的。。。
最後的最後,歡迎在下方大家評論,大夥們互關互關哦!!!!