概述 设有n个活动的集合E={1,2,…,n}
,其中每个活动都要求使用同一资源,如演讲会场等,而在同一时间内只有一个活动能使用这一资源。每个活动i都有一个要求使用该资源的起始时间$s_i$和一个结束时间$f_i$,且$s_i$<$f_i$。如果选择了活动i,则它在半开时间区间[$s_i$, $f_i$)内占用资源。若区间[$s_i$, $f_i$)与区间[$s_j$, $f_j$)不相交,则称活动i与活动j是相容的。也就是说,当$s_i$≥$f_j$或$s_j$≥$f_i$时,活动i与活动j相容。活动安排问题就是要在所给的活动集合中选出最大的相容活动子集合。
解题思路 假定活动已按结束时间的单调递增顺序排序(小到大排序):f1 ≤ f2 ≤ f3 ≤....≤ fn-1 ≤ fn
用i代表第i个活动,s[i]
代表第i个活动开始时间,f[i]
代表第i个活动的结束时间。按照从小到大排序,挑选出结束时间尽量早的活动,并且满足后一个活动的起始时间晚于前一个活动的结束时间,全部找出这些活动就是最大的相容活动子集合。 事实上系统一次检查活动i是否与当前已选择的所有活动相容。若相容活动i加入已选择活动的集合中,否则,不选择活动i,而继续i位置的下一个活动与集合A中活动比较相容性(集合A中的活动也就是上次比较的活动)。若活动i与之相容,则i成为最近加入集合A的活动,并取代活动j的位置(迭代方法)。
有2种解题方法 迭代方法 每次总是选择具有最早完成时间的相容活动加入集合A中(默认第1个加入集合A中)。直观上,按这种方法选择相容活动为未安排活动留下尽可能多的时间(因为添加到集合A中的时间最短,剩余的时间就长)。也就是说,该算法的贪心选择的意义是使剩余的可安排时间段极大化,以便安排尽可能多的相容活动。 迭代方法的计算过程如下图所示。图中每行相应于算法的一次迭代。阴影长条表示的活动是已选入集合A的活动,而空白长条表示的活动是当前正在检查相容性的活动。 若被检查的活动i的开始时间$s_i$小于最近选择的活动j的结束时间$f_i$,则不选择活动i,否则选择活动i加入集合A中。 迭代方法的效率极高。当输入的活动已按结束时间的非减序排列,算法只需O(n)
的时间安排n个活动,使最多的活动能相容地使用公共资源。如果所给出的活动未按非减序排列,可以用O(nlogn)
的时间重排。
递归方法 每次都是从相同位置开始,S
和F
开始比较是否兼容,如果F(i)<S(M)
,标记位M放入集合A中,下一次比较从M+1
标记位开始。
程序代码 迭代方法 伪代码 1 2 3 4 5 6 7 8 9 GreedyActivitySelector(s,f) n <- length(s) A <- {a1} i <- 1 for m <- 2 to n do if Sm ≤ Fi then A <- A U {am} i <- m return A
C++版本 定义:s[]
代表开始时间,f[]
代表结束时间,n表示活动个数。 准备工作:先对f[]
进行排序,而且保持s[]
与f[]
一致。1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static int [] GreedyAc(int s[], int f[], int n){ int x[] = new int [n]; x[0 ] = 1 ; int j = 0 ; for (int i = 1 ; i < n; i++){ if (s[i] >= f[j]){ x[i] = 1 ; j = i; } } return x; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include "stdafx.h" #include <iostream> using namespace std ; template <class Type > void GreedySelector (int n , Type s [], Type f [], bool A []); const int N = 11 ; int main () { int s[] = {0 ,1 ,3 ,0 ,5 ,3 ,5 ,6 ,8 ,8 ,2 ,12 }; int f[] = {0 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ,11 ,12 ,13 ,14 }; bool A[N+1 ]; cout <<"各活动的开始时间,结束时间分别为:" <<endl ; for (int i=1 ;i<=N;i++) { cout <<"[" <<i<<"]:" <<"(" <<s[i]<<"," <<f[i]<<")" <<endl ; } GreedySelector(N,s,f,A); cout <<"最大相容活动子集为:" <<endl ; for (int i=1 ;i<=N;i++) { if (A[i]){ cout <<"[" <<i<<"]:" <<"(" <<s[i]<<"," <<f[i]<<")" <<endl ; } } return 0 ; } template <class Type > void GreedySelector (int n , Type s [], Type f [], bool A []) { A[1 ]=true ; int j=1 ; for (int i=2 ;i<=n;i++) { if (s[i]>=f[j]) { A[i]=true ; j=i; } else { A[i]=false ; } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <stdio.h> # define N 11 void GreadyActivitySelector (int *s,int *f,int *A,int n) ;void RecursiveActivitySelector (int *s,int *f,int *A,int i,int n,int k) ;void main () { int s[N]={1 ,3 ,0 ,5 ,3 ,5 ,6 ,8 ,8 ,2 ,12 }; int f[N]={4 ,5 ,6 ,7 ,8 ,9 ,10 ,11 ,12 ,13 ,14 }; int A[N]={0 }; int n=N; GreadyActivitySelector(s,f,A,n); for (int i=0 ;i<n;i++) printf ("%d " ,A[i]); } void GreadyActivitySelector (int *s,int *f,int *A,int n) { A[0 ]=1 ; int i=0 ; int j=1 ; for (int m=1 ;m<n;m++) { if (s[m]>=f[i]) { i=m; A[j]=m+1 ; j++; } } }
Java版本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package demo2;public class ActionOrder { public int greedySelector (int [] s, int [] f, boolean [] a) { int n = s.length - 1 ; a[1 ] = true ; int j = 1 ; int count = 1 ; for (int i = 2 ; i <= n; i++) { if (s[i] >= f[j]) { a[i] = true ; j = i; count++; } else { a[i] = false ; } } return count; } public static void main (String[] args) { int s[] = { -1 , 1 , 3 , 0 , 5 , 3 , 5 , 6 , 8 , 8 , 2 , 12 }; int f[] = { -1 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 }; boolean [] a = new boolean [s.length]; ActionOrder ac = new ActionOrder(); int counts = ac.greedySelector(s, f, a); System.out.println("活动集合中最大相容活动数量为:" + counts); for (int i = 1 ; i <= s.length - 1 ; i++) { if (a[i]) { System.out.println("第" + i + "活动被选中,其开始时间为:" + s[i] + ",结束时间为:" + f[i]); } } } }
结果:1 2 3 4 5 活动集合中最大相容活动数量为:4 第1活动被选中,其开始时间为:1,结束时间为:4 第4活动被选中,其开始时间为:5,结束时间为:7 第8活动被选中,其开始时间为:8,结束时间为:11 第11活动被选中,其开始时间为:12,结束时间为:14
递归方法
//s 开始时间集合 //f 结束时间集合 //A 符合条件的集合 //i 结束集合的标记位 //n 总数目 //k A集合的标记位
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void RecursiveActivitySelector (int *s,int *f,int *A,int i,int n,int k) { int j=k; int m=i; while ((m<n)&&(s[m]<f[i])&&(m!=0 )){ m=m+1 ; } if (m<n) { A[j]=m+1 ; j++; RecursiveActivitySelector(s,f,A,m+1 ,n,j); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static void main (String[] args) { T *test=new T(); int s[N]={1 ,3 ,0 ,5 ,3 ,5 ,6 ,8 ,8 ,2 ,12 }; int f[N]={4 ,5 ,6 ,7 ,8 ,9 ,10 ,11 ,12 ,13 ,14 }; int A[N]={0 }; int n=N; test->RecursiveActivitySelector(s,f,A,0 ,n,0 ); for (int i=0 ;i<n;i++) { printf("%d " ,A[i]); } }
第1次执行时:m没有变化,因为m=0 第2次执行时:m=i=1
,进入循环体m+1
,i不变,m=2,i=1
,f[1]
和s[2]
比较,2位置开始的时间大于等于1位置的结束时间,否则m+1
,继续下一个位置和f[i]
比较,直到m=5
时,i=1
时,s[m]=f[i]
,开始的时间=结束的时间,m处于集合A中。 第3次执行时:m=i=5
,重复第2次执行的逻辑 递归每次调用时候,s
和f
都是从同一个位置开始执行比较是否兼容。