贪心算法应用:手术室排程难题详解

Java中的贪心算法应用:手术室排程问题详解
贪心算法是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是全局最好或最优的算法。在手术室排程问题中,贪心算法可以有效地安排手术顺序以最大化手术室利用率或最小化患者等待时间。
一、手术室排程问题概述
手术室排程问题是指如何在有限的手术室资源和时间约束下,合理安排多个手术的顺序和时间,以优化某些目标(如最大化手术室利用率、最小化患者等待时间、最小化手术完成时间等)。
问题描述
给定:
- n个手术,每个手术有一个预计持续时间tᵢ
- m个手术室(通常m=1时为单手术室问题)
- 可能的约束:手术之间的优先级关系、特殊设备需求、医生可用时间等
目标:
找到一个手术排程方案,使得:
- 所有手术都能按时完成
- 优化目标(如总完成时间最短、手术室利用率最高等)
二、贪心算法策略选择
对于手术室排程问题,常用的贪心策略包括:
- 最短手术时间优先(Shortest Processing Time first, SPT):优先安排持续时间短的手术
- 最长手术时间优先(Longest Processing Time first, LPT):优先安排持续时间长的手术
- 最早截止时间优先(Earliest Deadline First, EDF):按照手术的截止时间排序
- 最早开始时间优先(Earliest Start Time First):按照手术的最早可能开始时间排序
三、Java实现详解
1. 定义手术类
public class Surgery
{
private String id;
// 手术ID
private String name;
// 手术名称
private int duration;
// 预计持续时间(分钟)
private int deadline;
// 截止时间(从当天0点开始的分钟数)
private int priority;
// 优先级(1-10, 10为最高)
private String surgeon;
// 主刀医生
private String equipment;
// 所需特殊设备
// 构造函数
public Surgery(String id, String name, int duration, int deadline,
int priority, String surgeon, String equipment) {
this.id = id;
this.name = name;
this.duration = duration;
this.deadline = deadline;
this.priority = priority;
this.surgeon = surgeon;
this.equipment = equipment;
}
// Getters and Setters
public String getId() {
return id;
}
public String getName() {
return name;
}
public int getDuration() {
return duration;
}
public int getDeadline() {
return deadline;
}
public int getPriority() {
return priority;
}
public String getSurgeon() {
return surgeon;
}
public String getEquipment() {
return equipment;
}
@Override
public String toString() {
return String.format("Surgery[id=%s, name=%s, duration=%d, deadline=%d, priority=%d]",
id, name, duration, deadline, priority);
}
}
2. 手术室排程类实现
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class OperatingRoomScheduler
{
private List<
Surgery> surgeries;
private int numOperatingRooms;
private int operatingDayDuration;
// 手术室一天的工作时长(分钟)
public OperatingRoomScheduler(List<
Surgery> surgeries, int numOperatingRooms,
int operatingDayDuration) {
this.surgeries = new ArrayList<
>(surgeries);
this.numOperatingRooms = numOperatingRooms;
this.operatingDayDuration = operatingDayDuration;
}
/**
* 使用最短手术时间优先(SPT)策略进行排程
* @return 排程结果,每个手术室对应一个手术列表
*/
public List<
List<
Surgery>
> scheduleWithSPT() {
// 按照手术持续时间升序排序
Collections.sort(surgeries, Comparator.comparingInt(Surgery::getDuration));
return assignToRooms();
}
/**
* 使用最长手术时间优先(LPT)策略进行排程
* @return 排程结果,每个手术室对应一个手术列表
*/
public List<
List<
Surgery>
> scheduleWithLPT() {
// 按照手术持续时间降序排序
Collections.sort(surgeries, Comparator.comparingInt(Surgery::getDuration).reversed());
return assignToRooms();
}
/**
* 使用最早截止时间优先(EDF)策略进行排程
* @return 排程结果,每个手术室对应一个手术列表
*/
public List<
List<
Surgery>
> scheduleWithEDF() {
// 按照截止时间升序排序
Collections.sort(surgeries, Comparator.comparingInt(Surgery::getDeadline));
return assignToRooms();
}
/**
* 使用优先级加权的贪心策略进行排程
* @return 排程结果,每个手术室对应一个手术列表
*/
public List<
List<
Surgery>
> scheduleWithPriority() {
// 按照优先级降序排序,同优先级按持续时间升序排序
Collections.sort(surgeries,
Comparator.comparingInt(Surgery::getPriority).reversed()
.thenComparingInt(Surgery::getDuration));
return assignToRooms();
}
/**
* 将排序后的手术分配到手术室
* @return 排程结果
*/
private List<
List<
Surgery>
> assignToRooms() {
List<
List<
Surgery>
> schedule = new ArrayList<
>();
List<
Integer> roomEndTimes = new ArrayList<
>();
// 初始化手术室
for (int i = 0; i < numOperatingRooms; i++) {
schedule.add(new ArrayList<
>());
roomEndTimes.add(0);
// 每个手术室初始可用时间为0
}
for (Surgery surgery : surgeries) {
// 找到最早可用的手术室
int earliestRoom = findEarliestAvailableRoom(roomEndTimes, surgery.getDuration());
if (earliestRoom != -1) {
// 计算手术开始时间
int startTime = roomEndTimes.get(earliestRoom);
// 检查是否能在当天完成
if (startTime + surgery.getDuration() <= operatingDayDuration) {
schedule.get(earliestRoom).add(surgery);
roomEndTimes.set(earliestRoom, startTime + surgery.getDuration());
} else {
System.out.println("Warning: Surgery " + surgery.getId() +
" cannot be scheduled today.");
}
} else {
System.out.println("Warning: No available room for surgery " + surgery.getId());
}
}
return schedule;
}
/**
* 找到可以最早安排当前手术的手术室
* @param roomEndTimes 各手术室的当前结束时间
* @param duration 手术持续时间
* @return 手术室索引,-1表示没有可用手术室
*/
private int findEarliestAvailableRoom(List<
Integer> roomEndTimes, int duration) {
int earliestTime = Integer.MAX_VALUE;
int selectedRoom = -1;
for (int i = 0; i < roomEndTimes.size(); i++) {
if (roomEndTimes.get(i) < earliestTime) {
earliestTime = roomEndTimes.get(i);
selectedRoom = i;
}
}
return selectedRoom;
}
/**
* 打印排程结果
* @param schedule 排程结果
*/
public void printSchedule(List<
List<
Surgery>
> schedule) {
System.out.println("Operating Room Schedule:");
System.out.println("========================");
for (int i = 0; i < schedule.size(); i++) {
System.out.println("\nOperating Room " + (i + 1) + ":");
int currentTime = 0;
for (Surgery surgery : schedule.get(i)) {
System.out.printf("%02d:%02d - %02d:%02d | %s (Duration: %d min, Priority: %d)%n",
currentTime / 60, currentTime % 60,
(currentTime + surgery.getDuration()) / 60,
(currentTime + surgery.getDuration()) % 60,
surgery.getName(), surgery.getDuration(), surgery.getPriority());
currentTime += surgery.getDuration();
}
System.out.println("Total utilization: " + currentTime + " minutes (" +
(currentTime * 100.0 / operatingDayDuration) + "%)");
}
}
}
3. 测试用例与运行示例
import java.util.ArrayList;
import java.util.List;
public class Main
{
public static void main(String[] args) {
// 创建手术列表
List<
Surgery> surgeries = new ArrayList<
>();
surgeries.add(new Surgery("S001", "Appendectomy", 90, 480, 8, "Dr. Smith", "Laparoscope"));
surgeries.add(new Surgery("S002", "Cataract", 30, 240, 5, "Dr. Johnson", "Phacoemulsifier"));
surgeries.add(new Surgery("S003", "Hernia Repair", 120, 360, 7, "Dr. Williams", "Mesh"));
surgeries.add(new Surgery("S004", "Knee Arthroscopy", 60, 600, 6, "Dr. Brown", "Arthroscope"));
surgeries.add(new Surgery("S005", "Cholecystectomy", 90, 540, 9, "Dr. Smith", "Laparoscope"));
surgeries.add(new Surgery("S006", "Tonsillectomy", 45, 300, 4, "Dr. Davis", "None"));
surgeries.add(new Surgery("S007", "Hip Replacement", 180, 720, 10, "Dr. Wilson", "Prosthesis"));
surgeries.add(new Surgery("S008", "Carpal Tunnel", 45, 420, 3, "Dr. Miller", "None"));
// 创建排程器 (2个手术室,8小时工作制)
OperatingRoomScheduler scheduler = new OperatingRoomScheduler(surgeries, 2, 8 * 60);
// 使用不同策略进行排程并打印结果
System.out.println("\n=== SPT (Shortest Processing Time first) Schedule ===");
List<
List<
Surgery>
> sptSchedule = scheduler.scheduleWithSPT();
scheduler.printSchedule(sptSchedule);
System.out.println("\n=== LPT (Longest Processing Time first) Schedule ===");
List<
List<
Surgery>
> lptSchedule = scheduler.scheduleWithLPT();
scheduler.printSchedule(lptSchedule);
System.out.println("\n=== EDF (Earliest Deadline first) Schedule ===");
List<
List<
Surgery>
> edfSchedule = scheduler.scheduleWithEDF();
scheduler.printSchedule(edfSchedule);
System.out.println("\n=== Priority-based Schedule ===");
List<
List<
Surgery>
> prioritySchedule = scheduler.scheduleWithPriority();
scheduler.printSchedule(prioritySchedule);
}
}
四、算法分析与优化
1. 时间复杂度分析
- 排序阶段:使用Java的Collections.sort(),时间复杂度为O(n log n)
- 分配阶段:对于n个手术和m个手术室,时间复杂度为O(n m)
- 总体时间复杂度:O(n log n + n m)
2. 空间复杂度分析
- 需要O(n)空间存储手术列表
- 需要O(m)空间存储各手术室的结束时间
- 总体空间复杂度:O(n + m)
3. 算法优化方向
- 更智能的分配策略:当前实现只是简单选择最早可用的手术室,可以改进为考虑更多因素
- 并行处理:对于大规模数据,可以使用并行排序和分配
- 动态调整:在分配过程中动态调整策略
- 约束处理:增加对医生可用性、设备冲突等约束的处理
4. 扩展实现:考虑更多约束
/**
* 考虑医生可用性的排程方法
*/
public List<
List<
Surgery>
> scheduleWithDoctorAvailability(
Map<
String, List<
TimeRange>
> doctorAvailability) {
// 按照优先级和持续时间排序
Collections.sort(surgeries,
Comparator.comparingInt(Surgery::getPriority).reversed()
.thenComparingInt(Surgery::getDuration));
List<
List<
Surgery>
> schedule = new ArrayList<
>();
List<
Integer> roomEndTimes = new ArrayList<
>();
// 初始化手术室
for (int i = 0; i < numOperatingRooms; i++) {
schedule.add(new ArrayList<
>());
roomEndTimes.add(0);
}
for (Surgery surgery : surgeries) {
String surgeon = surgery.getSurgeon();
List<
TimeRange> availableTimes = doctorAvailability.get(surgeon);
int bestRoom = -1;
int bestStartTime = Integer.MAX_VALUE;
// 在所有手术室中寻找最佳安排
for (int room = 0; room < numOperatingRooms; room++) {
int roomAvailableFrom = roomEndTimes.get(room);
// 检查医生在该手术室的可用时间段
for (TimeRange slot : availableTimes) {
int start = Math.max(roomAvailableFrom, slot.getStart());
int end = start + surgery.getDuration();
if (end <= slot.getEnd() && end <= operatingDayDuration) {
if (start < bestStartTime) {
bestStartTime = start;
bestRoom = room;
}
break;
// 找到该医生的第一个可用时段即可
}
}
}
if (bestRoom != -1) {
schedule.get(bestRoom).add(surgery);
roomEndTimes.set(bestRoom, bestStartTime + surgery.getDuration());
} else {
System.out.println("Warning: Could not schedule surgery " + surgery.getId() +
" due to doctor availability constraints.");
}
}
return schedule;
}
五、不同策略的比较与选择
1. SPT (最短手术时间优先)
优点:
- 最小化平均等待时间
- 可以快速完成小手术,提高手术室周转率
- 实现简单
缺点:
- 可能导致大手术等待时间过长
- 可能无法满足紧急但耗时较长的手术需求
2. LPT (最长手术时间优先)
优点:
- 可以优先处理大手术,避免它们阻塞后续排程
- 对于资源受限的情况,可以更好地利用资源
缺点:
- 小手术可能需要等待很长时间
- 可能导致手术室利用率不高
3. EDF (最早截止时间优先)
优点:
- 可以满足有时间要求的手术
- 适用于有严格时间约束的场景
缺点:
- 可能无法优化手术室利用率
- 对截止时间估计敏感
4. 优先级加权策略
优点:
- 可以综合考虑手术紧急程度和持续时间
- 更符合实际医疗场景的需求
缺点:
- 需要准确评估优先级
- 实现相对复杂
六、实际应用中的考虑因素
在实际的手术室排程中,还需要考虑以下因素:
- 手术间的准备时间:不同手术之间的切换可能需要清洁和准备时间
- 资源冲突:特殊设备、医护人员等的可用性
- 急诊手术插入:如何处理急诊手术的插入问题
- 手术间的特殊性:某些手术可能需要特定的手术室
- 医护人员疲劳度:长时间连续手术的影响
- 患者准备时间:麻醉、消毒等术前准备时间
七、完整实现代码
以下是整合了更多实际考虑的完整实现:
import java.util.*;
import java.util.stream.Collectors;
public class AdvancedOperatingRoomScheduler
{
private List<
Surgery> surgeries;
private int numOperatingRooms;
private int operatingDayDuration;
private int cleaningTime;
// 手术间的清洁时间
// 手术室特殊设备配置
private Map<
Integer, Set<
String>
> roomEquipment;
// 医生可用时间段
private Map<
String, List<
TimeRange>
> doctorAvailability;
public AdvancedOperatingRoomScheduler(List<
Surgery> surgeries, int numOperatingRooms,
int operatingDayDuration, int cleaningTime,
Map<
Integer, Set<
String>
> roomEquipment,
Map<
String, List<
TimeRange>
> doctorAvailability) {
this.surgeries = new ArrayList<
>(surgeries);
this.numOperatingRooms = numOperatingRooms;
this.operatingDayDuration = operatingDayDuration;
this.cleaningTime = cleaningTime;
this.roomEquipment = new HashMap<
>(roomEquipment);
this.doctorAvailability = new HashMap<
>(doctorAvailability);
}
/**
* 综合排程方法,考虑多种因素
*/
public ScheduleResult scheduleWithMultipleFactors() {
// 1. 过滤掉无法在今天安排的手术(基于截止时间)
List<
Surgery> feasibleSurgeries = surgeries.stream()
.filter(s -> s.getDeadline() >= s.getDuration())
.collect(Collectors.toList());
// 2. 按照优先级、截止时间和持续时间排序
Collections.sort(feasibleSurgeries,
Comparator.comparingInt(Surgery::getPriority).reversed()
.thenComparingInt(Surgery::getDeadline)
.thenComparingInt(Surgery::getDuration));
// 3. 初始化排程数据结构
List<
OperatingRoom> rooms = new ArrayList<
>();
for (int i = 0; i < numOperatingRooms; i++) {
rooms.add(new OperatingRoom(i, operatingDayDuration,
roomEquipment.getOrDefault(i, Collections.emptySet())));
}
// 4. 分配手术到手术室
List<
Surgery> unassignedSurgeries = new ArrayList<
>();
for (Surgery surgery : feasibleSurgeries) {
boolean assigned = false;
// 尝试将手术分配到最合适的手术室
for (OperatingRoom room : rooms.stream()
.sorted(Comparator.comparingInt(OperatingRoom::getAvailableTime))
.collect(Collectors.toList())) {
if (canAssignSurgeryToRoom(surgery, room)) {
int startTime = findBestStartTime(surgery, room);
if (startTime + surgery.getDuration() <= operatingDayDuration) {
room.assignSurgery(surgery, startTime, cleaningTime);
assigned = true;
break;
}
}
}
if (!assigned) {
unassignedSurgeries.add(surgery);
}
}
// 5. 计算统计信息
int totalScheduledTime = rooms.stream()
.mapToInt(OperatingRoom::getTotalScheduledTime)
.sum();
double utilization = totalScheduledTime * 100.0 / (numOperatingRooms * operatingDayDuration);
// 6. 返回排程结果
return new ScheduleResult(rooms, unassignedSurgeries, utilization);
}
/**
* 检查手术是否可以分配到指定手术室
*/
private boolean canAssignSurgeryToRoom(Surgery surgery, OperatingRoom room) {
// 检查设备需求
if (!surgery.getEquipment().equals("None") &&
!room.getEquipment().contains(surgery.getEquipment())) {
return false;
}
// 检查医生可用性
if (!isDoctorAvailable(surgery.getSurgeon(), room.getAvailableTime(),
surgery.getDuration())) {
return false;
}
return true;
}
/**
* 检查医生在指定时间段是否可用
*/
private boolean isDoctorAvailable(String doctor, int startTime, int duration) {
if (!doctorAvailability.containsKey(doctor)) {
return false;
// 医生不在可用列表中
}
int endTime = startTime + duration;
for (TimeRange slot : doctorAvailability.get(doctor)) {
if (startTime >= slot.getStart() && endTime <= slot.getEnd()) {
return true;
}
}
return false;
}
/**
* 为手术找到最佳开始时间
*/
private int findBestStartTime(Surgery surgery, OperatingRoom room) {
int earliestStart = room.getAvailableTime();
String doctor = surgery.getSurgeon();
// 找到医生在手术室可用时间后的第一个可用时段
for (TimeRange slot : doctorAvailability.get(doctor)) {
if (slot.getEnd() > earliestStart) {
int possibleStart = Math.max(earliestStart, slot.getStart());
if (possibleStart + surgery.getDuration() <= slot.getEnd()) {
return possibleStart;
}
}
}
return earliestStart;
// 如果没有找到更早的医生可用时间,就使用手术室可用时间
}
/**
* 内部类表示手术室
*/
private static class OperatingRoom
{
private final int id;
private final int dayDuration;
private final Set<
String> equipment;
private final List<
AssignedSurgery> assignedSurgeries = new ArrayList<
>();
private int availableTime = 0;
public OperatingRoom(int id, int dayDuration, Set<
String> equipment) {
this.id = id;
this.dayDuration = dayDuration;
this.equipment = equipment;
}
public void assignSurgery(Surgery surgery, int startTime, int cleaningTime) {
assignedSurgeries.add(new AssignedSurgery(surgery, startTime));
this.availableTime = startTime + surgery.getDuration() + cleaningTime;
}
public int getAvailableTime() {
return availableTime;
}
public int getId() {
return id;
}
public Set<
String> getEquipment() {
return equipment;
}
public List<
AssignedSurgery> getAssignedSurgeries() {
return assignedSurgeries;
}
public int getTotalScheduledTime() {
return assignedSurgeries.stream()
.mapToInt(as -> as.surgery.getDuration())
.sum();
}
}
/**
* 内部类表示已分配的手术
*/
private static class AssignedSurgery
{
private final Surgery surgery;
private final int startTime;
public AssignedSurgery(Surgery surgery, int startTime) {
this.surgery = surgery;
this.startTime = startTime;
}
}
/**
* 排程结果类
*/
public static class ScheduleResult
{
private final List<
OperatingRoom> operatingRooms;
private final List<
Surgery> unassignedSurgeries;
private final double utilization;
public ScheduleResult(List<
OperatingRoom> operatingRooms,
List<
Surgery> unassignedSurgeries,
double utilization) {
this.operatingRooms = operatingRooms;
this.unassignedSurgeries = unassignedSurgeries;
this.utilization = utilization;
}
public void printSchedule() {
System.out.println("Advanced Operating Room Schedule");
System.out.println("=================================");
System.out.printf("Overall Utilization: %.2f%%%n", utilization);
if (!unassignedSurgeries.isEmpty()) {
System.out.println("\nUnassigned Surgeries (" + unassignedSurgeries.size() + "):");
unassignedSurgeries.forEach(s ->
System.out.println(" - " + s.getName() + " (ID: " + s.getId() + ")"));
}
for (OperatingRoom room : operatingRooms) {
System.out.println("\nOperating Room " + (room.getId() + 1) +
" (Equipment: " + String.join(", ", room.getEquipment()) + ")");
System.out.println("-------------------------------------------------");
for (AssignedSurgery as : room.getAssignedSurgeries()) {
Surgery s = as.surgery;
int start = as.startTime;
int end = start + s.getDuration();
System.out.printf("%02d:%02d-%02d:%02d | %-20s | %-10s | Dur: %3d min | Prio: %d%n",
start / 60, start % 60,
end / 60, end % 60,
s.getName(), s.getSurgeon(),
s.getDuration(), s.getPriority());
}
System.out.printf("Total scheduled time: %d minutes (%.1f%%)%n",
room.getTotalScheduledTime(),
room.getTotalScheduledTime() * 100.0 / dayDuration);
}
}
}
/**
* 时间段类
*/
public static class TimeRange
{
private final int start;
private final int end;
public TimeRange(int start, int end) {
this.start = start;
this.end = end;
}
public int getStart() {
return start;
}
public int getEnd() {
return end;
}
}
}
八、总结
贪心算法在手术室排程问题中提供了一种高效且易于实现的解决方案。通过选择合适的贪心策略(如SPT、LPT、EDF或优先级加权),可以在合理的时间内得到较好的排程结果。然而,实际应用中需要考虑更多约束和现实因素,如:
- 手术间的准备和清洁时间
- 特殊设备和人员的可用性
- 急诊手术的动态插入
- 多目标优化(如同时考虑利用率、等待时间和优先级)
对于更复杂的情况,可能需要结合其他算法如动态规划、回溯算法或元启发式算法(如遗传算法、模拟退火等)来获得更好的解决方案。但在许多实际场景中,精心设计的贪心算法已经能够提供足够好的结果,特别是在需要快速决策的情况下。

浙公网安备 33010602011771号