【毕业设计】基于java的自动排课系统算法讲解

这个项目运用到了遗传算法, 但是是改进版本(变异), 接下来我们分析一下算法流程

首先导入开课任务, 这一批课程有哪些, 老师是谁, 一个班有多少人等等
在这里插入图片描述
根据排课计划, 开始排课
在这里插入图片描述

将这些排课数据进行编码, 课程划分为固定时间的和不固定时间的, 输出染色体

染色体格式如下

在这里插入图片描述

示例: 2 01 20200101 10041 100051 04 14

数据表示例

idsemestergrade_noclass_nocourse_nocourse_nameteacher_norealnamecourseAttrstudentNumweeks_sumweeks_numberisFixclass_timedeletedcreate_timeupdate_time
632019-2020-10120200201100005高二语文必修510019汪莉莉01452061null02020-06-11 10:25:39null
642019-2020-10120200201100017高二化学选修210050黄三毛02452041null02020-06-11 10:25:40null
652019-2020-10120200201100029高二生物必修3:稳态与环境10031张小龙02452041null02020-06-11 10:25:40null
112019-2020-10120200101100066物理实验10025张德良034020220402020-06-11 10:25:36null
232019-2020-10120200102100066物理实验10025张德良034020220902020-06-11 10:25:37null
102019-2020-10120200101100051体育课10041张杰044020221402020-06-11 10:25:36null

接下来, 给不固定的课程编码随机分配时间(上一步不固定时间的课堂时间编码默认为 00)

将分配好时间的基因编码以班级分类成为以班级的个体(Map<String, List<String>>, 以班级id为key,班级课程编码list为value)

开始进化

迭代 50 次, 每一次都进行 选择, 交叉, 变异, 然后消除冲突继续进化

    private Map<String, List<String>> geneticEvolution(Map<String, List<String>> individualMap) {
        // 遗传代数
        int generation = ConstantInfo.GENERATION;
        List<String> resultGeneList;
        for (int i = 0; i < generation; ++i) {
            // 1、选择、交叉individualMap:按班级分的课表
            individualMap = hybridization(individualMap);
            // 2,3、变异
            resultGeneList = geneMutation(collectGene(individualMap));
            // 4,5、将消除冲突后的个体再分班进入下一次进化
            individualMap = transformIndividual(conflictResolution(resultGeneList));
        }
        return individualMap;
    }

在选择的过程中, 我们需要比较适应度值

private Map<String, List<String>> hybridization(Map<String, List<String>> individualMap) {
    // 对每一个班级的基因编码片段进行交叉
    for (String classNo : individualMap.keySet()) {
        // 得到每一个班级对应的基因编码
        List<String> individualList = individualMap.get(classNo);
        // 保存上一代
        List<String> oldIndividualList = individualList;
        // 交叉生成新个体,得到新生代
        individualList = selectGene(individualList);
        // 计算并对比子父代的适应度值,高的留下进行下一代遗传
        if (ClassUtil.calculatExpectedValue(individualList) >= ClassUtil.calculatExpectedValue(oldIndividualList)) {
            individualMap.put(classNo, individualList);
        } else {
            individualMap.put(classNo, oldIndividualList);
        }
    }
    return individualMap;
}

这里的适应度值主要通过课程的期望值获得, 期望值指的是每种课程在每种时间开展的适合度, 越适合期望值越高

public static double calculatExpectedValue(List<String> individualList) {
    double K1 = 0.3; // 主要课所占权重
    double K2 = 0.1; // 次要课所占权重
    double K3 = 0.1; // 体育课所占权重
    double K4 = 0.3; // 实验课所占权重
    double K5 = 0.2; // 课程离散程度所占权重

    int F1 = 0; // 主要课程期望总值
    int F2 = 0; // 次要课程期望总值
    int F3 = 0; // 体育课期望总值
    int F4 = 0; // 实验课期望总值
    int F5; // 课程离散程度期望总值

    double Fx; // 总适应度值

    // 开始计算每一个个体的适应度
    for (String gene : individualList) {
        // 获得课程属性
        String courseAttr = cutGene(ConstantInfo.COURSE_ATTR, gene);
        // 获得该课程的开课时间
        String classTime = cutGene(ConstantInfo.CLASS_TIME, gene);

        if (courseAttr.equals(ConstantInfo.MAIN_COURSE)) {
            F1 = F1 + calculateMainExpect(classTime);
        } else if (courseAttr.equals(ConstantInfo.SECONDARY_COURSE)) {
            F2 = F2 + calculateSecondaryExpect(classTime);
        } else if (courseAttr.equals(ConstantInfo.PHYSICAL_COURSE)) {
            F3 = F3 + calculatePhysicalExpect(classTime);
        } else {
            F4 = F4 + calculateExperimentExpect(classTime);
        }
    }
    // 计算期望值
    F5 = calculateDiscreteExpect(individualList);
    // 总适应度
    Fx = K1 * F1 + K2 * F2 + K3 * F3 + K4 * F4 + K5 * F5;
    return Fx; // 整个种群的适应度值
}

