利用哈夫曼樹和優先隊列求解帶權路徑長度

  帶權路徑長度

給定N個權值作爲N個葉子結點,構造哈夫曼樹,求其帶權路徑長度

輸入

輸入由多組數據組成。

每組數據分成兩行。第一行僅一個整數n2<=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挺耗內存的。。。

最後的最後,歡迎在下方大家評論,大夥們互關互關哦!!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章