创建学校时间表的算法


95

我一直想知道是否存在用于创建学校时间表的算法的已知解决方案。基本上,它是针对给定的班级-主体-老师关联优化“小时分散”(在教师和班级情况下均如此)。我们可以假设我们在输入时有一组相互关联的班级,课程主题和教师,并且时间表应该在上午8点至下午4点之间。

我猜可能没有准确的算法,但是也许有人知道很好的近似或提示来开发它。


2
感谢所有的答案。看起来该算法需要更多研究。我认为这是硕士论文或小型商业应用的一个很好的主题。如果我写一个,我会在这里让你知道;)
cand can's

10
正如StackOverflow的Ian Ringrose提到的另一个问题一样,“在调度软件中仍然有许多PHD。”
Reed Debaets 2010年

Answers:


85

这个问题是NP完成的
简而言之,需要探索所有可能的组合以找到可接受的解决方案列表。由于问题在不同学校出现的情况各不相同(例如:教室是否受到限制?,某些时间是否将某些课程分为小组分组?,这是每周时间表吗?等),没有一个与所有计划问题相对应的众所周知的问题类别。也许背包问题与这些问题有很多相似之处。

确认这既是一个艰巨的问题,也是人们一直在寻求解决方案的一个确认,是检查(主要是商业的)软件调度工具的这一(较长)列表

由于涉及大量变量,通常最大的来源是教师的愿望;-)...,考虑枚举所有可能的组合通常是不切实际的。相反,我们需要选择一种访问问题/解决方案空间子集的方法。
- 在另一个答案中引用的遗传算法(或者,恕我直言,似乎)设备齐全,可以执行这种半向导式搜索(问题是为下一代保留的候选者找到良好的评估功能)
- 图重写方法也可用于此类组合优化问题。

除了要关注自动计划生成器程序的特定实现之外,我还想提出一些可以在问题的定义级别上应用的策略
一般的理由是,在大多数现实世界中的调度问题中,将需要一些折衷方案,而不是所有明示和暗示的约束条件:将完全满足。因此,我们通过以下方式帮助自己:

  • 定义和排列所有已知约束
  • 通过手动提供一组附加约束来减少问题空间。
    这似乎是违反直觉的,但是例如通过提供一个初始的,部分填充的时间表(例如大约30%的时隙),以完全满足所有约束的方式,并考虑到该部分时间表是不可变的,我们可以大大减少产生候选解所需的时间/空间。
    附加约束的另一种帮助方法是,例如“人为地”添加约束,以防止在一周的某些天教某些主题(如果这是每周计划...);这种类型的约束导致减少问题/解决方案的空间,通常不会排除大量的良好候选人。
  • 确保可以快速计算出问题的某些约束条件。这通常与用于代表问题的数据模型的选择有关;这个想法是为了能够快速选择(或删除)某些选项。
  • 重新定义问题并允许一些约束被破坏几次(通常朝向图的末端节点)。这里的想法是消除一些用于填写时间表中最后几个空位的约束,或者使自动时间表生成器程序避开完成整个时间表,而向我们提供一打大概的清单候选人。如所示,人类通常处于更好的位置来完成拼图,可能会使用通常与自动逻辑共享的信息(例如,“下午无数学”规则有时会被打破)打破一些约束。对于“高级数学和物理”课程;或“违反史密斯女士的一项要求比史密斯女士的一项要好... ;-))

在校对这个答案的过程中,我意识到提供一个明确的答案是很害羞的,但是它仍然充满了实用的建议。我希望这种帮助毕竟是一个“难题”。


1
准确,详尽,详尽的答案,感谢您对NP-Completeness的提示和提及(这也是我的猜测)。
cand

3
您是否有任何引文说明了此问题的NP完整性?

49

一团糟。皇家混乱。为了补充已经很完整的答案,我想指出我的家庭经历。我的母亲是一位老师,曾经参与这个过程。

