最短路问题求解 | Python+Gurobi-COPT-SCIP

沉寂了很久,接下来小编要朝着目标努力了。目前初步规划了2期内容:用 Python + 三类主流求解器(Gurobi、COPT、SCIP),把网络流问题和铁路运输经典问题两条线一以贯之地讲透。第 1 期讲网络流家族,第 2 期沿着铁路运输组织规划流程逐个建模求解。

1. 问题场景

这个场景你一定不陌生:
早高峰你要从家赶到公司打卡,路网中有几个路口和地标,每条路的通行时间不同(堵车程度不同)。你想知道:走哪条路总时间最短?
把路网抽象一下:

  • 节点:家、若干路口/地标、公司
  • 边:路口间的路段,带通行时间(分钟)
  • 起点:家
  • 终点:公司
  • 目标:家到公司的总通行时间最小

这就是一个最典型的最短路问题:在带权有向图上,求从起点到终点权值和最小的路径。

2. 数学建模

2.1 集合、参数与决策变量

在这里插入图片描述

2.2 目标函数与约束条件

在这里插入图片描述

3. 求解案例

为了让模型落地且便于读者手算验证,我们构造一个小型路网。

在这里插入图片描述

4. Python+Gurobi求解

from gurobipy import GRB,Model,quicksum

# ============================================================
# 1. 定义案例数据
# ============================================================
N = [
    1, 2, 3, 4, 5, 6
]
A = [
    (1, 2),
    (1, 3),
    (2, 3),
    (2, 4),
    (3, 4),
    (3, 5),
    (4, 6),
    (5, 4),
    (5, 6)
]
C = {
    (1, 2): 6,
    (1, 3): 4,
    (2, 3): 2,
    (2, 4): 2,
    (3, 4): 1,
    (3, 5): 2,
    (4, 6): 7,
    (5, 4): 1,
    (5, 6): 3
}
s = 1
t = 6
b = {
    1: 1,
    2: 0,
    3: 0,
    4: 0,
    5: 0,
    6: -1
}

# ============================================================
# 2. 建模与求解
# ============================================================
def build_and_solve(N: list, A: list, C: dict,b: dict, s: int, t: int):
    # ---- 建模 ----
    model = Model("ShortestPath_Gurobi")

    # 决策变量 x_{ij} in {0,1}
    x = model.addVars(A, vtype=GRB.BINARY, name="x")

    # 目标: min sum c_{ij} * x_{ij}
    model.setObjective(
        quicksum(C[i, j] * x[i, j] for (i, j) in A),
        GRB.MINIMIZE,
    )
    # 约束: 流平衡
    for i in N:
        out_expr = quicksum(x[i, j] for (ii, j) in A if ii == i)
        in_expr = quicksum(x[j, i] for (j, ii) in A if ii == i)
        model.addConstr(out_expr - in_expr == b[i], name=f"flow_balance_{i}")

    # ---- 求解 ----
    model.optimize()

    # ---- 结果 ----
    status = model.Status
    if model.Status == GRB.OPTIMAL:
        chosen = [(i, j) for (i, j) in A if x[i, j].X > 0.5]
        total = model.ObjVal
        path = reconstruct_path(chosen, s, t)
        return {
            "status": "OPTIMAL",
            "total_cost": total,
            "path": path,
            "chosen_edges": chosen,
        }
    return {"status": f"status={status}", "total_cost": None, "path": None, "chosen_edges": None}


def reconstruct_path(chosen, s, t):
    """由选中的边集合按出度链还原有序路径。"""
    nxt = {i: j for (i, j) in chosen}
    path = [s]
    cur = s
    while cur != t:
        cur = nxt[cur]
        path.append(cur)
    return path

# ============================================================
# 3. 主流程
# ============================================================
def main():
    res = build_and_solve(N, A, C,b, s, t)
    if res["status"] == "OPTIMAL":
        print(f"求解状态: OPTIMAL")
        print(f"目标值:{res['total_cost']}")
        print(f"选中边: {res['chosen_edges']}")
        print(f"最短路径: {res['path']}")

    else:
        print(f"求解状态: {res['status']} (无可行径路)")


if __name__ == "__main__":
    main()

5. Python+COPT求解

from coptpy import COPT, Envr

# ============================================================
# 1. 定义案例数据
# ============================================================
N = [
    1, 2, 3, 4, 5, 6
]
A = [
    (1, 2),
    (1, 3),
    (2, 3),
    (2, 4),
    (3, 4),
    (3, 5),
    (4, 6),
    (5, 4),
    (5, 6)
]
C = {
    (1, 2): 6,
    (1, 3): 4,
    (2, 3): 2,
    (2, 4): 2,
    (3, 4): 1,
    (3, 5): 2,
    (4, 6): 7,
    (5, 4): 1,
    (5, 6): 3
}
s = 1
t = 6
b = {
    1: 1,
    2: 0,
    3: 0,
    4: 0,
    5: 0,
    6: -1
}


