算法一:线性规划 (LP) —— 连续决策的“最优配比”
1.1线性规划(Linear Programming)模型最核心的三要素
1. 决策变量(Decision Variables)
这是你要做的“选择题”。它们通常是未知数x1, x2, ..., xn,代表了你能掌控的具体行动方案(比如生产多少件产品 A、调配多少吨物资 B)。
2. 目标函数(Objective Function)
这是你要追求的“终极目标”。它是决策变量的线性函数,通常写为或
(比如追求利润最大化,或成本最小化)。
3. 约束条件(Constraints)
这是你面临的“现实限制”。它们是决策变量的线性等式或不等式组(比如原材料不能超过库存、人工工时有限、市场需求有上限等)。
1.2 线性规划模型建立步骤:
从实际问题中建立数学模型一般有以下三个步骤:
根据影响所要达到目的的因素找到决策变量
由决策变量和所在达到目的之间的函数关系确定目标函数
由决策变量所受的限制条件确定决策变量所要满足的约束条件
1.3 数学标准形式(一定要写在论文里的):
(约束条件)
(决策变量非负)
其中,c 是价值向量,x 是决策变量,A 是技术系数矩阵,b 是资源限量。
核心数学逻辑:可行域是凸集,最优解一定在凸集的顶点(基可行解)上。不需要遍历,单纯形法就是沿着棱找顶点。
经典例题(生产计划问题):
某工厂生产甲、乙两种产品。生产甲产品每件需消耗原料 A = 2kg,原料 B = 1kg;生产乙产品每件需消耗原料 A = 1kg,原料 B = 3kg。工厂现有原料 A = 100kg,原料 B = 120kg。甲产品每件利润40 元,乙产品每件利润30 元。问甲乙各生产多少,利润最大?建模步骤(手把手写式子):
设决策变量:设甲产品生产 x1 件,乙产品生产 x2 件。
写目标函数(求最大,统一转为最小 min−z):
写约束条件(资源不能超):
原料 A 限制:
原料 B 限制:
非负性:
求解结果:用
scipy.optimize.linprog或 Lingo 求解,得到 x1 = 36 件,x2 = 28 件。最大利润 z = 40×36 + 30×28 = 2280 元。纯小白避坑:约束条件必须统一方向。如果你的题里既有“≥”又有“≤”,记得乘以 -1 统一成“≤”再写进矩阵 A。另外,线性规划绝对不允许出现
这种乘法项,否则就是非线性了,算法要换!
import numpy as np from scipy.optimize import linprog 1. 目标函数系数 (注意:linprog 只能求最小值,求最大值要加负号) 原题:max z = 40x1 + 30x2 转换:min (-z) = -40x1 - 30x2 c = [-40, -30] 2. 不等式约束矩阵 (A_ub * x <= b_ub) 2x1 + 1x2 <= 100 1x1 + 3x2 <= 120 A_ub = [[2, 1], [1, 3]] b_ub = [100, 120] 3. 变量边界 (x1 >= 0, x2 >= 0) bounds = [(0, None), (0, None)] # None 代表正无穷 4. 求解 result = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs') 5. 打印结果 【输出】: x1=36.00, x2=28.00, 利润=2280.00算法二:整数 / 0-1 规划 —— 离散决策的“是非选择”
在规划问题中,有些最优解可能是分数或小数,但对于某些具体问题,常要求某些变量(全部或部分)的解必须是整数。例如,当变量代表的是机器的台数、工作的人数或装货的车数等。为了满足整数的要求,初看起来似乎只要把已有的非整数解舍入化整数就可以了。实际上化整后的数不见得是可行解和最优解,所以应该有特殊的方法来求解整数规划。在整数规划中,如果所有变量都限制为整数,则称为纯整数规划;如果仅一部分变量限制为整数,则称为混合整数规划。整数规划的一种特殊情形是0-1 规划,它的变量仅限于 0 或 1。
数学标准形式(在 LP 基础上加一行限制):
(约束条件)
(整数约束) 或
(二值约束)
核心数学逻辑:可行域不再是连续的凸集,而是离散的格点。没有了“顶点优先”的性质,只能用分支定界法(砍掉非整数区域)或割平面法(切掉不含整数的部分)来隐式枚举。
经典例题(指派问题 / 任务分配):
有 3 项任务(A、B、C)必须分别交给 3 个工人(甲、乙、丙)一人完成一项。不同工人做不同任务的成本如下表。求总成本最小的指派方案。
| 工人\任务 | A | B | C |
|---|---|---|---|
| 甲 | 6元 | 8元 | 5元 |
| 乙 | 7元 | 4元 | 9元 |
| 丙 | 3元 | 6元 | 7元 |
建模步骤(手把手写式子):
设 0-1 决策变量:设 xij 表示“是否派工人 i 去做任务 j”。(例如 x甲A=1 表示派甲做 A,=0 表示不派)。
写目标函数(总成本最小):
写约束条件(每个任务必须且只能有 1 个人做;每个人必须且只能做 1 个任务):
任务 A 只能被一人做:
任务 B 只能被一人做:
任务 C 只能被一人做:
甲只能做一个任务:
求解结果:用
scipy.optimize.milp求解,最优解是甲做 C(5 元)、乙做 B(4 元)、丙做 A(3 元),总成本12 元。纯小白避坑(线性化技巧):比赛中常遇到“如果选了 A,就必须选 B”的逻辑。绝对不能在论文里写 if 语句!必须转化为线性约束:xA−xB≤0。意思是:如果 xA=1,那么 xB 必须等于 1 才能满足 1−xB≤0;如果 xA=0,xB 随便。
import numpy as np from scipy.optimize import milp, LinearConstraint, Bounds 1. 目标函数系数 (按行展开: 甲A, 甲B, 甲C, 乙A, 乙B, 乙C, 丙A, 丙B, 丙C) c = [6, 8, 5, # 甲 7, 4, 9, # 乙 3, 6, 7] # 丙 2. 约束矩阵 (共6个约束,每个约束都是 "== 1") 约束1(甲): 甲A + 甲B + 甲C = 1 约束2(乙): 乙A + 乙B + 乙C = 1 约束3(丙): 丙A + 丙B + 丙C = 1 约束4(A任务): 甲A + 乙A + 丙A = 1 约束5(B任务): 甲B + 乙B + 丙B = 1 约束6(C任务): 甲C + 乙C + 丙C = 1 A_eq = [ [1, 1, 1, 0, 0, 0, 0, 0, 0], # 甲 [0, 0, 0, 1, 1, 1, 0, 0, 0], # 乙 [0, 0, 0, 0, 0, 0, 1, 1, 1], # 丙 [1, 0, 0, 1, 0, 0, 1, 0, 0], # 任务A [0, 1, 0, 0, 1, 0, 0, 1, 0], # 任务B [0, 0, 1, 0, 0, 1, 0, 0, 1], # 任务C ] b_eq = [1, 1, 1, 1, 1, 1] # 全都等于1 3. 定义变量范围:全是 0-1 变量 bounds = Bounds(lb=0, ub=1) # 所有变量统一在0到1之间 4. 定义整数类型:1 代表整数变量 (配合ub=1,自动变成0-1) integrality = np.ones(9) # 9个变量全是整数 5. 线性约束对象 constraints = LinearConstraint(A_eq, lb=b_eq, ub=b_eq) # lb=ub 代表等式约束 6. 求解 result = milp(c=c, constraints=constraints, bounds=bounds, integrality=integrality) 7. 打印结果(将结果还原成3x3矩阵好看) 【输出】: 甲->C, 乙->B, 丙->A, 总成本=12算法三:多目标规划 —— 冲突目标的“帕累托寻优”
多目标规划,简单说就是:在资源有限的情况下,同时追求多个“鱼和熊掌不可兼得”的目标,并找出一个最平衡、最满意的解决方案。
为了方便理解,可以拆成三个关键点:
1. 核心难点:目标之间“打架”(多个目标)
现实决策中,目标往往是互相冲突的。比如:
买东西:想质量最好,又想价格最低。质量好通常价格高,价格低质量可能差。
找工作:想工资高,又想工作清闲。高薪通常伴随高强度。
多目标规划要处理的就是这种“按下葫芦浮起瓢”的矛盾。
2. 和单目标规划的区别
单目标规划:只有一个明确指标(比如“只求利润最大”),直接算出唯一最优解就行。
多目标规划:没有“唯一最好”的答案,因为没有一个方案能让所有目标同时达到最大。它给出的是一组“折中方案”(也叫帕累托最优解)。
3. 怎么解决?(常用思路)
下面是各类方法的详细介绍。
🎯 帕累托方法 (Pareto-based Methods)
这类方法的核心是直接寻找并呈现一组帕累托最优解(即非劣解),将最终的选择权交给决策者,让决策者根据偏好做权衡。此类方法也称为后验方法 (A Posteriori Methods)。
帕累托最优:帕累托最优是指在资源分配中无法在不损害任何人的情况下让某些人变得更好的理想状态。因此,形成帕累托最优边界
帕累托最优边界:
可以这样理解:在X值不变的情况下,Y越高越好,从而绘制出任意组合集的上边界就是我们的帕累托最优解集
数学规划方法:
加权和法 (Weighted-Sum Approach):给每个目标赋予权重,合并成单目标求解。简单但可能无法找到非凸前沿上的所有解。
ε-约束法 (ε-Constraint Method):选择一个目标进行优化,将其他目标转化为约束条件。能处理非凸问题,但可能产生非帕累托最优解。
法线边界交叉法 (Normal Boundary Intersection, NBI)和标准化法线约束法 (Normalized Normal Constraint, NNC):旨在更均匀地生成帕累托前沿上的解。
进化算法 (Evolutionary Algorithms, EAs):基于种群并行搜索,特别适合一次性找到一组多样化的帕累托最优解。
经典算法:NSGA-II(非支配排序遗传算法)、SPEA2(改进型强度帕累托进化算法)、MOPSO(多目标粒子群优化)、VEGA(向量评估遗传算法)等。
🎯 非帕累托方法 (Non-Pareto Methods)
这类方法不直接寻找完整的帕累托解集,而是根据决策者预先设定的偏好或目标,直接导向一个最终解。它们通常更为高效,但解的质量高度依赖偏好信息的准确性。
目标规划法 (Goal Programming, GP):为每个目标设定一个期望达到的“目标值”,然后最小化实际值与目标值的偏差。这是最常用的方法之一,但缺点是有可能产生非帕累托最优的解。
字典序法 (Lexicographic Method):将所有目标按重要性严格排序,在保证更重要的目标最优的前提下,再去优化次要目标。
标量化/聚合方法 (Scalarization/Aggregation Methods):通过特定函数将多个目标合并成一个单目标,如理想点法、几何加权法等。
一句话总结:
多目标规划就是在冲突中找平衡,在妥协中找最优的决策工具。它不告诉你“哪个最好”,而是告诉你“有哪些不错的折中选择”,最终由决策者根据主观偏好拍板。
import numpy as np import pandas as pd from scipy.optimize import linprog ---------- 第一步:ε-约束法 生成帕累托前沿 ---------- 项目参数:项目1收益20%,风险0.5;项目2收益10%,风险0.1 设 x 为投项目1的比例,目标1收益最大:f1 = 0.2x + 0.1(1-x) = 0.1 + 0.1x 目标2风险最小:f2 = 0.5x + 0.1(1-x) = 0.1 + 0.4x 约束:0 <= x <= 1 pareto_solutions = [] # 存 (x, f1, f2) 让风险 f2 从 0.5 降低到 0.15(步长0.04),看收益最大能到多少 for epsilon in np.arange(0.50, 0.14, -0.04): # 目标函数:最大化 f1 = 0.1 + 0.1x,即最小化 -0.1x(常数0.1可以忽略,不影响x取值) c = [-0.1] # 因为 f1=0.1+0.1x, 最大化等价于最小化 -0.1x # 约束1: 风险 <= epsilon => 0.1 + 0.4x <= epsilon => 0.4x <= epsilon - 0.1 A_ub = [[0.4]] b_ub = [epsilon - 0.1] 约束2: 0 <= x <= 1 bounds = [(0, 1)] res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs') if res.success: x_opt = res.x[0] f1 = 0.1 + 0.1 * x_opt # 收益 f2 = 0.1 + 0.4 * x_opt # 风险 pareto_solutions.append([x_opt, f1, f2]) 转为DataFrame df = pd.DataFrame(pareto_solutions, columns=['x(投项目1比例)', '收益(f1)', '风险(f2)']) print("="*30) print("【ε-约束法生成的帕累托解集】") print(df) print("="*30) ---------- 第二步:TOPSIS 选出折中解 ---------- 注意:收益(f1) 是越大越好(正向指标),风险(f2) 是越小越好(负向指标) data = df[['收益(f1)', '风险(f2)']].values 矩阵正向化与归一化 (向量归一化) norm_data = data / np.sqrt((data**2).sum(axis=0)) 构造加权矩阵 (这里假设收益和风险同等重要,权重各0.5) weights = np.array([0.5, 0.5]) weighted_matrix = norm_data * weights 确定正理想解 (收益最大,风险最小) 和 负理想解 (收益最小,风险最大) ideal_best = np.array([max(weighted_matrix[:,0]), min(weighted_matrix[:,1])]) ideal_worst = np.array([min(weighted_matrix[:,0]), max(weighted_matrix[:,1])]) 计算欧氏距离 dist_best = np.sqrt(((weighted_matrix - ideal_best)**2).sum(axis=1)) dist_worst = np.sqrt(((weighted_matrix - ideal_worst)**2).sum(axis=1)) 计算贴近度 (得分越高越好) topsis_score = dist_worst / (dist_best + dist_worst) 将得分加入DataFrame并排序 df['TOPSIS得分'] = topsis_score df = df.sort_values('TOPSIS得分', ascending=False) df['排名'] = range(1, len(df)+1)总结
LP 结尾:“通过影子价格(对偶变量)分析,发现原料 B 的紧缺程度高于原料 A,建议优先采购原料 B。”
IP 结尾:“当变量规模超过 500 个时,采用启发式截断分支,在 5% 的容忍误差内求得满意解,确保算法在 2 小时内收敛。”
多目标结尾:“决策者若偏好稳健型,可选帕累托前沿左端方案;若偏好激进型,可选右端方案。本文给出 3 套推荐方案供管理层拍板。”