第7课 贪心.docx

上传人:b****7 文档编号:10821657 上传时间:2023-02-23 格式:DOCX 页数:11 大小:19.87KB
下载 相关 举报
第7课 贪心.docx_第1页
第1页 / 共11页
第7课 贪心.docx_第2页
第2页 / 共11页
第7课 贪心.docx_第3页
第3页 / 共11页
第7课 贪心.docx_第4页
第4页 / 共11页
第7课 贪心.docx_第5页
第5页 / 共11页
点击查看更多>>
下载资源
资源描述

第7课 贪心.docx

《第7课 贪心.docx》由会员分享,可在线阅读,更多相关《第7课 贪心.docx(11页珍藏版)》请在冰豆网上搜索。

第7课 贪心.docx

第7课贪心

贪心法

【定义1】贪心策略是指从问题的初始状态出发,通过若干次的贪心选择而得出最优值(或较优解)的一种解题方法。

从"贪心策略"一词我们便可以看出,贪心策略总是做出在当前看来是最优的选择,也就是说贪心策略并不是从整体上加以考虑,它所做出的选择只是在某种意义上的局部最优解,而许多问题自身的特性决定了该题运用贪心策略可以得到最优解或较优解。

所谓贪心是指应用同一规则f,将原问题变为一个相似的、但规模更小的子问题、而后的每一步都是当前看似最佳的选择。

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

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

 

例题1:

【合并果子】

描述Description

在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。

多多决定把所有的果子合成一堆。

  每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。

可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了。

多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。

  因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。

假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。

例如有3种果子,数目依次为1,2,9。

可以先将1、2堆合并,新堆数目为3,耗费体力为3。

接着,将新堆与原先的第三堆合并,又得到新的堆,数目为12,耗费体力为12。

所以多多总共耗费体力=3+12=15。

可以证明15为最小的体力耗费值。

输入格式InputFormat

输入包括两行,第一行是一个整数n(1<=n<=10000),表示果子的种类数。

第二行包含n个整数,用空格分隔,第i个整数ai(1<=ai<=20000)是第i种果子的数目。

输出格式OutputFormat

输出包括一行,这一行只包含一个整数,也就是最小的体力耗费值。

输入数据保证这个值小于2^31。

样例输入SampleInput

3

129

样例输出SampleOutput

15

时间限制TimeLimitation

每个测试点1s

参考程序:

programss;

var

a:

array[1..10000]oflongint;

i,j,n,t,s:

longint;

f:

boolean;

begin

readln(n);

fori:

=1tondo{读入各堆的果子数目}

read(a[i]);

i:

=1;

repeat{冒泡排序}

f:

=true;

forj:

=1ton-ido

ifa[j]

thenbegin

t:

=a[j];

a[j]:

=a[j+1];

a[j+1]:

=t;

f:

=false;

end;

i:

=i+1;

untilf;

 

s:

=0;

fori:

=1ton-1do{计算耗费的体力}

begin

j:

=i+1;

a[j]:

=a[i]+a[i+1];

s:

=s+a[j];{每合并两堆后累加体力}

whilea[j]>a[j+1]do{重新插入排序}

begin

t:

=a[j];

a[j]:

=a[j+1];

a[j+1]:

=t;

j:

=j+1;

end;

end;

write(s);

end.

 

例题2:

均分纸牌(NOIP2002tg)

[问题描述]有N堆纸牌,编号分别为1,2,…,N。

每堆上有若干张,但纸牌总数必为N的倍数。

可以在任一堆上取若干张纸牌,然后移动。

移牌规则为:

在编号为1堆上取的纸牌,只能移到编号为2的堆上;在编号为N的堆上取的纸牌,只能移到编号为N-1的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。

现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。

例如N=4,4堆纸牌数分别为:

① 9 ② 8 ③ 17 ④ 6

移动3次可达到目的:

  从③取4张牌放到④(981310)->从③取3张牌放到②(9111010)->从②取1张牌放到①(10101010)。

[输入格式]:

第一行输入N(N堆纸牌,1<=N<=100)

第二行输入A1A2…An(N堆纸牌,每堆纸牌初始数,l<=Ai<=10000)

[输出]:

输出至屏幕。

格式为:

所有堆均达到相等时的最少移动次数。

[输入样例]

 4

 98176

[输出样例]

3

算法分析:

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

我们用贪心法,按照从左到右的顺序移动纸牌。