事实证明,拥有一台计算机不仅本身很难编写代码,而且由于存在某些条件难以指定给预烘焙的计算机程序,因此这也很困难。例子:

  • 一位老师在您的学校和另一所学院任教。显然,如果他在10.30结束课程,则他不能在10.30开始在您的所在地开始学习,因为他需要一些时间在各个机构之间上下班。
  • 两位老师结婚了。通常,最好不要在同一班上安排两名已婚老师。因此,这两位老师必须参加两个不同的班级
  • 两个老师结婚了,他们的孩子上同一所学校。同样,您必须阻止两位老师在孩子所在的特定班级任教。
  • 学校有单独的设施,例如有一天在一个学院里上课,而另一天在另一个学院里上课。
  • 学校拥有共享的实验室,但是这些实验室仅在某些工作日可用(出于安全原因,例如,在需要更多人员的情况下)。
  • 有些老师对自由活动日有偏好:有些喜欢星期一,有些喜欢星期五,有些喜欢星期三。有些人喜欢一大早,有些人喜欢迟些。
  • 您不应遇到这样的情况:在第一个小时就讲历史,然后是三个小时的数学,然后再是一小时的历史。这对学生和老师都没有意义。
  • 您应该平均分配论点。只有一周的第一天只有数学,然后只有一周的其余时间只有文学是没有意义的。
  • 您应该连续两个小时给一些老师做评估测试。

如您所见,问题不是NP完全问题,而是NP疯狂。

因此,他们要做的是拥有一张大桌子,上面有一些小的塑料插图,然后他们将这些插图移动直到获得令人满意的结果。它们从不从零开始:它们通常从上一年的时间表开始进行调整。


12
“ NP-insane”-伟大的名字;)我同意这是一个非常复杂的问题,感谢您对“现实世界”对该问题的处理。我父亲和我的女友也是老师,我知道大多数学校的桌子上都装有塑料插板-这使我想到了解决此问题的可能算法-因为,如果一个人可以解决,也许可以写将其作为算法。
cand

10
您要编写的是一个专家系统:由许多启发式规则组成的系统。除了专家系统外,这是遗传算法在最佳领域之一。困难不仅在于解决问题。困难还在于陈述问题及其约束。
Stefano Borini

1
没错,这个问题需要提供一组复杂的条件和约束来满足,可能需要对“可接受”的解决方案进行评级。我同意遗传算法,它们应该很好地适合这个问题,而且我认为最好以简单的条件集开始开发,并在获得正确答案的情况下对其进行增强。
cand

1
您还可以将这些约束和目标直接转换为MAXSAT。一般而言,MAXSAT算法比遗传算法更可靠,但是由于诸如数学课之类的目标应在一周内分配,您可能会出现子句爆炸。
儒勒

26

国际竞争排课2007年有一个教训调度跟踪和考试安排的轨道。许多研究人员参加了比赛。尝试了许多启发式和元启发式方法,但最终,本地搜索元启发式方法(例如禁忌搜索和模拟退火)明显击败了其他算法(例如遗传算法)。

看一看一些决赛入围者使用的2个开源框架:


17

我的半年任务之一是遗传算法课桌一代。

整个餐桌是一种“有机体”。通用遗传算法方法有一些变化和警告:

  • 制定了“非法餐桌”的规则:同一教室中的两个班级,一个老师同时教两个小组,等等。这些突变立即被认为具有致命性,而新的“有机体”代替了“死者”。最初的尝试是通过一系列随机尝试生成的,以获得合法的(如果没有意义的话)。致命突变不计入迭代中的突变计数。

  • “交换”突变比“修改”突变更为普遍。变化只在有意义的基因部分之间发生,而没有用教室代替老师。

  • 为将某些2个小时捆绑在一起,为按顺序为同一小组分配相同的通用教室,以保持老师的工作时间和班级的连续性,分配了小额奖金。分配了适度的奖金,以便为给定的主题提供正确的教室,将上课时间保持在固定范围内(上午或下午),等等。最大的好处是分配正确数量的给定科目,给老师的工作量等。

  • 教师可以创建他们的工作量时间表,例如“想上班”,“可以上班”,“不喜欢上班”,“不能上班”,并分配适当的权重。整个24小时都是合法的工作时间,只是夜间非常不理想。

  • 重量功能...是的。权重函数是分配给选定特征和特性的权重的巨大,可怕的乘积(如乘积)。它非常陡峭,一种属性很容易将其改变一个或多个数量级-一个生物中有成百上千个属性。这样就产生了绝对数量巨大的权重,直接的结果是,需要使用bignum库(gmp)进行计算。对于一个由大约10个小组,10个老师和10个教室组成的小型测试用例,最初的设置以10 ^ -200左右开始,最后以10 ^ + 300左右结束。当它变得更平坦时,它完全没有效率。而且,随着更大的“学校”,价值观的距离越来越远。

  • 从计算时间的角度来看,长期以来的少量人口(100)与少代的较大人口(10k +)之间几乎没有差异。相同时间的计算产生的质量大致相同。

  • 对于所述10x10x10测试用例,计算(在1GHz的CPU上)需要大约1h才能稳定在10 ^ + 300附近,生成的时间表看起来非常不错。

  • 通过提供可以在运行计算的计算机之间交换最佳标本的联网工具,可以轻松地将问题并行化。

