排课V2.0 GA约束体系完整解读:从硬约束到软约束的技术实现

排课V2.0 GA约束体系完整解读:从硬约束到软约束的技术实现

排课V2.0 GA约束体系完整解读:从硬约束到软约束的技术实现

作者:某中学 | 技术实现:tiangolo | 2026年3月


排课V2.0 GA约束体系完整解读:从硬约束到软约束的技术实现

作者:tiangolo
日期:2026-04-03
脱敏:某中学

前言

遗传算法(GA)进化流程 初始化种群 适应度评估 选择 交叉 变异 新一代种群 适应度最优 or 达到最大代数 → 结束 颜色含义: 初始化 评估 选择 交叉 变异 新一代 ↑ 循环迭代,每一代都比上一代更优 ↑
染色体编码示意图 周一 周二 周三 周四 周五 第1节 第2节 第3节 第4节 第5节 第6节 第7节 第8节 gen[0] gen[1] ... gen[39] 编码规则:染色体索引 = (星期-1)×8 + (节次-1),每班独立轨道
T分数计算公式 T = 50 + 10 × (x - μ) / σ T = 50 → 正好平均水平 T > 50 → 高于平均水平 T < 50 → 低于平均水平
成绩分析数据流 数据输入 数据处理 核心算法 结果生成 报表输出
系统处理流程 数据输入 数据处理 核心算法 结果生成 报表输出
数据预览 层次 约束类型 惩罚系数 说明 ------ --------- --------- ------ 硬约束 体育不排前3节 ×5000 必须满足,否则课表不可用 硬约束 同教师不撞课 ×5000 必须满足,多班并发核心约束

排课是典型的约束满足问题(CSP)。当学校规模扩大(18班、63教师、161条课程需求),人工排课几乎不可能同时满足所有约束。遗传算法(GA)通过"惩罚机制"将硬约束转化为适应度函数的强制项,是工业级排课系统的标准方案。

