新闻详情

新闻详情

首页 / 资讯中心 / 详情

线性规划算法 Java 实现:从手写单纯形法到 SimplexSolver 生产实践

发布时间:2026/9/25 4:22:43来源:尧图网络
线性规划算法 Java 实现:从手写单纯形法到 SimplexSolver 生产实践
简介一份面向运筹学课程与Java开发者的线性规划算法实现解决在Java环境中求解一组线性约束条件下目标函数最大化或最小化的问题。资料覆盖线性规划模型构建、标准型转换含负变量处理、等式转不等式、最大化转换、人工变量引入与两阶段法求解并配有LP类与Main类完整源码支持控制台输入目标函数系数与约束条件直接输出最优解。压缩包共11个文件含2个Java源文件、2个class编译文件及5个xml工程配置等整体仅13KB轻量易读适合算法课程设计、期末实验或对运筹学编程实现感兴趣的开发者研读参考。已有3477人学习下载代码结构清晰标准化、人工变量设置与两阶段求解步骤均便于对照理解可在此基础上继续扩展单纯形法优化或可视化结果展示是一份结合理论与可运行代码的实用入门资料。1. 线性规划算法在 Java 里落地先搞清楚你要解哪种问题每周的排产计划、列车班组周转、配送车辆装载、产线人员分配这些业务说法差很远落到数学模型上却常常是一个形状线性规划。Java 后端团队接到这类需求绕不开“线性规划算法实现——Java版”这个技术点它背后是两件事把业务约束翻译成目标函数加 Ax≤b 的矩阵以及让 Java 进程能稳定拿到最优解。这里有个反直觉的结论你不一定要从零手写单纯形法但你必须能手写它否则连求解器抛出的异常都读不懂。这篇笔记会把两条路都讲透先给一版能跑、能看懂每一步的纯 Java 单纯形法再给生产环境可用的 SimplexSolver 最小配置最后把退化、精度、整数变量这些坑一次说明白。适合正在做排产调度或写课程设计、准备算法面试的人。2. 单纯形法的 Java 实现路线手写还是 Apache Commons Math2.1 单纯形法的核心流程一张表和四步循环线性规划模型长这样求一组非负变量 x在若干线性不等式约束下让目标函数达到最大。写成规范形式就是 max c^T xs.t. Ax ≤ bx ≥ 0。所有其他形式都能变换过来求最小就把目标系数取负约束是 ≥ 就两边乘 -1等式约束拆成两个 ≤。这一步不做后面写代码会反复翻车。单纯形法做的事不是枚举所有顶点而是从可行域的一个顶点出发沿着可行边走到相邻顶点每一步都保证目标值不下降。在 Java 里实现就是把这套几何游走翻译成一张 double 二维表上的行变换。这张表叫单纯形表表头从左侧到右侧依次是原始变量、松弛变量、右侧常数 b最下面一行放目标函数。松弛变量是给每个 ≤ 约束补出来的非负新变量作用是把不等式变成等式比如 x1 x2 ≤ 4 变成 x1 x2 s1 4s1 ≥ 0这样初始就能找到一组可行基。单纯形法的循环只有四步。第一步看目标行如果目标行里还有负系数列说明目标值还能提升当前顶点不是最优第二步从这些负系数里挑一个列作为入基变量第三步用最小比值规则在所有约束行里选出基变量保证换过去之后新顶点仍然可行第四步对选中行做旋转把入基列变成单位向量同时更新目标行。循环走到目标行没有负系数为止此时最优解和最优值已经从表里直接露出来了。这四步里最容易出问题的是第二步和第三步的判断条件。浮点运算下不能写 0和 0必须给一个容差阈值否则单纯形法会被 1e-16 这种舍入误差牵着鼻子走轻则不收敛重则陷入退化循环。2.2 三条可落地路线的对比手写、Commons Math 和专业求解器Java 里落地线性规划主流就三条路线差异不在“谁更强”而在“你处于什么阶段”。我直接用表格把边界画清楚。实现路线核心代码量数值稳定性能稳定处理的规模典型场景纯手写单纯形法100-150 行依赖 double退化时容易循环需要 EPSILON 和 Bland 规则兜底百级变量和约束课程设计、面试手撕、对照测试、理解原理Apache Commons Math SimplexSolver一行依赖加三四十行代码内部有容差处理和迭代上限常规问题稳定千级变量和约束生产环境常规 LP快速交付ojAlgo、OR-Tools Java 接口依赖稍重API 也要学数值骨干更扎实支持内点法、整数规划更大规模或混合整数问题供应链优化、大规模排程、带整数变量的场景手写方案不是作业专用。我手头始终保留一份手写单纯形实现它最大的用途是当“黑匣子说明书”Commons Math 抛出一个异常我能根据自己对单纯形表的理解判断是原问题无界、可行域为空还是单纯数值问题。没有这层理解调库就是瞎试参数翻车了都不知道去哪查。Commons Math 的 SimplexSolver 是大多数 Java 后端项目的默认选择。它本身也是用单纯形法实现的不是某种更高级的算法所以 2.1 节那四步循环对它同样成立。优点是不用自己处理退化、可行性判断、迭代上限这些脏活。专业求解器比如 ojAlgo、OR-Tools 带着更完整的优化生态。当问题规模到几千约束或者变量里混着整数变量时单纯形法本身就不够用了这时候需要内点法、分支定界这些更重的机制。我的看法是别贪大项目里只有几十个变量和约束上 OR-Tools 属于浪费交付时间Commons Math 完全够。2.3 选型判断什么规模手写什么规模直接调库我一般按三条规则判断。第一变量和约束都在 100 以内手写和 Commons Math 随便选如果这个项目要长期维护选 Commons Math因为后面有人接手时不需要先把单纯形表啃一遍。第二变量和约束在几百到一千的区间直接放弃手写用 Commons Math手写二维表的旋转操作在规模上来之后会明显拖慢。第三场景里出现整数要求比如“排班人数必须是整数”“开工的机器台数不能是小数”别在单纯形法上死磕换成支持整数规划的求解器才是正路。还有一个经常被忽略的因素约束条件会不会动态变化。业务上“临时追加一条规则”太常见了手写方案每追加一条约束都要重新看一遍 A 矩阵和基变量维护逻辑而 Commons Math 只需要往集合里 add 一个 LinearConstraint。从交付效率讲生产项目我默认选 Commons Math手写实现只留在本地做验证和教学。3. 用纯 Java 写一版能跑的单纯形法核心代码与参数详解3.1 建模约定把实际问题统一成 max Ax ≤ b x ≥ 0手写单纯形实现时我不建议一上来就支持各种花哨形式。只支持一个约定目标求最大约束全是不等式 ≤右侧常数 b 全为非负变量 x 全为非负。其他形式在进入单纯形表之前先统一转换这样核心代码能保持最简问题都暴露在建模层。常见的转换有三类。求最小化时把目标函数的系数全部取负变成求最大化记住最后算出的最优值要再取负恢复约束是 ≥ 时对整行系数和右侧常数同时乘 -1方向就翻成 ≤约束是等式时拆成两个方向相反的 ≤ 约束例如 x1 x2 2 拆成 x1 x2 ≤ 2 和 -x1 - x2 ≤ -2。右侧常数如果是负数本质上也要通过乘 -1 处理否则初始基解不可行。这段转换代码一般放在建模工具类里我常写成下面这样// minimize 为 true 时把最小化转成最大化 double sign minimize ? -1.0 : 1.0; double[] obj new double[n]; for (int j 0; j n; j) { obj[j] sign * c[j]; } // 把 约束统一翻转为 double[] row constraint.clone(); double rhs limit; if (relationIsGEQ) { for (int j 0; j row.length; j) { row[j] -row[j]; } rhs -rhs; }这段逻辑的核心是“符号变化必须作用在整行上”。很多人处理 ≥ 约束时只对系数矩阵取了负忘了右侧常数 b 也要取负结果约束整体被平移最优解自然偏到完全不同的顶点上。转换之后最好加一段校验把每一行约束打印出来人工核对一遍例如输出[1.0, 2.0] 12.0这样的形式。3.2 单纯形表的数据结构、主元选择与两个参数单纯形表在 Java 里最常见的存储方式是一个二维 double 数组table[m 1][n m 1]。行方向上面 m 行是约束最后一行是目标列方向前 n 列是原始变量中间 m 列是松弛变量最后一列是右侧常数。另外维护一个长度为 m 的 basis 数组记录当前第 i 行基变量是哪个变量的下标。初始化时每个约束行把自己的松弛变量列设为 1basis 数组直接指向第 n 到 n m - 1 号松弛变量。目标行填的是负的目标系数理解为等式 z - c^T x 0 的系数。之后每次旋转目标行也跟着做行变换最终目标行最后一列存的就是最优目标值。旋转操作是整个算法的核心代码很短private void pivot(int leaveRow, int enterCol) { double pivotValue table[leaveRow][enterCol]; // 主元行归一化 for (int j 0; j cols; j) { table[leaveRow][j] / pivotValue; } // 其余行包括目标行把 enterCol 列消成 0 for (int i 0; i rows; i) { if (i leaveRow) continue; double factor table[i][enterCol]; for (int j 0; j cols; j) { table[i][j] - factor * table[leaveRow][j]; } } basis[leaveRow] enterCol; }注意第二个循环的范围要覆盖目标行也就是i rows的 rows 是 m 1 而不是 m。漏掉目标行会让检验数不更新算法跑几步就停在一个非最优顶点上这是手写实现里最常见的低级错误。两个参数决定了这个实现能不能在真实数据上站稳。第一个是 EPSILON我固定用 1e-9所有与零比较的地方都换成table[i][enterCol] EPSILON避免把 1e-15 这种舍入误差当成有效主元。第二个是 maxIterations我给 1000 次单纯形理论上是不需要迭代上限的但浮点误差和退化会让它陷入循环这个上限就是保险丝。达到上限时抛异常比返回一个错误结果好一万倍至少你能意识到问题。3.3 完整可运行的 Simplex.java求解循环与旋转实现下面是一个可直接编译运行的完整实现。构造函数接收 A、b、c 三个输入分别对应约束矩阵、右侧常数、目标系数solve 方法返回最优解向量getOptimalValue 返回最优目标值。import java.util.Arrays; public class Simplex { private static final double EPSILON 1e-9; private final int m; // 约束条数 private final int n; // 原始变量个数 private final double[][] table; // 单纯形表m1 行nm1 列 private final int[] basis; // 每行当前的基变量下标 private double optimalValue; public Simplex(double[][] A, double[] b, double[] c) { this.m b.length; this.n c.length; table new double[m 1][n m 1]; basis new int[m]; // 约束行A 矩阵 松弛变量单位阵 RHS for (int i 0; i m; i) { for (int j 0; j n; j) { table[i][j] A[i][j]; } table[i][n i] 1.0; // 松弛变量列 table[i][n m] b[i]; // 右侧常数 basis[i] n i; // 初始基变量是松弛变量 } // 目标行z - c^T x 0 for (int j 0; j n; j) { table[m][j] -c[j]; } } public double[] solve() { int iterations 0; int maxIterations 1000; while (iterations maxIterations) { int enter enterColumn(); if (enter -1) { optimalValue table[m][n m]; return buildSolution(); } int leave leaveRow(enter); if (leave -1) { throw new IllegalStateException(问题无界unbounded请检查是否漏了约束); } pivot(leave, enter); } throw new IllegalStateException(超过最大迭代次数疑似退化循环或数值精度问题); } private int enterColumn() { int enter -1; for (int j 0; j n m; j) { if (table[m][j] -EPSILON) { // 默认选最负的检验数换成第一个负检验数就是 Bland 规则 if (enter -1 || table[m][j] table[m][enter]) { enter j; } } } return enter; } private int leaveRow(int enter) { int leave -1; double minRatio Double.MAX_VALUE; for (int i 0; i m; i) { if (table[i][enter] EPSILON) { double ratio table[i][n m] / table[i][enter]; if (ratio minRatio) { minRatio ratio; leave i; } } } return leave; } private void pivot(int leaveRow, int enterCol) { double pivotValue table[leaveRow][enterCol]; for (int j 0; j n m 1; j) { table[leaveRow][j] / pivotValue; } for (int i 0; i m; i) { if (i leaveRow) continue; double factor table[i][enterCol]; for (int j 0; j n m 1; j) { table[i][j] - factor * table[leaveRow][j]; } } basis[leaveRow] enterCol; } private double[] buildSolution() { double[] x new double[n]; for (int i 0; i m; i) { if (basis[i] n) { x[basis[i]] table[i][n m]; } } return x; } public double getOptimalValue() { return optimalValue; } public static void main(String[] args) { // max 3x1 2x2 // s.t. x1 x2 4 // 2x1 x2 5 double[][] A {{1, 1}, {2, 1}}; double[] b {4, 5}; double[] c {3, 2}; Simplex simplex new Simplex(A, b, c); double[] x simplex.solve(); System.out.println(x1 x[0] , x2 x[1]); System.out.println(最优目标值 z simplex.getOptimalValue()); System.out.println(基变量分布: Arrays.toString(simplex.basis)); } }solve 方法的循环逻辑对应 2.1 节那四步enterColumn 找出目标行里最负的负系数列没有负系数就说明已经最优leaveRow 用最小比值规则选出基行如果所有主元列系数都不大于零说明目标函数在这个方向上无界pivot 完成行变换并更新基变量。optimalValue 是在最优判定通过时从目标行最后一列读出来的不需要额外计算。这段代码里最值得调的两个参数是 EPSILON 和 maxIterations。EPSILON 设太大会把真实的优化空间截掉设太小又挡不住浮点噪声1e-9 在工程里比较平衡。maxIterations 不要盲目调大如果跑到 1000 次还没收敛优先去开 Bland 规则也就是把 enterColumn 的循环改成“取第一个负检验数列”这比把上限改成 10000 靠谱得多。3.4 跑通最小例子三步验证结果对不对上面 main 方法里的例子人工可以直接验算x1 x2 ≤ 4 和 2x1 x2 ≤ 5 这两个约束的交点是 x1 1x2 3目标值是 3 × 1 2 × 3 9。运行代码后输出基变量分布是 [1, 0]意思是第 0 个约束行的基变量是原始变量 x1第 1 个约束行的基变量是松弛变量最终解向量只取了 x1 和 x2 两个位置的值。跑通一个例子之后我强烈建议写一个简单的校验函数对所有未来求解结果做三件事回代约束看是否满足、重新算目标值、和单纯形表里读出来的值对比。代码如下public static void checkResult(double[][] A, double[] b, double[] c, double[] x) { double z 0; for (int j 0; j c.length; j) { z c[j] * x[j]; } for (int i 0; i A.length; i) { double lhs 0; for (int j 0; j A[i].length; j) { lhs A[i][j] * x[j]; } System.out.println(约束 i : lhs b[i]); } System.out.println(目标值 z); }这个函数不是为了验证简单例子而是为了后续把实现换成 Commons Math 时能拿同一组数据跑出对照结果。手写实现和库的结果一致只能说当前用例没问题跑过一组覆盖退化、无界、多组约束的用例之后仍然一致你才敢在项目里替换实现。4. 生产环境落地用 SimplexSolver 三步配好最小求解配置4.1 加 Maven 依赖一行坐标引入 commons-math3生产项目里我不会长期维护自己的单纯形表Apache Commons Math 是 Java 生态里最稳妥的线性规划库。引入方式很简单Maven 项目加下面这部分dependency groupIdorg.apache.commons/groupId artifactIdcommons-math3/artifactId version3.6.1/version /dependencyGradle 项目对应的一行是implementation org.apache.commons:commons-math3:3.6.1。这个依赖本身没有额外的传递依赖JDK 8 以上就能用不需要做什么环境配置。引入之后立刻能用的是org.apache.commons.math3.optim.linear包下的 SimplexSolver 和org.apache.commons.math3.optim.linear.LinearObjectiveFunction、LinearConstraint。这里有一个取舍Commons Math 底层用的也是 double 精度不是高精度计算所以它解决的是“代码健壮性”问题而不是“数值精度”问题。如果业务对结果精度有极高的财务级要求后面还需要自己处理精度详情放在第六章。4.2 写求解代码目标函数、约束、求解器三个对象用 SimplexSolver 求解一个线性规划本质上只需要构造三个东西目标函数、约束集合、求解器。下面是一个小型生产排产例子计算两种产品的最佳产量。import org.apache.commons.math3.optim.PointValuePair; import org.apache.commons.math3.optim.linear.LinearConstraint; import org.apache.commons.math3.optim.linear.LinearConstraintSet; import org.apache.commons.math3.optim.linear.LinearObjectiveFunction; import org.apache.commons.math3.optim.linear.NonNegativeConstraint; import org.apache.commons.math3.optim.linear.Relationship; import org.apache.commons.math3.optim.linear.SimplexSolver; import org.apache.commons.math3.optim.nonlinear.scalar.GoalType; import java.util.ArrayList; import java.util.Collection; public class ProductionPlan { public static void main(String[] args) { // 目标max 40*A 30*B LinearObjectiveFunction function new LinearObjectiveFunction(new double[] {40, 30}, 0); CollectionLinearConstraint constraints new ArrayList(); // 机器 M1 工时1.5A B 20 constraints.add(new LinearConstraint(new double[] {1.5, 1}, Relationship.LEQ, 20)); // 机器 M2 工时A 2B 25 constraints.add(new LinearConstraint(new double[] {1, 2}, Relationship.LEQ, 25)); // 产品 A 市场上限A 12 constraints.add(new LinearConstraint(new double[] {1, 0}, Relationship.LEQ, 12)); // 产品 B 市场上限B 10 constraints.add(new LinearConstraint(new double[] {0, 1}, Relationship.LEQ, 10)); SimplexSolver solver new SimplexSolver(); PointValuePair solution solver.optimize( function, new LinearConstraintSet(constraints), GoalType.MAXIMIZE, new NonNegativeConstraint(true) ); double[] point solution.getPoint(); System.out.println(产品 A 产量 point[0]); System.out.println(产品 B 产量 point[1]); System.out.println(最大利润 solution.getValue()); } }这份代码的逻辑很直接LinearObjectiveFunction 接收两个入参第一个是目标函数的系数数组对应 40A 30B 里的 [40, 30]第二个是常数项一般写 0。每条 LinearConstraint 接收三个入参约束行系数数组、关系符号、右侧常数。NonNegativeConstraint(true) 的作用是告诉求解器所有变量都必须大于等于零这个参数特别容易漏。跑这段代码会得到产品 A 产量 7.5、产品 B 产量 8.75、最大利润 562.5。如果人工手算约束 1 和约束 2 的交点恰好就是 (7.5, 8.75)代回去约束 3 和约束 4 也没有突破上限和手写实现的结果完全一致。这就是一个可以拿来做回归对照的标准用例。4.3 四个必调的参数与常见参数误区用 SimplexSolver 时最容易翻车的不是类用错而是参数没配对。我把关键参数整理成一张表。参数或对象作用注意事项GoalType.MAXIMIZE / GoalType.MINIMIZE指定优化方向必须显式给出没有默认值NonNegativeConstraint(true)限定所有变量 0最容易漏漏掉后变量允许取负值结果看起来“合法”但完全不符合业务LinearConstraintSet把约束集合传给求解器也可以用 Collection 但显式包装一层的可读性更好SimplexSolver()求解器主体内部有默认容差和迭代上限普通场景不用改这里要特别提醒一个双重反转的问题。Commons Math 本身就支持 GoalType.MINIMIZE你把问题交给它时直接传 MINIMIZE 即可不需要在构造 LinearObjectiveFunction 之前先把目标系数取负。如果你在外面已经做了 sign -1 的转换又传一个 MINIMIZE结果就是把原问题又翻回去算出来的最优值完全反了。这套逻辑和手写实现的约定不同手写实现只处理 max所以必须外部转一次库实现两条路都通选一条走别两条混着用。异常处理也要留意。约束过强导致可行域为空时求解器会抛 NoFeasibleSolutionException目标无界时抛 UnboundedSolutionException。生产代码里建议分别捕获并打印业务含义比如“排产约束过强请检查各产线上限”否则监控里突然出现一个 exception stack trace排查的人还得去翻求解器文档。try { PointValuePair solution solver.optimize( function, new LinearConstraintSet(constraints), GoalType.MAXIMIZE, new NonNegativeConstraint(true) ); System.out.println(最优解 solution.getValue()); } catch (org.apache.commons.math3.exception.NoFeasibleSolutionException e) { System.out.println(可行域为空约束方向写反或约束之间有冲突); } catch (org.apache.commons.math3.exception.UnboundedSolutionException e) { System.out.println(无界目标方向下不存在最大值多半是少了上限约束); }这段异常处理和手写实现里leaveRow返回 -1 抛出的 IllegalStateException 语义一样但在生产环境里更规范。遇到无解时优先检查所有约束的 Relationship 方向这是最常出现的低级失误。5. 线性规划实现避坑五个反复翻车的常见问题5.1 退化导致迭代卡死目标值原地打转循环到上限现象手写实现运行到迭代上限被强制中断打印出来的目标值在两个相近的小数之间反复横跳看起来是收敛了但代码就是不退出。 原因退化基导致主元选择不稳定。当多个约束在同一个顶点上相交时单纯形法可能连续几次旋转都在同一个几何顶点打转而 double 的舍入误差又让检验数一直在 -1e-16 和 1e-16 之间晃动选择逻辑反复无常。 解决把 EPSILON 设到 1e-9同时把入基变量选择从“最负检验数”改成“第一个负检验数”也就是 Bland 规则。Bland 规则保证不会在退化的顶点上无限循环代价是收敛速度可能略慢一点。private int enterColumnBland() { for (int j 0; j n m; j) { if (table[m][j] -EPSILON) { return j; // 第一个负检验数列按最小下标选 } } return -1; }把这段逻辑和 3.3 节默认的最负检验数选择对比一下区别只是“挑最负”还是“挑第一个”但数值稳定性差很多。生产里如果用的是 Commons Math这类问题库内部已经处理不需要你干预。5.2 约束方向写反求解结果和手工核算完全对不上现象拿一个手算能解出来的小问题喂给程序结果却是一堆奇怪的边界值甚至直接抛 NoFeasibleSolutionException。 原因最常见的是处理 ≥ 约束时只翻转了系数没有翻转右侧常数。比如把2A B ≥ 10转成2A B ≤ -10约束的几何意义完全变了。等式约束拆成两个 ≤ 时也容易方向写反。 解决所有约束在进入求解层之前统一规约成 ≤ 形式并且在转换函数里用整行操作。我会在建模工具类里留一个打印函数把每一行转换后的结果输出成[1.0, 2.0] 12.0肉眼扫一遍就能发现方向问题。for (int i 0; i constraints.size(); i) { LinearConstraint lc constraints.get(i); System.out.println(Arrays.toString(lc.getCoefficients().toArray()) lc.getRelationship() lc.getValue()); }这个打印函数在切换到 Commons Math 时尤其重要因为 LinearConstraint 构造器不会校验你的关系符号是否合理写反了它一样编译通过只是结果很荒唐。5.3 最小化目标取负后忘记恢复符号最优值正负颠倒现象求最小成本结果返回一个很大的正数或者目标值的符号和预期完全相反。 原因手写实现只支持求最大值把最小化问题转成最大化之后最优值仍然是从单纯形表里直接读的没有乘回 -1。Commons Math 场景里则可能是自己在外部取负之后又传了 MINIMIZE造成双重反转。 解决在手写实现里用一个 sign 变量记录转换方向求解结束后执行optimalValue * sign。在 Commons Math 里则简单得多明确走一条路要么外部不取负、传 MINIMIZE要么外部取负、传 MAXIMIZE不要两段逻辑都写。这段踩坑记录其实是个提醒每次转换都要留下文字注释。单纯形代码本身不难难的是这些符号约定在几天之后会被忘得干干净净。注释里写明“当前 sign -1因为原始问题是最小化”能帮你省掉大量回头排查的时间。5.4 整数变量用单纯形硬解结果出现“半个工人”现象排班问题输出显示某个岗位需要 0.6 个人、某条产线需要开启 2.3 台机器。 原因单纯形法求解的线性规划是连续问题顶点解不保证整数。只要变量没有整数约束LP 松弛解里出现小数是完全正常的。 解决如果业务必须要求整数不要在手写单纯形法上继续加四舍五入。四舍五入后的点很可能不在可行域内比如约束是 A B ≤ 4解出 A1.4、B2.6四舍五入成 A1、B3 还在可行域内但换成约束 2A B 6.4取整之后就直接违背约束。正确做法是换成混合整数线性规划求解器或者在业务上允许对取整结果做一次回代验证再落地。遇到这种情况我会直接换 ojAlgo 或 OR-Tools 这类支持整数变量的问题模型而不是尝试在纯 Java 单纯形实现上做二次取整。单纯形法本身没有整数感知所有后处理都是碰运气。5.5 规模一大就跑不快从全表扫描到数据结构瓶颈现象变量数量到几百个之后手写单纯形明显变慢每次迭代都像卡住一样。 原因手写实现用的是稠密二维数组选主元要扫整行整列单次旋转要更新整张表复杂度是 O(m × n) 量级。变量一多迭代次数和单次迭代的时间一起涨性能自然崩。 解决先判断规模。几千约束以内的常规问题直接换 Commons Math它内部对旋转操作做了不少优化。更大规模的问题需要稀疏矩阵存储、按列选主元、甚至换内点法这已经超出“手写单纯形表”的范畴了。我的建议是手写实现固定放在测试目录里当对照业务代码从一开始就调库别让两种用途混在一起。6. 验证与进阶用 Bland 规则和 BigDecimal 把 Java 实现做稳6.1 用已知答案的小规模问题做回归校验手写实现完成之后我会维护一个固定的小测试集每个用例都有期望解和期望目标值。其中一个特别适合用来验证入基选择逻辑// max x1 2x2s.t. x1 x2 1x 0 double[][] A {{1, 1}}; double[] b {1}; double[] c {1, 2}; Simplex simplex new Simplex(A, b, c); double[] x simplex.solve(); assert Math.abs(simplex.getOptimalValue() - 2.0) 1e-6; assert Math.abs(x[1] - 1.0) 1e-6;这个用例的目标函数系数是 [1, 2]如果入基列选择的是最负检验数会直接选中 x2 一步到最优如果选的是第一个负检验数会先走进 x1再走到 x2多花几步但不影响终值。用它就能直观看出你的入基策略是什么。回归测试集里还应该包含无界解、退化问题、多个最优解的用例这类用例能暴露单纯形法在边界条件下的真实行为。6.2 值得尝试的 BigDecimal 改造精度和速度的取舍当目标函数是金额、价格、费率这类对小数点后精度敏感的系数时double 的 1e-9 容差不够稳定。把单纯形表从 double 改成 BigDecimal 是可行的进阶改造但要注意三点。第一pivot 里的除法必须指定 MathContext否则 divide 会因为除不尽直接抛 ArithmeticException。第二所有比较运算不能用 equals 或 0要用 compareTo 和阈值比较。第三速度会明显变慢体感上慢一个数量级都说得过去所以只建议在系数本身是十进制精确值且精度要求高的场景用。BigDecimal pivotValue tableRow[enterCol]; for (int j 0; j tableRow.length; j) { tableRow[j] tableRow[j].divide(pivotValue, MathContext.DECIMAL64); }BigDecimal 改造不适合作为默认实现更适合作为手写实现的一个可切换策略。接口保持统一内部用 double 还是 BigDecimal 由建模层决定。6.3 我的一个习惯让手写实现和求解器互相印证我长期保留一份十来条用例的小测试集里面有唯一最优解、无穷多最优解、退化顶点、故意构造的无界问题。每次要改求解器、调容差参数或者换建模方式都会先让这份测试集跑一遍同时用 Commons Math 的 SimplexSolver 跑同一个输入两边结果对上才继续往下走。这个习惯救过我很多次。有一回业务侧给的目标系数带着几个极小的小数手写实现算出的最优值和库相差 0.001最后定位到是 EPSILON 设太大把真实的优化空间裁掉了。如果没有手写实现做对照这个量级的误差放到排产方案里很难被肉眼发现。单纯形法这类数值算法恰恰是在没人注意的角落最容易悄悄出错。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

