207. Course Schedule (M)

本文探讨了课程调度问题,即在存在课程前置条件的情况下,如何判断能否完成所有课程的学习。介绍了两种经典解决方法:BFS和DFS,通过示例详细解释了算法原理及实现。

Course Schedule (M)

There are a total of n courses you have to take, labeled from 0 to n-1.

Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1]

Given the total number of courses and a list of prerequisite pairs, is it possible for you to finish all courses?

Example 1:

Input: 2, [[1,0]] 
Output: true
Explanation: There are a total of 2 courses to take. 
             To take course 1 you should have finished course 0. So it is possible.

Example 2:

Input: 2, [[1,0],[0,1]]
Output: false
Explanation: There are a total of 2 courses to take. 
             To take course 1 you should have finished course 0, and to take course 0 you should
             also have finished course 1. So it is impossible.

Note:

  1. The input prerequisites is a graph represented by a list of edges, not adjacency matrices. Read more about how a graph is represented.
  2. You may assume that there are no duplicate edges in the input prerequisites.

题意

假设选课时有一些课程有前置课程要求,给定一系列课程及其所有的前置课程,判断能否上完所有课。

思路

很典型的拓扑排序问题,经典解法有 BFSDFS 两种。当图中存在环时则没有办法上完所有课。


代码实现 - BFS

class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        int[] inDegree = new int[numCourses];				// 入度数组
        List<List<Integer>> edge = new ArrayList<>();		// 记录图信息
        // 初始化邻接表
        for (int i = 0; i < numCourses; i++) {
            edge.add(new ArrayList<>());
        }
        // 生成图并记录初始入度信息
        for (int i = 0; i < prerequisites.length; i++) {
            inDegree[prerequisites[i][0]]++;
            edge.get(prerequisites[i][1]).add(prerequisites[i][0]);
        }
        // 将所有入度为0的结点入队
        Queue<Integer> q = new ArrayDeque<>();
        for (int i = 0; i < numCourses; i++) {
            if (inDegree[i] == 0) {
                q.offer(i);
            }
        }
        // 更新结点入度和队列,并记录拓扑序列中结点的个数
        int count = 0;
        while (!q.isEmpty()) {
            int u = q.poll();
            count++;
            for (int i = 0; i < edge.get(u).size(); i++) {
                int v = (int) edge.get(u).get(i);
                inDegree[v]--;
                if (inDegree[v] == 0) {
                    q.offer(v);
                }
            }
        }
        // 如果有结点不在拓扑序列中,说明图存在环
        return count == numCourses;
    }
}

代码实现 - DFS

// DFS方法实际上就是判断是否为有向无环图
class Solution {
    private boolean[] visit;				// 记录结点是否被访问
    private boolean[] inStack;				// 记录结点是否还在递归栈中
    private List<List<Integer>> edge;		// 记录图

    public boolean canFinish(int numCourses, int[][] prerequisites) {
        visit = new boolean[numCourses];
        inStack = new boolean[numCourses];
        edge = new ArrayList<>();
        // 初始化邻接表
        for (int i = 0; i < numCourses; i++) {
            edge.add(new ArrayList<>());
        }
        // 生成图
        for (int i = 0; i < prerequisites.length; i++) {
            edge.get(prerequisites[i][1]).add(prerequisites[i][0]);
        }
        // DFS处理
        for (int i = 0; i < numCourses; i++) {
            if (!visit[i]) {
                boolean flag = dfs(i);
                if (!flag) return false;		// 存在环则不可能有拓扑序列
            }
        }
        return true;
    }

