单纯形法的分析和实现
单纯形法是求解一类被称为“线性规划(LP)”问题的通用方法。高二学习的一类含有两个变量的简单线性规划可以通过在平面上作图得出,同理三个变量可以投到空间里计算,但超过四个变量呢?单纯形法即是对这种方法的推广,其过程类似于在n维几何体的顶点上不断尝试,但由于高维几何体过于玄学,大多数资料都使用了更直观的代数角度阐释。
一个线性规划的标准型被定义为:
由于不等式
用矩阵表示为:
而单纯形法的思路是:每次将一个“可以增加值的”变量的值增加到临界点,直到所有变量都不能增加值。例如线性规划:
(
则当 当前的非松弛变量
观察到
选定
整理得到:
这时当 当前的非松弛变量
是不是一次就能取得最大值呢?不是。因此我们需要多次转动直到找不到更大的值。事实上,转动次数的上界是指数级的。
代码实现
#include <bits/stdc++.h>
using namespace std;
const double eps = 1e-9;
const double inf = 1e100;
inline int sgn(double d)
{ return abs(d) <= eps ? 0 : (d > 0 ? 1 : -1); }
const int maxn = 1005, maxm = 1005;
int n, m; // 等式数,变量数(不含松弛变量)
int A[maxn][maxm];
void pivot(int l, int e) // 一次转动, l为替出变量,e为替入变量
{
cout << l << " " << e << endl;
for (int i = 1; i <= n; i++) if (i != l && sgn(A[i][e])) {
for (int j = 1; j <= m; j++)
if (j != e) A[i][j] -= A[l][j] * A[i][e]/A[l][e];
A[i][e] /= -A[l][e];
}
for (int i = 1; i <= m; i++) if (i != e) A[l][i] /= A[l][e]; A[l][e] = 1/A[l][e];
}
double simplex()
{
for (int e, l; ; ) {
double t = inf;
for (e = 1; e < m; e++) if (sgn(A[n][e]) > 0) break;
if (e == m) return -A[n][m];
for (int i = 1; i < n; i++)
if (sgn(A[i][e]) > 0 && t > A[i][m]/A[i][e]) t = A[l=i][m]/A[i][e];
if (t == inf) return inf;
pivot(l, e);
}
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
cin >> A[i][j];
printf("%g", simplex());
return 0;
}
把问题表述成线性规划
一个经典的例子是最短路径,详细介绍在《算法导论》上有,在此不再赘述。给出一个利用单纯形法求解最短路径的代码:
#include <bits/stdc++.h>
using namespace std;
const double eps = 1e-9;
const double inf = 1e100;
inline int sgn(double d)
{ return abs(d) <= eps ? 0 : (d > 0 ? 1 : -1); }
const int maxn = 10005, maxm = 1005;
int n, m; // 等式数,变量数(不含松弛变量)
int A[maxn][maxm], B[maxn][maxm];
void pivot(int l, int e) // 一次转动, l为替出变量,e为替入变量
{
//cout << l << " " << e << endl;
for (int i = 1; i <= n; i++) if (i != l && sgn(A[i][e])) {
for (int j = 1; j <= m; j++)
if (j != e) A[i][j] -= A[l][j] * A[i][e]/A[l][e];
A[i][e] /= -A[l][e];
}
for (int i = 1; i <= m; i++) if (i != e) A[l][i] /= A[l][e]; A[l][e] = 1/A[l][e];
}
double simplex()
{
for (int e, l; ; ) {
double t = inf;
for (e = 1; e < m; e++) if (sgn(A[n][e]) > 0) break;
if (e == m) return -A[n][m];
for (int i = 1; i < n; i++)
if (sgn(A[i][e]) > 0 && t > A[i][m]/A[i][e]) t = A[l=i][m]/A[i][e];
if (t == inf) return inf;
pivot(l, e);
// cout << "Get : " << -A[n][m] << endl;
}
}
int main()
{
int v, e, s, t;
cin >> v >> e >> s;
memset(A, 0, sizeof A);
n = 0, m = v+1;
for (int i = 1; i <= e; i++) {
int a, b; double c;
scanf("%d%d%lf", &a, &b, &c);
A[++n][b] = 1, A[n][a] = -1, A[n][m] = c;
}
A[++n][s] = 1; ++n;
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) B[i][j] = A[i][j];
for (int i = 1; i <= v; i++) {
for (int j = 1; j <= n; j++) for (int k = 1; k <= m; k++) A[j][k] = B[j][k];
A[n][i-1] = 0, A[n][i] = 1;
double ans = simplex();
if (ans == inf) printf("2147483647 ");
else printf("%g ", ans);
}
return 0;
}
相关资料
本文的代码风格来自 http://blog.csdn.net/Fuxey/article/details/51039914?locationNum=1&fps=1,这篇文章中的叙述简介有力,代码也很易读【但是我蒟蒻看了一下午】。
《算法导论》的译本是不错的中文资料,其对于线性规划的介绍简直是全书中最详细的一部分……