人員排班與輪值系統

人員排班是 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("找不到可行排班!")

會員專屬免費教學

本章節為註冊會員專屬的免費開放內容!請先登入或註冊會員,即可立即解鎖閱讀。

立即登入 / 註冊