vlambda博客
学习文章列表

ACM算法 | 你真的搞懂贪心算法了吗?

点击上方 “蓝字” 关注广外ACM!



贪心算法



引言

箱子里面有N个苹果,现在你要在里面拿走3个苹果,你想使这三个苹果的重量是最大的,那么你会怎么拿?

当然,最好的办法是你第一次在N个苹果里面拿走最大的,第二次在剩下的N-1个苹果里面拿走最大的,第三次在剩下的N-2个苹果里面拿走最大的。

是的,人都是贪心的,拿苹果的时候,虽然我们不知道最终拿的3个是不是重量最大的,但是我们每一次拿的苹果重量都是最大的,这就是贪心。



贪心理论

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

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

贪心算法关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。

贪心算法是一种极为重要的算法,在哈夫曼树,最小生成树的Prim算法,Kruskal算法,单源最短路径的Dijkstra等许多经典算法都是以贪心算法为基础的。



基本要素

1、贪心选择性质——局部最优达到全局最优。

贪心选择的性质是指所求问题的整体最优解可以通过一系列的局部最优解来求出。这是贪心算法的第一个基本要素。

对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所做的贪心算则最终导致问题的整体最优解。在ACM竞赛中考察贪心算法,即考察你的证明能力,看你是否能证明出你的贪心算法是正确的,如果不能证明,就要尝试使用其他算法。贪心算法往往是我们考虑问题的第一步!


2、最优子结构——问题的最优解包含了子问题的最优解。

具体问题的最优子结构性质是该问题可用动态规划算法或贪心算法求解的关键特征。你要证明你的最优子结构是正确的!



基本思路

1、建立数学模型来描述问题。(证明的过程)

2、把求解的问题分成若干子问题。(往往是排序操作或使用优先队列)

3、对每一个子问题求解,得到子问题的局部最优解。(取出队列里第一个数做操作)

4、把子问题的局部最优解合成原来问题的一个解。(这个过程有时候是隐性的)



讲了一大堆理论,最后还是要靠做题去体会~


贪心算法是ACM竞赛中最基础的也是最容易被忽视的算法。


贪心算法拥有它独特的美感,很多复杂巧妙地算法都离不开贪心。


可以说贪心算法是所有算法的基础,是一块基石。很多问题都可以通过巧妙的贪心来解决。或者当你找到最优子结构后,再用其他算法去求局部最优解。最后再合并所有解~



部分背包问题

现在有很多物品(它们是可以分割的),我们知道它们每个物品的单位重量的价值v和重量w(1<=v,w<=10);如果给你一个背包它能容纳的重量为m(10<=m<=20),你所要做的就是把物品装到背包里,使背包里的物品的价值总和最大。

输入:

第一行输入一个正整数n(1<=n<=5),表示有n组测试数据;
随后有n测试数据,每组测试数据的第一行有两个正整数s,m(1<=s<=10);s表示有s个物品。接下来的s行每行有两个正整数v,w。


输出:

输出每组测试数据中背包内的物品的价值和,每次输出占一行。


样例输入:

1

3 15

5 10

2 8

3 9


样例输出:

65


【分析】

目标函数:∑vi*wi最大

约束条件:装入背包的物品总重量不超过背包的容量:∑wi

根据贪心策略:

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

(2)每次挑选所占重量最小的物品装入背包,得到的结果是否最优?

(3)每次选取单位重量价值最大的物品,可以得到本题的最优解。

 

贪心算法都是需要证明后才能真正运用到题目的算法中。

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

对上述三种贪心策略证明如下:

(1)选取价值最大的物品。反证:

背包容量:W=30

物品pi:A B C

重量wi:30 12 18

价值vi:30 24 27

根据策略,首先选取物品A,接下来不能再选取其他物品,然而选取B、C更好。

(2)选取所占重量最大的物品。反证:

背包容量:W=30

物品pi:A B C

重量wi:30 12 18

价值vi:30 24 27

根据策略,首先选取物品A,接下来不能再选取其他物品,然而选取B、C更好。

(3)选取单位重量价值最大的物品。

背包容量:W=30

物品pi:A B C

重量wi:30 12 18

价值vi:30 24 27

根据策略,B物品单位重量价值为2,C为1.5,A为1。我们先选取B,接着选取C,则可得最大价值51,即问题所求解。

 

注意此题所说明的是部分背包问题,而不是0-1背包,即物品可被分割,不需要完整取整个物品,与动态规划的背包问题不同。

AC代码:

#include 
#include 
using namespace std;
const int maxn = 20;
struct res
{
    int v,w;
}arr[maxn];
int s,m;
bool cmp(res x,res y)
{
     return x.v>y.v;
}
int main()
{
    int n,i,sum;
    scanf("%d",&n);
    while(n--)
    {
       scanf("%d%d",&s,&m);
       for(i=0;i0)     //对一个物品能取完全部都取
           {
              sum+=arr[i].v*arr[i].w;
              m-=arr[i].w;
           }
           else                 //不能取完时,只取其一部分
           {
              sum+=m*arr[i].v;
              break;
           }
       }
       printf("%d\n",sum);
    }
    return 0;
}




看完上面的分析,相信大家对贪心算法也有所体会了~在下一篇推送我们会给出更多的例题


长按图片