`
128kj
  • 浏览: 583730 次
  • 来自: ...
社区版块
存档分类
最新评论

贪心算法

阅读更多
    贪心算法是指从问题的初始状态出发,通过若干次的贪心选择而得出最优值(或较优解)的一种解题方法。并不是从整体上加以考虑,它所做出的选择只是在某种意义上的局部最优解。

   例如平时购物找钱时,为使找回的零钱的硬币数最少,不考虑找零钱的所有各种方案,
而是从最大面值的币种开始,按递减的顺序考虑各币种,先尽量用大面值的币种,当不足大面值币种的金额时才去考虑下一种较小面值的币种。这就是在使用贪婪法。这种方法在这里总是最优,是因为银行对其发行的硬币种类和硬币面值的巧妙安排。

   如只有面值分别为1、5和11单位的硬币,而希望找回总额为15单位的硬币。按贪婪算法,应找1个11单位面值的硬币和4个1单位面值的硬币,共找回5个硬币。但最优的解应是3个5单位面值的硬币。

    贪心算法可以简单描述为:对一组数据进行排序,找出最小值,进行处理,再找出最小值, 再处理。

    也就是说贪心算法是一种在每一步选择中都采取在当前状态下最好或最优的选择,从而希望得到结果是最好或最优的算法。经典的例子:
(1)最小生成树的Prim算法
http://128kj.iteye.com/blog/1667993
(2)最小生成树的Kruskal(克鲁斯卡尔)算法http://128kj.iteye.com/blog/1669539
(3)单源最短路径http://128kj.iteye.com/blog/1678532

    于一个具体的问题,怎么知道是否可用贪心算法来解决问题,以及能否得到问题的一个最优解呢?从许多可以用贪心算法求解的问题中可以看到它们一般具有两个重要的性质:
①贪心选择性质;②最优子结构性质。

1.贪心选择性质。所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。每作一次贪心选择就将所求问题简化为规模更小的子问题。

对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所作的贪心选择最终导致问题的整体最优解。

2. 当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。
问题的最优子结构性质是该问题可用贪心算法求解的关键特征。

贪心算法解题步骤:
①从问题的某个初始解出发;
②采用循环语句,根据局部最优策略,得到一个部分解,缩小问题的范围或规模;
③将所有部分解综合起来,得到问题的最终解。

例:最优服务次序问题

一、问题描述:
    设有n个顾客同时等待一项服务。顾客i需要的服务时间为ti, 1≦i ≦n 。共有s处可以提供此服务。应如何安排n个顾客的服务次序才能使平均等待时间达到最小?平均等待时间是n 个顾客等待服务时间的总和除以n。

二、贪心选择策略
   假设原问题为T(先假设只有一个服务点),而我们已经知道了某个最优服务系列,即最优解为
   A={t(1),t(2),….t(n)}
(其中t(i)为第i个用户需要的服务时间),则每个用户等待时间为:
   T(1)=t(1);T(2)=t(1)+t(2);...T(n)=t(1)+t(2)+t(3)+…+t(n);

那么总等待时问,即最优值为:
   TA=T(1)+T(2)+T(3)+...+T(n)=n*t(1)+(n-1)*t(2)+…+(n+1-j)*t(i)+…+2*t(n-1)+t(n);

    由于平均等待时间是n个顾客等待时间的总和除以n,故本题实际上就是求使顾客等待时间的总和最小的服务次序。

   本问题采用贪心算法求解,贪心策略如下:
   对服务时间最短的顾客先服务的贪心选择策略。首先对需要服务时间最短的顾客进行服务,即做完第一次选择后,原问题T变成了需对n-1个顾客服务的新问题T'。新问题和原问题相同,只是问题规模由n减小为n-1。基于此种选择策略,对新问题T',选择n-1顾客中选择服务时间最短的先进行服务,如此进行下去,直至所有服务都完成为止 。

三、问题的贪心选择性质
  先来证明该问题具有贪心选择性质,即最优服务A中t(1)满足条件:t(1)<=t(i)(2<i<n)。
用反证法来证明:假设t(1)不是最小的,不妨设t(1)>t(i)(i>1)。
设另一服务序列B=(t(i),t(2),…,t(1)…,t(n))
那么TA-TB=n*[t(1)-t(i)]+(n+1-i)[t(i)-t(1)]=(1-i)*[t(i)-t(1)]>0
即TA>TB,这与A是最优服务相矛盾。
故最优服务次序问题满足贪心选择性质。

四、问题的最优子结构性质
  在进行了贪心选择后,原问题T就变成了如何安排剩余的n-1个顾客的服务次序的问题T',是原问题的子问题。
若A是原问题T的最优解,则A'={t(2),…t(i)…t(n))是服务次序问题子问题T'的最优解。
   证明:假设A'不是子问题T'的最优解,其子问题的最优解为B',则有TB'<TA',
而根据TA的定义知,TA'十t(1)=TA。因此TB'+t(1)<TA'+t(1)=TA,
即存在一个比最优值TA更短的总等待时间,而这与TA为问题T的最优值相矛盾。因此,A'是子问题T'的最优值。

    从以上贪心选择及最优子结构性质的证明,可知对最优服务次序问题用贪心算法可求得最优解。

根据以上证明,最优服务次序问题可以用最短服务时间优先的贪心选择可以达到最优解。
故只需对所有服务先按服务时间从小到大进行排序,然后按照排序结果依次进行服务即可。平均等待时间即为TA/n。

五、算法实现
   由多处最优服务次序问题具有贪心选择性质和最优子结构性质,容易证明算法greedy的正确性。本算法采用最短服务时间优先的贪心策略。首先将每个顾客所需要的服务时间从小到大排序。然后申请2个数组:
st[]是服务数组,st[j]为第j个队列上的某一个顾客的等待时间;su[]是求和数组,su[j]的值为第j个队列上所有顾客的等待时间;

import java.util.Scanner;
import java.util.Arrays;
public class BestFuWu{
   static  public double greedy(int[] x,int s){
    int n=x.length;
    Arrays.sort(x);
    //System.out.println(Arrays.toString(x));
    int st[]=new int[n];
    int su[]=new int[n];
    int i=1,j=1;
    while(i<n){
	 st[j]+=x[i];
	 su[j]+=st[j];
	 i++;
	 j++;
	 if(j>s){
            j=(s!=1)?j%s:1;//循环分配顾客到每一个服务点上
         }
    }
    double t=0;
    for(i=1;i<=s;i++){
     System.out.println("第"+i+"个服务点队列上所有顾客的等待时间su["+i+"]="+su[i]);
        t+=su[i];
    }
    t/=(n-1);
    return t;
  }

  public static void  main(String[] args){
    Scanner in=new Scanner(System.in);
    int n;//等待服务的顾客人数
    int s;//服务点的个数
    double t;//平均服务时间
    System.out.println("请输入等待服务的顾客人数:");
    n=in.nextInt();
    int[] x=new int[n+1];
    System.out.println("请输入服务点个数:");
    s=in.nextInt();
    
    System.out.println("请输入每个顾客需要服务的时间:");
     for(int i=1;i<=n;i++){
	// System.out.println("No."+i);
	 x[i]=in.nextInt();
	
     }
    t=greedy(x, s);
   System.out.println("平均等待时间最小值="+t);
  }		
}


运行结果:

C:\test>java   BestFuWu
请输入等待服务的顾客人数:
10
请输入服务点个数:
1
请输入每个顾客需要服务的时间:
56 12 1 99 1000 234 33 55 99 812
第1个服务点队列上所有顾客的等待时间su[1]=5320
平均等待时间最小值=532.0

C:\test>java   BestFuWu
请输入等待服务的顾客人数:
9
请输入服务点个数:
3
请输入每个顾客需要服务的时间:
20 3 5 6 18 14 15 11 10
第1个服务点队列上所有顾客的等待时间su[1]=44
第2个服务点队列上所有顾客的等待时间su[2]=55
第3个服务点队列上所有顾客的等待时间su[3]=66
平均等待时间最小值=18.333333333333332

例: 活动安排问题
  设有n个活动的集合E={1,2,…,n},其中每个活动都要求使用同一资源,如演讲会场等,
而在同一时间内只有一个活动能使用这一资源。每个活动i都有一个要求使用该资源的起始时间si和一个结束时间fi,且si <fi 。如果选择了活动i,则它在半开时间区间[si, fi]内占用资源。若区间[si, fi]与区间[sj, fj]不相交,则称活动i与活动j是相容的。也就是说,当si≥fj或sj≥fi时,活动i与活动j相容。

   活动安排问题就是要在所给的活动集合中选出最大的相容活动子集合,是可以用贪心算法有效求解的很好例子。该问题要求高效地安排一系列争用某一公共资源的活动。
贪心算法提供了一个简单、漂亮的方法使得尽可能多的活动能兼容地使用公共资源。

public class Activearr  
{  
    public static int greedselector(int [] s,int [] f,boolean [] a)  
    {  
        int n = s.length - 1;  
        a [0] = true;  
        int j = 0;  
        int count = 1;  
          
        for (int i = 1;i <= n;i ++)  
        {  
            if (s [i] >= f [j])  
          {  
            a [i] = true;  
            j = i;  
            count ++;  
               
          }   
          else a [i] = false;  
                  
        }  
          
        return count;  
      
    }  
    public static void main(String args [])  
    {  
        int count;  
        int s [] = {1,3,0,5,3,5,6,8,8,2,12};  
        int f [] = {4,5,6,7,8,9,10,11,12,13,14};  
        boolean a [] = new boolean [11];  
          
        //Activearr aa = new Activearr();  
        count = Activearr.greedselector(s,f,a);  
        System.out.println("共有" + count + "活动可以举行:");  
        System.out.println();  
        for (int i = 0;i <= 10;i ++)  
           if (a [i] == true)  
              System.out.println("第" + i + "活动可以举行");  
                
    }  
            
}  


程序运行:
共有4活动可以举行:

第0活动可以举行
第3活动可以举行
第7活动可以举行
第10活动可以举行

    由于输入的活动以其完成时间的非减序排列,所以算法greedySelector每次总是选择具有最早完成时间的相容活动加入集合A中。直观上,按这种方法选择相容活动为未安排活动留下尽可能多的时间。也就是说,该算法的贪心选择的意义是使剩余的可安排时间段极大化,以便安排尽可能多的相容活动。

       此算法的效率极高。当输入的活动已按结束时间的非减序排列,
算法只需O(n)的时间安排n个活动,使最多的活动能相容地使用公共资源。如果所给出的活动未按非减序排列,可以用O(nlogn)的时间重排。

下载源码:
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics