高效率地安排见面会
1. 问题描述:
在校园招聘的季节里,为了能让学生们更好地了解微软亚洲研究院各研究组的情况,HR部门计划为每一个研究组举办一次见面会,让各个研究组的员工能跟学生相互了解和交流。已知有n位学生,他们分别对m个研究组中的若干个感兴趣。为了满足所有学生的要求,HR希望每个学生都能参加自己感兴趣的所有见面会。如果每个见面会的时间为t,那么,如何安排才能够使得所有见面会的总时间最短?
最简单的办法,就是把m个研究组的见面会时间依次排开,那我们就要用m * t的总时间,我们有10多个研究小组,时间会拖得很长,能否进一步提高效率?
2. 分析与解法
【解法一】假设某一个同学同时对k个小组感兴趣, 那么这k个小组两两之间都要有一条边, 这样就转化成了最少着色问题. 图的最少着色问题至今没有有效解法, 可以使用dfs枚举, 时间复杂度很高。
【解法二】我们可以尝试对这个图进行K着色, 首先把K设置成1, 看看有没有适合的方案, 再逐渐把K提高. 当假设待求的图最少着色数远小于图的定点数时, 这个算法的复杂度远低于【解法一】。
扩展问题一
假设有 N 个面试要同时进行, 他们的面试时间分别是 B[i], E[i]. 我们希望将这N个面试安排在若干地点, 不同的面试在同一时间不能在相同的面试点。如果你是微软亚洲研究院的HR,现在给定这N个面试的时间之后,你能计算出至少需要多少个面试点吗?比如下图所示的4个面试。
【解法一】不过这个问题和原始问题有些不同,因为每个面试者对应于一个时间区间。由这些区间之间的约束关系转化得到的图,属于区间图。 我们可以通过贪心算法来解决。算法就是对所有的面试,按照 B[i] 进行排序,对当前区间 i 着色时,必须保证所着的颜色没有出现在这个区间之前且时间段与当前区间有重叠的区间用到。假设面试的总数为N,那么相应的代码如下:
1 package chapter1youxizhileMeeting; 2 /** 3 * 面试时间段类 4 * @author DELL 5 * 6 */ 7 class Timeperiod{ 8 public int b; //开始时间 9 public int e; //结束时间 10 public Timeperiod(int b, int e){ 11 this.b = b; 12 this.e = e; 13 } 14 }
1 package chapter1youxizhileMeeting; 2 /** 3 * 高效率的安排见面会 4 * 扩展问题一【解法一】贪心算法 5 * @author DELL 6 * 7 */ 8 public class Meeting1 { 9 private int n; //面试总数 10 private Timeperiod[] tp; //用于存放相应面试时间段的数组 11 //构造函数 12 public Meeting1(int n, Timeperiod[] tp){ 13 this.n = n; 14 this.tp = tp; 15 } 16 17 /** 18 * 判断两个时间段是否重叠 19 * @param tp1 时间段1 20 * @param tp2 时间段2 21 * @return 判断结果 22 */ 23 public boolean isOverlap(Timeperiod tp1, Timeperiod tp2){ 24 if((tp1.e<=tp2.b)||(tp1.b>=tp2.e)){ 25 return false; 26 }else 27 return true; 28 } 29 30 /** 31 * 计算所需的最少颜色 32 * @return 最少颜色 33 */ 34 public int getminColors(){ 35 int minColors = 0; //所需最少颜色数 36 boolean isForbidden[]; //判断颜色是否被禁止 37 int color[]; //存放颜色编号 38 color = new int[n]; 39 isForbidden = new boolean[n]; 40 int i,j,k; 41 for(i=0;i<n;i++){ 42 for(j=0;j<minColors;j++){ //初始赋值都不禁止 43 isForbidden[j] = false; 44 } 45 for(k=0;k<i;k++){ 46 if(isOverlap(tp[k],tp[i])) 47 isForbidden[color[k]]=true; //如果有重叠就禁止 48 } 49 for(j=0;j<minColors;j++){ 50 if(!isForbidden[j]) //遍历已用的颜色,看是否都冲突 51 break; 52 } 53 if(j<minColors) //如果有不冲突的 54 color[i]=j; 55 else 56 color[i] = minColors++; //否则新加一种颜色 57 } 58 return minColors; 59 } 60 public static void main(String[] args) { 61 Timeperiod tp1 = new Timeperiod(1,5); 62 Timeperiod tp2 = new Timeperiod(2,3); 63 Timeperiod tp3 = new Timeperiod(3,4); 64 Timeperiod tp4 = new Timeperiod(3,6); 65 Timeperiod tp[]={tp1,tp2,tp3,tp4}; 66 Meeting1 mt = new Meeting1(4,tp); 67 System.out.println("所需的最少颜色数为:"+mt.getminColors()); 68 } 69 70 }
程序运行结果如下:
所需的最少颜色数为:3
通过简单分析,我们可以知道这个算法的时间复杂度为O(N2)。
【解法二】有一个更简单的思路,先对面试数组排序,然后遍历面试数组,每见到一个 B(开始),color +1,并维护全局最大 color 数,每遇到一个对应的 E,color 数减一。返回全局最大 color 值。要注意的是:排序时要用到双关键字比较,当两个值相等时,属于时间段开始的一定要排在属于时间段结束的后面,只有这样才能保证结果的正确性。相应代码如下:
1 package chapter1youxizhileMeeting; 2 /** 3 * 高效率的安排见面会 4 * 扩展问题一【解法二】 5 * @author DELL 6 * 7 */ 8 public class Meeting2 { 9 private int n; //面试总数 10 private Timeperiod[] tp; //用于存放相应面试时间段的数组 11 private Tarr[] ta; //由所有时间段的时间组成的数组 12 //构造函数 13 public Meeting2(int n, Timeperiod[] tp){ 14 this.n = n; 15 this.tp = tp; 16 ta = new Tarr[2*n]; 17 } 18 19 //时间数据结构 20 class Tarr { 21 public int time; //时间 22 public char type; //类型,‘B'表示开始,'E'表示结束 23 public int tpi; //改数据在tp数组中的位置 24 //构造函数 25 public Tarr(int time, char type, int tpi){ 26 this.time = time; 27 this.type = type; 28 this.tpi = tpi; 29 } 30 } 31 32 /** 33 * 对时间数组进行排序 34 */ 35 private void insertSort(){ 36 int i,j; 37 Tarr temp; 38 for(i=1;i<ta.length;i++){ 39 temp = ta[i]; 40 for(j=i-1;j>=0&&ta[j].time>temp.time;j--){ 41 ta[j+1]=ta[j]; 42 } 43 //保证当两个值相等时,属于时间段开始的一定要排在属于时间段结束的后面 44 if(j>=0&&ta[j].time==temp.time){ 45 if(ta[j].type=='E'&&temp.type=='B'){ 46 ta[j+1]=ta[j]; 47 ta[j]=temp; 48 }else 49 ta[j+1]=temp; 50 }else 51 ta[j+1]=temp; 52 } 53 } 54 55 /** 56 * 计算所需的最少颜色 57 * @return 最少颜色 58 */ 59 public int getminColors(){ 60 int nColorUsing = 0; //当前使用的颜色数量 61 Tarr begin = null; //记录循环中当前最近的开始时间 62 int i; 63 //为ta赋值 64 for(i=0;i<tp.length;i++){ 65 ta[2*i] = new Tarr(tp[i].b,'B',i); 66 ta[2*i+1] = new Tarr(tp[i].e,'E',i); 67 } 68 insertSort(); 69 for(i=0;i<2*n;i++){ 70 if(ta[i].type=='B'){ 71 nColorUsing++; 72 begin = ta[i]; 73 }else if((ta[i].type=='E')&&(ta[i].tpi==begin.tpi)){ 74 nColorUsing--; 75 } 76 } 77 return nColorUsing; 78 } 79 public static void main(String[] args) { 80 Timeperiod tp1 = new Timeperiod(1,5); 81 Timeperiod tp2 = new Timeperiod(2,3); 82 Timeperiod tp3 = new Timeperiod(3,4); 83 Timeperiod tp4 = new Timeperiod(3,6); 84 Timeperiod tp[]={tp1,tp2,tp3,tp4}; 85 Meeting2 mt = new Meeting2(4,tp); 86 System.out.println("所需的最少颜色数为:"+mt.getminColors()); 87 } 88 89 }
程序运行结果如下:
所需的最少颜色数为:3
【解法三】计算最大重叠次数
问题的本质实际上就是求区间的最大重叠次数,代码如下:
1 package chapter1youxizhileMeeting; 2 /** 3 * 面试时间段类 4 * @author DELL 5 * 6 */ 7 class Timeperiod{ 8 public int b; //开始时间 9 public int e; //结束时间 10 public Timeperiod(int b, int e){ 11 this.b = b; 12 this.e = e; 13 } 14 }
1 package chapter1youxizhileMeeting; 2 /** 3 * 高效率的安排见面会 4 * 扩展问题一【解法三】计算最大重叠次数 5 * @author DELL 6 * 7 */ 8 public class Meeting3 { 9 private int n; //面试总数 10 private Timeperiod[] tp; //用于存放相应面试时间段的数组 11 //构造函数 12 public Meeting3(int n, Timeperiod[] tp){ 13 this.n = n; 14 this.tp = tp; 15 } 16 17 /** 18 * 判断两个时间段是否重叠 19 * @param tp1 时间段1 20 * @param tp2 时间段2 21 * @return 判断结果 22 */ 23 public boolean isOverlap(Timeperiod tp1, Timeperiod tp2){ 24 if((tp1.e<=tp2.b)||(tp1.b>=tp2.e)){ 25 return false; 26 }else 27 return true; 28 } 29 30 /** 31 * 计算所需的最少颜色 32 * @return 最少颜色 33 */ 34 public int getminColors(){ 35 int max = 0; //最大重叠次数 36 int count; //记录每个循环的重叠次数 37 for(int i=n-1;i>=0;i--){ 38 count = 1; //本身为1 39 for(int j=0;j<i;j++){ 40 if(isOverlap(tp[i],tp[j])) 41 count++; 42 } 43 if(count>max) 44 max = count; 45 } 46 return max; 47 } 48 public static void main(String[] args) { 49 Timeperiod tp1 = new Timeperiod(1,5); 50 Timeperiod tp2 = new Timeperiod(2,3); 51 Timeperiod tp3 = new Timeperiod(3,4); 52 Timeperiod tp4 = new Timeperiod(3,6); 53 Timeperiod tp[]={tp1,tp2,tp3,tp4}; 54 Meeting3 mt = new Meeting3(4,tp); 55 System.out.println("所需的最少颜色数为:"+mt.getminColors()); 56 } 57 58 }
程序运行结果如下:
所需的最少颜色数为:3
其它参考链接: