算法——贪心法

算法——贪心法

在求最优解问题的过程中,依据某种贪心标准,从问题的初始状态出发,直接去求每一步的最优解,通过若干次的贪心选择,最终得出整个问题的最优解,这种求解方法就是贪心算法。

从贪心算法的定义可以看出,贪心法并不是从整体上考虑问题,它所做出的选择只是在某种意义上的局部最优解,而由问题自身的特性决定了该题运用贪心算法可以得到最优解。

例如平时购物找钱时,为使找回的零钱的硬币数最少,不考虑找零钱的所有各种发表方案,而是从最大面值的币种开始,按递减的顺序考虑各币种,先尽量用大面值的币种,当不足大面值币种的金额时才去考虑下一种较小面值的币种。这就是在使用贪婪法。这种方法在这里总是最优,是因为银行对其发行的硬币种类和硬币面值的巧妙安排。如只有面值分别为1511单位的硬币,而希望找回总额为15单位的硬币。按贪婪算法,应找111单位面值的硬币和41单位面值的硬币,共找回5个硬币。但最优的解应是35单位面值的硬币。

我们看看下面的例子:

1 均分纸牌(NOIP2002tg

[问题描述] 有 N 堆纸牌,编号分别为 12,…, N。每堆上有若干张,但纸牌总数必为 N 的倍数。可以在任一堆上取若干张纸牌,然后移动。移牌规则为:在编号为 1 堆上取的纸牌,只能移到编号为 2 的堆上;在编号为 N 的堆上取的纸牌,只能移到编号为 N-1 的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。例如 N=44 堆纸牌数分别为:

  ① 9 ② 8 ③ 17 ④ 6

移动3次可达到目的:

  从 4 张牌放到 9 8 13 10 -> 3 张牌放到 ②(9 11 10 10-> 1 张牌放到①(10 10 10 10)。

[ ]:键盘输入文件名。

文件格式:NN 堆纸牌,1 <= N <= 100

  A1 A2 An N 堆纸牌,每堆纸牌初始数,l<= Ai <=10000

[ ]:输出至屏幕。格式为:所有堆均达到相等时的最少移动次数。

[输入输出样例]

a.in

 4

 9 8 17 6

屏慕显示:3

 

算法分析:

a[i]为第i堆纸牌的张数(0<=i<=n),v为均分后每堆纸牌的张数,s为最小移到次数。

我们用贪心法,按照从左到右的顺序移动纸牌。如第i(0的纸牌数a[i]不等于平均值,则移动一次(s1),分两种情况移动:

1    a[i]>v,则将a[i]-v张纸牌从第I堆移动到第I+1堆;

2    a[i],则将v -a[i]张纸牌从第I+1堆移动到第I堆;

为了设计的方便,我们把这两种情况统一看作是将a[I]-v张牌从第I堆移动到第I+1堆;移动后有:a[I]:=va[I+1]:=a[I+1]+a[I]-v

在从第i+1堆中取出纸牌补充第i堆的过程中,可能会出现第i+1堆的纸牌数小于零(a[i+1]+a[i]-v<0 )的情况。

n=3,三堆纸牌数为(1227)这时v=10,为了使第一堆数为10,要从第二堆移9张纸牌到第一堆,而第二堆只有2张纸牌可移,这是不是意味着刚才使用的贪心法是错误的呢?

我们继续按规则分析移牌过程,从第二堆移出9张到第一堆后,第一堆有10张纸牌,第二堆剩下-7张纸牌,再从第三堆移动17张到第二堆,刚好三堆纸牌数都是10,最后结果是对的,从第二堆移出的牌都可以从第三堆得到。我们在移动过程中,只是改变了移动的顺序,而移动的次数不变,因此此题使用贪心法是可行的。

 

源程序:

var

  i,n,s:integer;v:longint;

  a:array[1..100]of longint;

  f:text;fil:string;

begin

  readln(fil);

  assign(f,fil);reset(f);

  readln(f,n);v:=0;

  for i:=1 to n do begin

    read(f,a[i]); inc(v,a[i]);

  end;

  v:=v div n; {每堆牌的平均数}

  for i:=1 to n-1 do

if a[i]<>v then {贪心选择}

begin

      inc(s);{移牌步数计数}

      a[i+1]:=a[i+1]+a[i]-v;{使第i堆牌数为v}

    end;{then}

  writeln(s);

end.

 

利用贪心算法解题,需要解决两个问题:

一是问题是否适合用贪心法求解。我们看一个找币的例子,如果一个货币系统有3种币值,面值分别为一角、五分和一分,求最小找币数时,可以用贪心法求解;如果将这三种币值改为一角一分、五分和一分,就不能使用贪心法求解。用贪心法解题很方便,但它的适用范围很小,判断一个问题是否适合用贪心法求解,目前还没有一个通用的方法,在信息学竞赛中,需要凭个人的经验来判断何时该使用贪心算法。

二是确定了可以用贪心算法之后,如何选择一个贪心标准,才能保证得到问题的最优解。在选择贪心标准时,我们要对所选的贪心标准进行验证才能使用,不要被表面上看似正确的贪心标准所迷惑,如下面的列子。

 

2 NOIP1998tg)设有n个正整数,将他们连接成一排,组成一个最大的多位整数。例如:n=3时,3个整数13,312,343,连成的最大整数为:34331213

又如:n=4,4个整数7,13,4,246连接成的最大整数为7424613

输入:N

N个数

输出:连接成的多位数

 

算法分析:

此题很容易想到使用贪心法,在考试时有很多同学把整数按从大到小的顺序连接起来,测试题目的例子也都符合,但最后测试的结果却不全对。按这种贪心标准,我们很容易找到反例:12121 应该组成12121而非12112,那么是不是相互包含的时候就从小到大呢?也不一定,如:12123 就是12312而非12112,这样情况就有很多种了。是不是此题不能用贪心法呢?

其实此题是可以用贪心法来求解,只是刚才的贪心标准不对,正确的贪心标准是:先把整数化成字符串,然后再比较a+bb+a,如果a+b>b+a,就把a排在b的前面,反之则把a排在b的后面。

源程序:

var

  s:array[1..20] of string;

  t:string;i,j,k,n:longint;

begin

  readln(n);

  for i:=1 to n do begin

    read(k);

    str(k,s[i]);

  end;

  for i:=1 to n-1 do

    for j:=i+1 to n do

      if s[i]+s[j]

      begin{交换}

        t:=s[i];

        s[i]:=s[j];

        s[j]:=t;

      end;

  for i:=1 to n do write(s[i]);

end.

 

贪心算法所作的选择可以依赖于以往所作过的选择,但决不依赖于将来的选择,也不依赖于子问题的解,因此贪心算法与其它算法相比具有一定的速度优势。如果一个问题可以同时用几种方法解决,贪心算法应该是最好的选择之一。

 

总结:

贪心法是一种不追求最优解,只希望得到较为满意解的方法。贪婪法一般可以快速得到满意的解,因为它省去了为找最优解要穷尽所有可能而必须耗费的大量时间。贪婪法常以当前情况为基础作最优选择,而不考虑各种可能的整体情况,所以贪婪法不要回溯。

 

 

你可能感兴趣的:(C及算法学习笔记)