需求分析
课程表需满足以下约束条件:
- 无冲突规则:同一时间段内,每位教师只能教授一门课;每个教室同一时间仅能安排一门课;学生不能同时参加两门不同的课程。
- 资源限制:可用教室数量有限,需合理分配空间。
- 优先级处理:优先安排必修课或高人气选修课。
- 可视化展示:最终结果应以表格形式清晰呈现每日作息。
数据结构设计
实体类定义
// 课程基本信息
class Course {
String id; // 课程编号(如"CS101")
String name; // 课程名称(如"数据结构")
int duration; // 课时长度(单位:节)
List<String> teachers; // 可选教师列表
Set<String> students; // 选课学生集合
boolean isMandatory; // 是否为必修课标记
}
// 时间槽位定义(每天划分为多个时段)
class TimeSlot {
int dayOfWeek; // 星期几(1=周一...7=周日)
int startPeriod; // 开始节次(如第1节对应8:00-9:30)
int endPeriod; // 结束节次
}
// 排课结果记录
class SchedulingResult {
String courseId; // 关联的课程ID
TimeSlot assignedSlot; // 被分配到的具体时间段
String teacherAssigned; // 实际指派的教师姓名
String classroom; // 使用的教室编号
}
全局配置参数
通过配置文件管理基础设置:
# config.properties示例内容 total_classrooms=5 # 总教室数 periods_per_day=6 # 每日总课时数(含午休间隔) morning_break_start=4 # 上午大课间开始于第4节课后 allowed_overlap=false # 是否允许跨天连堂授课
使用java.util.Properties加载此类参数,便于动态调整系统行为。
核心算法实现
采用回溯法+剪枝优化解决NP难的组合优化问题:
步骤1:初始化候选集
遍历所有未安排的课程,构建待处理队列,对每个课程生成其所有可能的时间组合(基于教师空闲时间和教室可用性)。
步骤2:冲突检测函数
关键辅助方法用于验证新调度是否合法:
boolean hasConflict(SchedulingResult newEntry, List<SchedulingResult> existingEntries) {
// 检查教师冲突
String targetTeacher = newEntry.getTeacherAssigned();
for (var old : existingEntries) {
if (old.getTeacherAssigned().equals(targetTeacher) && overlappingTimeRanges(newEntry, old)) {
return true;
}
}
// 检查教室占用情况
String targetRoom = newEntry.getClassroom();
for (var old : existingEntries) {
if (old.getClassroom().equals(targetRoom) && overlappingTimeRanges(newEntry, old)) {
return true;
}
}
return false;
}
其中overlappingTimeRanges()判断两个时间区间是否存在交集。
步骤3:递归搜索可行解
主逻辑框架如下:
void scheduleCourses(List<Course> remainingCourses, List<SchedulingResult> currentSolution) {
if (remainingCourses.isEmpty()) {
// 找到完整方案,保存至结果库
return;
}
Course nextCourse = selectMostConstrainedCourse(remainingCourses); // 启发式选择策略
for (PossibleSlot slot : generateValidSlotsForCourse(nextCourse)) {
SchedulingResult tentative = new SchedulingResult(nextCourse, slot);
if (!hasConflict(tentative, currentSolution)) {
currentSolution.add(tentative);
scheduleCourses(removeCourseFromList(remainingCourses, nextCourse), currentSolution);
currentSolution.removeLast(); // 回溯撤销选择
}
}
}
此处selectMostConstrainedCourse()优先处理剩余可选项最少的课程,显著减少搜索空间。
多目标优化策略
当基本可行性达成后,进一步优化以下指标:
| 优化维度 | 实现方式 |
|—————-|————————————————————————–|
| 均匀分布 | 计算标准差衡量各时段负载均衡度,通过模拟退火算法迭代改进 |
| 特殊需求适配 | 实验室课程强制安排在下午;体育课避开高温时段 |
| 教师偏好 | 收集教师提交的期望时间段问卷数据,作为软约束纳入评分体系 |
| 学生便利性 | 尽量将同专业核心课程集中在相邻时间段 |
实现负载均衡度的量化评估:
double calculateLoadBalancingScore(Map<Integer, Integer> periodUsageStats) {
double mean = calculateAverage(periodUsageStats.values());
double variance = computeVariance(periodUsageStats.values(), mean);
return 1 / (1 + variance); // 方差越小得分越高
}
界面与输出
控制台打印示例
================= 课程表 ================= 周一: 第1节 [8:00-9:30] 教室A101 张教授 《高等数学》 (理工科必修) 第2节 [9:40-11:10] 教室B205 李副教授 《线性代数》 (交叉学科选修) ... 周二: 第3节 [14:00-15:30] 教室C302 王博士 《机器学习导论》 (人工智能方向) ...
HTML可视化扩展
利用Thymeleaf模板引擎生成交互式网页:
- 不同颜色标注理论/实验/实践类课程
- 点击课程条目显示详细信息弹窗
- 支持按周/日视图切换
异常处理机制
常见错误及应对方案:
| 错误类型 | 解决方案 |
|————————|———————————————|
| 无解情况 | 提示用户放宽某些硬性约束(如增加并行上课比例) |
| 输入数据矛盾 | 自动修正明显错误(如某教师被重复预约同一时间段) |
| 性能瓶颈 | 启用缓存机制存储中间结果,避免重复计算 |
| 外部依赖失效 | 提供默认虚拟数据集保证程序可运行性 |
完整代码片段参考
以下是简化版的主流程演示:
public class TimetableGenerator {
private static final int MAX_ATTEMPTS = 1000;
private List<SchedulingResult> bestSolution = new ArrayList<>();
private double bestScore = Double.NEGATIVE_INFINITY;
public void generate(List<Course> courses) {
for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
List<SchedulingResult> candidate = attemptScheduling(courses);
double currentScore = evaluateSolutionQuality(candidate);
if (currentScore > bestScore) {
bestScore = currentScore;
bestSolution = new ArrayList<>(candidate);
}
}
printResults();
}
// ...其他辅助方法省略...
}
FAQs
Q1: 如果某些课程无论如何都无法合理安排怎么办?
A: 系统会逐步放宽次要约束条件(如允许轻微超员),同时建议教学管理部门调整开课计划,极端情况下可输出诊断报告指出具体冲突环节。
Q2: 如何导入历史排课数据作为参考模板?
A: 支持从Excel/CSV文件批量读取过往记录,新学期排课时保持80%以上的原有结构不变,仅对新增课程进行局部调整,可通过`FileUtils.readScheduleFromX
