利用哈夫曼树和优先队列求解带权路径长度

  带权路径长度

给定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挺耗内存的。。。

最后的最后,欢迎在下方大家评论,大伙们互关互关哦!!!!

发布了14 篇原创文章 · 获赞 8 · 访问量 1613

猜你喜欢

转载自blog.csdn.net/Zhongtongyi/article/details/94006025