1、概述
(1)案例:
多个社团申请同一间教室开会,问如何分配和安排,使得此教室在一天中能够分配给更多的社团使用
一台公共电脑可以被许多人申请使用,如何分配使得此电脑在一天中能够为更多的人服务使用
(2)活动安排问题概述
活动安排问题就是要在所给的活动集合中选出最大的相容活动子集合。该问题要求高效地安排一系列争用某一公共资源的活动。贪心算法提供了一个简单、漂亮的方法使得尽可能多的活动能兼容地使用公共资源。
设有n个活动的集合E={1,2,…,n},其中每个活动都要求使用同一资源,而在同一时间内只有一个活动能使用这一资源。每个活动i都有一个要求使用该资源的起始时间si和一个结束时间fi,且si <fi 。如果选择了活动i,则它在半开时间区间[si, fi)内占用资源。若区间[si, fi)与区间[sj, fj)不相交,则称活动i与活动j是相容的。也就是说,当si≥fj或sj≥fi时,活动i与活动j相容。活动安排问题就是要在所给的活动集合中选出最大的相容活动子集合。
此问题可以采用贪心算法来进行求解,贪心策略就是优先选择结束时间早的活动开始安排,这样的话就能保证留下更多的时间安排剩余的活动。
2、实战代码
(1)思路:
对所有活动,按照活动结束时间从早到晚的顺序排序,记排序后的数组为a,然后选择结束时间早的活动优先安排,每安排一项活动a[i]后,判断a[i]的后面第一个活动a[i+1]的开始时间startTime与活动a[i]的结束时间endTime的关系,如果前者早(数值小),则活动a[i+1]无法安排,否则可以安排此活动。
(2)题目数据:
11个活动
各个活动的开始时间:1,3,0,5,3,5,6,8,8,2,12
各个活动的结束时间:4,5,6,7,8,9,10,11,12,13,14(注意题目做了简化处理,活动的结束时间已经升序排序)
(3)代码
import java.util.*;
public class Main {
public static void main(String[] args) {
// 各个活动的开始时间
int[] start={1,3,0,5,3,5,6,8,8,2,12};
// 各个活动的结束时间
int[] finish={4,5,6,7,8,9,10,11,12,13,14};
// 用来存储被安排的活动编号
ArrayList<Integer> list = maxActivityNum(start,finish);
System.out.println("有"+list.size()+"个活动可以被安排");
for(Integer fac : list){
System.out.print(fac+"--");
}
}
static ArrayList<Integer> maxActivityNum(int [] start, int end[]){
ArrayList<Integer> list = new ArrayList<Integer>();
// 此endTime用来实时记录当下被安排的活动的结束时间
int endTime = end[0];
// 安排活动1,所以序号1加入list
list.add(1);
int index = 1;
for(int i = 1; i < end.length; i++){
if(start[i] >= endTime){
endTime = end[i];
// 活动i可以被安排,加入list中
list.add(i+1);
}
}
return list;
}
}
(4)输出:
(5)注意:如果结束时间没有升序排序,需要对结束时间升序排序,但是升序排序后,会破坏原来的活动顺序,此时可以定义一个类,包含num(活动编号),start(开始时间)和end(结束时间),然后通过Collections.sort方法,自定义排序规则,达到升序排序的目的同时又不破坏原先的活动编号。
比如:
11个活动(与上述的每个活动对应,只是此时的活动没有按照结束时间排序)
开始时间:0,1,6,3,5,3,5,12,8,8,2
结束时间:6,4,10,5,7,8,9,14,11,12,13
因为需要对活动按照结束时间升序排序,同时又不想破坏当前活动的编号以及开始时间和活动时间的对应关系,所以定义一个类Act,类中封装了活动编号num、开始时间start和结束时间end。然后对此类自定义排序规则。
import java.util.*;
public class Main {
public static void main(String[] args) {
// 各个活动的开始时间
int[] start={0,1,6,3,5,3,5,12,8,8,2};
// 各个活动的结束时间
int[] finish={6,4,10,5,7,8,9,14,11,12,13};
// 如下通过for循环来构造Act类型的list
List<Act> list = new ArrayList<>();
for(int i = 0; i < start.length; i++){
Act act = new Act(i+1,start[i],finish[i]);
list.add(act);
}
// 对list定义排序规则,升序排序
Collections.sort(list, new Comparator<Act>() {
@Override
public int compare(Act o1, Act o2) {
return o1.getEnd() - o2.getEnd();
}
});
// 存放被安排活动的原始(排序之前)序列号
ArrayList<Integer> arrayNum = new ArrayList<>();
arrayNum.add(list.get(0).getNum());
// 保存当前被安排的活动的结束时间
int currentEnd = list.get(0).getEnd();
for (int i = 1; i < list.size(); i++){
// 通过判断下个活动的开始时间与当前被安排的活动的结束时间的大小关系来决定是否安排此活动
if(list.get(i).getStart() >= currentEnd){
arrayNum.add(list.get(i).getNum());
currentEnd = list.get(i).getEnd();
}
}
System.out.println("活动数是"+arrayNum.size());
for(Integer fac : arrayNum){
System.out.print(fac+"--");
}
}
}
class Act{
int num;
int start;
int end;
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public int getEnd() {
return end;
}
public void setEnd(int end) {
this.end = end;
}
public Act(int num, int start, int end) {
this.num = num;
this.start = start;
this.end = end;
}
}
输出结果:活动编号从1开始
此时,编号2对应1-4,5对应5-7,9对应8-11,8对应12-14,与(2)中的活动安排是一致的。