由此产生的程序从来没有见过外面的阳光,使我整个学期都取得了不错的成绩。它显示了一些希望,但我没有足够的动力来添加任何GUI并使其对公众可用。


5
因此,打开它并做广告并尝试吸引一些学者参加?再用于其他项目?从技术上讲,仅用于300名员工的程序对于学校制定最佳计划来说是值得的,他们不介意花几天时间来遗传计算最佳计划。考虑批处理。你好硬件和软件合同;)
jcolebrand'5

1
听起来不错!我想知道权重函数是否可以用0..1范围内的浮点数完成。
Craig McQueen

1
@克雷格:随着时间的推移,单位面积会产生质量停滞甚至退化的种群,因为随机突变带来的负面变化比繁殖/选择所能抵消的更多。只有极其陡峭的质量函数才能提供稳定的增长。当然,可以使功能正常化,但是“更好的缺口”基因必须有更高的存活机会。
SF。

9

这个问题比看起来要难。

正如其他人提到的那样,这是一个NP完全问题,但是让我们分析一下这是什么意思。

基本上,这意味着您必须查看所有可能的组合。

但是,“看看”并不能告诉您您需要做什么。

生成所有可能的组合很容易。它可能会产生大量数据,但是了解问题的这一部分的概念应该不会有太多问题。

第二个问题是判断给定可能的组合是好,坏还是比以前的“好”解决方案更好的问题。

为此,您不仅需要“这是否是可能的解决方案”。

例如,同一位老师每周工作5天,连续X周吗?即使这是一个可行的解决方案,也可能不是两个人轮流让每个老师每个星期做一个更好的解决方案。哦,你没想到吗?请记住,这是您正在处理的人员,而不仅仅是资源分配问题。

即使一个老师可以连续工作16个星期,但与您尝试在老师之间轮换的解决方案相比,这可能不是一个最佳的解决方案,而且这种平衡很难在软件中建立。

总而言之,对于许多人来说,为这个问题提供一个好的解决方案将是非常有价值的。因此,要分解并解决这个问题并不容易。准备好提出一些并非100%的目标并将其称为“足够好”。


1
好吧,我认为一开始很难知道所有限制因素,需要经验和对此事的调查。我宁愿将问题分为两个单独的部分并同时进行开发。首先是一般的算法结构-说明如何填充“下一个时间表生成”,而不是机制草稿,而后面又没有太多“主题逻辑”(可能是遗传算法)。第二个将是具有一组约束的规则提供程序,这些约束检查时间表的“正确性”-首先可以很简单,然后再增强。
Cand 2010年

8

我的计时算法在FET(免费计时软件,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)许多其他约束(为简单起见,此处不包括在内)。

