python模拟退火算法解决教学任务指派问题

参考资料:
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<b,即[a,b),输出随机数的尺寸,比如size = (m * n* k)则输出同规模即m * n* k个随机数。
            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<b,即[a,b),输出随机数的尺寸,比如size = (m * n* k)则输出同规模即m * n* k个随机数。
            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 GijSi呈正相关关系,而与 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=1nj=1mTijXij

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<b,即[a,b),输出随机数的尺寸,比如size = (m * n* k)则输出同规模即m * n* k个随机数。
            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<b,即[a,b),输出随机数的尺寸,比如size = (m * n* k)则输出同规模即m * n* k个随机数。
            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)

在这里插入图片描述
对于一个较复杂的问题,若转换成经典指派问题,匈牙利算法有其优越性。
对于复杂的问题,使用模拟退火算法更简单些。
因为没找到大型的指派问题数据,本实验的测试数据是随机指定的,最后对比匈牙利算法与模拟退火算法的运行时间情况仅供参考。

  • 2
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值