我们以主要课的期望计算来分析

private static int calculateMainExpect(String classTime) {
    // 主要课程期望值为10时的时间片值,放在第一节课
    String[] tenExpectValue = {"01", "06", "11", "16", "21"};
    // 主要课程期望值为8时的时间片值
    String[] eightExpectValue = {"02", "07", "12", "17", "22"};
    // 主要课程期望值为4时的时间片值
    String[] fourExpectValue = {"03", "08", "13", "18", "23"};
    // 主要课程期望值为2时的时间片值
    String[] twoExpectValue = {"04", "09", "14", "19", "24"};
    // 主要课程期望值为0时的时间片值
    //String [] zeroExpectValue = {"05","10","15","20","25"};

    if (ArrayUtils.contains(tenExpectValue, classTime)) {
        return 10;
    } else if (ArrayUtils.contains(eightExpectValue, classTime)) {
        return 10;
    } else if (ArrayUtils.contains(fourExpectValue, classTime)) {
        return 4;
    } else if (ArrayUtils.contains(twoExpectValue, classTime)) {
        return 2;
    } else {
        return 0;
    }
}

当课程在第一节课的时候, 其期望是最高的(一周5天, 每天5节课)

期望中, 有一个 F5 值是最复杂的, 这个值是课程离散度期望值

private static int calculateDiscreteExpect(List<String> individualList) {
    // 离散程度期望值
    int F5 = 0;
    // 返回每个班级的对应课程下面的排序上课时间
    Map<String, List<String>> classTimeMap = courseGrouping(individualList);

    for (List<String> classTimeList : classTimeMap.values()) {
        if (classTimeList.size() > 1) {
            for (int i = 0; i < classTimeList.size() - 1; ++i) {
                // 计算一门课上课的时间差
                int temp = Integer.parseInt(classTimeList.get(++i)) - Integer.parseInt(classTimeList.get(i - 1));
                F5 = F5 + judgingDiscreteValues(temp);
            }
        }
    }
    return F5;
}

首先, courseGrouping 计算出该班级每门课排课的时间片, 并且进行排序

根据每门课程之间相差的时间, 进行期望计算

/**
* 判断两课程的时间差在哪个区间
* 并返回对应的期望值
*/
private static int judgingDiscreteValues(int temp) {
    int[] tenExpectValue = {5, 6, 7, 8}; // 期望值为10时两课之间的时间差
    int[] sixExpectValue = {4, 9, 10, 11, 12, 13}; // 期望值为6时两课之间的时间差
    int[] fourExpectValue = {3, 14, 15, 16, 17, 18}; // 期望值为4时两课之间的时间差
    int[] twoExpectValue = {2, 19, 20, 21, 22, 23}; // 期望值为2时两课之间的时间差
    //int [] zeroExpectValue = {1,24};//期望值为0时两课之间的时间差
    if (ArrayUtils.contains(tenExpectValue, temp)) {
        return 10;
    } else if (ArrayUtils.contains(sixExpectValue, temp)) {
        return 6;
    } else if (ArrayUtils.contains(fourExpectValue, temp)) {
        return 4;
    } else if (ArrayUtils.contains(twoExpectValue, temp)) {
        return 2;
    } else {
        return 0;
    }
}

最后进行加权, 得到适应度值, 值越大, 这个效果越好

// 总适应度
Fx = K1 * F1 + K2 * F2 + K3 * F3 + K4 * F4 + K5 * F5;

选择是一个班级的课程(通常是20节课), 交换上课时间

变异是让所有课程(所有班级课程的总和)的随机一些课程时间发生改变

上述两个操作均未考虑冲突

接下来考虑冲突问题

冲突在不考虑教室的情况下, 主要是一个老师不能再同一时间上多门课, 一旦出现这个问题, 随机一个时间, 再次进行冲突检查

/**
     * 冲突消除,同一个讲师同一时间上多门课。解决:重新分配一个时间,直到所有的基因编码中
     * 不再存在上课时间冲突为止
     * 因素:讲师-课程-时间-教室
     * @param resultGeneList 所有个体集合
     * @return
     */
