哈夫曼树--九度OJ-1172

地址点击打开链接

题目1172:哈夫曼树

时间限制:1 秒

内存限制:32 兆

特殊判题:

提交:11229

解决:5101

题目描述:

哈夫曼树,第一行输入一个数n,表示叶结点的个数。需要用这些叶结点生成哈夫曼树,根据哈夫曼树的概念,这些结点有权值,即weight,题目需要输出所有结点的值与权值的乘积之和。

输入:

输入有多组数据。
每组第一行输入一个数n,接着输入n个叶节点(叶节点权值不超过100,2<=n<=1000)。

输出:

输出权值。

样例输入:
5  
1 2 2 5 9
样例输出:
37
来源:
2010年北京邮电大学计算机研究生机试真题
答疑:
解题遇到问题?分享解题心得?讨论本题请访问: http://t.jobdu.com/thread-7895-1-1.html

解题思路:结果即为哈夫曼树的所有非叶子结点值之和,所以本题可以不用建树,直接累加每次从集合中取出的两个较小结点值之和就OK

5                                      5
1 2 2 5 9   37							1 3 3 4	5    36

                     
                     
                     
                       19                              16
       9*1         10     9 					   11    5    5*1 5
      5*2        5     5  						 7    4		  4*2 8
      3*2     3    2     					  4   3           3*3  9
     4*1    1  2  2*4                        1 3			  3*4  12  1*4 4  38
         4 +8+ 6 + 10 + 9  =   37    


哈夫曼树知识

1、 路径长度

从树中一个结点到另一个结点之间的分支构成两个结点之间的路径,路径上的分支数目称做路径长度。


图1  从根节点到D节点的路径长度为4


1、 树的路径长度

路径长度就是从树根到每一结点的路径长度之和。


图2 树的路径长度为1+1+2+2+3+3+4+4=20


1、 哈夫曼树:

带权路径长度WPL(Weighted Path Length)最小的二叉树,也称为最优二又树.

例: 上图的WPL=1*5 + 2*15 + 3*40 + 4*30 + 4*10= 315

 

先了解通过刚才的步骤,我们可以得出构造哈夫曼树的算法描述。

1、根据给定的n个权值{w[1],w[2],…,w[n]}构成n棵二叉树的集合F={T[1],T[2],…T[n]},    其中每棵二叉树T[i];中只有一个带权为w[i]的根结点,其左右子树均为空。


2、在F中选取两棵根结点的权值最小的树作为左右子树构造一棵新的.二叉树,且置新的二叉树的根结点的权值为其左右子树上根结点的权值之和,


3、在F中删除这两棵树,同时将新得到的二义树加入F中。


4重复2和3步骤,直到F只含一棵树为止。这棵树便是哈夫曼树.

结合例题说明一下这个算法







图3 哈夫曼树的构造过程示意图




图4 最终结果


那么可以由上面的哈夫曼树计算出最小带权路径长度

WPL = 1*9 + 2*5 + 3*2 + 4*1 + 4*2 =37

 

另外还可以有另外一个方法,结合算法描述仔细观察发现最小带权路径长度为非叶子结点的和 ,即

WPL= 19 + 10 +5 +3=37


至于算法的正确性,一下子也想不到什么好的办法来证明,不过应该是可以逻辑推导过来的。


那么要实现这段程序,由上面的算法描述图我们已经知道差不多了,主要分为三步:

一、排序,直到数组中只有一个数则退出

二、最小两个数加起来,即为非叶子节点,累加到累加器中

三、把最小两个数加起来作为一个新的值保存在数组中,去掉最小两个值,跳回第一步


#include <stdio.h>  
#include <string.h>  
#include <algorithm>  
#include<iostream>  
#include<stack>  
using namespace std;  
int result[1001];  
//哈夫曼树的权值就是除了所有叶子  
//的节点的权值的和  
int main(){  
    int n,i,sum,num;  
    while(scanf("%d",&n)!=EOF){  
        memset(result,0,n);  
        for(i=0;i<n;i++)  
            scanf("%d",&result[i]);  
        //进行排序,从小到大  
        sort(result,result+n);  
        i=1;  
        sum=0;  
        while(i<n){  
            //每次都要进行重新排序,因为生成了新的节点  
           sort(result+i-1,result+n);  
           //计算父亲  
           num = result[i-1]+result[i];  
           sum+=num;  
           //将新的节点赋值  
           result[i]=num;  
           i++;  
        }  
        printf("%d\n",sum);  
    }  
    return 0;  
}  

#include <queue>  
#include <stdio.h>  
#include <iostream>  
using namespace std;   
  
int main()  
{   
    int n;   
    while(cin>>n)  
    {   
        priority_queue<int, vector<int> , greater<int> > Q; //建立一个小顶堆  
        for(int i= 1;i<= n;i++) { //输入n个叶子结点权值  
            int x;   
            cin>>x;   
            Q.push(x); //将权值放入堆中  
        }   
        int ans= 0; //保存答案  
        while(Q.size() > 1) { //当堆中元素大于1个  
            int a= Q.top();   
            Q.pop();   
            int b= Q.top();   
            Q.pop(); //取出堆中两个最小元素,他们为同一个结点的左右儿子,且该双亲结点的权值为它们的和  
            ans+= a+ b;//该父亲结点必为非叶子结点,固累加其权值  
            Q.push(a+ b); //将该双亲结点的权值放回堆中  
            }   
        cout<<ans; //输出答案  
    }   
    return 0;   
}   


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值