贪心算法

贪心算法

基本概念

所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,它所做出的仅仅是在某种意义上的局部最优解。贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性(即某个状态以后的过程不会影响以前的状态,只与当前状态有关。)

基本例题

case 1:会议室安排

**问题描述:**设有n个会议的集合C={1,2,…,n},其中每个会议都要求使用同一个资源(如会议室)而在同一时间内只能有一个会议使用该资源。每个会议i都有要求使用该资源的起始时间bi和结束时间ei,且bi < ei 。如果选择了会议i使用会议室,则它在半开区间[bi, ei)内占用该资源。如果[bi, ei)[bj , ej)不相交,则称会议i与会议j是相容的。会场安排问题要求在所给的会议集合中选出最大的相容活动子集,也即尽可能地选择更多的会议来使用资源。

**求解思路:**直观上想,一个会议的结束时间越早,留下给其他会议的时间也就越多,进而可能安排的会议数目也就越多。将各个会议按照结束时间从小到大排序,每次所选择的下一个会议需要满足两个条件:该会议的开始时间大于等于上一个会议的结束时间,且是所满足条件的会议中结束时间最早的那一个。

示例代码

#include <iostream>
#include <algorithm>
using namespace std;
struct Meeting {
	int index;
	int startTime;
	int endTime;
}; 

bool cmp(Meeting x, Meeting y) {
	return x.endTime < y.endTime;
}

void Print(Meeting x) {
	cout<<x.index<<" "<<x.startTime<<" "<<x.endTime<<endl;
}

int main() {
	int n;
	cin>>n;
	Meeting meeting[n];
	for(int i=0; i<n; i++) {
		cin>>meeting[i].index>>meeting[i].startTime>>meeting[i].endTime;
	}
	sort(meeting,meeting+n,cmp);
	cout<<"======会议安排如下========"<<endl; 
	Print(meeting[0]);
	int lastTime = meeting[0].endTime;
	for(int i=1; i<n; i++) {
		if(meeting[i].startTime >= lastTime) {
			Print(meeting[i]);
			lastTime = meeting[i].endTime;
		}
	}
	return 0;
}

case 2:分配畜栏

**问题描述:**农场有N头牛,每头牛会在一个特定的时间区间[A, B](包括AB)在畜栏里挤奶,且一个畜栏里同时只能有一头牛在挤奶。现在农场主希望知道最少几个畜栏能满足上述要求,并要求给出每头牛被安排的方案。对于多种可行方案,只输出一种即可。

**输入:**输入的第一行包含一个整数N(1 ≤ N ≤ 50, 000),表示有N牛头;接下来N行每行包含两个数,分别表示这头牛的挤奶时间[Ai, Bi](1 ≤ A≤ B ≤ 1, 000, 000)

**输出:**输出的第一行包含一个整数,表示最少需要的畜栏数;接下来N行,第i+1行描述了第i头牛所被分配的畜栏编号(从1开始)。

**解题思路:**每头牛都有一个挤奶开始时间和结束时间,当然要优先处理挤奶开始时间早的奶牛。因此首先将所有奶牛按照挤奶开始时间从小到大进行排序,然后按照顺序处理即可,在处理第i头奶牛的时候,首先先查看已经建成的畜栏中是否有空着的(此处可以采用队列,将畜栏按照结束时间从小到大进行排序,队头的畜栏永远是最先结束的,在判断时,只需要判断第一个即可。畜栏的结束时间也就是该畜栏里奶牛挤奶的结束时间),如果有空着的,直接将该头奶牛放进已建成的畜栏即可,如果没有空着的,那么就需要新建一个畜栏,将奶牛放进去挤奶。

示例代码

