[知识点] 2.3 贪心算法

前言

算法基础的第三部分!这几部分其实讲述的都不太详细,因为许多内容都算比较清楚了,也不想太深入讨论,不过应该后面会进行补充。

(总目录:https://www.cnblogs.com/jinkun113/p/12528423.html

子目录列表

1、贪心

2、背包问题

3、正确性证明

2.3 贪心算法

1、什么是贪心

顾名思义,贪心算法是指以一种贪婪的思维去计算每一个步骤并做出决策的过程,它往往是“目光短浅”的,即只顾眼前利益的最大化,而不会考虑长期收益。从现实生活中看,这种思维并非正确,甚至大多数情况下都是无法获得最佳结果的,所以其实贪心算法可行范围不广,但换言之,在满足条件时是可行的。

2、背包问题

先给出一道例题。

【自定义】【基础背包问题】

有 7 个物品和一个容量为 150kg 的背包,每个物品有重量和价值两种属性,如下:

物品  A  B  C  D  E  F  G

重量  35kg  30kg  6kg 50kg  40kg  10kg  25kg

价值  10   40   30   50      35      40      30

要求选若干物品放入背包使背包中物品的总价值最大且背包中物品的总重量不超过背包的容量。

考虑以下几种思路:

① 每次都选择价值最高的?

根据该策略,我们从 7 个物品中,先后选择了 D, F, B, E 三个物品,获得了 165 的总价值。

② 每次都选择重量最小的?

根据该策略,先后选择了 C, F, G, B, A, E 六个物品,获得了 185 的总价值。

上述两种方法其实很容易被证明是错误的,两个参数不具有一致单调性,怎么能用其中一个来作为判断依据呢?① 显然不是最优解;② 看似不错,但同样不是最优解。更何况,在下面的极端例子中更是不尽人意。

3 个物品,容量 20:

物品  X  Y  Z

重量       10kg  10kg  20kg

价值  5   5  100

则会先后选择 X, Y,获得 10 的总价值,显然不如直接选择 Z。

于是萌生出一个更好的想法,既然有两个参数要考虑,那么为何不综合考虑?以价值和重量的比作为每个物品的单位重量价值,得到:

物品      A  B  C  D  E  F  G

单位重量价值  0.29   1.33  5.00   1.00   0.88  4.00  1.20

③ 每次都选择单位重量价值最大的?

根据该策略,先后选择了 C, F, B, G, D 五个物品,获得了 190 的总价值。

看起来挺合理的想法,也确实是该例的最优解了,可它是无懈可击的吗?

3 个物品,容量:

物品      X  Y  Z

重量           10kg  10kg  12kg

价值      10   10   18

单位重量价值  1.00   1.00   1.50

则会先选择 Z,然后就没有空间了,获得 18 的总价值,显然不如选择 X, Y。

上述三种贪心思路均失败告终。

其实这道题没有正确的贪心算法,它的最朴素做法为动态规划(背包型),如想了解,请参见:https://www.cnblogs.com/jinkun113/p/12539607.html

那么给出这道题的意义是什么?上述过程我们考虑贪心思路其实是漫无目的而凭直观来决定的,它们可能适用于样例,甚至适用于你考虑到的很多情况,但并无任何直接或间接证据来证明算法正确性。如果找到了反例可以说明思路错误,可能会死心;但如果没找到,就存在可能你认为算法没有问题,但其实并不合理而导致不能 AC 的现象。

也就是说,在确定来用贪心来解题时,前提是能够证明其正确性

3、正确性证明

如何证明正确性?我觉得数学归纳法是个不错的选择。再来一道例题。

【NOIP2012】【国王游戏】https://vijos.org/p/1779

恰逢 H 国国庆,国王邀请 n 位大臣来玩一个有奖游戏。首先,他让每个大臣在左、右手上面分别写下一个整数,国王自己也在左、右手上各写一个整数。然后,让这 n 位大臣排成一排,国王站在队伍的最前面。排好队后,所有的大臣都会获得国王奖赏的若干金币,每位大臣获得的金币数分别是:排在该大臣前面的所有人的左手上的数的乘积除以他自己右手上的数,然后向下取整得到的结果。
国王不希望某一个大臣获得特别多的奖赏,所以他想请你帮他重新安排一下队伍的顺序,使得获得奖赏最多的大臣,所获奖赏尽可能的少。注意,国王的位置始终在队伍的最前面。

同上面那道题一样,每一位大臣由左手数 x 和右手数 y 两个独立参数来决定获得金币数,所以单独以其中一个参数来决定顺序显然不正确。现在试着用数学归纳法来考虑:

根据题意,令 t[i] 表示站在第 i 个大臣前面的 x 的乘积,则:

站在第 i 个位置的大臣是 1,能获得的奖赏为:t[i] / y1

站在第 i + 1 个位置的大臣是 2,能获得的奖赏为:t[i + 1] / y2 = t[i] * x1 / y2

如果交换第 i 个和第 i + 1 个大臣的位置,则:

站在第 i 个位置的大臣 2 能获得的奖赏为:t[i] / y2

站在第 i + 1 个位置的大臣 1 能获得的奖赏为:t[i + 1] / y1 = t[i] * x2 / y1

当且仅当满足下列式子时,交换前结果更优:

max(t[i] / y1, t[i + 1] / y2) < max(t[i] / y2, t[i + 1] / y1)

两边同时除以 t[i],得:

max(1 / y1, x1 / y2) < max(1 / y2, x2 / y1)

化成整式,得:

max(y2, x1 * y1) < max(y1, x2 * y2)

又 y1 < x1 * y1, y2 < x2 * y2,故上式可化为:

x1 * y1 < x2 * y2(具体步骤略)

也就是说,当大臣 1 的左右手数乘积小于大臣 2 的乘积,那么 1 站在 2 前面更优,即不交换。

由数学归纳法可知,对于任何一对相邻的大臣,都存在这样的关系,即可以推得:大臣的 x 和 y 的乘积越小,就应该站在越前面,也就是说直接根据这个乘积从小到大给大臣们排个序就行了!

有了这个证明,贪心起来是不是就安心多了呢。

代码(非 AC 代码!原题还有 40 分需要高精度计算(请参见:<施工中>)此处省略):

#include <bits/stdc++.h> 
using namespace std;

#define MAXN 1005

class Hand {
public:
    int x, y;
    bool operator < (const Hand &a) const {
        return x * y < a.x * a.y;
    }
} a[MAXN];

int n, o = 1, ans;

int main() {
    cin >> n;
    for (int i = 0; i <= n; i++)
        cin >> a[i].x >> a[i].y;
    sort(a + 1, a + n + 1);
    for (int i = 1; i <= n; i++)
        o *= a[i - 1].x, ans = max(ans, o / a[i].y);
    cout << ans;
    return 0;
}

相关推荐