为什么贪婪算法是最优的?

时间:2014-10-08 16:56:05

标签: algorithm greedy proof

Codility,第14课,任务TieRopes(https://codility.com/demo/take-sample-test/tie_ropes)。简而言之,问题是将正整数的列表A划分为总和至少为K的(连续)子列表的最大数量。

我只想出一个贪婪的解决方案,因为这是课程的名称。它通过了所有测试,但我不知道为什么它是最佳解决方案(如果它是最佳的)。

int solution(int K, vector<int> &A) {
    int sum = 0, count = 0;
    for (int a : A)
    {
        sum += a;
        if (sum >= K)
        {
            ++count;
            sum = 0;
        }
    }
    return count;
}

有人可以告诉我这个解决方案是否以及为何最佳?

2 个答案:

答案 0 :(得分:4)

也许我天真或在这里犯了一些错误,但我认为看到算法确实是最优的并不是太难(虽然不是很明显)。

假设您具有列表的最佳分区,该分区具有最大数量的子列表。您可能拥有或不拥有列表中的所有元素,但由于向有效列表添加元素会生成有效列表,因此我们假设最初未分配给任何子列表的任何可能的“剩余”元素都被任意分配给其中一个相邻的子列表;所以我们有一个适当的列表最佳分区,我们称之为P1。

现在让我们考虑一下贪婪算法会产生的分区,比如说P2。 P2中的第一个子列表可能会发生两件事:

  1. 它可以与P1中的第一个子列表相同。
  2. 它可以比P1中的第一个子列表短。
  3. 在1.中,您将在第一个子列表之后的下一个元素中重复推理。如果算法产生的每个后续子列表都等于P1中的子列表,则P1和P2将相等。

    在2.你也会重复推理,但现在你至少有一个“额外”项目可用。所以,再次,下一个子列表可以:

    2.1。获取P1中的下一个子列表 2.2。在P1中的下一个子列表之前结束。

    重复一遍。因此,在每种情况下,您都将至少与P1一样多的子列表。这意味着,P2 至少与列表的任何可能分区一样好,特别是任何最佳分区。

    这不是一个非常正式的演示,但我认为它是有效的。请指出您认为可能出错的任何内容。

答案 1 :(得分:1)

以下是导致正式证明的想法。

  1. 如果AB的后缀,则A的最大分区大小小于或等于B的最大分区大小,因为我们可以扩展A分区的第一个子列表,以包含新元素而不减少其总和。

  2. 贪婪解决方案中每个子列表的每个正确前缀总和小于K

  3. 没有任何意义,因为我们可以将缺少的元素添加到相邻的列表中(我认为我的问题的措辞已经排除了这种可能性,但无论如何我会说出来)

  4. 可以通过归纳进行形式证明,表明对于每个非负整数i,存在一个最优解,它与每个i子列表上的贪婪解一致。因此,当i足够大时,唯一同意贪婪的解决方案是贪婪的,所以贪婪的解决方案是最优的。

    基础i = 0是微不足道的,因为任意最优解都可以。归纳步骤包括找到一个最佳解决方案,该解决方案与第一个i子列表中的贪婪一致,然后缩小i+1子列表以匹配贪婪解决方案(通过观察2,我们确实缩小了该子列表,因为它开始于与贪婪相同的位置;通过观察1,我们可以相应地扩展最优解的i+2子列表。