#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
struct Cow {
	int a,b;	// 挤奶区间的起终点
	int No;		// 编号
	bool operator<(const Cow & c) const {
		return a < c.a;
	} 
} cows[50100];
int pos[50100];	// pos[i]表示编号为i的奶牛去的畜栏编号
struct Stall {
	int end;	// 结束时间
	int No;		// 编号
	// 重载<,规定了畜栏被排序的规则:结束时间最早的位于队列的最前方 
	bool operator<(const Stall & s) const {
		return end > s.end;
	}
	Stall(int e, int n):end(e),No(n) { }
}; 
int main() {
	// 读入数据 
	int n;
	scanf("%d", &n);
	for(int i=0; i<n; i++) {
		scanf("%d%d", &cows[i].a, &cows[i].b);
		cows[i].No = i;
	}
	sort(cows,cows+n);	// 对奶牛按照开始挤奶的时间从小到大排序
	int total = 0;		// 记录总共分配了多少畜栏
	priority_queue<Stall> pq;	// 优先队列,用来存放畜栏,按照结束时间从小到大排序 
	for(int i=0; i<n; i++) {
		if(pq.empty()) {
			total ++;
			pq.push(Stall(cows[i].b, total));
			pos[cows[i].No] = total; 
		} else {
			Stall st = pq.top();
			if(st.end < cows[i].a) {
				pq.pop();
				pos[cows[i].No] = st.No;
				pq.push(Stall(cows[i].b,st.No));
			} else {
				total ++;
				pq.push(Stall{cows[i].b, total});
				pos[cows[i].No] = total;
			}
		}
	} 
	cout<<"供分配了 "<<total <<" 个畜栏"<<endl;
	for(int i=0; i<n; i++) {
		cout<<"奶牛编号: "<<cows[i].No+1<<" 畜栏编号: "<<pos[i]<<endl;
	}
	return 0;
}

case 3:放置雷达

问题描述:x轴是海岸线,x轴上方是海洋。海洋中有n个岛屿,可以看作点。给定每个岛屿的坐标(x,y)x,y都是整数。当一个雷达(可看作点)到岛屿的距离不超过d(整数),则认为该雷达覆盖了该岛屿。雷达只能放置在x轴上。问至少需要多少个雷达才可以覆盖全部岛屿。

**求解思路:**原问题比较复杂,首先我们将问题进行一个转化:对于每个岛屿,可以计算出,覆盖它的雷达,必须位于x轴上的区间[Ps,Pe]。如下图所示:

在这里插入图片描述

如果有雷达位于某个x轴区间[a,b],称该雷达覆盖此区间。问题转化为,至少要在x轴上放置几个雷达(点),才能覆盖全部区间。

  • 重要结论:如果可以找到一个雷达同时覆盖多个区间,那么把这多个区间按照起点坐标从小到大排序,则最后一个区间(起点最靠右)k的起点,就能覆盖所有区间。因此,我们就可以只挑区间的起点来放置雷达了
  • 首先将所有区间按照从小到大排序,并编号0~n-1。依次考察每个区间的起点,看要不要在那里放置雷达。开始,所有的区间都没有被覆盖,定义一个变量firstNoConvered指向编号最小的且未被覆盖的区间编号,因此最初始状态firstNoConvered=0。接着我们依次遍历起点,在第0个区间起点可放置的情况下,先不着急放置雷达,再看放置第1个区间起点是否可以覆盖前面的0区间,如果可以继续往下看第2个区间的起点,看放置在该点是否可以覆盖前面的0,1号区间,如果不可以的话,就将第一个雷达放置在1号区间的起点,然后firstNoConvered=2指向第二个区间;如果可以的话继续按照该思路往下看

**输入与输出:**输入有两部分构成。第一行输入mrm代表海上岛屿的数量,r代表雷达的覆盖半径。接下来的m行分别表示各个雷达的坐标(x,y)。输出放置最小数量的雷达数目即可。

示例代码

#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
int R = 2;
struct Point {
	int x;
	int y;
};

struct QuJian {
	int No;
	int left;
	int right;
	bool operator<(const QuJian & qujian) const {
		return left < qujian.left;
	} 
};

int main() {
	// 输入数据 
	int m,r;
	cin>>m>>r;
	Point point[m];
	for(int i=0; i<m; i++) {
		cin>>point[i].x>>point[i].y;
	}
	// 转化为区间 
	QuJian qj[m];
	for(int i=0; i<m; i++) {
		qj[i].left = point[i].x - sqrt(R*R-point[i].y*point[i].y);
		qj[i].right = point[i].x + sqrt(R*R-point[i].y*point[i].y);
	}
	// 区间按照左端点从小到大排序
	sort(qj,qj+m);
	int total = 0;
	for(int i=0; i<m; i++) {
		if(i == m-1) {
			cout<<"最少放置雷达数: "<<total+1<<endl;
			return 0;
		}
		int minRight = qj[i].right;
		for(int j=i+1; j<m; j++) {
			if(qj[j].left <= minRight) {
				minRight = min(minRight,qj[j].right);
				if(j == m-1) {
					cout<<"最少放置雷达数: "<<total+1<<endl;
					return 0;
				}
			} else {
				total ++;	// 放置雷达在编号为j-1的区间起点
				i = j;
				i--;		// break之后i还要+1,所以要先-1,才能将j的值赋给i 
				break; 
			}
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

steven_moyu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值