用于创建学校时间表的算法

时间:2010-02-01 15:39:52

标签: algorithm language-agnostic np

我一直想知道是否有创建学校时间表算法的已知解决方案。基本上,它是关于优化给定班级 - 学科 - 教师协会的“小时分散”(在教师和班级案例中)。我们可以假设我们在输入中有相互关联的课程,课程科目和教师,并且时间表应该适合在上午8点到下午4点之间。

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

17 个答案:

答案 0 :(得分:76)

此问题 NP-Complete
简而言之,需要探索所有可能的组合以找到可接受的解决方案列表。由于问题出现在各个学校的情况有所不同(例如:教室是否存在限制?有些课程的某些课程在某些时间是分开的吗?这是一个每周安排吗?不存在对应于所有调度问题的众所周知的问题类。也许,Knapsack problem与这些问题有很多相似之处。

确认这既是一个难题,也是人们常年寻求解决方案的问题,就是检查这个(长)list of (mostly commercial) software scheduling tools

由于涉及大量变量,其中最大的变量通常是教师的愿望; - )......,考虑列举所有可能的组合通常不切实际。相反,我们需要选择一种访问问题/解决方案空间子集的方法 - 遗传算法,在另一个答案中引用(或者,恕我直言,似乎),能够很好地执行这种半导向搜索(问题是找到一个好的评价为下一代保留候选人的职能)
- Graph Rewriting方法也适用于此类组合优化问题。

我不想专注于自动计划生成器程序的特定实现,而是建议一些可以应用的策略,在问题定义的层面 < / strong>即可。
一般的理由是,在大多数现实世界的调度问题中,需要一些妥协,而不是所有的约束,表达和暗示:将完全满足。因此,我们通过以下方式帮助自己:

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

在校对这个答案时,我意识到提供一个明确的答案是非常害羞的,但它却充满了实用的建议。我希望这有帮助,毕竟这是一个“难题”。

答案 1 :(得分:44)

这是一团糟。皇家一团糟。为了增加答案,已经非常完整,我想指出我的家庭经历。我母亲是一名教师,曾经参与过这个过程。

事实证明,拥有一台计算机不仅难以编码本身,而且也很困难,因为有些条件很难指定给预先计算机的程序。例子:

  • 老师在你的学校和另一所学院教授。显然,如果他在10点30分结束那里的课程,他不能在10点30分开始你的工作,因为他需要一些时间在学院之间通勤。
  • 两位老师结婚了。一般来说,不要让两个已婚教师在同一个班级,这被认为是一种好习惯。因此,这两位教师必须有两个不同的班级
  • 两位老师结婚,他们的孩子就读同一所学校。同样,你必须阻止两位老师在他们孩子所在的特定班级教学。
  • 学校有独立的设施,比如有一天班级在一个学院,另一天班级在另一个学院。
  • 学校有共享实验室,但这些实验室仅在某些工作日可用(出于安全考虑,例如,需要额外人员)。
  • 一些老师喜欢免费的一天:有些人喜欢星期一,有些喜欢星期五,有些喜欢星期三。有些人喜欢早上来,有些人宁愿晚点来。
  • 你不应该有这样的情况:第一个小时的历史,然后是三个小时的数学,然后是另一个小时的历史。这对学生和老师都没有意义。
  • 你应该均匀地传播参数。让一周中的第一天只有数学,然后是本周剩下的时间只有文献是没有意义的。
  • 你应该连续两个小时给一些老师做评估测试。

正如你所看到的,问题不是NP完全,而是NP-insane。

所以他们所做的就是他们有一个带有小塑料插入物的大桌子,他们移动插图直到获得令人满意的结果。他们从不从头开始:他们通常从上一年的时间表开始并进行调整。

答案 2 :(得分:23)

International Timetabling Competition 2007有一个课程安排跟踪和考试安排轨道。许多研究人员参加了那场比赛。尝试了大量的启发式和元启发式,但最终本地搜索元启发式(如禁忌搜索和模拟退火)明显优于其他算法(如遗传算法)。

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

答案 3 :(得分:16)

