当一个问题有最优解结构的性质时候,可以用昨天提到的动态规划算法来解决,但是有时候问题满足一定的条件的时候可以用更为简单的贪心算法的思想来解决,这个条件也就是具有贪心选择的性质——所求问题的整体最优解可以通过一系列局部最优选择来达到,而找出局部最优解的方法是通过定义这个问题的贪婪准则。一个比较形象的例子就是找零钱如何找最少数目的钞票的问题,收银员要找给顾客89元,他会优先选择满足需求的最大面额的钞票也就是50元的,然后剩下的39元再依次优先选择20元,10元,5元,四张一元的钞票。这实际上就是一个贪心算法。在每一步选择零钱选择一个最优的零钱而不考虑这次选择对整体的影响。但是有的时候贪心算法并不能求出整体最优解,比如说同样的找零钱问题,假如零钱面值改为11元,5元,1元,而要找出15元的零钱,显然如果使用贪心选择,会找出1张11元的和4张1元的,但是最优选择却是3张5元的。
对于活动安排问题来讲,贪心算法可以得到最优解,因为贪心算法和找零钱不一样,时间维度不会像钱的面值一样重叠。
问题简介:设有n个活动需要进行,而且这n个活动都要占用同一个会场,但是同一时间内只能有同一个活动使用会场,每个活动i都有开始时间begin[i]和结束时间end[i],如果选择了活动i那么它在时间区间[begin[i],end[i])之间要占用会场,如果时间区间[begin[i],end[i])和时间区间[begin[j],end[j])不相交,则可以在同一天进行,问题要求这n个活动一天能最多进行的数目。
例如:
贪婪算法解决问题首先要考虑这个问题的贪婪准则。这个问题可以选择的贪婪准则有很多,比如以最早开始优先,以最早结束优先,以最小区间优先,以任务最少冲突个数优先。每一种策略都需要进行升序排列,比如我选择的最早结束优先的策略,就是将end[i]进行排序,选取最早结束的活动,然后将结束时间作为新的开始时间选择下一个最早结束的活动,以此类推。详细代码如下:
#include <iostream>
using namespace std;
int const MAX = 101;
int n, begin[MAX], end[MAX];
//init()用于输入数据
void init(){
cout <<"请输入活动的总个数:";
cin >>n;
for(int i=1; i <= n; i++){
cout <<"第"<<i<<"个活动的开始时间为:";
cin >>begin[i];
cout <<"第"<<i<<"个活动的结束时间为:";
cin >>end[i];
}
}
//非常标准的快速排序来对结束时间进行排序
void quicksort(int x, int y){
int i, j, mid, t;
i = x;
j = y;
mid = end[(x+y)/2];
while(i <= j){
while(end[i] < mid)
++i;
while(end[j] > mid)
--j;
//注意将两个数组的数据都进行交换
if(i <= j){
t = begin[j];
begin[j] = begin[i];
begin[i] = t;
t = end[j];
end[j] = end[i];
end[i] = t;
++i;
--j;
}
}
if(x < j) quicksort(x, j);
if(i < y) quicksort(i, y);
}
//选择活动
void solve(){
int k = 0;
//t是当前选择活动的结束时间
//t=-1保证从0开始的活动可以被计算
for(int i=1, t=-1; i <= n; ++i){
if(begin[i] >= t){
++k;
t = end[i];
}
}
cout <<"最多进行的会议数为:"<<k<<endl;
}
int main(){
init();
quicksort(1, n);
solve();
return 0;
}
我最开始定义了两个数组来表示开始和结束时间,后面用了时间复杂度比较低的快速排序来对结束时间进行的排序,还有一种方法就是定义一个struct,如下
struct Activity{
int begin;
int end;
} Act[101];
然后用sort()对Act[i].end进行排序,如果快排运用的不是很熟练可以这样做。
代码运行后的效果图如下:
本算法的时间复杂度为快速排序的时间复杂度+选择活动的时间复杂度=O(nlogn)+O(n)=O(n(logn+1))。