WechatDecrypt安全使用指南:解密微信消息后的隐私风险与注意事项 2026/9/25 5:38:28

WechatDecrypt安全使用指南:解密微信消息后的隐私风险与注意事项

WechatDecrypt安全使用指南:解密微信消息后的隐私风险与注意事项 【免费下载链接】WechatDecrypt 微信消息解密工具 项目地址: https://gitcode.com/gh_mirrors/we/WechatDecrypt WechatDecrypt 是一款微信消息解密工具,能把 Windows 版微信本地加…

阅读更多 →
lego 集成 Simply.com DNS Provider 签发通配符证书:配置、参数与实现原理详解 2026/9/25 5:38:28

lego 集成 Simply.com DNS Provider 签发通配符证书:配置、参数与实现原理详解

网络安全密码学 【免费下载链接】lego Lets Encrypt/ACME client and library written in Go 项目地址: https://gitcode.com/gh_mirrors/le/lego 点击查看 免费下载 本指南以 lego(Lets Encrypt/ACME client,Go 语言实现)内置的…

阅读更多 →
Flexibility如何用position:absolute模拟Flexbox?Polyfill核心原理深度揭秘 2026/9/25 5:38:28

Flexibility如何用position:absolute模拟Flexbox?Polyfill核心原理深度揭秘

