我一直在想是否有已知的算法来创建学校课程表。基本上,它是关于优化给定班级-科目-教师关联下的“时间分散”(教师和班级均适用)。我们可以假设在输入时,我们有班级、课程主题和与之相关联的教师集合,并且课程表应该在早上8点到下午4点之间。
我猜可能没有精确的算法,但也许有人知道一个良好的近似值或开发它的提示。
我一直在想是否有已知的算法来创建学校课程表。基本上,它是关于优化给定班级-科目-教师关联下的“时间分散”(教师和班级均适用)。我们可以假设在输入时,我们有班级、课程主题和与之相关联的教师集合,并且课程表应该在早上8点到下午4点之间。
我猜可能没有精确的算法,但也许有人知道一个良好的近似值或开发它的提示。
在校对本答案时,我意识到它没有提供一个明确的回答,但它仍然充满了实用的建议。希望这能帮助解决这个“难问题”。
这是一团糟,一团混乱。除了其他答案已经非常完整,我想指出我的家庭经验。我的母亲是一名教师,曾参与过排课。
结果发现让计算机完成这项任务不仅难以编码本身,而且因为有些条件很难针对预先制作的计算机程序进行规定。例如:
正如您所看到的,这个问题并不是NP完全问题,而是NP疯狂问题。
所以他们做的是,用小塑料嵌块制成一张大表,并将这些嵌块移动到满意的位置上。他们从不从头开始制定计划:通常从去年的时间表开始,进行调整。
国际排课竞赛2007设有课程调度和考试安排两个项目。许多研究者参与了该竞赛。尝试了许多启发式算法和元启发式算法,但最终局部搜索元启发式算法(如禁忌搜索和模拟退火)明显击败其他算法(如遗传算法)。
看一下部分决赛选手使用的两个开源框架:
我的其中一个学期作业是基于遗传算法的班级课表生成。
整个课表就像一个“有机体”。对通用遗传算法方法进行了一些改变和限制:
建立了“非法课表”规则:同一教室的两个班级,一个老师同时教授两个小组等情况。这些突变立即被视为致命并立即产生新的“有机体”代替“死亡”的有机体。初始课表是通过一系列随机尝试来生成合法(但毫无意义)的。致命突变不计入迭代中的变异数量。
“交换”变异比“修改”变异更常见。变化仅在有意义的基因部分之间发生 - 不会将教师替换为教室。
指定某些2小时的捆绑、为同一组连续分配相同的通用教室、保持教师工作时间和班级负载连续性分配了一些小奖励。为给定科目分配正确的教室,保持课程时间在范围内(上午或下午)等分配适度奖励。为指定科目的正确数量、为教师分配适当的工作量等指定大奖励。
教师可以创建他们的工作量时间表:“想要在这个时间工作”、“可以在这个时间工作”、“不喜欢在这个时间工作”、“不能在这个时间工作”,并分配适当的权重。全天24小时都是合法工作时间,除了晚上非常不受欢迎。
权重函数...哦,对了。权重函数是选定的特征和属性赋值的巨大、庞大的乘积。它非常陡峭,一个属性就能轻松地使它增加或减少一个数量级——而在一个生物体内有数百或数千个属性。这导致权重的数字非常大,因此需要使用大数库(gmp)进行计算。对于一个小规模测试用例,包括10组、10名教师和10间教室,初始设置以10^-200something为起点,最终以10^+300something为终点。当函数更为平缓时效率完全低下。同时,当学校规模变大时,数值的增长距离也会变得更大。
从计算时间上看,较小的人口(100人)在较长时间内与较大的人口(10k+)在较少的世代内所产生的结果质量基本相同。
在某个1GHz CPU上,计算稳定到10^+300大约需要1小时,生成出来的课程表看起来相当不错,适用于以上述10x10x10的测试用例。
这个问题可以通过提供网络设施来并行化处理,以便在正在运行计算的多台电脑之间交换最佳标本。
最终的程序从未见过日光,只为我赢得了一个良好的学期成绩。它展示了一些潜力,但我从未有足够的动力添加任何GUI,使其对普通公众可用。
我的排课算法使用FET (Free Timetabling Software, http://lalescu.ro/liviu/fet/) 实现,是一个成功的应用程序。
该算法是启发式的。我称其为“递归交换”。
输入:一组活动 A_1...A_n 和相关约束条件。
输出:一组时间 TA_1...TA_n(每个活动的时间段。这里为了简单起见不考虑教室)。算法必须将每个活动放在一个时间段内,并遵守约束条件。每个 TA_i 的取值范围为 0 (T_1) 到 max_time_slots-1 (T_m)。
约束条件:
C1)基本约束条件:不能同时进行的一组活动的列表(例如,A_1 和 A_2,因为它们有相同的老师或学生);
C2)许多其他约束条件(为了简单起见,此处不予考虑)。
排课算法(我称之为“递归交换”):
按照上述顺序,逐个尝试将每个活动(A_i)放入允许的时间段中。搜索可用于A_i的时间段(T_j),并在遵守约束条件的情况下将该活动放置在其中。如果有多个可用时间段,请选择一个随机的时间段。如果没有可用时间段,请进行递归交换:
a. 对于每个时间段T_j,请考虑将A_i放入T_j会发生什么。将会有一系列其他活动与此移动不同意(例如,活动A_k在同一时间段T_j中,并且具有与A_i相同的教师或学生)。为每个时间段T_j保留冲突活动列表。
b. 选择具有最少冲突活动数量的时间段(T_j)。假设此时间段中的活动列表包含3个活动:A_p,A_q,A_r。
c. 将A_i放置在T_j,并使A_p,A_q,A_r未分配。
d. 递归地尝试将A_p,A_q,A_r(如果递归级别不太大,例如14,并且自步骤2开始计算的递归调用总数不太大,例如2*n)放置在A_i上(如步骤2所述)。
e. 如果成功放置了A_p,A_q,A_r,则返回成功,否则尝试其他时间段(转到步骤2b),选择下一个最佳时间段。
f. 如果所有(或合理数量的)时间段都没有成功尝试,请返回失败。
g. 如果我们处于级别0,并且没有成功放置A_i,请像步骤2b和2c中那样放置它,但不进行递归。现在我们还有3-1 = 2个活动要放置。 转到步骤2)(这里使用了一些避免循环的方法)。
这个问题比看上去的要难。
正如其他人所暗示的,这是一个NP完全问题,但让我们分析一下这意味着什么。
基本上,这意味着你必须查看所有可能的组合。
但“查看”并没有告诉你需要做什么。
生成所有可能的组合很容易。它可能会产生大量数据,但你不应该在理解问题的这部分概念方面遇到太多问题。
第二个问题是判断某个给定的可能组合是好还是坏,或者比以前的“好”解决方案更好。
为此,你需要的不仅仅是“它是否是可能的解决方案”。
例如,同一位教师是否在连续X周内每周工作5天?即使这是一个可行的解决方案,它可能不是一个比交替两个人更好的解决方案,使每位教师每周各做一周。哦,你没有考虑过这个吗?请记住,你正在处理的是人,而不仅仅是资源分配问题。
即使一名教师可以连续全职工作16周,与尝试交替使用教师的解决方案相比,那也可能是一个次优解决方案,而这种平衡非常难以构建到软件中。
总之,为这个问题提供一个好的解决方案将对许多人具有重要意义。因此,将其分解并解决不是一件容易的事情。要准备好立下一些不是100%的目标,并称它们为“足够好”。
更新:根据评论...应该也要有启发式算法!
我会选择Prolog ... 然后使用Ruby或Perl等语言将您的解决方案清理成更漂亮的形式。
teaches(Jill,math).
teaches(Joe,history).
involves(MA101,math).
involves(SS104,history).
myHeuristic(D,A,B) :- [test_case]->D='<';D='>'.
createSchedule :- findall(Class,involves(Class,Subject),Classes),
predsort(myHeuristic,Classes,ClassesNew),
createSchedule(ClassesNew,[]).
createSchedule(Classes,Scheduled) :- [the actual recursive algorithm].
我正在进行类似于这个问题的工作,但是使用刚才提到的相同路径。Prolog(作为一种函数式语言)确实使解决NP难问题更容易。