private List<String> conflictResolution(List<String> resultGeneList) {
    int conflictTimes = 0;
    eitx:
    for (int i = 0; i < resultGeneList.size(); i++) {
        // 得到集合中每一条基因编码的编码信息
        String gene = resultGeneList.get(i);
        String teacherNo = ClassUtil.cutGene(ConstantInfo.TEACHER_NO, gene);
        String classTime = ClassUtil.cutGene(ConstantInfo.CLASS_TIME, gene);
        String classNo = ClassUtil.cutGene(ConstantInfo.CLASS_NO, gene);
        for (int j = i + 1; j < resultGeneList.size(); j++) {
            // 再找剩余的基因编码对比
            String tempGene = resultGeneList.get(j);
            String tempTeacherNo = ClassUtil.cutGene(ConstantInfo.TEACHER_NO, tempGene);
            String tempClassTime = ClassUtil.cutGene(ConstantInfo.CLASS_TIME, tempGene);
            String tempClassNo = ClassUtil.cutGene(ConstantInfo.CLASS_NO, tempGene);
            // 冲突检测
            if (classTime.equals(tempClassTime)) {
                if (classNo.equals(tempClassNo) || teacherNo.equals(tempTeacherNo)) {
                    System.out.println("出现冲突情况");
                    conflictTimes ++;
                    String newClassTime = ClassUtil.randomTime(gene, resultGeneList);
                    String newGene = gene.substring(0, 24) + newClassTime;
                    resultGeneList = replace(resultGeneList, gene, newGene);
                    i = -1;
                    continue eitx; // 外循环
                }
            }
        }
    }
    System.out.println("冲突发生次数:" + conflictTimes);
    return resultGeneList;
}

解决冲突后, 继续进化, 直到50轮结束

接下来考虑教室问题

/**
     * 开始给进化完的基因编码分配教室,即在原来的编码中加上教室编号,6
     * @param individualMap
     * @return
     */
private List<String> finalResult(Map<String, List<String>> individualMap) {
    // 存放编上教室编号的完整基因编码
    List<String> resultList = new ArrayList<>();
    // 将map集合中的基因编码再次全部混合
    List<String> resultGeneList = collectGene(individualMap);
    String classroomNo = "";
    // 得到课程任务的年级列表 01 02 03
    List<String> gradeList = classTaskDao.selectByColumnName(ConstantInfo.GRADE_NO);
    // 将基因编码按照年级分配
    Map<String, List<String>> gradeMap = collectGeneByGrade(resultGeneList, gradeList);
    // 这里需要根据安排教学区域时选的教学楼进行安排课程
    for (String gradeNo : gradeMap.keySet()) {
        // 如果一个年级设置多个教学楼,则需要使用List ===============>>>>>>>
        List<String> teachBuildNoList = teachBuildInfoDao.selectTeachBuildList(gradeNo);

        // 得到不同年级的课程基因编码
        List<String> gradeGeneList = gradeMap.get(gradeNo);

        // 年级设置多个教学楼栋的情况
        QueryWrapper wrapper = new QueryWrapper();
        wrapper.in("teachbuild_no", teachBuildNoList);
        List<Classroom> classroomList2 = classroomDao.selectList(wrapper);

        for (String gene : gradeGeneList) {
            // 分配教室
            classroomNo = issueClassroom(gene, classroomList2, resultList);
            // 基因编码中加入教室编号,至此所有基因信息编码完成,得到染色体
            gene = gene + classroomNo;
            // 将最终的编码加入集合中
            resultList.add(gene);
        }
    }
    // 完整的基因编码,即分配有教室的
    return resultList;
}

分配教室的策略是这样的, 首先判断课程类型, 每种课程类型有着不一样的可用教室

private String issueClassroom(String gene, List<Classroom> classroomList, List<String> resultList) {
    // 处理特殊课程,实验课,体育课
    // 体育课
    List<Classroom> sportBuilding = classroomDao.selectByTeachbuildNo("12");
    // 实验课
    List<Classroom> experimentBuilding = classroomDao.selectByTeachbuildNo("08");
    // 获得班级编号
    String classNo = ClassUtil.cutGene(ConstantInfo.CLASS_NO, gene);
    // 得到该班级的学生人数
    int studentNum = classInfoDao.selectStuNum(classNo);
    // 得到课程属性
    String courseAttr = ClassUtil.cutGene(ConstantInfo.COURSE_ATTR, gene);

    if (courseAttr.equals(ConstantInfo.EXPERIMENT_COURSE)) {
        // 03为实验课
        return chooseClassroom(studentNum, gene, experimentBuilding, resultList);
    } else if (courseAttr.equals(ConstantInfo.PHYSICAL_COURSE)) {
        // 04为体育课
        return chooseClassroom(studentNum, gene, sportBuilding, resultList);
    } else {
        // 剩下主要课程、次要课程都放在普通的教室
        // 如果还有其他课程另外加判断课程属性,暂时设定4种:主要,次要,实验,体育。音乐舞蹈那些不算
        return chooseClassroom(studentNum, gene, classroomList, resultList);
    }
}

接下来, 根据不同的课程类型, 按人数随机分配教室, 同时还要判断该教室是否在同一时间有别的班级使用了

