活动选择问题:有N个活动,仅安排一个人去完成,求一个人在不限时的情况下最多能完成几个活动,要求:活动包含开始时间和结束时间,活动的时间点不能变,就是说到时了,到点的活动必须开始。
这个问题为什么能够用greedy思想来求解,一个最直接的想法就是:活动已经按照时间点计划好了,我要尽可能完成多的活动,那么当前活动如果能尽快结束,我就能尽快接着去完成下一个活动,反过来如果当前正在完成的活动一直到其他活动都结束了,当前的活动才结束,那么最后我就只完成了一个活动。所以greedy策略就是每次选择下一个最早时间结束的活动去完成,那么如何每次都能选择除当前活动外剩余结束时间最早的活动呢,一个方法就是对所有活动按照最早结束时间进行排序。因此该题的解法核心实现首先在于对最早结束时间进行排序,然后依次选择符合条件(下一个活动的开始时间必须不小于当前活动的结束时间,因为只有一个人去完成)的最早结束时间的活动,并进行累加实现。不管该greedy是否能够得出最优解,但是从当前来看至少可以得到一个不错的解答。注:greedy注重于求解问题,而不是求解的效率,但是可以在greedy求解问题的时候,其中的某些步骤或环节可以采用效率较高的分治法进行求解,比如在对最早结束时间进行排序时,本文就采用了基于分治法的归并排序,这样实现对问题的高效求解。
主体框架算法如下:
//***************************activity*****************************
int GreedyStrategy::activitySelectProblem(map<double, double> &activities)
{
//仅对map迭代器指针进行排序,map<earily,later>
map<double, double>::iterator iter = activities.begin(); //本质上是赋值迭代器以头地址
vector<map<double, double>::iterator> activitiesNumPtr; //然后将所有的指针放入vector中进行排序
while (iter != activities.end())
{
activitiesNumPtr.push_back(iter);
iter++;
}
mergeSortActivities(activitiesNumPtr, 0, activitiesNumPtr.size() - 1); //已按照later从小到大排序,按照最早结束的最先安排
//-----------选择和判断符合要求的最早结束的活动------------------------------------
int number = 0;
if (activitiesNumPtr.size())
{
number++;
}
for (int i = 0, j = 1; j < activitiesNumPtr.size();) //activitiesNumPtr是已经排序好的结束时间点
{
if (activitiesNumPtr[j]->first >= activitiesNumPtr[i]->second)
{
number++;
i = j;
j++;
}
else
{
j++;
}
}
return number;
}
以上需要注意的是,在使用greedy策略选择已排序活动时,使用i下标指向当前正在执行的活动,而j指向待选择的活动,如果j活动不满足条件,那么j++,直到j遍历完所有的活动为止。
以下是活动的高效分治归并排序算法:
void GreedyStrategy::mergeSortActivities(vector<map<double, double>::iterator> &activitiesPtr, int low, int high)
{
if (low < high)
{
int mid = (low + high) / 2;
mergeSortActivities(activitiesPtr, low, mid);
mergeSortActivities(activitiesPtr, mid + 1, high);
conquerActivities(activitiesPtr, low, mid, high);
}
}
void GreedyStrategy::conquerActivities(vector<map<double, double>::iterator> &activitiesPtr, int low, int mid, int high)
{
int i = low;
int j = mid + 1;
vector<map<double, double>::iterator> num;
while (i <= mid&&j <= high)
{
if (activitiesPtr[i]->second < activitiesPtr[j]->second) // 编程原因:越早结束,则活动数越多
{
num.push_back(activitiesPtr[i]);
i++;
}
else
{
num.push_back(activitiesPtr[j]);
j++;
}
}
while (i <= mid)
{
num.push_back(activitiesPtr[i]);
i++;
}
while (j <= high)
{
num.push_back(activitiesPtr[j]);
j++;
}
for (int i = low, k = 0; i <= high; i++, k++)
{
activitiesPtr[i] = num[k]; //对这一段进行排序赋值
}
}