排课问题的本质是将课程、教师和学生在合适的时间段内分配到合适的教室中,涉及到的因素较多,是一个多目标的调度问题,在运筹学中被称为时间表问题(Timetable Problem,TTP)。设一个星期有n个时段可排课,有m位教师需要参与排课,平均每位教师一个星期上k节课,在不考虑其他限制的情况下,能够推出的可能组合就有 种,如此高的复杂度是目前计算机所无法承受的。因此众多研究者提出了多种其他排课算法,如模拟退火,列表寻优搜索和约束满意等。
Github : https://github.com/xiaochus/GeneticClassSchedule
遗传算法(Genetic Algorithm)是模拟达尔文生物进化论的自然选择和遗传学机理的生物进化过程的计算模型,是一种通过模拟自然进化过程搜索最优解的方法。遗传算法的流程如下所示:
遗传算法首先针对待解决问题随机生成一组解,我们称之为种群(Population)。种群中的每个个体都是问题的解,在优化的过程中,算法会计算整个种群的成本函数,从而得到一个与种群相关的适应度的序列。如下图所示:
为了得到新的下一代种群,首先根据适应度对种群进行排序,从中挑选出最优的几个个体加入下一代种群,这一个过程也被称为精英选拔。新种群余下的部分通过对选拔出来的精英个体进行修改得到。
对种群进行修改的方法参考了生物DAN进化的方法,一般使用两种方法: 变异 和 交叉 。 变异 的做法是对种群做一个微小的、随机的改变。如果解的编码方式是二进制,那么就随机选取一个位置进行0和1的互相突变;如果解的编码方式是十进制,那么就随机选取一个位置进行随机加减。 交叉 的做法是随机从最优种群中选取两个个体,以某个位置为交叉点合成一个新的个体。
经过突变和交叉后我们得到新的种群(大小与上一代种群一致),对新种群重复重复上述过程,直到达到迭代次数(失败)或者解的适应性达到我们的要求(成功),GA算法就结束了。
算法实现
首先定义一个课程类,这个类包含了课程、班级、教师、教室、星期、时间几个属性,其中前三个是我们自定义的,后面三个是需要算法来优化的。
接下来定义cost函数,这个函数用来计算课表种群的冲突。当被测试课表冲突为0的时候,这个课表就是个符合规定的课表。冲突检测遵循下面几条规则:
使用遗传算法进行优化的过程如下,与上一节的流程图过程相同。
init_population :随机初始化不同的种群。
mutate :变异操作,随机对 Schedule 对象中的某个可改变属性在允许范围内进行随机加减。
crossover :交叉操作,随机对两个对象交换不同位置的属性。
evolution :启动GA算法进行优化。
实验结果
下面定义了3个班,6种课程、教师和3个教室来对排课效果进行测试。
优化结果如下,迭代到第68次时,课程安排不存在任何冲突。
选择1203班的课表进行可视化,如下所示,算法合理的安排了对应的课程。
# teachers=['a','b','c','d','e','f','g','h','j','k','m']# offices=[[],[],[],[]]
# 要求是将11名老师随机分配到4个办公室,每个办公室保证至少分配两名老师。
import random
teachers = ['a','b','c','d','e','f','g','h','j','k','m']
offices = [[],[],[],[]]
class Office:
def __init__(self, num):
self.teachers_list = []
self.num = num
def add(self, x):
self.teachers_list.append(x)
def ret(self):
return self.teachers_list
def __str__(self):
return str(self.num)
# 调用系统时间,实现随机数
random.seed()
# 一共3种情况:
# 3 3 3 2 = 11
# 4 2 3 2 = 11
# 5 2 2 2 = 11
case_index = random.randrange(1, 4)
offices_list = []
if case_index == 1:
# 3 3 3 2
for e in [3,3,3,2]:
offices_list.append(Office(e))
elif case_index == 2:
# 4 2 3 2
for e in [4,3,2,2]:
offices_list.append(Office(e))
else:
# 5 2 2 2
for e in [5,2,2,2]:
offices_list.append(Office(e))
# 打乱顺序
random.shuffle(offices_list)
print("办公室随机分配名额如下:")
for office in offices_list:
print(office, end=" ")
print()
print("开始分配老师:")
# 分配老师
for teacher in teachers:
while True:
index = random.randrange(0, len(offices))
office = offices_list[index]
if len(office.teachers_list) >= office.num:
continue
office.add(teacher)
break
for i in range(len(offices_list)):
office = offices_list[i]
offices[i] = office.ret()
print(offices[i])
可以运行!请指教!
以教学任务为基本单位,在计算教学任务排课优先级的基础上,对教学任务的时间和教室的安排均采用优化资源查找的算法.为简化算法,先安排教学任务的时间,然后再安排教室,设计并实现了一个高效智能排课系统.