private String chooseClassroom(int studentNum, String gene, List<Classroom> classroomList, List<String> resultList) {
    // 使用随机分配教室的方式,只要可以放下所有学生即可满足条件
    int min = 0;
    int max = classroomList.size() - 1;
    // 用于随机选取教室
    int temp = min + (int)(Math.random() * (max + 1 - min));
    Classroom classroom = classroomList.get(temp);
    // 判断是否满足条件
    if (judgeClassroom(studentNum, gene, classroom, resultList)) {
        // 该教室满足条件
        return classroom.getClassroomNo();
    } else {
        // 不满足,继续找教室
        return chooseClassroom(studentNum, gene, classroomList, resultList);
    }
}

// 判断教室是否符合上课班级所需
// 即:不同属性的课要放在对应属性的教室上课
private Boolean judgeClassroom(int studentNum, String gene, Classroom classroom, List<String> resultList) {

    String courseAttr = ClassUtil.cutGene(ConstantInfo.COURSE_ATTR, gene);
    // 只要是语数英物化生政史地这些课程都是放在普通教室上课
    if (courseAttr.equals(ConstantInfo.MAIN_COURSE) || courseAttr.equals(ConstantInfo.SECONDARY_COURSE)) {
        // 找到普通教室,普通教室的属性都是01
        if (classroom.getAttr().equals("01")) {
            // 判断上课人数与教室容量
            if (classroom.getCapacity() >= studentNum) {
                // 还要判断该教室是否在同一时间有别的班级使用了
                return isFree(gene, resultList, classroom);
            } else {
                // 教室容量不够
                return false;
            }
        } else {
            return false;
        }
    } else {
        // 剩余的课程应该要放在相对应的教室上课
        if (ClassUtil.cutGene(ConstantInfo.COURSE_ATTR, gene).equals(classroom.getAttr())) {
            // 判断人数
            if (classroom.getCapacity() >= studentNum) {
                // 判断该教室上课时间是否重复
                return isFree(gene, resultList, classroom);
            } else {
                return false;
            }
        } else {
            return false;
        }
    }
}

private Boolean isFree(String gene, List<String> resultList, Classroom classroom) {
    // 如果resultList为空说明还没有教室被分配,直接返回true
    if (resultList.size() == 0) {
        return true;
    } else {
        for (String resultGene : resultList) {
            // 如果当前教室在之前分配了则需要去判断时间是否有冲突
            if (ClassUtil.cutGene(ConstantInfo.CLASSROOM_NO, resultGene).equals(classroom.getClassroomNo())) {
                // 判断时间是否一样,一样则表示有冲突
                if (ClassUtil.cutGene(ConstantInfo.CLASS_TIME, gene).equals(ClassUtil.cutGene(ConstantInfo.CLASS_TIME, resultGene))) {
                    return false;
                }
            }
        }
    }
    return true;
}

最后就是写入数据库, 大工告成。

需要项目远程部署与项目讲解的请联系扣扣: 2402668109

  • 27
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
自动排课系统是一种利用计算机技术和优化算法对学校、培训机构等进行排课的软件系统。基于遗传算法自动排课系统可以通过模拟自然界的进化过程,不断优化排课方案,从而得到更优的排课结果。下面是具体的设计思路: 1. 确定适应度函数:适应度函数是遗传算法中的重要组成部分,它用于评估每个个体的优劣程度。在自动排课系统中,适应度函数可以考虑以下因素:教室利用率、教师利用率、时间利用率、课程冲突等。 2. 确定编码方式:编码方式是将排课问题转化为遗传算法能够处理的问题。在自动排课系统中,可以采用二进制编码方式表示每个课程的时间、地点和讲师等信息。 3. 确定遗传算法的参数:遗传算法有很多参数需要确定,如交叉概率、变异概率、种群大小等。这些参数对遗传算法的性能有重要影响,需要根据具体情况进行调整。 4. 生成初始种群:在遗传算法中,初始种群的质量对后续优化的效果有很大影响。为了得到更好的排课方案,可以采用启发式算法生成一些较好的初始解,然后加以改进。 5. 进行遗传操作:遗传算法的核心是遗传操作,包括选择、交叉和变异。在自动排课系统中,可以采用轮盘赌选择、单点交叉和位变异等操作。 6. 评估适应度并选择优秀个体:在遗传操作后,需要重新计算每个个体的适应度,并选择出优秀的个体作为下一代种群的基础。 7. 终止条件判断:遗传算法需要确定一个终止条件,当达到终止条件时,算法停止运行并输出最优解。在自动排课系统中,可以设置迭代次数或者达到一定适应度值时停止算法。 8. 输出结果:最终优化的结果可以输出到文件或数据库中,方便用户查看和使用。 以上是基于遗传算法自动排课系统的设计思路,实现过程中还需要进行调试和优化,使系统能够更好地适应实际需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值