时间表算法(我将其称为“递归交换”):

  1. 排序活动,最困难的首先。这不是关键步骤,但可以将算法加速10倍甚至更多倍。
  2. 尝试按照上述顺序,一次将每个活动(A_i)放在一个允许的时隙中。在A_i上搜索可用的插槽(T_j),可以根据约束在其中放置此活动。如果有更多插槽可用,请随机选择一个。如果没有可用的,请进行递归交换:

    一个。对于每个时隙T_j,请考虑将A_i放入T_j会发生什么情况。将会列出与该举动不同意的其他活动(例如,活动A_k与A_i在同一时间段T_j且具有相同的老师或学生)。保留每个时隙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,如步骤2)。

    e。如果成功放置了A_p,A_q,A_r,则返回成功,否则尝试其他时隙(转到步骤2b)并选择下一个最佳时隙。

    f。如果未成功尝试所有(或合理数量的)时隙,则返回失败。

    。如果我们处于0级,并且没有成功放置A_i,请像在步骤2b)和2c)中一样放置它,但是不能递归。现在,我们还有3-1 = 2个其他活动。转到步骤2)(此处使用了一些避免循环的方法)。


1
FET对我非常有用。感谢@Liviu Lalescu!
Noel Llevares 2014年

6

更新:从评论...也应该有启发式!

我会选择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-Hard问题变得更加容易。


1
Prolog当然是表达所需问题的一种很好的语言,但是正如您所指出的那样:问题是NP完整的,即使不是NP-Hard。这意味着Prolog对于实际的实现可能不够快。
Poindexter

3
如果它与NP有关系,而我们不能满足于近似,那么任何确定性算法都将成倍增加:)
GabrielŠčerbák2010年

1
然后,目标是实施有效的启发式方法...例如,一个简单的9任务调度算法需要3.078s才能完成,但是使用最小的WindowFirst启发式方法实现时,相同的问题仅需:0.123s
Reed Debaets 2010年

2
哈哈,序言(单独)永远不会解决这个问题。很抱歉使用大写字母,但是10或15年前我有相同的想法,但完全失败了。并不是说它很慢,不。简单地解决不了任何现实情况;)!
卡塞尔2012年




3

我正在使用一个广泛使用的调度引擎来完成此任务。是的,它是NP-Complete;最好的方法是寻求近似最佳解决方案。而且,当然,有很多不同的方式来说明哪种是“最佳”解决方案-例如,让您的老师对自己的日程安排感到满意还是让学生参加所有课程是否更为重要?

您需要尽早解决的绝对最重要的问题是,哪种方法比另一种更好地调度此系统?就是说,如果我有一个时间表,琼斯太太在8岁时教数学,史密斯先生在9岁时教数学,那是比一个都在10岁时教数学的人更好还是更坏?它比琼斯夫人在8岁时教书而琼斯先生在2岁时教书好还是坏?为什么?

我在这里给出的主要建议是尽可能地将问题分解-也许逐课程,也许逐老师,也许逐房间-并首先解决子问题。在那里,您最终应该有多种解决方案可供选择,并且需要选择一种最可能的最佳方案。然后,进行“较早”子问题的工作时,要在计分潜在问题时考虑到较后子问题的需求。然后,当进入“无有效解决方案”状态时,也许研究如何使自己摆脱困境的情况(假设您无法在较早的子问题中预见到这些情况)。

本地搜索优化过程通常用于“抛光”最终答案以获得更好的结果。

请注意,通常我们在学校日程安排中会处理资源紧张的系统。一年当中,学校75%的一天都不会有很多空房间或老师坐在休息室里。在解决方案丰富的环境中最有效的方法不一定适用于学校调度。


2

我为班级时间表和考试时间表设计了商业算法。首先,我使用整数编程。第二种启发式方法是基于通过选择槽位交换来最大化目标函数的方法,这与已开发的原始手动过程非常相似。使这些解决方案被接受的主要目的是能够代表所有现实世界的约束。对于人类时间表来说,他们将无法找到改进解决方案的方法。最后,与数据库的准备,用户界面,报告房间使用率,用户教育等统计数据的能力相比,算法部分相当简单易行。


1

通常,约束编程是解决此类调度问题的一种好方法。在堆栈溢出和Google上搜索“约束编程”和调度或“基于约束的调度”将产生一些很好的参考。这并非不可能-使用线性或整数优化之类的传统优化方法时,要考虑的有点困难。一个输出将是-是否存在一个满足所有要求的时间表?这本身显然是有帮助的。

祝好运 !


1

您可以使用遗传算法处理它,是的。但是你不应该:)。它可能太慢,参数调整可能太费时等。