如第i堆(0

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

(2)若a[i]

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

a[I]:

=v;a[I+1]:

=a[I+1]+a[I]-v;

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

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

我们继续按规则分析移牌过程,从第二堆移出9张到第一堆后,第一堆有10张纸牌,第二堆剩下-7张纸牌,再从第三堆移动17张到第二堆,刚好三堆纸牌数都是10,最后结果是对的,从第二堆移出的牌都可以从第三堆得到。

我们在移动过程中,只是改变了移动的顺序,而移动的次数不变,因此此题使用贪心法是可行的。

参考源程序:

programaa;

var

i,n,s:

integer;

v:

longint;

a:

array[1..100]oflongint;

begin

readln(n);

v:

=0;

fori:

=1tondo

begin

read(a[i]);

inc(v,a[i]);

end;

v:

=vdivn;{每堆牌的平均数}

fori:

=1ton-1do

ifa[i]<>v{贪心选择}

thenbegin

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

a[i+1]:

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

end;

writeln(s);

end.

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

一、问题是否适合用贪心法求解。

用贪心法解题很方便,但它的适用范围很小,判断一个问题是否适合用贪心法求解,目前还没有一个通用的方法,在信息学竞赛中,需要凭个人的经验来判断何时该使用贪心算法。

二、确定了可以用贪心算法之后,如何选择一个贪心标准,才能保证得到问题的最优解。

在选择贪心标准时,我们要对所选的贪心标准进行验证才能使用,不要被表面上看似正确的贪心标准所迷惑,如下面的列子:

例题3:

(NOIP1998tg)设有n个正整数,将他们连接成一排,组成一个最大的多位整数。

例如:

n=3时,3个整数13,312,343,连成的最大整数为:

34331213

又如:

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

输入:

N个数

输出:

连接成的多位数

算法分析:

此题很容易想到使用贪心法,在考试时有很多同学把整数按从大到小的顺序连接起来,测试题目的例子也都符合,但最后测试的结果却不全对。

按这种贪心标准,我们很容易找到反例:

12,121应该组成12121而非12112,那么是不是相互包含的时候就从小到大呢?

也不一定,如:

12,123就是12312而非12112,这样情况就有很多种了。

是不是此题不能用贪心法呢?

其实此题是可以用贪心法来求解,只是刚才的贪心标准不对,正确的贪心标准是:

先把整数化成字符串,然后再比较a+b和b+a,如果a+b>b+a,就把a排在b的前面,反之则把a排在b的后面。

参考程序:

programbb;

var

s:

array[1..20]ofstring;

t:

string;i,j,k,n:

longint;

begin

readln(n);

fori:

=1tondo

begin

read(k);

str(k,s[i]);

end;

fori:

=1ton-1do

forj:

=i+1tondo

ifs[i]+s[j]

thenbegin{交换}

t:

=s[i];

s[i]:

=s[j];

s[j]:

=t;

end;

fori:

=1tondowrite(s[i]);

end.

◆贪心算法所做出的选择只是在某种意义上的局部最优解,所以得到的结果不一定是最优解。

例题4:

装箱问题

问题描述:

装箱问题可简述如下:

设有编号为0、1、…、n-1的n种物品,体积分别为v0、v1、…、vn-1。

将这n种物品装到容量都为V的若干箱子里。

约定这n种物品的体积均不超过V,即对于0≤i<n,有0<vi≤V。

不同的装箱方案所需要的箱子数目可能不同。

装箱问题要求使装尽这n种物品的箱子数要少。

(n<=100)

分析:

若考察将n种物品的集合分划成n个或小于n个物品的所有子集,最优解就可以找到。

但所有可能划分的总数太大,对比较大的n,找出所有可能的划分要花费的时间是无法承受的。

为此,我们可以对装箱问题采用非常简单的近似算法,即贪心法。

该算法依次将物品放到它第一个能放进去的箱子中,如果前面已经用到的箱子均放置不下此物体,则增加一个箱子,直到所有的物体均被放入。

此种算法虽不能保证找到最优解,但还是能找到非常好的解,不失一般性。

具体做法:

将n件物品按照体积从大到小排好序,放置在数组a中,定义另一个数组b作为箱子,开始时只有一个箱子,然后将物品按次序检查能否存放到已用的箱子中去,如果可以就放入第一个可以放入的箱子中,否则将箱子的数量增加一个,将物体放入,直到所有的物体均被放入就可以得到箱子的数量。

参考程序:

programexample4;

var

a,b:

array[0..100]oflongint;{a数组用来放置物品的体积,b数组用来表示箱子}

i,j,n,v,t,k:

longint;

f:

boolean;

begin

readln(n,v);{读入物品数量及箱子的容积}

fori:

=0ton-1do

begin

read(a[i]);{读入各物品的体积}

b[i]:

=0;{将所有的箱子均置为空}

end;

i:

=1;

repeat{对所有物品的体积从大到小进行冒泡排序}

f:

=true;

forj:

=0ton-i-1do

ifa[j]

thenbegin

t:

=a[j];

a[j]:

=a[j+1];

a[j+1]:

=t;

f:

=false;

end;

i:

=i+1;

untilf;

k:

=0;{第一个箱子的下标为0}

fori:

=0ton-1do{将每一个物体依次放入箱子中}

begin

f:

=true;

forj:

=0tokdo{判断前面已经用过的箱子中能不能放下当前的物体}

ifa[i]+b[j]<=v

thenbegin{能放入已有箱子的话就放入}

b[j]:

=b[j]+a[i];

f:

=false;

break;{遇到第一个能放入的箱子并放入之后就不要试后面的箱子了}

end;

iff{如果前面用过的箱子均放不下当前物体,则增加一个箱子放置此物体}

thenbegin

k:

=k+1;

b[k]:

=b[k]+a[i];

end;

end;

write(k+1);{输出箱子的个数}

end.

上述算法能求出需要的箱子数,并能求出各箱子所装物品。

下面的例子说明该算法不一定能找到最优解,设有6种物品,它们的体积分别为:

60、45、35、20、20和20单位体积,箱子的容积为100个单位体积。

按上述算法计算,需三只箱子,各箱子所装物品分别为:

第一只箱子装物品1、3;第二只箱子装物品2、4、5;第三只箱子装物品6。

而最优解为两只箱子,分别装物品1、4、5和2、3、6。

贪心算法所作的选择可以依赖于以往所作过的选择,但决不依赖于将来的选择,也不依赖于子问题的解,因此贪心算法与其它算法相比具有一定的速度优势。

如果一个问题可以同时用几种方法解决,贪心算法应该是最好的选择之一。

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 哲学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1