    private boolean dfs(int u) {
        visit[u] = true;
        inStack[u] = true;
        for (int v : edge.get(u)) {
            if (!visit[v]) {
                boolean flag = dfs(v);
                if (!flag) return false;
            } else if (inStack[v]) {
                return false;		// 如果下一条边连接的结点还在递归栈中,说明存在环
            }
        }
        inStack[u] = false;			// 所有出边处理完后,将当前结点排除在递归栈外
        return true;
    }
}
可以幫我加入這些新功能嗎?1.自动排课推荐:当学生选课冲突时,推荐其他时间段的相同课程。 2.容量预警:当课程容量使用超过90%时,标记为“即将满员”。 3.冲突高亮:在可视化课表中用红色标注冲突课程,謝謝 import csv from collections import defaultdict # 定义时间段区间 time_periods = { "上午": [("8:00","10:00"),("8:00","11:00"),("10:00","12:00"),("9:00","12:00")], "下午": [("1:00","3:00"),("1:00","4:00"),("3:00","5:00"),("2:00","5:00")] } def convert_time_format(time_slots): converted_slots = [] for day, start, end in time_slots: period = None # 检查是否属于上午时间段 for am_start, am_end in time_periods["上午"]: if start == am_start and end == am_end: period = "上午" break # 如果不属于上午,检查是否属于下午 if not period: for pm_start, pm_end in time_periods["下午"]: if start == pm_start and end == pm_end: period = "下午" break # 如果都不属于,默认为上午(根据需求可以调整) if not period: period = "上午" converted_slots.append((day, f"{period}{start}-{end}")) return converted_slots # 课程类 class Course: def __init__(self, course_ID, name, time, capacity=50, current_student=0, allowed_majors=None, prerequisites=None, classroom=""): self.course_ID = course_ID self.name = name self.time = convert_time_format(time) # 转换时间格式 self.capacity = capacity self.current_student = current_student self.allowed_majors = allowed_majors if allowed_majors else [] self.prerequisites = prerequisites if prerequisites else [] self.classroom = classroom def is_time_conflict(self, other_course): for slot1 in self.time: for slot2 in other_course.time: if slot1[0] == slot2[0]: # 检查两个课程时间段是否同一天 # 提取时间部分(去掉上午/下午) time1 = slot1[1][2:] if len(slot1[1]) > 5 else slot1[1] # 处理可能已经转换过的格式 time2 = slot2[1][2:] if len(slot2[1]) > 5 else slot2[1] start1, end1 = time1.split('-') start2, end2 = time2.split('-') if max(start1, start2) < min(end1, end2): # 检查两个课程时间段是否重叠 return True # 如果两个时间段在同一天且有重叠,返回True return False # 否则返回False # 管理员类 class Admin: def __init__(self): self.courses = {} # 存储所有课程的字典 def add_course(self, course): if course.course_ID in self.courses: print(f"课程 {course.course_ID} 已存在,无法添加。") else: self.courses[course.course_ID] = course print(f"您已成功添加课程{course.course_ID}。") def add_courses_from_csv(self, file_path): added_courses = [] skipped_courses = [] try: with open(file_path, 'r', encoding='utf-8') as file: reader = csv.DictReader(file) for row in reader: try: course_ID = row['course_ID'] name = row['name'] time = eval(row['time']) # 假设时间以列表形式存储 capacity = int(row.get('capacity', 50)) allowed_majors = eval(row.get('allowed_majors', "[]")) prerequisites = eval(row.get('prerequisites', "[]")) classroom = row.get('classroom', "") new_course = Course(course_ID, name, time, capacity, 0, allowed_majors, prerequisites, classroom) self.add_course(new_course) added_courses.append(course_ID) except Exception as e: skipped_courses.append((row.get('course_ID', '未知'), str(e))) except FileNotFoundError: print("文件未找到,请检查路径。") return print(f"您已成功添加课程:{added_courses}") if skipped_courses: print(f"已跳過格式错误的行:{skipped_courses}") def update_course(self, course_ID, **kwargs): if course_ID in self.courses: course = self.courses[course_ID] for key, value in kwargs.items(): if key == 'time' and isinstance(value, list): value = convert_time_format(value) # 转换时间格式 if hasattr(course, key): setattr(course, key, value) print(f"课程 {course_ID} 更新成功。") else: print(f"课程 {course_ID} 不存在,无法更新。") def delete_course(self, course_ID): if course_ID in self.courses: del self.courses[course_ID] print(f"成功删除课程{course_ID}") else: print(f"课程 {course_ID}不存在,无法删除!") # 学生类 class Student: def __init__(self, student_id, major): self.student_id = student_id self.major = major self.selected_courses = [] def select_course(self, course, admin): if course.course_ID not in admin.courses: print(f"选课失敗:课程 {course.course_ID} 不存在,无法选课。") return if course.current_student >= course.capacity: print(f"选课失敗:课程 {course.course_ID} 学生容量已满,无法选课。") return if course.allowed_majors and self.major not in course.allowed_majors: print(f"选课失敗:您的专业 {self.major}不符合课程 {course.course_ID} 的专业限制要求。") return missing_prereqs = [p for p in course.prerequisites if p not in [c.course_ID for c in self.selected_courses]] if missing_prereqs: print(f"选课失敗:您尚未完成先修课程:{missing_prereqs},因此无法选择这门课程。") return if any(selected_course.is_time_conflict(course) for selected_course in self.selected_courses): conflicting_courses = [c.course_ID for c in self.selected_courses if c.is_time_conflict(course)] print(f"选课失敗:与课程{conflicting_courses}时间冲突") return self.selected_courses.append(course) course.current_student += 1 print(f"您已成功选课:{course.course_ID}{course.name}") # def drop_course(self, course): if course in self.selected_courses: self.selected_courses.remove(course) course.current_student -= 1 print(f"您已成功退选:{course.course_ID}") else: print(f"您未选择课程 {course.course_ID},无法退选。") def visualize_schedule(self): schedule = defaultdict(list) for course in self.selected_courses: for day, time_slot in course.time: schedule[day].append(f"{time_slot}: {course.name}") print(f"== 学生 {self.student_id} 的课表 ==") for day, slots in sorted(schedule.items()): print(f"{day}:") for slot in sorted(slots): print(f" {slot}") # def validate_course_id(course_id): """校验课程ID格式""" if not course_id or not isinstance(course_id, str): return False, "课程ID必须是非空字符串" return True, "" def validate_course_name(name): """校验课程名称格式""" if not name or len(name.strip()) == 0: return False, "课程名称不能为空" return True, "" def validate_time_slot(time_str): """校验单个时间段格式""" try: day, period = time_str.strip().split(" ", 1) if day not in ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]: return False, f"无效的星期格式: {day}" start_end = period.split("-") if len(start_end) != 2: return False, "时间段格式应为'开始时间-结束时间'" start, end = start_end start = start.strip() end = end.strip() # 检查时间格式 for t in [start, end]: if ":" not in t: return False, f"时间格式应为HH:MM: {t}" hours, mins = map(int, t.split(":")) if hours < 0 or hours > 23 or mins < 0 or mins > 59: return False, f"无效的时间值: {t}" # 检查是否为整点 if not (start.endswith(":00") and end.endswith(":00")): return False, "时间段必须为整点开始和结束" # 检查时间段是否符合规定 start_hour = int(start.split(":")[0]) end_hour = int(end.split(":")[0]) duration = end_hour - start_hour if start_hour < 12: # 上午 if not ((start_hour == 8 and end_hour == 10) or # 2节课 (start_hour == 8 and end_hour == 11) or # 3节课 (start_hour == 10 and end_hour == 12) or # 2节课 (start_hour == 9 and end_hour == 12)): # 3节课 return False, "上午时间段不符合规定" else: # 下午 if not ((start_hour == 13 and end_hour == 15) or # 2节课 (start_hour == 13 and end_hour == 16) or # 3节课 (start_hour == 15 and end_hour == 17) or # 2节课 (start_hour == 14 and end_hour == 17)): # 3节课 return False, "下午时间段不符合规定" return True, "" except Exception as e: return False, f"时间格式错误: {str(e)}" def validate_capacity(capacity_str): """校验课程容量""" try: capacity = int(capacity_str) if capacity <= 0: return False, "课程容量必须是正整数" return True, "" except ValueError: return False, "课程容量必须是整数" def validate_majors(majors_str): """校验专业列表""" if not majors_str: return True, "" majors = [m.strip() for m in majors_str.split(",") if m.strip()] if not all(m for m in majors): return False, "专业名称不能为空" return True, "" def validate_prerequisites(prereqs_str, existing_courses): """校验先修课程""" if not prereqs_str: return True, "" prereqs = [p.strip() for p in prereqs_str.split(",") if p.strip()] for p in prereqs: if p not in existing_courses: return False, f"先修课程 {p} 不存在于系统中" return True, "" #主頁面 def main_menu(): print("\n===高校选课系统主頁面===") print("1.管理员登录") print("2.学生登录") print("3.退出系统") choice=input("请选择登录身份(1-3):") return choice # 管理员菜单 def admin_menu(admin): while True: print("\n===管理員功能頁面===") print("1.手動添加單條课程") print("2.批量导入课程(CSV)") print("3.更新课程信息") print("4.删除课程") print("5.查看所有课程") print("6.返回主菜单") choice=input("请选择功能(1-6):") if choice=="1": # 课程ID校验 while True: course_ID=input("请输入课程ID(唯一字符串,如CS101):") valid, msg = validate_course_id(course_ID) if valid and course_ID not in admin.courses: break if not valid: print(f"输入错误:{msg}") else: print("该课程ID已存在!") print(f"课程ID校验通过:{course_ID}") # 课程名称校验 while True: name=input("请输入课程名称(中英文,长度≥1):") valid, msg = validate_course_name(name) if valid: break print(f"输入错误:{msg}") print(f"课程名称校验通过: {name}") # 授课时间校验 time = [] while True: time_input = input("请输入课程时间(格式:星期X 时间段,多个时间段用分号分隔,如'周一 8:00-10:00;周三 10:00-12:00'): ") time_slots = [s.strip() for s in time_input.split(";") if s.strip()] if not time_slots: print("输入错误:至少需要一个时间段") continue all_valid = True time = [] for slot in time_slots: valid, msg = validate_time_slot(slot) if not valid: print(f"输入错误:时间段'{slot}' - {msg}") all_valid = False break # 解析时间 day, period = slot.split(" ", 1) start, end = period.split("-") time.append((day, start.strip(), end.strip())) if all_valid: break print("授课时间校验通过") # 课程容量校验 while True: capacity_str = input("请输入课程容量(正整数,默认50):") or "50" valid, msg = validate_capacity(capacity_str) if valid: capacity = int(capacity_str) break print(f"输入错误:{msg}") print(f"课程容量校验通过:{capacity}") # 允许专业校验 restrict = input("是否限制专业?(是/否):").strip().lower() == "是" allowed_majors = [] if restrict: while True: majors_str = input("请输入允许修讀此課程的专业(用逗号分隔):") valid, msg = validate_majors(majors_str) if valid: allowed_majors = [m.strip() for m in majors_str.split(",") if m.strip()] break print(f"输入错误:{msg}") print(f"允许专业校验通过: {allowed_majors}") # 先修课程校验 has_prereq = input("是否有先修课程要求?(是/否): ").strip().lower() == "是" prerequisites = [] if has_prereq: existing_courses = list(admin.courses.keys()) if not existing_courses: print("警告: 系统中暂无其他课程,无法设置先修课程") else: while True: print(f"现有课程: {', '.join(existing_courses)}") prereqs_str = input("请输入先修课程ID(用逗号分隔):") valid, msg = validate_prerequisites(prereqs_str, existing_courses) if valid: prerequisites = [p.strip() for p in prereqs_str.split(",") if p.strip()] break print(f"输入错误:{msg}") print(f"先修课程校验通过: {prerequisites}") # 授课地点校验 classroom = input("请输入上课教室:") if not classroom: print("警告: 未输入教室信息") # 创建课程 new_course = Course(course_ID, name, time, capacity, 0, allowed_majors, prerequisites, classroom) admin.add_course(new_course) elif choice == "2": file_path = input("请输入CSV文件路径:") admin.add_courses_from_csv(file_path) elif choice == "3": course_ID= input("请输入要更新的课程ID:") if course_ID in admin.courses: print("当前课程信息:") course = admin.courses[course_ID] print(f"ID:{course.course_ID},課程名称:{course.name},上課时间:{course.time}") print(f"容量:{course.capacity}, 当前人数:{course.current_student}") print(f"允许专业:{course.allowed_majors},先修课程:{course.prerequisites}") print(f"教室: {course.classroom}") new_name = input(f"新名称(当前: {course.name}):") or course.name new_time_input = input(f"新时间(当前: {course.time}):") if new_time_input: time_slots = [s.strip() for s in new_time_input.split(";") if s.strip()] new_time = [] for slot in time_slots: day, period = slot.split(" ", 1) start, end = period.split("-") new_time.append((day, start.strip(), end.strip())) else: new_time = course.time new_capacity_str = input(f"新容量(当前:{course.capacity}):") or str(course.capacity) valid, msg = validate_capacity(new_capacity_str) if not valid: print(f"错误: {msg}") continue new_capacity = int(new_capacity_str) new_classroom = input(f"新教室(当前:{course.classroom}):") or course.classroom admin.update_course(course_ID, name=new_name, time=new_time, capacity=new_capacity, classroom=new_classroom) else: print("课程不存在") elif choice == "4": course_ID=input("请输入要删除的课程ID:") admin.delete_course(course_ID) elif choice == "5": print("\n=== 所有课程 ===") for course_ID, course in admin.courses.items(): print(f"ID: {course_ID}, 名称: {course.name}, 时间: {course.time}") print(f"容量: {course.capacity}/{course.current_student}, 教室: {course.classroom}") print(f"允许专业: {course.allowed_majors}, 先修课程:{course.prerequisites}") print("---") elif choice == "6": break else: print("无效输入,请重新选择") # 学生菜单 def student_menu(student, admin): while True: print("\n===学生功能頁面===") print("1.选擇课程") print("2.退選课程") print("3.查看课表") print("4.查看可选课程") print("5.返回主菜单") choice = input("请选择功能(1-5):") if choice =="1": print("\n===可选课程列表===") for course_ID,course in admin.courses.items(): if course.current_student < course.capacity: print(f"ID: {course_ID}, 名称: {course.name}, 时间: {course.time}") print(f"容量: {course.capacity}/{course.current_student}, 教室: {course.classroom}") print("---") course_ID = input("请输入要选的课程ID:") if course_ID in admin.courses: student.select_course(admin.courses[course_ID], admin) else: print("课程不存在") elif choice == "2": if not student.selected_courses: print("您当前没有选任何课程") continue print("\n===已选课程===") for course in student.selected_courses: print(f"ID: {course.course_ID}, 名称: {course.name}, 时间: {course.time}") print("---") course_ID=input("请输入要退选的课程ID: ") selected=next((c for c in student.selected_courses if c.course_ID == course_ID), None) if selected: student.drop_course(selected) else: print("無效退課:您先前未选择该课程") elif choice == "3": student.visualize_schedule() elif choice == "4": print("\n===所有可选课程===") for course_ID, course in admin.courses.items(): if course.current_student < course.capacity: print(f"ID: {course_ID}, 名称: {course.name}, 时间: {course.time}") print(f"容量: {course.capacity}/{course.current_student}, 教室: {course.classroom}") print("---") elif choice=="5": break else: print("无效输入,请重新选择") # 主程序 if __name__ == "__main__": # 初始化系统 admin = Admin() #先添加一些示例课程供演示所用 course1 = Course("CS101", "计算机导论", [("周一", "8:00", "10:00"), ("周三", "10:00", "12:00")], capacity=30, allowed_majors=["计算机"], prerequisites=["CS100"], classroom="教学楼A301") course2 = Course("MATH101", "高等数学", [("周二", "1:00", "3:00"), ("周四", "3:00", "5:00")], capacity=40, classroom="教学楼B202") course3 = Course("CS102", "数据结构", [("周一", "10:00", "12:00"), ("周三", "8:00", "10:00")], capacity=25, allowed_majors=["计算机"], prerequisites=["CS101"], classroom="教学楼A302") course4= Course("FD470", "形势与政策", [("周三", "1:00", "4:00")], capacity=48, classroom="教学楼A212") course5= Course("PE102","網球", [("周三", "1:00", "4:00"),("周三","8:00", "10:00")], capacity=25, classroom="體育館116") course6= Course("AC352","中级财务会计", [("周三","1:00", "4:00")], capacity=25, current_student=25, classroom="教学楼C105") admin.add_course(course1) admin.add_course(course2) admin.add_course(course3) admin.add_course(course4) admin.add_course(course5) admin.add_course(course6) # 主循环 while True: choice = main_menu() if choice == "1": # 管理员登录 print("您的身份:管理员") admin_menu(admin) elif choice == "2": # 学生登录 print("您的身份:学生") student_id = input("请输入学号:") major = input("请输入专业:") student = Student(student_id, major) student_menu(student, admin) elif choice == "3": print("感谢使用,再见!") break else: print("无效选择,请重新输入")
最新发布
05-31
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值