我一直想知道是否存在用于创建学校时间表的算法的已知解决方案。基本上,它是针对给定的班级-主体-老师关联优化“小时分散”(在教师和班级情况下均如此)。我们可以假设我们在输入时有一组相互关联的班级,课程主题和教师,并且时间表应该在上午8点至下午4点之间。
我猜可能没有准确的算法,但是也许有人知道很好的近似或提示来开发它。
我一直想知道是否存在用于创建学校时间表的算法的已知解决方案。基本上,它是针对给定的班级-主体-老师关联优化“小时分散”(在教师和班级情况下均如此)。我们可以假设我们在输入时有一组相互关联的班级,课程主题和教师,并且时间表应该在上午8点至下午4点之间。
我猜可能没有准确的算法,但是也许有人知道很好的近似或提示来开发它。
Answers:
这个问题是NP完成的!
简而言之,需要探索所有可能的组合以找到可接受的解决方案列表。由于问题在不同学校出现的情况各不相同(例如:教室是否受到限制?,某些时间是否将某些课程分为小组分组?,这是每周时间表吗?等),没有一个与所有计划问题相对应的众所周知的问题类别。也许背包问题与这些问题有很多相似之处。
确认这既是一个艰巨的问题,也是人们一直在寻求解决方案的一个确认,是检查(主要是商业的)软件调度工具的这一(较长)列表
由于涉及大量变量,通常最大的来源是教师的愿望;-)...,考虑枚举所有可能的组合通常是不切实际的。相反,我们需要选择一种访问问题/解决方案空间子集的方法。
- 在另一个答案中引用的遗传算法(或者,恕我直言,似乎)设备齐全,可以执行这种半向导式搜索(问题是为下一代保留的候选者找到良好的评估功能)
- 图重写方法也可用于此类组合优化问题。
除了要关注自动计划生成器程序的特定实现之外,我还想提出一些可以在问题的定义级别上应用的策略。
一般的理由是,在大多数现实世界中的调度问题中,将需要一些折衷方案,而不是所有明示和暗示的约束条件:将完全满足。因此,我们通过以下方式帮助自己:
在校对这个答案的过程中,我意识到提供一个明确的答案是很害羞的,但是它仍然充满了实用的建议。我希望这种帮助毕竟是一个“难题”。
一团糟。皇家混乱。为了补充已经很完整的答案,我想指出我的家庭经历。我的母亲是一位老师,曾经参与这个过程。
事实证明,拥有一台计算机不仅本身很难编写代码,而且由于存在某些条件难以指定给预烘焙的计算机程序,因此这也很困难。例子:
如您所见,问题不是NP完全问题,而是NP疯狂。
因此,他们要做的是拥有一张大桌子,上面有一些小的塑料插图,然后他们将这些插图移动直到获得令人满意的结果。它们从不从零开始:它们通常从上一年的时间表开始进行调整。
的 国际竞争排课2007年有一个教训调度跟踪和考试安排的轨道。许多研究人员参加了比赛。尝试了许多启发式和元启发式方法,但最终,本地搜索元启发式方法(例如禁忌搜索和模拟退火)明显击败了其他算法(例如遗传算法)。
看一看一些决赛入围者使用的2个开源框架:
我的半年任务之一是遗传算法课桌一代。
整个餐桌是一种“有机体”。通用遗传算法方法有一些变化和警告:
制定了“非法餐桌”的规则:同一教室中的两个班级,一个老师同时教两个小组,等等。这些突变立即被认为具有致命性,而新的“有机体”代替了“死者”。最初的尝试是通过一系列随机尝试生成的,以获得合法的(如果没有意义的话)。致命突变不计入迭代中的突变计数。
“交换”突变比“修改”突变更为普遍。变化只在有意义的基因部分之间发生,而没有用教室代替老师。
为将某些2个小时捆绑在一起,为按顺序为同一小组分配相同的通用教室,以保持老师的工作时间和班级的连续性,分配了小额奖金。分配了适度的奖金,以便为给定的主题提供正确的教室,将上课时间保持在固定范围内(上午或下午),等等。最大的好处是分配正确数量的给定科目,给老师的工作量等。
教师可以创建他们的工作量时间表,例如“想上班”,“可以上班”,“不喜欢上班”,“不能上班”,并分配适当的权重。整个24小时都是合法的工作时间,只是夜间非常不理想。
重量功能...是的。权重函数是分配给选定特征和特性的权重的巨大,可怕的乘积(如乘积)。它非常陡峭,一种属性很容易将其改变一个或多个数量级-一个生物中有成百上千个属性。这样就产生了绝对数量巨大的权重,直接的结果是,需要使用bignum库(gmp)进行计算。对于一个由大约10个小组,10个老师和10个教室组成的小型测试用例,最初的设置以10 ^ -200左右开始,最后以10 ^ + 300左右结束。当它变得更平坦时,它完全没有效率。而且,随着更大的“学校”,价值观的距离越来越远。
从计算时间的角度来看,长期以来的少量人口(100)与少代的较大人口(10k +)之间几乎没有差异。相同时间的计算产生的质量大致相同。
对于所述10x10x10测试用例,计算(在1GHz的CPU上)需要大约1h才能稳定在10 ^ + 300附近,生成的时间表看起来非常不错。
通过提供可以在运行计算的计算机之间交换最佳标本的联网工具,可以轻松地将问题并行化。
由此产生的程序从来没有见过外面的阳光,使我整个学期都取得了不错的成绩。它显示了一些希望,但我没有足够的动力来添加任何GUI并使其对公众可用。
这个问题比看起来要难。
正如其他人提到的那样,这是一个NP完全问题,但是让我们分析一下这是什么意思。
基本上,这意味着您必须查看所有可能的组合。
但是,“看看”并不能告诉您您需要做什么。
生成所有可能的组合很容易。它可能会产生大量数据,但是了解问题的这一部分的概念应该不会有太多问题。
第二个问题是判断给定可能的组合是好,坏还是比以前的“好”解决方案更好的问题。
为此,您不仅需要“这是否是可能的解决方案”。
例如,同一位老师每周工作5天,连续X周吗?即使这是一个可行的解决方案,也可能不是两个人轮流让每个老师每个星期做一个更好的解决方案。哦,你没想到吗?请记住,这是您正在处理的人员,而不仅仅是资源分配问题。
即使一个老师可以连续工作16个星期,但与您尝试在老师之间轮换的解决方案相比,这可能不是一个最佳的解决方案,而且这种平衡很难在软件中建立。
总而言之,对于许多人来说,为这个问题提供一个好的解决方案将是非常有价值的。因此,要分解并解决这个问题并不容易。准备好提出一些并非100%的目标并将其称为“足够好”。
我的计时算法在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)许多其他约束(为简单起见,此处不包括在内)。
时间表算法(我将其称为“递归交换”):
尝试按照上述顺序,一次将每个活动(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)(此处使用了一些避免循环的方法)。
更新:从评论...也应该有启发式!
我会选择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问题变得更加容易。
遗传算法通常用于这种调度。
找到了这个示例(使用遗传算法制作课程表),该示例非常符合您的要求。
本文很好地描述了学校的时间表问题及其解决方法:“ SYLLABUS的发展-面向学校的交互式,基于约束的调度程序。 ” [PDF]
作者告诉我SYLLABUS软件仍在这里使用/开发:http : //www.scientia.com/cn/
我正在使用一个广泛使用的调度引擎来完成此任务。是的,它是NP-Complete;最好的方法是寻求近似最佳解决方案。而且,当然,有很多不同的方式来说明哪种是“最佳”解决方案-例如,让您的老师对自己的日程安排感到满意还是让学生参加所有课程是否更为重要?
您需要尽早解决的绝对最重要的问题是,哪种方法比另一种更好地调度此系统?就是说,如果我有一个时间表,琼斯太太在8岁时教数学,史密斯先生在9岁时教数学,那是比一个都在10岁时教数学的人更好还是更坏?它比琼斯夫人在8岁时教书而琼斯先生在2岁时教书好还是坏?为什么?
我在这里给出的主要建议是尽可能地将问题分解-也许逐课程,也许逐老师,也许逐房间-并首先解决子问题。在那里,您最终应该有多种解决方案可供选择,并且需要选择一种最可能的最佳方案。然后,进行“较早”子问题的工作时,要在计分潜在问题时考虑到较后子问题的需求。然后,当进入“无有效解决方案”状态时,也许研究如何使自己摆脱困境的情况(假设您无法在较早的子问题中预见到这些情况)。
本地搜索优化过程通常用于“抛光”最终答案以获得更好的结果。
请注意,通常我们在学校日程安排中会处理资源紧张的系统。一年当中,学校75%的一天都不会有很多空房间或老师坐在休息室里。在解决方案丰富的环境中最有效的方法不一定适用于学校调度。
我不知道有人会同意此代码,但是我在自己的算法的帮助下开发了此代码,并在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