# ============================================================
# 2. 建模与求解
# ============================================================
def build_and_solve(N: list, A: list, C: dict,b: dict, s: int, t: int):

    # ---- 建模 ----
    model = Envr().createModel("ShortestPath_COPT")

    # 决策变量 x_{ij} in {0,1}
    x = model.addVars(A,vtype=COPT.BINARY, nameprefix="x")

    # 目标: min sum c_{ij} * x_{ij}
    model.setObjective(sum(C[i, j] * x[i, j] for (i, j) in A), COPT.MINIMIZE)

    # 约束: 流平衡
    for i in N:
        out_expr = sum(x[i, j] for (ii, j) in A if ii == i)
        in_expr = sum(x[j, i] for (j, ii) in A if ii == i)
        model.addConstr(out_expr - in_expr == b[i], name=f"flow_balance_{i}")

    # ---- 求解 ----
    model.solve()

    # ---- 结果 ----
    status = model.status
    if status == COPT.OPTIMAL:
        chosen = [(i, j) for (i, j) in A if x[i, j].X > 0.5]
        total = float(model.getAttr(COPT.Attr.BestObj))
        path = reconstruct_path(chosen, s, t)
        return {"status": "OPTIMAL", "total_cost": total, "path": path, "chosen_edges": chosen}
    return {"status": f"status={status}", "total_cost": None, "path": None, "chosen_edges": None}


def reconstruct_path(chosen, s, t):
    """由选中的边集合按出度链还原有序路径。"""
    nxt = {i: j for (i, j) in chosen}
    path = [s]
    cur = s
    while cur != t:
        cur = nxt[cur]
        path.append(cur)
    return path


# ============================================================
# 3. 主流程
# ============================================================
def main():
    res = build_and_solve(N, A, C,b, s, t)
    if res["status"] == "OPTIMAL":
        print(f"求解状态: OPTIMAL")
        print(f"目标值:{res['total_cost']}")
        print(f"选中边: {res['chosen_edges']}")
        print(f"最短路径: {res['path']}")

    else:
        print(f"求解状态: {res['status']} (无可行径路)")


if __name__ == "__main__":
    main()

6. Python+SCIP求解

from pyscipopt import Model, quicksum

# ============================================================
# 1. 数据读取
# ============================================================
N = [
    1, 2, 3, 4, 5, 6
]
A = [
    (1, 2),
    (1, 3),
    (2, 3),
    (2, 4),
    (3, 4),
    (3, 5),
    (4, 6),
    (5, 4),
    (5, 6)
]
C = {
    (1, 2): 6,
    (1, 3): 4,
    (2, 3): 2,
    (2, 4): 2,
    (3, 4): 1,
    (3, 5): 2,
    (4, 6): 7,
    (5, 4): 1,
    (5, 6): 3
}
s = 1
t = 6
b = {
    1: 1,
    2: 0,
    3: 0,
    4: 0,
    5: 0,
    6: -1
}

# ============================================================
# 2. 建模与求解
# ============================================================
def build_and_solve(N: list, A: list, C: dict,b: dict, s: int, t: int):
    # ---- 建模 ----
    model = Model("ShortestPath_SCIP")

    # 决策变量 x_{ij} in {0,1}
    x = {}
    for (i, j) in A:
        x[i, j] = model.addVar(lb=0, ub=1, vtype="BINARY", name=f"x_{i}_{j}")

    # 目标: min sum c_{ij} * x_{ij}
    model.setObjective(quicksum(C[i, j] * x[i, j] for (i, j) in A), "minimize")

    # 约束: 流平衡
    for i in N:
        out_expr = quicksum(x[i, j] for (ii, j) in A if ii == i)
        in_expr = quicksum(x[j, i] for (j, ii) in A if ii == i)
        model.addCons(out_expr - in_expr == b[i], name=f"flow_balance_{i}")

    # ---- 求解 ----
    model.setRealParam("limits/time", 30)
    model.optimize()

    # ---- 结果 ----
    status = model.getStatus()
    if status == "optimal":
        chosen = [(i, j) for (i, j) in A if model.getVal(x[i, j]) > 0.5]
        total = model.getObjVal()
        path = reconstruct_path(chosen, s, t)
        return {"status": "OPTIMAL", "total_cost": total, "path": path, "chosen_edges": chosen}
    return {"status": f"status={status}", "total_cost": None, "path": None, "chosen_edges": None}


def reconstruct_path(chosen, s, t):
    """由选中的边集合按出度链还原有序路径。"""
    nxt = {i: j for (i, j) in chosen}
    path = [s]
    cur = s
    while cur != t:
        cur = nxt[cur]
        path.append(cur)
    return path


# ============================================================
# 3. 主流程
# ============================================================
def main():
    res = build_and_solve(N, A, C, b, s, t)
    if res["status"] == "OPTIMAL":
        print(f"求解状态: OPTIMAL")
        print(f"目标值:{res['total_cost']}")
        print(f"选中边: {res['chosen_edges']}")
        print(f"最短路径: {res['path']}")

    else:
        print(f"求解状态: {res['status']} (无可行径路)")


if __name__ == "__main__":
    main()

参考资料

[1] Ahuja, Ravindra K. et al. “Network Flows: Theory, Algorithms, and Applications.” (1993).

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Better.C

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值