场景:在大学的里,有不少社团组织会要组织中的成员值班,当然这个值班时间是学生无课的时间才会被安排值班。
假设现有如下需求:一天中有3个时间段要有人值班,每周周一到周五都要值班,就是共有15个值班段,每个时间段值班的人都不一样,现有40个学生,要求根据这些学生的无课表情况安排值班,要求每个值班段必须有两个人,每个人一周只值班一次(如果某一值班段只有一人无课,那该值班段就只能一人值班)。
小插一题,做一排列组合题:
有10个相同的糖果,颁给3个小人,要求每个人至少有2个糖果,问共有多少种分法?
(自行解答,2014年阿里实习生招有个这样的题:有10个相同的糖果,颁给3个小人,要求每个人至少有1个糖果,问共有多少种分法?)
排班算法设计的难点主要在于每个人的无课表都不一样,如果像上面的题那样的,不用根据每个人的无课表,每个人在任意一值班段都能值班,那就简单啦。
以下讲讲个人算法的设计:
1. 基于回溯法的遍历:(实现思路:当安排到第N个值班段时,发现没有满足条件的可以选,就回溯到第N-1个值班段重新安排,如果第N-1个值班段的人都试过但第N个值班段还是没有满足条件的可以选,就回溯到第N-2个值班段,以此类推不断回溯)
现将上面的问题数据量缩小下来分析下这个问题:
假设有三个值班段,三个人,根据这三个人的无课表情况给值班段安排人值班。
思路:
先在第一个值班段无课的人中选择一个人安排到该值班段值班,被安排过的人就被标志为被安排值班(flag为0表示未被安排,flag为1表示已被安排),就不能再被安排到其他值班段值班了,以此类推,安排完一个值班段就安排下一个值班段。
最好情况:
最好情况下就是安排到每个值班段都有人未被安排过值班的人,这样一次性所有就排完了,如下图:
(1、2、3表示值班段,每个值班段里的字母表示该值班段无课的人,如上面第1个值班段有A和B两个人无课,可以安排在该值班段值班)
A被安排到1值班段,
B被安排到2值班段,
C被安排到3值班段。
最坏情况:
如下图所示:
安排到第三个值班段时因为A已经被安排过值班,所以要回溯到第二个值班段重新安排,就给第二个值班段安排C值班,但此时第三个值班段还是没人可以值班,而第二个值班段的人也都遍历过了,所以此时就要回溯到第一个值班段重新安排,此时:
B被安排到1值班段,
C被安排到2值班段,
A被安排到3值班段。
上面的是基本思路,但如果对15个值班段,40个人来排班,考虑下最坏情况,如果刚好到第十五个值班段时没人可以值班,而最后回溯到第一个值班段重新遍历,那查找比较次数都会大大增多,所以最后还是没有采取这种方法。不过个人也没能递归实现这个算法,有空再研究研究。
2. 基于优先级的选择:(实现思路:安排值班时不是从第一个值班段顺序地安排到第十五个值班段,因为在15个值班段中,有些值班段无课的人比较多,此时的选择就比较多,而有此值班段无课的人很少,那么这个值班就要优先安排人值班,比如有个值班段只有两人无课,那就要先安排这两个人在该值班段值班,这是第一个优先级;每个人一周内无课的次数都不一样,有些人一周只有一两次无课的时间,而有些人一周有五六次无课的时间,在一个值班段选择谁在该值班段值班时优先选择无课次数少的,这是第二个优先级)
<span style="font-family:SimSun;font-size:14px;">// 值班段类 public class DutyTime { private String time; // 值班时间 private List<User> freeUsers = new ArrayList<User>(); // 该时间段无课的人 private List<User> dutyUsers = new ArrayList<User>(); // 该时间段值班的人员 public DutyTime(){} public DutyTime(String time) { super(); this.time = time; } // 省略get set } </span>
<span style="font-family:SimSun;font-size:14px;">// 人员 public class User { private String name; private int num; // 该人一周无课时间段数 public User(){} public User(String name) { super(); this.name = name; } <span style="white-space:pre"> </span>// 省略get set } </span>
<span style="font-family:SimSun;font-size:14px;">/** * 值班安排: * 先为每个值班段安排一名人员值班(第一轮),安排一轮后再进行第二轮、第三轮 * 按优先级安排值班: * 1.各个值班段可值班人员数少的优先安排值班 * 2.在一个值班段选择人员时,优先选择总无课次数少的人 * 注:人员在被安排后就会从该值班段中的无课人员集合中删除,这样之后就不用再遍历到;在一轮安排后开始下一轮时,每个值班段的排人顺序会重新调整(因为去掉了已被安排的人) * */ public class Schedule3 { public static void main(String[] args) { User A = new User("A", 4); // A一周有4个时间段无课 ... DutyTime time1 = new DutyTime("1"); // 第一个值班段 ... time1.getFreeUsers().add(A); ... List<DutyTime> dutyTimeList = new ArrayList<>(); dutyTimeList.add(time1); ... for (int w = 0; w < 3; w++) { // 排序,可用于值班的人数少的值班段排在前面,下面安排值班是可用于值班的人数少的值班段优先安排 dutyTimeList = dutyTimeSort(dutyTimeList); // 遍历已排好序的值班段,为每个值班段安排值班人员 for (int i = 0; i < dutyTimeList.size(); i++) { // 如果该值班段有无课的人才安排值班 if (dutyTimeList.get(i).getFreeUsers().size() > 0) { // 得到的都是未被安排过值班的人,并排好序,总无课次数少的人排前面 List<User> freeUserList = freeUserSort(dutyTimeList.get(i).getFreeUsers()); // 将可在该值班段值班的人安排到该值班段值班 dutyTimeList.get(i).getDutyUsers().add(freeUserList.get(0)); System.out.println(freeUserList.get(0).getName() + " 被安排到" + dutyTimeList.get(i).getTime() + "值班;该值班段现有"+dutyTimeList.get(i).getDutyUsers().size()+"人值班,"+freeUserList.get(0).getName()+"一周总无课次数为:"+freeUserList.get(0).getNum()); // 将被安排的人从各个值班段(有该人的值班段)中删除,下次不用再遍历 User delUser = freeUserList.get(0); for (int j = 0; j < dutyTimeList.size(); j++) { if (dutyTimeList.get(j).getFreeUsers().contains(delUser)) { dutyTimeList.get(j).getFreeUsers().remove(delUser); System.out.println("---"+delUser.getName()+"已从"+dutyTimeList.get(j).getTime()+"值班段删除,目前该值班段剩余未被安排人数有:"+dutyTimeList.get(j).getFreeUsers().size()); } } }else { System.out.println(dutyTimeList.get(i).getTime()+"该值班段已无人可安排值班"); } } System.out.println("---------------------安排了一轮--------------------"); } } // 对某一值班段中无课的人进行排序(一周总无课次数少的人排在前面) public static List<User> freeUserSort(List<User> freeUserList); // 对值班段排序(可用于值班的人数少的值班段排在前面) public static List<DutyTime> dutyTimeSort(List<DutyTime> dutyTimeList); }</span>
使用这种方法实现排班只能生成一张值班表,但现实中根据总的无课表是可以排出不同方案的值班表的,以上的算法小编只是在满足个人项目需求的情况下设计的,并不是通用的,小编查过许多高校的教授发表的关于排课算法实现的论文,那些情况就更加复杂了,各种回溯各种优先级。
排课问题在70年代就证明是一个NP完全问题,即算法的计算时间是呈指数增长的,这一论断确立了排课问题的理论深度。对于NP问题完全问题目前在数学上是没有一个通用的算法能够很好地解决。
Author:顾故
Sign:别输给曾经的自己
原文:http://blog.csdn.net/lzgs_4/article/details/44756857