您的位置:首页 > 编程语言 > C语言/C++

C语言贪心算法

2015-08-29 21:57 363 查看
贪心算法

所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。

贪心算法不是对所有问题都能得到整体最优解,但对范围相当广泛的许多问题他能产生整体最优解或者是整体最优解的近似解。

贪心算法的基本思路如下:

1.建立数学模型来描述问题。

2.把求解的问题分成若干个子问题。

3.对每一子问题求解,得到子问题的局部最优解。

4.把子问题的解局部最优解合成原来解问题的一个解。

实现该算法的过程:

从问题的某一初始解出发;

while 能朝给定总目标前进一步 do

   求出可行解的一个解元素;

由所有解元素组合成问题的一个可行解;

二、例题分析

1、[背包问题]有一个背包,背包容量是M=150。有7个物品,物品可以分割成任意大小。

要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。

物品 A B C D E F G

重量 35 30 60 50 40 10 25

价值 10 40 30 50 35 40 30

分析:

目标函数: ∑pi最大

约束条件是装入的物品总重量不超过背包容量:∑wi<=M( M=150)

(1)根据贪心的策略,每次挑选价值最大的物品装入背包,得到的结果是否最优?

(2)每次挑选所占重量最小的物品装入是否能得到最优解?

(3)每次选取单位重量价值最大的物品,成为解本题的策略。 ?

值得注意的是,贪心算法并不是完全不可以使用,贪心策略一旦经过证明成立后,它就是一种高效的算法。

贪心算法还是很常见的算法之一,这是由于它简单易行,构造贪心策略不是很困难。

可惜的是,它需要证明后才能真正运用到题目的算法中。

一般来说,贪心算法的证明围绕着:整个问题的最优解一定由在贪心策略中存在的子问题的最优解得来的。

对于例题中的3种贪心策略,都是无法成立(无法被证明)的,解释如下:

(1)贪心策略:选取价值最大者。反例:

W=30

物品:A B C

重量:28 12 12

价值:30 20 20

根据策略,首先选取物品A,接下来就无法再选取了,可是,选取B、C则更好。

(2)贪心策略:选取重量最小。它的反例与第一种策略的反例差不多。

(3)贪心策略:选取单位重量价值最大的物品。反例:

W=30

物品:A B C

重量:28 20 10

价值:28 20 10

根据策略,三种物品单位重量价值一样,程序无法依据现有策略作出判断,如果选择A,则答案错误。

所以需要说明的是,贪心算法可以与随机化算法一起使用,具体的例子就不再多举了。(因为这一类算法普及性不高,而且技术含量是非常高的,需要通过一些反例确定随机的对象是什么,随机程度如何,但也是不能保证完全正确,只能是极大的几率正确)

贪心算法

例题:畜棚修理(1999年USACO春季公开赛试题)

有一长列畜棚,其中一些被木板覆着。你可以使用总共N个木板,这些木板可以覆盖任意连续的畜棚。覆盖所有必要的畜棚,使得所用的木板尽可能的少。

贪心算法很快,一般的为O(n)到O(n^2)而且需要很少的额外空间。不幸地是,它一般是不正确的。但是,当它正确,它就会远快于其它算法。

算法

在贪心算法背后所隐含的基本算法,是从小规模问题中生成大规模问题。这不像其他算法,在贪心算法的进行过程中,它只关心它发现的最好的策略。因此,对于例题,为了生成N=5时的策略,它先寻找N=4时的最优策略,然后以此生成。此时,对于其它N=4时的策略,不予考虑。

问题

贪心时,有两个基础的问题需要考虑。

怎么生成?

怎样从小规模策略中生成大规模策略?一般地,有这样一个运作过程。对于例题,由四块木板生成五块木板的最显然的方法,就是将其中一块木板中的一部分拿掉,这样一块木板变成了两块。而你要做的,是在那些不需要被木板覆盖的畜棚区域中,选出最长的片段(这样,就使得所用的木板最少了)。

拿掉被覆盖畜棚的木板中的一个片段,使得它变成两段。一段在该片段之前,一段在该片段之后。

它有效吗?

