人員排班與輪值系統
人員排班是 ILP/CP 最經典的應用場景之一:如何安排員工的班次,在滿足所有人力需求的同時,最大化員工滿意度?
模型建立
from ortools.sat.python import cp_model
# === 定義排班需求 ===
DAYS = 7 # 一週 7 天
SHIFTS = ["早班", "中班", "晚班"]
NUM_SHIFTS = len(SHIFTS)
EMPLOYEES = [f"員工{i}" for i in range(10)]
NUM_EMPLOYEES = len(EMPLOYEES)
# 每天每個班次需要的人力
shift_requirements = {
(0, 0): 2, (0, 1): 2, (0, 2): 1, # 週一
(1, 0): 2, (1, 1): 2, (1, 2): 2, # 週二
(2, 0): 2, (2, 1): 1, (2, 2): 1, # 週三
(3, 0): 3, (3, 1): 2, (3, 2): 1, # 週四
(4, 0): 3, (4, 1): 2, (4, 2): 2, # 週五
(5, 0): 1, (5, 1): 1, (5, 2): 2, # 週六
(6, 0): 1, (6, 1): 1, (6, 2): 2, # 週日
}
# === 建立模型 ===
model = cp_model.CpModel()
# 變數:shifts[e][d][s] = 1 表示員工 e 在第 d 天的第 s 班
shifts = {}
for e in range(NUM_EMPLOYEES):
for d in range(DAYS):
for s in range(NUM_SHIFTS):
shifts[(e, d, s)] = model.NewBoolVar(f"shift_e{e}_d{d}_s{s}")
# 限制 1:每個班次的人力需求必須滿足
for d in range(DAYS):
for s in range(NUM_SHIFTS):
if (d, s) in shift_requirements:
model.Add(
sum(shifts[(e, d, s)] for e in range(NUM_EMPLOYEES))
== shift_requirements[(d, s)]
)
# 限制 2:每個員工每天最多一個班次
for e in range(NUM_EMPLOYEES):
for d in range(DAYS):
model.Add(sum(shifts[(e, d, s)] for s in range(NUM_SHIFTS)) <= 1)
# 限制 3:每個員工每週最多 5 天
for e in range(NUM_EMPLOYEES):
model.Add(
sum(shifts[(e, d, s)] for d in range(DAYS) for s in range(NUM_SHIFTS))
<= 5
)
# 限制 4:不能連上晚班+早班(禁止晚班後隔天早班)
for e in range(NUM_EMPLOYEES):
for d in range(DAYS - 1):
model.Add(shifts[(e, d, 2)] + shifts[(e, d + 1, 0)] <= 1)
# 目標:公平分配(最大化最低班次數)
min_shifts = model.NewIntVar(0, DAYS * NUM_SHIFTS, "min_shifts")
for e in range(NUM_EMPLOYEES):
total = sum(shifts[(e, d, s)] for d in range(DAYS) for s in range(NUM_SHIFTS))
model.Add(min_shifts <= total)
model.Maximize(min_shifts)
# === 求解 ===
solver = cp_model.CpSolver()
solver.parameters.max_time_in_seconds = 30
status = solver.Solve(model)
if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
print(f"\n=== 人員排班結果 ===")
# 建立排班表
schedule = [["" for _ in range(DAYS)] for _ in range(NUM_EMPLOYEES)]
for e in range(NUM_EMPLOYEES):
for d in range(DAYS):
for s in range(NUM_SHIFTS):
if solver.Value(shifts[(e, d, s)]):
schedule[e][d] = SHIFTS[s]
# 輸出
headers = ["員工"] + [f"第{d+1}天" for d in range(DAYS)]
print(" | ".join(headers))
print("-" * 50)
for e in range(NUM_EMPLOYEES):
row = [EMPLOYEES[e]] + [schedule[e][d] or "-" for d in range(DAYS)]
print(" | ".join(row))
# 統計
print(f"\n=== 員工班次統計 ===")
for e in range(NUM_EMPLOYEES):
total = sum(1 for d in range(DAYS) if schedule[e][d])
print(f"{EMPLOYEES[e]}: {total} 天")
else:
print("找不到可行排班!")