【算法】贪心

本文为算法学习笔记,讲解贪心。欢迎交流

简单贪心

贪心法总是考虑当前状态的局部最优,从而全局结果达到最优。贪心法的证明使用反证法证明策略最优,然后用数学归纳法保证全局最优。因为贪心法证明较为困难,如果在想到可行策略,自己却无法举出反例时,可大胆尝试。

下面是一个简单的例子:

【PAT B1020 月饼】

区间贪心

区间不相交问题:给出 N 个开区间 (x,y),选择尽可能多的开区间,使得开区间两两没有交集。

如果开区间 I 1 I_1 I1 被开区间 I 2 I_2 I2 包含,则应选择 I 1 I_1 I1,这样能有更大的空间容纳其他开区间。

将所有开区间按左端点 x 从大到小排序(如下图),如果除去区间包含的情况,则有 y 1 > . . . > y n y1>...>y_n y1>...>yn 成立。

【算法】贪心_第1张图片

通过观察发现, I 1 I_1 I1 右边有一段一定不会与其他区间重叠,如果将其去掉,则 I 1 I_1 I1 左边剩余部分会被 I 2 I_2 I2 包含。因此应该选择 I 1 I_1 I1。所以我们总是选择左端点最大的区间

#include 
#include 
#include 
using namespace std;

const int maxn = 110;

vector<pair<int, int>> I; // pair表示开区间左右端点
bool cmp(pair<int, int> a, pair<int, int> b) {
	if (a.first != b.first) return a.first > b.first; // 先按左端点从大到小排序
	else return a.second < b.second;
}

int main() {
	int n;
	while (scanf("%d", &n), n != 0) {
		I.clear(); I.resize(n);
		for (int i = 0; i < n; ++i)
			scanf("%d%d", &I[i].first, &I[i].second);
		sort(I.begin(), I.end(), cmp); // 区间排序
		// ans记录不相交区间的个数,lastX记录上一个被选中区间的左端点
		int ans = 1, lastX = I[0].first;
		for (int i = 1; i < n; ++i)
			if (I[i].second <= lastX) { // 如果该区间右端点在lastX左边
				lastX = I[i].first; // 以I[x]作为选中的新区间
				++ans;
			}
		printf("%d\n", ans);
	}
	return 0;
}

同理也可以先选择右端点最小的区间

类似的问题,区间选点问题:给出 N 个闭区间 [x,y],求至少需要确定多少个点,才能使每个闭区间中都至少存在一个点。对于前面图中的 I 1 I_1 I1,取左端点可以让它尽可能多地覆盖其他区间。

因为当闭区间相接时只需要左端点,将上面的代码中 I[i].second <= lastX 改为 I[i].second < lastX 即可。

贪心算法满足最优子结构性质,即一个问题的最优解可以由其子问题的最优解有效构造出来。

你可能感兴趣的:(数据结构与算法)