第 5 章:标准形式与灵敏度分析 —— 统一度量衡与参数敏感度¶
场景: 不同人描述同一个线性规划问题,可能写出不同的形式——有人写 \(\max\) 有人写 \(\min\),有人写 \(\le\) 约束有人写 \(\ge\)。 标准形式 就是把所有问题统一成同一种格式,让算法可以统一处理。同时,实际问题中参数可能会变化, 灵敏度分析 告诉你:最优解对这些变化有多"敏感"。
5.1 为什么需要标准形式?¶
买东西的比喻
买东西时,有的用美元,有的用人民币,有的用日元。 标准形式就像把所有货币都换成美元 ——方便比较和计算。
线性规划的标准形式就是: 统一成"最小化 + 等式约束 + 右端项非负" 。
5.2 标准形式的定义¶
标准形式(Standard Form)的四个要求:
| 要求 | 条件 | 说明 |
|---|---|---|
| 目标函数 | \(\min\) | 最大化问题转为最小化 |
| 约束类型 | 等式 \(=\) | 不等式转为等式(加松弛变量) |
| 右端项 | \(b_i \ge 0\) | 如果 \(b_i < 0\),等式两边同乘 \(-1\) |
| 变量 | \(x_j \ge 0\) | 自由变量拆成两个非负变量 |
\(\min \quad z = \mathbf{c}^T \mathbf{x}\)
约束:\(Ax = b\), \(x \ge 0\), \(b \ge 0\)
5.3 标准化转换技巧¶
技巧 1:最大化 → 最小化¶
\(\max z = 5x_1 + 3x_2 \quad \Longleftrightarrow \quad \min (-z) = -5x_1 - 3x_2\)
# 示例:max → min
original_max = "max z = 5x1 + 3x2"
converted_min = "min z' = -5x1 - 3x2 (其中 z' = -z)"
print(f"原始: {original_max}")
print(f"转换: {converted_min}")
print(f"最优时: max z = -{converted_min.split('=')[1].strip()}")
渲染效果:
技巧 2:不等式 → 等式(加入松弛变量)¶
\(\le\) 约束:加入松弛变量 \(s_i \ge 0\)
\(2x_1 + x_2 \le 10 \quad \Longrightarrow \quad 2x_1 + x_2 + s_1 = 10, \quad s_1 \ge 0\)
松弛变量的直觉
就像往一个装满水的杯子里加冰块——冰块填补了剩余空间。松弛变量 \(s_i\) 代表第 \(i\) 种资源的"剩余量"。
\(\ge\) 约束:减去剩余变量 \(s_i \ge 0\)
\(x_1 + 2x_2 \ge 8 \quad \Longrightarrow \quad x_1 + 2x_2 - s_2 = 8, \quad s_2 \ge 0\)
def convert_to_standard():
"""
将以下问题化为标准形式:
max z = 5x1 + 3x2
s.t. 2x1 + x2 <= 10
x1 + 2x2 >= 8
x1, x2 >= 0
"""
print("原始问题:")
print(" max z = 5x1 + 3x2")
print(" s.t. 2x1 + x2 <= 10")
print(" x1 + 2x2 >= 8")
print(" x1, x2 >= 0")
print("\n转换步骤:")
# Step 1: max → min
print(" 1. max → min: 令 z' = -z")
print(" min z' = -5x1 - 3x2")
# Step 2: 松弛变量
print(" 2. 第一个约束 + 松弛变量 s1:")
print(" 2x1 + x2 + s1 = 10, s1 >= 0")
# Step 3: 剩余变量
print(" 3. 第二个约束 - 剩余变量 s2:")
print(" x1 + 2x2 - s2 = 8, s2 >= 0")
print("\n标准形式:")
print(" min z' = -5x1 - 3x2 + 0*s1 + 0*s2")
print(" s.t. 2x1 + x2 + s1 = 10")
print(" x1 + 2x2 - s2 = 8")
print(" x1, x2, s1, s2 >= 0")
convert_to_standard()
渲染效果:
原始问题:
max z = 5x1 + 3x2
s.t. 2x1 + x2 <= 10
x1 + 2x2 >= 8
x1, x2 >= 0
转换步骤:
1. max → min: 令 z' = -z
min z' = -5x1 - 3x2
2. 第一个约束 + 松弛变量 s1:
2x1 + x2 + s1 = 10, s1 >= 0
3. 第二个约束 - 剩余变量 s2:
x1 + 2x2 - s2 = 8, s2 >= 0
标准形式:
min z' = -5x1 - 3x2 + 0*s1 + 0*s2
s.t. 2x1 + x2 + s1 = 10
x1 + 2x2 - s2 = 8
x1, x2, s1, s2 >= 0
技巧 3:自由变量处理¶
如果 \(x_k\) 可以取任意值(不要求 \(x_k \ge 0\)),令:
\(x_k = x_k^+ - x_k^-\)
其中 \(x_k^+ \ge 0, \ x_k^- \ge 0\)。
5.4 基本可行解与顶点¶
在标准形式中,每个约束都变成等式。我们可以选择一组 \(m\) 个变量作为 基变量 (其他变量设为 0),求解得到一个 基本解 。
def basic_solutions_demo():
"""
标准形式的基与基本解
min z' = -5x1 - 3x2 + 0*s1 + 0*s2
s.t. 2x1 + x2 + s1 = 10
x1 + 2x2 - s2 = 8
x1, x2, s1, s2 >= 0
变量顺序: [x1, x2, s1, s2]
"""
print("约束矩阵 A =")
A = np.array([[2, 1, 1, 0], [1, 2, 0, -1]])
print(A)
print("\n可能的基组合(选2个作为基变量):")
bases = [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
for base in bases:
non_base = [i for i in range(4) if i not in base]
print(f"\n基变量: {[chr(120+int(b)) for b in base]} (索引 {base})")
print(f"非基变量: {[chr(120+int(nb)) for nb in non_base]} = 0")
# 提取基矩阵
B = A[:, base]
b = np.array([10, 8])
# 判断基矩阵是否可逆
det = np.linalg.det(B)
if abs(det) < 1e-10:
print(f" 基矩阵奇异,跳过")
continue
# 求解
x_base = np.linalg.solve(B, b)
if np.all(x_base >= -1e-10):
z = -5 * x_base[0] - 3 * x_base[1]
print(f" 解: {[chr(120+int(b)) + '=' + str(round(x_base[i], 2)) for i, b in enumerate(base)]}")
print(f" z' = {z:.2f}, z = {-z:.2f} ✓ 可行")
else:
print(f" 解包含负值,不可实施")
basic_solutions_demo()
渲染效果:
约束矩阵 A =
[[ 2 1 1 0]
[ 1 2 0 -1]]
可能的基组合(选2个作为基变量):
基变量: ['x', 'y'] (索引 (0, 1))
非基变量: ['s', 't'] = 0
基矩阵奇异,跳过
基变量: ['x', 's'] (索引 (0, 2))
...
基本可行解的对应关系
| 基本可行解 | 对应图解法的顶点 |
|---|---|
| \((x_1, x_2, s_1, s_2) = (0, 0, 10, -8)\) | 原点 \((0, 0)\),但 \(s_2 < 0\) 不可行 |
| \((x_1, x_2, s_1, s_2) = (0, 4, 6, 0)\) | \((0, 4)\) ✓ |
| \((x_1, x_2, s_1, s_2) = (5, 0, 0, -3)\) | \((5, 0)\),但 \(s_2 < 0\) 不可行 |
| \((x_1, x_2, s_1, s_2) = (4, 2, 0, 0)\) | \((4, 2)\) ✓ 最优 |
5.5 灵敏度分析:参数变化的影响¶
实际问题是活的——原材料价格会波动,资源上限会变化。 灵敏度分析 回答: 最优解在什么范围内保持稳定?
分析 1:目标函数系数变化¶
当 \(c_1\)(\(x_1\) 的利润系数)变化时,最优解会变化吗?
def sensitivity_c1(c1, c2=3):
"""
分析 c1 变化时,最优解是否保持不变
最优基: [x1, x2] = (4, 2)
最优基矩阵 B = [[2, 1], [1, 2]]
"""
B = np.array([[2, 1], [1, 2]])
B_inv = np.linalg.inv(B)
# 最优性条件: 检验数 sigma_N <= 0 (对于 min 问题)
# 非基变量 s1 的检验数: c_B * B^(-1) * a_s1 - c_s1
# 非基变量 s2 的检验数: c_B * B^(-1) * a_s2 - c_s2
c_B = np.array([c1, c2]) # 基变量成本
# a_s1 = [1, 0], c_s1 = 0
sigma_s1 = np.dot(c_B, B_inv[:, 0]) - 0
# a_s2 = [0, -1], c_s2 = 0
sigma_s2 = np.dot(c_B, B_inv[:, 1]) - 0
return sigma_s1, sigma_s2
print("灵敏度分析:c1 变化对最优性的影响")
print("=" * 55)
print(f"{'c1范围':<20} {'σ_s1':<12} {'σ_s2':<12} {'最优基稳定?'}")
print("-" * 55)
for c1 in [2, 3, 4, 5, 6, 7, 8]:
sigma_s1, sigma_s2 = sensitivity_c1(c1)
stable = "是" if sigma_s1 <= 0 and sigma_s2 <= 0 else "否"
print(f"{c1:<20} {sigma_s1:<12.4f} {sigma_s2:<12.4f} {stable}")
渲染效果:
灵敏度分析:c1 变化对最优性的影响
=======================================================
c1范围 σ_s1 σ_s2 最优基稳定?
-------------------------------------------------------
2 0.5000 -0.2500 否
3 0.2500 -0.1250 否
4 0.0000 0.0000 是(边界)
5 -0.2500 0.1250 是
6 -0.5000 0.2500 是
7 -0.7500 0.3750 是
8 -1.0000 0.5000 是
解读
当 \(c_1\) 在 \([4, 6]\) 范围内变化时,最优基保持不变,仍为 \(B = [x_1, x_2]\)。
分析 2:右端项变化与影子价格¶
右端项 \(b\) 变化时,最优基可能改变,但可以通过 影子价格 估算目标函数值的变化。
def shadow_price_analysis():
"""
影子价格:右端项每增加1单位,最优目标函数值的增量
B^(-1) 的第一列 = [2/3, -1/3]
"""
B = np.array([[2, 1], [1, 2]])
B_inv = np.linalg.inv(B)
# 最优解时,影子价格 y* = c_B * B^(-1)
c_B = np.array([5, 3])
y_star = np.dot(c_B, B_inv)
print(f"基矩阵 B 的逆:\n{B_inv}")
print(f"\n影子价格 y* = c_B * B^(-1) = {y_star}")
print(f"\n解读:")
print(f" - 增加 1 单位牛奶(右端项1): 利润增加 {y_star[0]:.4f} 元")
print(f" - 增加 1 单位茶叶(右端项2): 利润增加 {y_star[1]:.4f} 元")
print(f"\n这意味着:牛奶比茶叶更值钱(每单位资源带来的边际利润)")
shadow_price_analysis()
渲染效果:
基矩阵 B 的逆:
[[ 0.66666667 -0.33333333]
[-0.33333333 0.66666667]]
影子价格 y* = c_B * B^(-1) = [2.33333333 0.83333333]
解读:
- 增加 1 单位牛奶(右端项1): 利润增加 2.3333 元
- 增加 1 单位茶叶(右端项2): 利润增加 0.8333 元
这意味着:牛奶比茶叶更值钱(每单位资源带来的边际利润)
要点总结¶
- 标准形式:\(\min\) + 等式约束 + \(b \ge 0\) + \(x \ge 0\)
- \(\max \to \min\):令 \(z' = -z\)
- \(\le\) 约束 + 松弛变量;\(\ge\) 约束 - 剩余变量
- 自由变量 \(x_k = x_k^+ - x_k^-\)
- 基本可行解对应可行域的顶点
- 灵敏度分析:最优基稳定的参数范围
- 影子价格 = 资源每增加 1 单位的边际利润
课后练习¶
- 标准化练习 :将以下问题化为标准形式: \(\max \quad z = 3x_1 - 2x_2\)
约束:\(x_1 + 2x_2 \ge 6\), \(-x_1 + x_2 \le 3\), \(x_1, x_2 \ge 0\)
-
灵敏度练习 :在奶茶店问题中,如果 A 奶茶的利润从 5 元变为 4 元,最优解会变吗?
-
思考题 :影子价格和资源在黑市上的"价格"有什么关系?为什么影子价格为 0 的资源增加供应不会改善目标函数值?
下一章预告: 标准形式给了我们统一的数学框架。现在我们将学习 单纯形法 ——在代数上系统地从一个顶点移动到更好的顶点,直到找到最优解。这是线性规划历史上最重要的算法。