参考资料:
https://wenku.baidu.com/view/0c12fffaaef8941ea76e0512.html
模拟退火算法求解指派问题
匈牙利算法求解教学任务指派问题
组合优化理论里的第六章_指派问题的课件
在生活中经常遇到这样的问题,某单位需完成n项任务,恰好有n个人可承担这些任务。由于每人的专长不同,各人完成任务不同(或所费时间),效率也不同。于是产生应指派哪个人去完成哪项任务,使完成n项任务的总效率最高(或所需总时间最小)。这类问题称为指派问题或分派问题。
教学任务指派问题为指派问题中的一种,考虑教师对课程的擅长程度,教学任务饱满序列和学生对教师的满意度,通过匈牙利算法求得最优课程指派。
指派问题一般模型:
(与匈牙利算法不同的是,不需要把max变为min)
模拟退火算法:
程序实现:
import numpy as np
#教师与课程一样多
#各个教师对各个课的擅长程度矩阵
goodAt =np.array([[18,5,7,16],[10,16,6,5],[11,6,4,7],[13,12,9,11]])
d=20-goodAt
num=d.shape[0]#number of worker and job.读取矩阵的长度,比如shape[0]就是读取矩阵第一维度的长度
num2=d.shape[1]
def cal_total_time(x):
total_time=0
for i in range(num):
if x[i]<num2:
total_time+=goodAt[i,x[i]]#目标函数
return total_time
T=100 #initial temperature初始温度
t=1# final temperature最终温度
alpha=0.9#annealing rate
markov_len=1000 #inner loop,aka,markov length,内层循环长度
current_x=np.random.permutation(num)#initialize the solution随机排列一个序列,或者数组。如果x是多维数组,则沿其第一个坐标轴的索引随机排列数组。
current_time=cal_total_time(current_x)#目标函数值(current_x是一个随机不重复序列)
new_x=current_x.copy()#x为解
best_x=current_x.copy()
best_time=np.min(d)#初始最优目标函数值
while T>t:#迭代温度
for i in range(markov_len):#内层循环
while True:
k=np.random.randint(0,num,2)#(生成2个在[0,num)间的整数)用于生成一个指定范围内的整数。其中参数a是下限,参数b是上限,生成的随机数n:a<=n
if k[0]!=k[1]:
break
new_x[k[0]],new_x[k[1]]=new_x[k[1]],new_x[k[0]]#交换产生新解
new_time=cal_total_time(new_x)
if new_time>current_time:#accept new solution with no condition.#接受新解
current_time=new_time
current_x=new_x.copy()
if new_time>best_time:
best_time=new_time
best_x=new_x.copy()
else:
delta=new_time-current_time
if np.random.rand()<np.exp(delta/T):#accept new solution with an probability.#以一定概率接受新解
current_time=new_time
current_x=new_x.copy()
else:
new_x=current_x.copy()#refuse new solution.
T=T*alpha#更新温度
print (current_x,current_time)
print("最优教师课程指派:")
for i in range(len(best_x)):
if best_x[i]<num2:
print("教师",i,"->课程",best_x[i],end='; ')
print()
只需修改擅长矩阵即可。
程序实现:
#当教师少课程多
#各个教师对各个课的擅长程度矩阵
#当教师少课程多
goodAt =np.array([[7, 3, 7, 4, 5, 5],[7, 3, 7, 4, 5, 5],
[4, 9, 2, 6, 8, 3],[4, 9, 2, 6, 8, 3],
[8, 3, 5, 7, 6, 4],[8, 3, 5, 7, 6, 4],
[4, 6, 2, 3, 7, 8],[4, 6, 2, 3, 7, 8]])
d=10-goodAt
输出结果:
结果表明:教师A教课程2;教师B教课程1和课程4;教师C教课程0和课程3;教师D教课程5.
设想每个教师都有个分身,问题变为8个教师完成6项教学任务,虚设两门课程,擅长程度一个为最大值,另一个为最小值。
程序实现:
import numpy as np
#教师与课程一样多
#各个教师对各个课的擅长程度矩阵
goodAt =np.array([[18,5,7,16],[10,16,6,5],[11,6,4,7],[13,12,9,11]])
d=20-goodAt
#当教师少课程多
goodAt =np.array([[7, 3, 7, 4, 5, 5],[7, 3, 7, 4, 5, 5],
[4, 9, 2, 6, 8, 3],[4, 9, 2, 6, 8, 3],
[8, 3, 5, 7, 6, 4],[8, 3, 5, 7, 6, 4],
[4, 6, 2, 3, 7, 8],[4, 6, 2, 3, 7, 8]])
d=10-goodAt
#教师少课程多且一个教师最多教两门课,最少一门
goodAt =np.array([[7,3,7,4,5,5,0,0],[7,3,7,4,5,5,100,100],
[4, 9, 2, 6, 8, 3,0,0],[4, 9, 2, 6, 8, 3,100,100],
[8, 3, 5, 7, 6, 4,0,0],[8, 3, 5, 7, 6, 4,100,100],
[4, 6, 2, 3, 7, 8,0,0],[4, 6, 2, 3, 7, 8,100,100]])
d=100-goodAt
num=d.shape[0]#number of worker and job.读取矩阵的长度,比如shape[0]就是读取矩阵第一维度的长度
num2=d.shape[1]
def cal_total_time(x):
total_time=0
for i in range(num):
if x[i]<num2:
total_time+=goodAt[i,x[i]]#目标函数
return total_time
T=100 #initial temperature初始温度
t=1# final temperature最终温度
alpha=0.9#annealing rate
markov_len=1000 #inner loop,aka,markov length,内层循环长度
current_x=np.random.permutation(num)#initialize the solution随机排列一个序列,或者数组。如果x是多维数组,则沿其第一个坐标轴的索引随机排列数组。
current_time=cal_total_time(current_x)#目标函数值(current_x是一个随机不重复序列)
new_x=current_x.copy()#x为解
best_x=current_x.copy()
best_time=np.min(d)#初始最优目标函数值
while T>t:#迭代温度
for i in range(markov_len):#内层循环
while True:
k=np.random.randint(0,num,2)#(生成2个在[0,num)间的整数)用于生成一个指定范围内的整数。其中参数a是下限,参数b是上限,生成的随机数n:a<=n
if k[0]!=k[1]:
break
new_x[k[0]],new_x[k[1]]=new_x[k[1]],new_x[k[0]]#交换产生新解
new_time=cal_total_time(new_x)
if new_time>current_time:#accept new solution with no condition.#接受新解
current_time=new_time
current_x=new_x.copy()
if new_time>best_time:
best_time=new_time
best_x=new_x.copy()
else:
delta=new_time-current_time
if np.random.rand()<np.exp(delta/T):#accept new solution with an probability.#以一定概率接受新解
current_time=new_time
current_x=new_x.copy()
else:
new_x=current_x.copy()#refuse new solution.
T=T*alpha#更新温度
print (current_x,current_time)
print("最优教师课程指派:")
for i in range(len(best_x)):
if best_x[i]<num2:
print("教师",i,"->课程",best_x[i],end='; ')
print()
输出结果:
课程6,7不存在,虚设的。
结果表明:教师A教课程2;教师B教课程1和课程4;教师C教课程0和课程3;教师D教课程5.
(参照《匈牙利算法求解教学任务指派问题》)
某高校计划开设创客空间,需要开展的教学任务有焊接、车工、钳铣磨工、数控、3D打印、切割。现有8名教师可承担相关课程教学,教师对教学课程的擅长矩阵G已知。根据救师自身安排、专家组打分和课时等分析,得到教师教学任务的饱满程度序列U。通过问卷调查、往届课程成绩、学生座谈等形式,得到学生对教师的满意 度序列S。请问如何如何安排教师教授的课程?
由于该问题涉及因素较多,因此,采用解析方法或传统的匈牙利算法难以给出合适结果。总体最优化的前提是教师擅长课程、精力和学生满意度满足基本要求,木文用比值的方式求解三种因素的综合表现。
首先归一化擅长矩阵G, G i j G_{ij} Gij 值越高,表明第i名教师对第j门课程的握长程度越好。序列U和S经过归一化处理后,也可表现为百分比形式, U i U_i Ui值越高,表明第i名教师的教学任务越饱满; S i S_i Si值越高,表明学生对第i名教师的满意度越高。以 T i j T_{ij} Tij表现三种因素综合影响下第i名教师教授第j门课程的情况。 T i j T_{ij} Tij与 G i j 、 S i G_{ij}、S_i Gij、Si呈正相关关系,而与 U i U_i Ui呈现负相关关系,计算得到:
T i j = G i j S i U i T_{ij}=\frac {G_{ij}S_i} {U_i} Tij=UiGijSi
模型的目标方程为:
m a x Z = ∑ i = 1 n ∑ j = 1 m T i j X i j maxZ=\sum_{i=1}^{n} {\sum_{j=1}^{m} {T_{ij}X_{ij}}} maxZ=i=1∑nj=1∑mTijXij
n为教师数量,m为教学课程数量。
程序代码:
import numpy as np
import time
start = time.clock()
#教师少与课程多
#各个教师对各个课的擅长程度矩阵(求max)
goodAt =np.array([[9,2,8,0,0,3],[1,7,1,9,9,8],[8,9,4,1,10,0],[2,0,0,10,8,9],[9,0,9,6,8,0],[0,9,9,7,4,0],[8,2,7,0,8,8],[6,7,5,3,4,9]])
taskfull=np.array([6,8,12,15,2,20,23,17])#教学任务饱和序列
satisfaction=np.array([5,4,6,3,7,6,8,7])#满意度序列
goodAt1=goodAt/goodAt.max()#归一化
taskfull1=taskfull/taskfull.max()
satisfaction1=satisfaction/satisfaction.max()
T=np.zeros(goodAt.shape)#构造综合矩阵
for i in range(len(taskfull)):
T[i,:]=(goodAt1[i,:]*satisfaction1[i]/taskfull1[i])*10
d=T
num=d.shape[0]#number of worker and job.读取矩阵的长度,比如shape[0]就是读取矩阵第一维度的长度
num2=d.shape[1]
def cal_total_time(x):
total_time=0
for i in range(num):
if x[i]<num2:
total_time+=d[i,x[i]]#目标函数
return total_time
T=100 #initial temperature初始温度
t=1# final temperature最终温度
alpha=0.9#annealing rate
markov_len=1000 #inner loop,aka,markov length,内层循环长度
current_x=np.random.permutation(num)#initialize the solution随机排列一个序列,或者数组。如果x是多维数组,则沿其第一个坐标轴的索引随机排列数组。
current_time=cal_total_time(current_x)#目标函数值(current_x是一个随机不重复序列)
new_x=current_x.copy()#x为解
best_x=current_x.copy()
best_time=np.min(d)#初始最优目标函数值
while T>t:#迭代温度
for i in range(markov_len):#内层循环
while True:
k=np.random.randint(0,num,2)#(生成2个在[0,num)间的整数)用于生成一个指定范围内的整数。其中参数a是下限,参数b是上限,生成的随机数n:a<=n
if k[0]!=k[1]:
break
new_x[k[0]],new_x[k[1]]=new_x[k[1]],new_x[k[0]]#交换产生新解
new_time=cal_total_time(new_x)
if new_time>current_time:#accept new solution with no condition.#接受新解
current_time=new_time
current_x=new_x.copy()
if new_time>best_time:
best_time=new_time
best_x=new_x.copy()
else:
delta=new_time-current_time
if np.random.rand()<np.exp(delta/T):#accept new solution with an probability.#以一定概率接受新解
current_time=new_time
current_x=new_x.copy()
else:
new_x=current_x.copy()#refuse new solution.
T=T*alpha#更新温度
print (current_x,current_time)
print("最优教师课程指派:")
for i in range(len(best_x)):
if best_x[i]<num2:
print("教师",i,"->课程",best_x[i],end='; ')
print(cal_total_time(best_x))
elapsed = (time.clock() - start)
print("Time used:",elapsed)
输出结果:
157.85为求出来的T矩阵和最大值。
(运行时间显然长于匈牙利算法)
测试运行时间:
import numpy as np
#教师与课程一样多
#各个教师对各个课的擅长程度矩阵
import time
start = time.clock()
goodAt =np.random.randn(500,500)+10
d=goodAt.max()-goodAt
num=d.shape[0]#number of worker and job.读取矩阵的长度,比如shape[0]就是读取矩阵第一维度的长度
num2=d.shape[1]
def cal_total_time(x):
total_time=0
for i in range(num):
if x[i]<num2:
total_time+=goodAt[i,x[i]]#目标函数
return total_time
T=100 #initial temperature初始温度
t=1# final temperature最终温度
alpha=0.9#annealing rate
markov_len=1000 #inner loop,aka,markov length,内层循环长度
current_x=np.random.permutation(num)#initialize the solution随机排列一个序列,或者数组。如果x是多维数组,则沿其第一个坐标轴的索引随机排列数组。
current_time=cal_total_time(current_x)#目标函数值(current_x是一个随机不重复序列)
new_x=current_x.copy()#x为解
best_x=current_x.copy()
best_time=np.min(d)#初始最优目标函数值
while T>t:#迭代温度
for i in range(markov_len):#内层循环
while True:
k=np.random.randint(0,num,2)#(生成2个在[0,num)间的整数)用于生成一个指定范围内的整数。其中参数a是下限,参数b是上限,生成的随机数n:a<=n
if k[0]!=k[1]:
break
new_x[k[0]],new_x[k[1]]=new_x[k[1]],new_x[k[0]]#交换产生新解
new_time=cal_total_time(new_x)
if new_time>current_time:#accept new solution with no condition.#接受新解
current_time=new_time
current_x=new_x.copy()
if new_time>best_time:
best_time=new_time
best_x=new_x.copy()
else:
delta=new_time-current_time
if np.random.rand()<np.exp(delta/T):#accept new solution with an probability.#以一定概率接受新解
current_time=new_time
current_x=new_x.copy()
else:
new_x=current_x.copy()#refuse new solution.
T=T*alpha#更新温度
#print (current_x,current_time)
'''
print("最优教师课程指派:")
for i in range(len(best_x)):
if best_x[i]课程",best_x[i],end='; ')
'''
print(cal_total_time(best_x))
elapsed = (time.clock() - start)
print("Time used:",elapsed)
对于一个较复杂的问题,若转换成经典指派问题,匈牙利算法有其优越性。
对于复杂的问题,使用模拟退火算法更简单些。
因为没找到大型的指派问题数据,本实验的测试数据是随机指定的,最后对比匈牙利算法与模拟退火算法的运行时间情况仅供参考。