【实验目的】
应用贪心算法求解活动安排问题。
【实验要求】
活动安排问题是可以用贪心算法有效求解的很好的例子。
问题:有n个活动的集合A={1,2,…,n},其中每个活动都要求使用同一资源,如演讲会场等,而在同一时间内只有一个活动能使用这一资源。
求解:安排尽量多项活动在该场地进行,即求A的最大相容子集。
设待安排的11个活动的开始时间和结束时间按结束时间的升序排列如下:
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
s[i] | 1 | 3 | 0 | 5 | 3 | 5 | 6 | 8 | 8 | 2 | 12 |
f[i] | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
将此表数据作为实现该算法的测试数据。
【算法思想及处理过程】
贪心算法是一种基于贪心策略的算法,它在每一步选择中都采取当前状态下最优的选择,以期望能够获得全局最优解。它通常适用于满足以下两个条件的问题:
最优子结构性质:问题的最优解包含了其子问题的最优解。
贪心选择性质:通过局部最优选择,能够导出全局最优解。
贪心算法的基本思想是通过每一步的局部最优选择来构建问题的解决方案,从而实现对问题的全局最优解。它不像动态规划算法那样考虑所有可能的选择并进行比较,而是在每一步选择中只考虑局部最优解,然后逐步构建出全局最优解。
对于活动安排问题,贪心算法的思想是:
1.首先,按照活动的结束时间对活动进行升序排序。
2.选择第一个活动,并将其加入到最大相容子集中。
3.对于剩余的活动,依次检查每个活动的开始时间是否晚于等于前一个选中活动的结束时间,如果是,则将该活动加入到最大相容子集中。
4.重复步骤 3,直到所有活动都被检查完毕。
对于我的思路为:
1. 对活动按照结束时间进行升序排序。
2. 初始化一个结果列表,将第一个活动加入结果中。
3. 遍历剩余的活动,依次检查每个活动的开始时间是否晚于等于前一个选中活动的结束时间。
4. 如果是,则将该活动加入结果中,并更新最后选中活动的索引。
5. 重复步骤 3 和步骤 4,直到所有活动都被检查完毕。
6. 返回结果列表,即为最大相容子集。
流程图为:
同时为了跟好的记录数据,我创建了一个结构体Activity来记录活动的开始时间与结束时间
同时为了保证程序的正确性加入了check函数来保证不会出现相同的活动名,与此同时还会判断活动时间的正确性。
【程序代码】
#include <stdio.h>
#define MaxSize 9999
struct Activity {
char name[MaxSize];
int start;
int finish;
};
int check(struct Activity activities[], int current, char name[]) {
int i;
for (i = 0; i < current; i++) {
if (strcmp(activities[i].name, name) == 0) {
return 0; // 发现重复的活动名
}
}
return 1; // 没有发现重复的活动名
}
void activitySelection(struct Activity activities[], int n) {
int i, j;
// 按结束时间升序排序活动
for (i = 0; i < n-1; i++) {
for (j = 0; j < n-i-1; j++) {
if (activities[j].finish > activities[j+1].finish) {
struct Activity temp = activities[j];
activities[j] = activities[j+1];
activities[j+1] = temp;
}
}
}
// 初始化结果列表,将第一个活动加入结果中
printf("选中的活动:\n");
printf("%s:(%d, %d)\n",activities[0].name, activities[0].start, activities[0].finish);
// 从第二个活动开始遍历
int next = 0;
for (i = 1; i < n; i++) {
// 如果当前活动的开始时间晚于等于前一个选中活动的结束时间,则将该活动加入结果中
if (activities[i].start >= activities[next].finish) {
printf("%s:(%d, %d)\n",activities[i].name, activities[i].start, activities[i].finish);
next = i;
}
}
}
int main() {
int n,i;
printf("请输入活动个数:");
scanf("%d", &n);
struct Activity activities[n];
printf("请输入每个活动的开始时间和结束时间(中间用空格隔开):\n");
for (i = 0; i < n; i++) {
fflush(stdin);
printf("活动名:");
gets(activities[i].name);
while(check(activities,i,activities[i].name) == 0){
printf("活动名重复,请重新输入:");
gets(activities[i].name);
}
printf("活动时间:");
scanf("%d %d", &activities[i].start, &activities[i].finish);
while((activities[i].start > activities[i].finish) || (activities[i].start == activities[i].finish)){
printf("活动时间输入违反规则,请重新输入:");
scanf("%d %d", &activities[i].start, &activities[i].finish);
}
}
activitySelection(activities, n);
return 0;
}
【运行结果】
【算法分析】
1. 读取输入数据:这一步的时间复杂度为 O(n),其中 n 是活动的数量,因为需要对每个活动读取其开始时间和结束时间。
2. 对活动按结束时间升序排序:排序的时间复杂度取决于排序算法。在你的程序中,使用的是冒泡排序算法,其平均时间复杂度为 O(n^2)。虽然这不是最优的排序算法,但由于活动数量通常不会太大,因此对时间复杂度的影响并不太大。
3. 选择最大相容子集:在排序完成后,遍历一次活动数组并检查每个活动的开始时间是否晚于等于前一个选中活动的结束时间。由于只需要一次遍历,因此时间复杂度为 O(n)。
因此,总体时间复杂度为 O(n^2),其中 n 是活动的数量。