贪心算法
最优化问题的算法往往都包含一系列的步骤,每个步骤都要做出最优化的选择(相对全局来说也是最优的);
贪心算法所做的选择看起来是当前最佳的,(相对于局部来讲是最佳的, 相对于全局来讲并不是最佳的),贪心算法希望通过局部的最优解得到一个全局最优解;
注意:贪心算法有时候你能够产生全局最优解,有时候并不能产生全局最优解;
贪心算法是先做出选择,然后再求解子问题,先选择后求解会导致:
1) 由于先做选择,选择的比较标准不是子问题的解,因此求得的可能不是全局最优解;
2) 由于经过了选择之后再求解子问题,子问题的数目减少为O(1), 可以降低时间复杂度;
一种称为拟阵的组合结构, 对于这种结构, 贪心算法总是能够给出最优解;
最小生成树是贪心算法的一个经典的例子;
一、活动选择问题
n个活动, S = {a1, a2, …, an};
每个活动有,
开始时间: s1, s2, …, sn
结束时间: f1, f2, …, fn;
活动选择问题是选择具有最多兼容的活动的集合;
1) 寻找活动选择问题的最优子结构
首先假设其子问题空间是一维的, 即S(1, i)的最优解由S(1, i-1)的最优解构成。但是发现S(1, i)的最优解可能是由S(2, i-1) 或是S(3,i-1)的最优解构成。因此,会发现子问题空间是一维是不够的, 子问题空间是二维的;
其子问题空间是二维的,对于活动S(i, j), 需要做出j-i-1种选择;
选择活动ak(其中i<=k<=j), 那么:
S(i, j) = S(i, k) + {ak} + S(k, j);
S(i, j) 具有最优化结构, 可以使用动态规划算法得到,时间复杂度是O(n^3)
假设c[i, j] 为问题S(i, j)中最大兼容活动的个数,有递归式:
其边界条件是当S(i, j)是空集的时候, c[i, j] = 0;
因此,完整的递归式是:
子问题空间是二维的, 对每个子问题都要进程O(n)次选择,因此总的时间复杂度是O(n^3);
2) 将动态规划转化为贪心算法
再次分析活动选择问题, 有两个发现:
对于任何非空子问题S(i, j), 设am是S(i, j)中具有最早结束时间的活动,那么:
1) 活动am一定是S(i, j)的最大兼容活动子集;
2) 子问题S(i, m)为空, 选择了am,那么子问题S(m, j)是唯一可能的非空;
首先了解, 最优子结构会随着原问题最优解的子问题数目以及确定子问题是的选择(受子问题空间以及每个子问题的选择影响);
动态规划中, 每个子问题有j-i-1种选择;
贪心算法中, 通过选择集合中最早结束时间的活动,使得S(i, m)为空, 这样就只用考虑子问题S(m, j). 在解决子问题中,贪心算法只考虑了一种选择,并且子问题空间也变为一维的;这样时间复杂度就是O(n);
最优子结构会随着原问题最优解的子问题数目以及确定子问题时的选择数目变化;
此外,由于子问题空间将为一维,子问题的选择只有一种,贪心算法在这里可以选用自顶向下解决,使得代码更加简单,时间复杂度从O(n^3)变为O(n);
贪心算法求解活动选择:
为了解决子问题S(i, j), 选择S(i, j)中具有最早结束时间的am, 并将子问题S(m, j)的最优解中的活动集合加入到S(i, j)的解中去;
贪心算法与动态规划的区别:
仔细分析问题,简化问题的子问题空间和选择,使得最优化结构简化,从而得到局部最优解;
问题最优化结构的简化会降低算法的时间复杂度;
二、 求解活动选择问题的代码
1) 使用动态规划求解活动选择问题
子问题空间是二维的S(i, j);
问题的选择是在S(i, j)选择一个ak,之后原问题的解 S(i, j) = S(i, k) + {ak} + S(k, j);
分析原问题个子问题的类型,可以发现子问题的长度小于原问题的长度(j-i), 因此建议在外循环使用长度l,最先求解长度l =2的所有子问题,长度l = 1的问题是边界条件;
确定选择的是ak的时候, 还要分别确定S(i,k) 和S(k, j), 原则是前缀子问题的最后一个活动的结束时间要早于ak的开始时间, ak的结束时间要早于后缀子问题第一个活动的开始时间;
#include <stdio.h>
#include <stdlib.h>
#define N 11
int s[N] = {
1, 3, 0, 5, 3, 5, 6, 8, 8, 2, 12 };
int f[N] = {
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 };
//使用m数组存储(i, j)的活动选择数
int m[N + 1][N + 1];
void DynamicActivitySelect(int low, int high) {
//边界条件
if (low >= high)
return;
for (int i = 1; i <= N