回溯算法在课程排序中的应用

 

问题描述

 最近需要设计一个基于python的排课算法。已知条件:每门课程(共25门课)对应的知识范围(多个)和老师(20位老师)的知识范围(多个),每天上五次课,一星期五天。约束条件是:每个课程必须分配给一个插槽(上课时间)。 每个插槽都需要分配一个导师。将导师和模块分配到同一插槽意味着辅导员将教该模块。 只有在导师的专业知识主题包括该模块涵盖的每个主题时,该教员才能教该模块。 辅导老师最多只能教两个不同的模块。 导师不能在一天内教多个模块。最终需求:得到一个可行的排课列表。

关键问题点

如何保证每个模块分配的老师都涵盖该课程范围;如何保证每天老师上课次数和每周上课次数满足约束条件;由于每门课对应上课老师可能是一个也可能是多个,要是在前面的课程安排中出现了某个老师,而下一个课程只能选择该老师时,这个时候老师就可能违反教课程的次数。

算法选择思路

关于老师满足课程的涵盖范围这个点很好满足,主要的问题是如何解决老师上课次数的冲突问题。在这个问题中,以我们人工处理思想为例,在排课问题中如果遇到老师A冲突,首先想到的是该位置能不能换一个老师,如果不能够换,则向前找到老师A出现的位置,在此处将老师A换成其他老师。这样就可以保证后面老师A能够顺利安排了。这一处理思路完美的契合了回溯算法的思想,故选用回溯算法处理该问题。

回溯算法简单介绍

回溯算法的基本原理在此不做介绍,如果没有基础的同学(在代码的世界里,我们都是学生),建议去力扣上去查看全排列问题求解https://leetcode-cn.com/problems/permutations/和八皇后求解算法https://leetcode-cn.com/problems/n-queens/,在给出的答案中都有很详细的介绍。简单思路就是遍历每一个节点,如果当前选择满足约束条件,保存当前节点至路径,进入下一个节点。如不满足约束条件,选择另一个老师。当这一门课的每一个老师都不满足时,返回上一层节点,选择其他老师。这个思路课我们人工处理的思路很类似,但如果数据量较多时,只能依靠计算机来解决了。

1.回溯函数主要的三个内容:

①路径:也就是已经做出的选择。

路径在本题中对应的是已经选择的课程,比如星期一的课程已经选择,则星期一的课程就是路径。剩下的未排课的星期就是选择列表。

②选择列表:也就是你当前可以做的选择。

③结束条件:也就是到达决策树底层,无法再做选择的条件。

本题的结束条件是当每一天的课程被排满,即安排了25节课则结束

2.回溯算法的结构:

result = []

def backtrack(路径, 选择列表):

    if 满足结束条件:

        result.add(路径)

        return

    for 选择 in 选择列表:

        做选择

        backtrack(路径, 选择列表)

        撤销选择

3.编写选择函数

用来判断该老师是否与前面安排的课表发生冲突。思路是判断当前老师最多只能教两个不同的模块。导师不能在一天内教多个模块。

排课问题解决思路(重点来了!!!)

1.解决老师和课程匹配问题

在这个步骤中,按照给出的课程顺序排列课程对应可上课的老师列表,即解决了老师和课程匹配问题,也简化了模型。我们只需要对得到的老师列表进行判断即可。

#创建一个列表来储存每一个课程可以选择的老师,按课程顺序排序
module_tutor = []
for module in self.moduleList:
	# 创建一个列表保存当前课程所能参与的老师
	teacher = []
	for tutor in self.tutorList:
		#判断课程是否在老师的范围内
		if set(module.topics).issubset(set(tutor.expertise)):
			#加入老师名单
			teacher.append(tutor)
	#加入当前课程的老师名单
	module_tutor.append(teacher)

2.回溯算法编译

在步骤1中得到了依据课程顺序排列的老师顺序,形如[ [ tutor1,tutor2],[tutor3,tutor4],……],课表顺序为[class1,class2,……],class1对应的可上课的老师为 [tutor1,tutor2]。这个时候我们就按照得到的可上课老师列表的顺序来排课,主要考虑的是老师教课次数的冲突。

①冲突函数的编写

#判断一个老师一天只上一节课和一周最多只上两节课,用于在排课是判断该老师是否与前面的课程冲突。输入一段排好的课程,输入一个待排课的老师
def conflict(tut_result,tut):
	n = len(tut_result)%5
	return False if tut_result.count(tut)<2 and tut not in tut_result[-n:] else True

②判断生成的课程表是否满足约束条件(我们只需要得到一个可用列表即输出,回溯函数则是遍历所有结果),当我们得到一个解时,如满足条件,便输出,并结束回溯函数)

#该函数用来判断排课方式是否满足约束条件,输入排好的课程,输出True of False.判断条件,同一天老师不能上两次,一周老师不能超过两次。
def if_Ok(A):
	B = dict.fromkeys(A)
	for i in B.keys():
		if A.count(i) > 2:
			return False
	for i in range(0, len(A), 5):
		if len(dict.fromkeys(A[i:i + 5])) != 5:
			return False
	return True

③回溯函数的编写

# 回溯函数,用于选择一种合适的课程排序,输入课程可供选择的老师,输出排好的课程
def backtrack(module_tutor, tut_result):
	#回溯函数结束条件
	if len(module_tutor) == len(tut_result):
		return
	for i in module_tutor[len(tut_result)]:
		#判断是否冲突
		if not conflict(tut_result, i):
			tut_result.append(i)
			A.append(i)
			backtrack(module_tutor, tut_result)
			#得到一个可行的课程排序,退出函数,输出结果
			if len(A) == len(module_tutor) and if_Ok(A):
				break
			tut_result.pop()
			A.pop()
	return A

# 创建一个列表A保存排好的课程表,
A = []
A=backtrack(module_tutor, [])

3.将课程加入排序内容

由于排好的只是老师的顺序,我们再把课程加入即可。如:老师列表 [teacher1,teacher2,……],初始给出课程列表 [class1,class2,……],最终结果 [ [class1,teacher1],[class2,teacher2],……],其中[class1,teacher1]代表将该课安排在周一第一节课,以此类推。

总结

整个排课系统包括三个问题,这是第一个问题,在接触这些问题之前并不了解回溯算法和启发式算法(后面问题会用到),花了五天时间现学现用给解决掉,如果有不对的地方,请大神指出。在后面的排课问题中,有给出聘请每个老师需要花费的费用和一些费用制定规则,求如何安排课表使得整个排课所需费用最小。使用启发式算法中的遗传算法成功求出,后续如果有时间会写出来。转载请说明出处!

  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值