本文深入解析排课V2.0的GA约束体系设计,包含:

  • 约束层次(硬约束 vs 软约束)
  • 适应度函数数学建模
  • V2.0新增约束(连堂/互斥/时段偏好)
  • 关键代码实现
  • 实测数据与效果

  • 一、约束分类体系

    排课V2.0将约束分为三层:

    层次约束类型惩罚系数说明 硬约束体育不排前3节×5000必须满足,否则课表不可用 硬约束同教师不撞课×5000必须满足,多班并发核心约束 硬约束科目互斥×500如体育与语文不能同天 软约束连堂课优化×5语数英尽量连排,提升教学效果 软约束时段偏好×2主科上午,体美下午 课时约束周课时偏差×1各科课时尽量接近目标

    为什么硬约束用这么大惩罚系数?

    GA的适应度函数是"越大越好"。假设个体A违反硬约束但软约束满分,适应度 = 0 + 100 = 100;如果硬约束惩罚系数只有10,个体B满足硬约束但软约束差,适应度 = -10 + 80 = 70 → 算法会错误选择A。

    惩罚系数 ≥ 10³ 确保:任何违反硬约束的个体,在进化中必然被淘汰


    二、适应度函数数学建模

    基础公式

    fitness = w₁×H_硬约束 + w₂×H_软约束 + w₃×H_课时 + H_时段奖励

    各项展开:

    硬约束惩罚:

    H_体育 = Σ(每节体育课排在前3节) × 5000
    H_教师 = Σ(同一教师同时上两个班) × 5000
    H_互斥 = Σ(同班同天出现互斥科目对) × 500

    课时惩罚:

    H_课时 = Σ|某科目实际排课节数 - 目标周课时|

    软约束奖励(越大越好):

    R_连堂 = 连堂课对数 × 8        # 每增加一对连堂,+8分
    R_时段 = Σ时段权重(period)     # 上午权重高,奖励大

    完整适应度函数

    def fitness(self, chrom):
        H_pe   = self.count_pe_conflicts()       # 体育冲突数 × 5000
        H_tc   = self.count_teacher_conflicts()   # 教师冲突数 × 5000
        H_me   = self.count_mutual_exclusions()  # 互斥冲突数 × 500
    
        P_pe   = H_pe * 5000
        P_tc   = H_tc * 5000
        P_me   = H_me * 500
    
        # 课时偏差(越小越好 → 转为负惩罚)
        hour_dev = sum(sum(d.values()) for d in self._hours_by_class(chrom).values())
        P_hour = -hour_dev  # 偏差越大,适应度越低
    
        # 连堂课奖励(软约束,越多越好)
        n_consec = self.count_consecutive_pairs(chrom)
        R_consec = n_consec * 8
    
        # 时段偏好奖励
        R_period = self._period_score(chrom)
    
        return -(P_pe + P_tc + P_me) + P_hour + R_consec + R_period

    适应度曲线解读

    在3班并发压力测试中(200代):

    Gen  1: fitness = -8444  (大量冲突)
    Gen 31: fitness =    3.45(硬约束归零)
    Gen 61: fitness =   11.50(开始优化软约束)
    Gen120: fitness =   25.40(软约束持续改善)
    Gen200: fitness =   57.35(稳定收敛)

    关键转折点在Gen31:硬约束归零后,适应度由负转正,说明惩罚机制有效


    三、硬约束实现细节

    3.1 体育不排前3节(单班独立轨道)

    每班有独立的8节×5天时间槽轨道。染色体中,体育课(tiyu)出现在第0、1、2位(对应第1、2、3节)时,计数+1:

    TIYU_NO_PERIODS = {0, 1, 2}  # 第1-3节
    
    def count_pe_conflicts(self, chrom):
        count = 0
        slots = self.slots_per_class  # 40节/班
        for ci in range(self.n_classes):
            offset = ci * slots
            for day in range(self.days):
                for period in range(self.ppd):
                    idx = offset + day * self.ppd + period
                    if chrom[idx] == "tiyu" and period in self.TIYU_NO_PERIODS:
                        count += 1
        return count

    3.2 同教师不撞课(多班并发检测)

    每班有独立轨道,但同一教师可能教多个班。在同一时间槽,检查该教师是否被两个班同时占用:

    def count_teacher_conflicts(self, chrom):
        conflicts = 0
        for day in range(self.days):
            for period in range(self.ppd):
                teachers_at_slot = {}
                for ci, class_id in enumerate(self.class_ids):
                    idx = ci * self.slots_per_class + day * self.ppd + period
                    subj_id = chrom[idx]
                    teacher_id = self.teacher_subjects.get(subj_id)
                    if teacher_id:
                        if teacher_id in teachers_at_slot:
                            conflicts += 1  # 同一教师同一时间上两个班
                        teachers_at_slot[teacher_id] = class_id
        return conflicts

    3.3 科目互斥(同一班级同一天)

    某些科目不能排在同一天(如体育课当天不宜安排其他主科考试):

    def count_mutual_exclusions(self, chrom):
        # mutual_exclusions: List[Tuple[str, str]]
        # 例如 [("tiyu", "yuwen")] = 体育与语文不能同天
        violations = 0
        for ci in range(self.n_classes):
            offset = ci * self.slots_per_class
            for day in range(self.days):
                subjects_today = set()
                for period in range(self.ppd):
                    subj = chrom[offset + day * self.ppd + period]
                    subjects_today.add(subj)
                # 检查互斥对
                for (a, b) in self.mutual_exclusions:
                    if a in subjects_today and b in subjects_today:
                        violations += 1
        return violations

    四、软约束实现细节

    4.1 连堂课优化(变异操作改造)

    连堂课(同一科目连续两节)有助于教学连贯性。在GA变异阶段,主动将孤立节次与相邻节交换:

    def _mutate(self, chrom, prob):
        for ci in range(self.n_classes):
            offset = ci * self.slots_per_class
            for day in range(self.days):
                for period in range(self.ppd):
                    if random.random() > prob:
                        continue
                    idx = offset + day * self.ppd + period
                    subj = chrom[idx]
    
                    # 判断是否为孤立节(前后节不同科目)
                    is_single = True
                    if period > 0 and chrom[idx - 1] == subj:
                        is_single = False
                    if period < self.ppd - 1 and chrom[idx + 1] == subj:
                        is_single = False
    
                    if not is_single:
                        continue
    
                    # 尝试与相邻节交换
                    candidates = []
                    if period > 0:
                        candidates.append(idx - 1)
                    if period < self.ppd - 1:
                        candidates.append(idx + 1)
    
                    if candidates and random.random() < 0.3:
                        swap_idx = random.choice(candidates)
                        if chrom[swap_idx] in self.consecutive_subjects:
                            chrom[idx], chrom[swap_idx] = chrom[swap_idx], chrom[idx]
        return chrom

    4.2 时段偏好权重

    主科(语数英)排在上午(1-4节)效果更好,体美排在下午。GA在适应度函数中奖励时段分配合理的个体:

    PERIOD_WEIGHTS = {
        0: 1.0, 1: 1.0,  # 第1-2节,权重最高
        2: 0.9, 3: 0.9,  # 第3-4节,权重较高
        4: 0.6, 5: 0.6,  # 第5-6节
        6: 0.5, 7: 0.5   # 第7-8节,下午最低
    }
    
    def _period_score(self, chrom):
        """计算时段偏好得分:主科在上午得正分,体美在下午得正分"""
        score = 0.0
        main_subjects = {"yuwen", "shuxue", "yingyu"}  # 语数英
        afternoon_subjects = {"tiyu", "meishu", "yinyue", "laoji"}  # 体美
    
        for ci in range(self.n_classes):
            offset = ci * self.slots_per_class
            for day in range(self.days):
                for period in range(self.ppd):
                    subj = chrom[offset + day * self.ppd + period]
                    w = PERIOD_WEIGHTS.get(period, 0.5)
                    if subj in main_subjects:
                        score += w       # 主科上午:加分
                    elif subj in afternoon_subjects:
                        score -= w * 0.5 # 体美下午:轻微减分
        return score

    五、实测数据与效果

    5.1 多班并发压力测试(200代)

    指标Gen 1Gen 31Gen 200变化 适应度-8444+3.45+57.35↗ 大幅提升 体育冲突200 ✅立即收敛 教师冲突200 ✅立即收敛 互斥冲突500 ✅立即收敛 连堂对数5611↗ 持续优化

    5.2 约束满足率

  • 硬约束满足率:100%(所有硬约束归零)
  • 软约束优化率:+120%(连堂对数从5→11)
  • 计算效率:7.9秒/200代(峰值内存31MB)

  • 六、工程实践建议

    6.1 惩罚系数调参经验

    惩罚系数是GA排课的核心超参数。经验法则:

    约束类型系数范围建议值 绝对硬约束(不可违反)10³ ~ 10⁶5000 强约束(几乎不可违反)10² ~ 10³500 软约束(可适当违反)1 ~ 105~8 奖励项(越多越好)正分2~10

    6.2 内存优化

    当学校规模较大(>20班)时,染色体数组可能占用大量内存:

    # 使用 numpy 而非 Python list,内存节省约10倍
    self.population = np.array([...])  # uint8 足矣
    
    # 每50代强制GC,防止内存碎片化
    if gen % 50 == 0:
        gc.collect()

    6.3 早停策略

    当硬约束归零后,可提前停止以节省计算资源:

    if H_pe == 0 and H_tc == 0 and H_me == 0:
        print("硬约束已全部满足,可停止进化")
        break

    结语

    排课V2.0的GA约束体系体现了"硬约束用惩罚过滤,软约束用奖励优化"的经典思想。实测数据表明,3班并发场景下,200代内即可同时满足所有硬约束并显著优化软约束。

    下一步方向:

  • 合班课约束(多班同一节次,同一教师)
  • 教师互斥约束(A老师与B老师不能同时上课)
  • 单双周课程(排0.5节,交替出现)
  • 完整源码:https://github.com/xxx/paike-v2


    本文为某中学教务教研系统技术博客系列第五篇。相关项目:排课系统 / 成绩分析报表 / 教务自动化


    某中学 · 教务教研数字化实践 · 2026
    技术栈:Python · NumPy · FastAPI · python-docx

    posted @ 2026-04-04 20:51  yarata  阅读(3)  评论(0)    收藏  举报