我的一个半学期任务是遗传算法学校表生成。

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

  • 制定了“非法表格”的规则:同一教室的两个班级,一个教师同时教两个班级等。这些突变立即被视为致命,一个新的“有机体”被发芽代替“死者”立刻。最初的一个是由一系列随机尝试产生的,以获得合法的(如果没有意义)。致死突变不计入迭代中的突变计数。

  • “交换”突变比“修改”突变更常见。变化只发生在有意义的基因部分之间 - 不能替代老师和教室。

  • 小额奖金被分配用于捆绑约2个小时,用于为同一组分配相同的通用教室,以保持教师的工作时间和班级负荷连续。分配适度的奖金用于为给定的主题提供正确的教室,将课时保持在债券(上午或下午)等等。给予教师的工作量等,给予正确数量的给定主题奖金很大。

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

  • 体重功能......哦,是的。权重函数是分配给所选特征和属性的权重的巨大,巨大的产品(如乘法)。它非常陡峭,一个属性很容易将其改变一个数量级的上升或下降 - 并且在一个生物体中有数百或数千个属性。这导致绝对巨大的数字作为权重,并且作为直接结果,需要使用bignum库(gmp)来执行计算。对于一个包含10个小组,10个教师和10个教室的小型测试用例,初始设置以10 ^ -200something的注释开始,并以10 ^ + 300something结束。当它更平坦时,效率非常低。此外,与更大的“学校”相比,这些价值观的距离越来越远。

  • 计算时间方面,很长一段时间内的小人口(100人)和较少人口的人口(10k +)之间几乎没有差别。同一时间的计算产生了相同的质量。

  • 对于10x10x10的测试用例,计算(在1GHz的CPU上)需要1h才能稳定在10 ^ + 300附近,产生的计划看起来相当不错。

  • 通过提供可在运行计算的计算机之间交换最佳标本的网络设施,可以轻松解决问题。

由此产生的节目从来没有见过日光,让我在这个学期取得好成绩。它显示了一些承诺,但我没有足够的动力来添加任何GUI并使其可用于普通公众。

答案 4 :(得分:8)

这个问题比看上去更难。

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

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

但是“看看”并没有告诉你你需要做什么。

生成所有可能的组合很容易。它可能会生成大量数据,但您在理解这部分问题的概念时应该没有太多问题。

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

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

例如,是否同一位老师每周工作5天,连续X周?即使这是一个有效的解决方案,它可能不是一个比两个人之间交替更好的解决方案,因此每个老师每个人都会做一周。哦,你没想到这个?请记住,这是您正在处理的人,而不仅仅是资源分配问题。

即使一名教师可以连续工作16周全职工作,与您尝试在教师之间进行替代的解决方案相比,这可能是次优解决方案,而且这种平衡很难构建到软件中。

总而言之,对许多人来说,为这个问题找到一个好的解决方案将是值得的。因此,分解和解决并不是一个容易的问题。准备好放弃一些不是100%的目标并称之为“足够好”。

答案 5 :(得分:5)

更新:来自评论......也应该有启发式方法!

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

答案 6 :(得分:4)

遗传算法通常用于此类调度。

找到符合您要求的this example (Making Class Schedule Using Genetic Algorithm)

答案 7 :(得分:4)

以下是我发现的一些链接:

School timetable - 列出了一些问题

A Hybrid Genetic Algorithm for School Timetabling

Scheduling Utilities and Tools

答案 8 :(得分:4)

我的时间表算法,在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在同一个插槽T_j上,并且与A_i具有相同的教师或相同的学生)。保留每个时间段T_j的冲突活动列表。

    <强> B'/ strong>即可。选择具有最少冲突活动的插槽(T_j)。假设此插槽中的活动列表包含3个活动:A_p,A_q,A_r。

    <强> C 即可。将A_i放在T_j并使A_p,A_q,A_r未分配。

    <强> d 即可。递归尝试在A_i上开始放置A_p,A_q,A_r(如果递归级别不是太大,比如14,并且如果从步骤2开始计算的递归调用总数)不是太大,比如2 * n),如步骤2)。

    <强>电子即可。如果成功放置A_p,A_q,A_r,则返回成功,否则尝试其他时隙(转到步骤2b)并选择下一个最佳时段。

    <强>˚F即可。如果尝试了所有(或合理数量的)时隙但未成功,则返回但未成功。

    <强>克即可。如果我们处于0级,并且我们没有成功放置A_i,则将其放置在步骤2 b)和2 c)中,但不进行递归。我们现在有3到1 = 2个以上的活动。转到步骤2)(这里使用了一些避免循环的方法)。