对于程序员,真正的挑战是谈心算法不总有效。即便它可能对于一些简单的数据、随意的数据,以及所有你能考虑到的情况有效,但只要有一种情况不奏效,至少一个(假设没有更多),评测系统就会由此停下。

对于例题,看看上述的谈心算法的工作工程,要考虑如下几点:

假设,答案中不包括贪心算法所拿掉的较大缺口,而是较小的。那么通过结合较小缺口,使其与两边的木板相连。答案是通过用一定量的木板覆盖尽量少的畜棚。新的答案更好些,所以假设是错误的,我们总是去掉最大缺口是正确的。

如果答案不包含特殊的缺口,但是包含一个与之同样大的缺口,做相同的转换,发现所需的木板量并没有变化,这个新的答案与原来的答案是一样的。我们可以选取任意一个。

因此,所产生的结果是最优的,它包含的是最大的切除片段。每一步都是这样,最终也一定是这样。

结论

如果一个贪心算法存在,那就用它。它们的编写、编译、运行都很快,唯一令人遗憾地,它地正确性。如果贪心算法可以得到正确的答案,那就努力地寻找这个算法。但是,不要指望贪心算法能解决所有问题

贪心算法的3个相当经典的程序

1.线段覆盖(linescover)

题目大意:在一维空间中告诉你N条线段的起始坐标与终止坐标,要求求出这些线段一共占了多大的长度。

解题思路:将线段按其实坐标进行排序,使之依次递增。然后定义一个变量last记录考虑到当前线段之时被线段覆盖的最大的坐标值。因为已经排过序所以当前线段的效应长度为(x为起始坐标,y为终止坐标):

length:=0 (y<=last)

y-last (x<=last & y>last)

y-x (x>last & y>last)

并且注意同时更新last的值。最后统计每条线段的效应长度就为我们所需要的答案。

2.最优数对(bestpair)

题目大意:按递增的顺序告诉你N个正整数和一个实数P,要求求出求出该数列中的比例最接近P的两个数(保证绝对没有两个数使得其比值为P)。

解题思路:定义两个指针i和j,然后进行如下操作:当code[j]/code[i]>p时inc(j),当code[j]/code[i]<p时inc(i),然后记录其中产生的最优值即可。

3.连续数之和最大值(maxsum)

题目大意:给你N个数,要求求出其中的连续数之和的最大值。(也可以加入a和b来限制连续数的长度不小于a且不大于b)。

解题思路:定义一个统计变量tot,然后用循环进行如下操作:inc(tot,item) 其中如果出现tot<0的情况,则将tot赋值为0。在循环过程之中tot出现的最大值即为答案。如果加入了限制条件的话,问题就变得难一些了(这句真的不是废话)。为此我们需要定义:数组sum[i]来表示code[1]到code[i]之和(这样的话code[a]~code[b]的和我们就可以用sum[b]-sum[a-1]来表示了。),数组hash[i]来表示满足条件的sum[a-1]所对应的下标(并使之按sum[i]的递增顺序排列,一定要处理好sum[i]进出hash的过程)。这样的话对于终止坐标为i的连续数列其最大和必定为sum[i]-sum[hash[1]],求出其最大值即可。

还是举个例子,贪心算法最经典的例子,给钱问题。

比如中国的货币,只看元,有1元2元5元10元20、50、100

如果我要16元,可以拿16个1元,8个2元,但是怎么最少呢?

如果用贪心算,就是我每一次拿那张可能拿的最大的。

比如16,我第一次拿20拿不起,拿10元,OK,剩下6元,再拿个5元,剩下1元

也就是3张 10、5、1。

每次拿能拿的最大的,就是贪心。

但是一定注意,贪心得到的并不是最优解,也就是说用贪心不一定是拿的最少的张数

贪心只能得到一个比较好的解,而且贪心算法很好想得到。

再注意,为什么我们的钱可以用贪心呢?因为我们国家的钱的大小设计,正好可以使得贪心算法算出来的是最优解(一般是个国家的钱币都应该这么设计)。如果设计成别的样子情况就不同了

比如某国的钱币分为 1元3元4元

如果要拿6元钱 怎么拿?贪心的话 先拿4 再拿两个1 一共3张钱

实际最优呢? 两张3元就够了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息