还有其他成功的方法。全部在开源项目中实现:

  • 基于约束的方法
    • UniTime中实施(并非真正适用于学校)
    • 您还可以进一步使用Integer编程。使用商业软件(ILOG CPLEX)在乌迪内大学和拜罗伊特大学(我曾在那参与)成功完成
    • 基于Heuristisc的基于规则的方法-请参阅Drools规划师
  • 不同的试探法-FET我自己的

请参阅此处以获得时间表软件列表


0

我认为您应该使用遗传算法,因为:

  • 最适合大型问题实例。
  • 它以不正确的答案为代价降低了时间复杂度(不是最终的最佳选择)
  • 您可以通过调整未满足要求的适应度惩罚来轻松指定约束和偏好。
  • 您可以指定程序执行的时间限制。
  • 解决方案的质量取决于您打算花费多少时间来解决该程序。

    遗传算法定义

    遗传算法教程

    带有GA的课程安排项目

还看看:一个类似的问题另一个


0

我不知道有人会同意此代码,但是我在自己的算法的帮助下开发了此代码,并在ruby中为我工作。希望它将帮助在下面的代码中搜索它们的人periodflag,dayflag subjectflag和Teacherflag是具有相应ID和Boolean值的哈希值。有任何问题请与我联系.......(-_-)

periodflag.each做| k2,v2 |

            if(TimetableDefinition.find(k2).period.to_i != 0)
                subjectflag.each do |k3,v3|
                    if (v3 == 0)
                        if(getflag_period(periodflag,k2))
                            @teachers=EmployeesSubject.where(subject_name: @subjects.find(k3).name, division_id: division.id).pluck(:employee_id)
                            @teacherlists=Employee.find(@teachers)
                            teacherflag=Hash[teacher_flag(@teacherlists,teacherflag,flag).to_a.shuffle] 
                            teacherflag.each do |k4,v4|
                                if(v4 == 0)
                                    if(getflag_subject(subjectflag,k3))
                                        subjectperiod=TimetableAssign.where("timetable_definition_id = ? AND subject_id = ?",k2,k3)
                                        if subjectperiod.blank?
                                            issubjectpresent=TimetableAssign.where("section_id = ? AND subject_id = ?",section.id,k3)
                                            if issubjectpresent.blank?
                                                isteacherpresent=TimetableAssign.where("section_id = ? AND employee_id = ?",section.id,k4)
                                                if isteacherpresent.blank?
                                                    @finaltt=TimetableAssign.new
                                                    @finaltt.timetable_struct_id=@timetable_struct.id
                                                    @finaltt.employee_id=k4
                                                    @finaltt.section_id=section.id
                                                    @finaltt.standard_id=standard.id
                                                    @finaltt.division_id=division.id
                                                    @finaltt.subject_id=k3
                                                    @finaltt.timetable_definition_id=k2
                                                    @finaltt.timetable_day_id=k1
                                                    set_school_id(@finaltt,current_user)
                                                    if(@finaltt.save)

                                                        setflag_sub(subjectflag,k3,1)
                                                        setflag_period(periodflag,k2,1)
                                                        setflag_teacher(teacherflag,k4,1)
                                                    end
                                                end
                                            else
                                                @subjectdetail=TimetableAssign.find_by_section_id_and_subject_id(@section.id,k3)
                                                @finaltt=TimetableAssign.new
                                                @finaltt.timetable_struct_id=@subjectdetail.timetable_struct_id
                                                @finaltt.employee_id=@subjectdetail.employee_id
                                                @finaltt.section_id=section.id
                                                @finaltt.standard_id=standard.id
                                                @finaltt.division_id=division.id
                                                @finaltt.subject_id=@subjectdetail.subject_id
                                                @finaltt.timetable_definition_id=k2
                                                @finaltt.timetable_day_id=k1
                                                set_school_id(@finaltt,current_user)
                                                if(@finaltt.save)

                                                    setflag_sub(subjectflag,k3,1)
                                                    setflag_period(periodflag,k2,1)
                                                    setflag_teacher(teacherflag,k4,1)
                                                end
                                            end
                                        end
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.