Flexibility如何用position:absolute模拟Flexbox?Polyfill核心原理深度揭秘 【免费下载链接】flexibility A JavaScript polyfill for Flexbox 项目地址: https://gitcode.com/gh_mirrors/fl/flexibility Flexibility 是一个纯 JavaScript 编写的 Flexbox po…

阅读更多 →
Rematch config.redux 配置详解:定制 Redux 的 initialState、reducer 合并、中间件与 Devtools 2026/9/25 5:38:28

Rematch config.redux 配置详解:定制 Redux 的 initialState、reducer 合并、中间件与 Devtools

前端 【免费下载链接】rematch The Redux Framework 项目地址: https://gitcode.com/gh_mirrors/re/rematch 点击查看 免费下载 Rematch 在创建 store 时封装了 Redux 的整套创建流程,而 init() 的 redux 属性是打开这层封装的唯一入口。本文覆盖 confi…

阅读更多 →
74LS74四分频电路设计与Multisim仿真全流程 2026/9/25 5:38:16

74LS74四分频电路设计与Multisim仿真全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
ng-zorro-antd 幽灵折叠面板(Ghost Collapse)实战指南:nzGhost 透明背景实现与源码解析 2026/9/25 5:38:10

ng-zorro-antd 幽灵折叠面板(Ghost Collapse)实战指南:nzGhost 透明背景实现与源码解析

UI组件前端 【免费下载链接】ng-zorro-antd Angular UI Component Library based on Ant Design 项目地址: https://gitcode.com/gh_mirrors/ng/ng-zorro-antd 点击查看 免费下载 导读 本文聚焦 ng-zorro-antd 中 Collapse(折叠面板)组件的…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