答案 9 :(得分:2)

我为课堂时间表和考试时间表设计了商业算法。对于第一个我使用整数编程;第二种是基于通过选择时隙交换来最大化目标函数的启发式算法,非常类似于已经发展的原始手动过程。获得这些解决方案的主要内容是能够代表所有现实世界的约束;而且人类的时间表无法看到改进解决方案的方法。最后,与数据库的准备,用户界面,报告房间利用率,用户教育等统计数据的能力相比,算法部分非常简单易行。

答案 10 :(得分:2)

本文详细描述了学校时间表问题及其对算法的处理方法:“The Development of SYLLABUS—An Interactive, Constraint-Based Scheduler for Schools and Colleges.”[PDF]

作者告诉我SYLLABUS软件仍然在这里使用/开发:http://www.scientia.com/uk/

答案 11 :(得分:2)

我正在使用一个广泛使用的调度引擎,它正是这样做的。是的,它是NP-Complete;最好的方法寻求近似最优解。当然,有很多不同的方式可以说出哪一个是“最佳”解决方案 - 例如,您的老师对他们的日程安排感到满意,或者学生进入他们所有的课程更重要吗?

您需要尽早解决的绝对最重要的问题是是什么让一种方式更好地安排此系统?也就是说,如果我有一个时间表,琼斯夫人在8岁时教数学,而史密斯先生在9岁时教数学,那么他们两人在10岁时教数学是好还是坏?琼斯夫人在8岁时教学,琼斯先生在2岁时教学,这是好还是坏?为什么呢?

我在这里给出的主要建议是尽可能地分解问题 - 当然可能是课程,也许是老师的教师,也许是逐个房间 - 并且首先解决子问题。在那里你应该有多种解决方案可供选择,并且需要选择一种最可能的最佳解决方案。然后,制定“早期”子问题的工作考虑了后续子问题在评估其潜在解决方案时的需求。然后,当你进入“无效解决方案”状态时,可能会研究如何让自己脱离绘画到角落的情况(假设你无法预测早期子问题中的那些情况)。

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

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

答案 12 :(得分:1)

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

祝你好运!

答案 13 :(得分:1)

你可以使用遗传算法来获取它,是的。但你不应该:)。它可能太慢,参数调整可能太耗费时间等。

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

  • 基于约束的方法
    • UniTime(实际上不是学校)实施
    • 您还可以进一步使用整数编程。使用商业软件(ILOG CPLEX)在Udine university和拜罗伊特大学(我参与其中)成功完成了
    • 基于规则的heuristisc方法 - 见Drools planner
  • 不同的启发式方法 - FETmy own

请点击此处查看timetabling software list

答案 14 :(得分:0)

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

另请注意:a similar questionanother one

答案 15 :(得分:0)

我不知道是否会有人同意这段代码,但是我在自己的算法的帮助下开发了这段代码,并且正在ruby中为我工作。希望它能帮助那些正在搜索它的人 在以下代码中,periodflag,dayflag subjectflag和teacherflag是具有相应id的哈希值和布尔值的标志值。 有任何问题请联系我.......( - _-)

periodflag.each do | 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

答案 16 :(得分:0)

这个问题在我工作的地方非常严重 - 假设有 1800 个科目/模块和 350 000 名学生,每个学生做 5 到 10 个模块,并且您想在 10 周内建立一个考试,其中论文的长度为 1 小时到 3 天。 .. 一个加分点 - 所有考试都在线,但又糟糕,不能超过系统最大 5k 并发的负载。所以是的,我们现在正在云中扩展服务器上执行此操作。 我们使用的“解决方案”只是根据它们与降序“冲突”的其他模块(学生同时执行)的数量对模块进行排序,然后“打包”它们,允许这些长论文实际上重叠,否则它根本无法完成。 所以当事情变得太大时,我发现这种“启发式”是实用的......至少。