单纯形法是解决线性规划问题的一个有效的算法。线性规划就是在一组线性约束条件下,求解目标函数最优解的问题。
在约束条件下,寻找目标函数z的最大值。
满足线性规划问题约束条件的所有点组成的集合就是线性规划的可行域。若可行域有界(以下主要考虑有界可行域),线性规划问题的目标函数最优解必然在可行域的顶点上达到最优。
单纯形法就是通过设置不同的基向量,经过矩阵的线性变换,求得基可行解(可行域顶点),并判断该解是否最优,否则继续设置另一组基向量,重复执行以上步骤,直到找到最优解。所以,单纯形法的求解过程是一个循环迭代的过程。
图1 可行域
在说明单纯形法的原理之前,需要明白线性规划的标准形式。因为单纯形算法是通过线性规划的标准形来求解的。一般,规定线性规划的标准形式为:
写成矩阵形式:
标准形的形式为:
1)目标函数要求max
2)约束条件均为等式
3)决策变量为非负约束
普通线性规划化为标准形:
1)若目标函数为最小化,可以通过取负,求最大化
2)约束不等式为小于等于不等式,可以在左端加入非负松弛变量,转变为等式,比如:
同理,约束不等式为大于等于不等式时,可以在左端减去一个非负松弛变量,变为等式。
3)若存在取值无约束的变量,可转变为两个非负变量的差,比如:
本文最开始的线性规划问题转化为标准形为:
在标准形中,有m个约束条件(不包括非负约束),n个决策变量,且(n>=m)。首先,选取m个基变量
,基变量对应约束系数矩阵的列向量线性无关。通过矩阵的线性变换,基变量可由非基变量表示:
如果令非基变量等于0,可求得基变量的值 :
如果为可行解的话,Ci大于0。那么它的几何意义是什么呢?
还是通过上述具体的线性规划问题来说明。
如果选择x2、x3为基变量,那么令x1、x4等于0,可以去求解基变量x2、x3的值。对系数矩阵做行变换,如下所示,x2=9/2,x3=15/2
X1=0表示可行解在x轴上;X4=0表示可行解在x1+2x2=9的直线上。那么,求得的可行解即表示这两条直线的交点,也是可行域的顶点,如图所示:
图2
所以,通过选择不同的基变量,可以获得不同的可行域的顶点。
如前所述,基变量可由非基变量表示:
目标函数z也可以完全由非基变量表示:
当达到最优解时,所有的
应小于等于0。当存在j,
>0时,当前解不是最优解,为什么?
当前的目标函数值为z0,其中所有的非基变量值均取0。由之前分析可知,
=0代表可行域的某个边界,是
的最小值。如果可行解逐步离开这个边界,
会变大,因为
>0,显然目标函数的取值也会变大,所以当前解不是最优解。我们需要寻找新的基变量。
如果存在多个
>0,选择最大的
>0对应的变量作为基变量,这表示目标函数随着
的增加,增长的最快。
假如我们选择非基变量
作为下一轮的基变量,那么被替换基变量
在下一轮中作为非基变量,等于0。选择
的原则:替换后应该尽量使
值最大(因为上面已分析过,目标函数会随着
的增大而增大)。
继续通过上面的例子来说明:
从最后一行可以看到,x1的系数为1/2>0,所以选x2、x3为基变量并没有是目标函数达到最优。下一轮选取x1作为基变量,替换x2、x3中的某个变量。
第一行是符号
第二行:若x1替换x3作为基变量,x3=0时,x1=(15/2)/(3/2)=5
第三行:若x1替换x2作为基变量,x2=0时,x1=(9/2)/(1/2)=9
显然,应该把x2作为非基变量。
当目标函数用非基变量的线性组合表示时,所有的系数均不大于0,则表示目标函数达到最优。
如果,有一个非基变量的系数为0,其他的均小于0,表示目标函数的最优解有无穷多个。这是因为目标函数的梯度与某一边界正交,在这个边界上,目标函数的取值均相等,且为最优。
使用单纯型法来求解线性规划,输入单纯型法的松弛形式,是一个大矩阵,第一行为目标函数的系数,且最后一个数字为当前轴值下的 z 值。下面每一行代表一个约束,数字代表系数每行最后一个数字代表 b 值。
算法和使用单纯性表求解线性规划相同。
对于线性规划问题:
Max x1 + 14* x2 + 6*x3
s . t . x1 + x2 + x3 <= 4
x1<= 2
x3 <= 3
3*x2 + x3 <= 6
x1,x2,x3 >= 0
我们可以得到其松弛形式:
Max x1 + 14*x2 + 6*x3 s.t. x1 + x2 + x3 + x4 = 4 x1 + x5 = 2 x3 + x6 = 3 3*x2 + x3 + x7 = 6 x1 , x2 , x3 , x4 , x5 , x6 , x7 ≥ 0
我们可以构造单纯性表,其中最后一行打星的列为轴值。
x1 | x2 | x3 | x4 | x5 | x6 | x7 | b |
---|---|---|---|---|---|---|---|
c1=1 | c2=14 | c3=6 | c4=0 | c5=0 | c6=0 | c7=0 | -z=0 |
1 | 1 | 1 | 1 | 0 | 0 | 0 | 4 |
1 | 0 | 0 | 0 | 1 | 0 | 0 | 2 |
0 | 0 | 1 | 0 | 0 | 1 | 0 | 3 |
0 | 3 | 1 | 0 | 0 | 0 | 1 | 6 |
* | * | * | * |
在单纯性表中,我们发现非轴值的x上的系数大于零,因此可以通过增加这些个x的值,来使目标函数增加。我们可以贪心的选择最大的c,再上面的例子中我们选择c2作为新的轴,加入轴集合中,那么谁该出轴呢?
其实我们由于每个x都大于零,对于x2它的增加是有所限制的,如果x2过大,由于其他的限制条件,就会使得其他的x小于零,于是我们应该让x2一直增大,直到有一个其他的x刚好等于0为止,那么这个x就被换出轴。
我们可以发现,对于约束方程1,即第一行约束,x2最大可以为4(4/1),对于约束方程4,x2最大可以为3(6/3),因此x2最大只能为他们之间最小的那个,这样才能保证每个x都大于零。因此使用第4行,来对各行进行高斯行变换,使得二列第四行中的每个x都变成零,也包括c2。这样我们就完成了把x2入轴,x7出轴的过程。变换后的单纯性表为:
x1 | x2 | x3 | x4 | x5 | x6 | x7 | b |
---|---|---|---|---|---|---|---|
c1=1 | c2=0 | c3=1.33 | c4=0 | c5=0 | c6=0 | c7=-4.67 | -z=-28 |
1 | 0 | 0.67 | 1 | 0 | 0 | -0.33 | 2 |
1 | 0 | 0 | 0 | 1 | 0 | 0 | 2 |
0 | 0 | 1 | 0 | 0 | 1 | 0 | 3 |
0 | 1 | 0.33 | 0 | 0 | 0 | 0.33 | 2 |
* | * | * | * |
继续计算,我们得到:
x1 | x2 | x3 | x4 | x5 | x6 | x7 | b |
---|---|---|---|---|---|---|---|
c1=-1 | c2=0 | c3=0 | c4=0 | c5=-2 | c6=0 | c7=0 | -z=-32 |
1.5 | 0 | 1 | 1.5 | 0 | 0 | -0.5 | 3 |
1 | 0 | 0 | 0 | 1 | 0 | 0 | 2 |
0 | 0 | 1 | 0 | 0 | 1 | 0 | 3 |
0 | 1 | 0.33 | 0 | 0 | 0 | 0.33 | 2 |
* | * | * | * |
此时我们发现,所有非轴的x的系数全部小于零,即增大任何非轴的x值并不能使得目标函数最大,从而得到最优解32.
整个过程代码如下所示:
1 #include <bits/stdc++.h>
2 using namespace std;
3 vector<vector<double> > Matrix;
4 double Z;
5 set<int> P;
6 size_t cn, bn;
7
8 bool Pivot(pair<size_t, size_t> &p)//返回0表示所有的非轴元素都小于0
9 {
10 int x = 0, y = 0;
11 double cmax = -INT_MAX;
12 vector<double> C = Matrix[0];
13 vector<double> B;
14
15 for( size_t i = 0 ; i < bn ; i++ )
16 {
17 B.push_back(Matrix[i][cn-1]);
18 }
19
20 for( size_t i = 0 ; i < C.size(); i++ )//在非轴元素中找最大的c
21 {
22 if( cmax < C[i] && P.find(i) == P.end())
23 {
24 cmax = C[i];
25 y = i;
26 }
27 }
28 if( cmax < 0 )
29 {
30 return 0;
31 }
32
33 double bmin = INT_MAX;
34 for( size_t i = 1 ; i < bn ; i++ )
35 {
36 double tmp = B[i]/Matrix[i][y];
37 if( Matrix[i][y] != 0 && bmin > tmp )
38 {
39 bmin = tmp;
40 x = i;
41 }
42 }
43
44 p = make_pair(x, y);
45
46 for( set<int>::iterator it = P.begin() ; it != P.end() ; it++)
47 {
48 if( Matrix[x][*it] != 0 )
49 {
50 //cout<<"erase "<<*it<<endl;
51 P.erase(*it);
52 break;
53 }
54 }
55 P.insert(y);
56 //cout<<"add "<<y<<endl;
57 return true;
58 }
59
60 void pnt()
61 {
62 for( size_t i = 0 ; i < Matrix.size() ; i++ )
63 {
64 for( size_t j = 0 ; j < Matrix[0].size() ; j++ )
65 {
66 cout<<Matrix[i][j]<<"\t";
67 }
68 cout<<endl;
69 }
70 cout<<"result z:"<<-Matrix[0][cn-1]<<endl;
71 }
72
73 void Gaussian(pair<size_t, size_t> p)//行变换
74 {
75 size_t x = p.first;
76 size_t y = p.second;
77 double norm = Matrix[x][y];
78 for( size_t i = 0 ; i < cn ; i++ )//主行归一化
79 {
80 Matrix[x][i] /= norm;
81 }
82 for( size_t i = 0 ; i < bn && i != x; i++ )
83 {
84 if( Matrix[i][y] != 0)
85 {
86 double tmpnorm = Matrix[i][y];
87 for( size_t j = 0 ; j < cn ; j++ )
88 {
89 Matrix[i][j] = Matrix[i][j] - tmpnorm * Matrix[x][j];
90 }
91 }
92 }
93 }
94
95 void solve()
96 {
97 pair<size_t, size_t> t;
98 while(1)
99 {
100
101 pnt();
102 if( Pivot(t) == 0 )
103 {
104 return;
105 }
106 cout<<t.first<<" "<<t.second<<endl;
107 for( set<int>::iterator it = P.begin(); it != P.end() ; it++ )
108 {
109 cout<<*it<<" ";
110 }
111 cout<<endl;
112 Gaussian(t);
113 }
114 }
115
116 int main(int argc, char *argv[])
117 {
118 //ifstream fin;
119 //fin.open("./test");
120 cin>>cn>>bn;
121 for( size_t i = 0 ; i < bn ; i++ )
122 {
123 vector<double> vectmp;
124 for( size_t j = 0 ; j < cn ; j++)
125 {
126 double tmp = 0;
127 cin>>tmp;
128 vectmp.push_back(tmp);
129 }
130 Matrix.push_back(vectmp);
131 }
132
133 for( size_t i = 0 ; i < bn-1 ; i++ )
134 {
135 P.insert(cn-i-2);
136 }
137 solve();
138 }
139 /////////////////////////////////////
140 //glpk input:
141 ///* Variables */
142 //var x1 >= 0;
143 //var x2 >= 0;
144 //var x3 >= 0;
145 ///* Object function */
146 //maximize z: x1 + 14*x2 + 6*x3;
147 ///* Constrains */
148 //s.t. con1: x1 + x2 + x3 <= 4;
149 //s.t. con2: x1 <= 2;
150 //s.t. con3: x3 <= 3;
151 //s.t. con4: 3*x2 + x3 <= 6;
152 //end;
153 /////////////////////////////////////
154 //myinput:
155 /*
156 8 5
157 1 14 6 0 0 0 0 0
158 1 1 1 1 0 0 0 4
159 1 0 0 0 1 0 0 2
160 0 0 1 0 0 1 0 3
161 0 3 1 0 0 0 1 6
162 */
163 /////////////////////////////////////
【理论罗列】:
1.标准型
m个约束 n个变量用x向量表示 A是一个m*n的矩阵 c是一个n的向量 b是一个m的向量
最大化 cx
满足约束 Ax<=b x>0
2.松弛型
基本变量 B |B|=m 一个约束对应一个 表示松弛量 叫做松弛变量(基本变量)
非基变量 N |N|=n
xn+i=bi-sigma{aijxj}>=0
3.替入变量 xe(非基变量)
替出变量 xl(基本变量)
4.可行解
基本解:所有非基变量设为0
基本可行解
5.单纯形法的过程中B和N不断交换,在n维空间中不断走
“相当于不等式上的高斯消元”
【代码实现】:
pivot是转动操作
基本思想就是改写l这个约束为xe作为基本变量,然后把这个新xe的值带到其他约束和目标函数中,就消去xe了
改写和带入时要修改b和a 目标函数则是 c和v
转动时l和e并没有像算法导论上一样a矩阵用了两行分别是a[l][]和a[e][](这样占用内存大),而是用了同一行,这样a矩阵的行数=|B|,列数=|N|
也就是说,约束条件只用m个,尽管B和N不断交换,但同一时间还是只有m个约束(基本变量)n个非基变量
注意改写成松弛型后a矩阵实际系数为负
(一个优化 a[i][e]为0的约束没必要带入了
simplex是主过程
基本思想是找到一个c[e]>0的,然后找对这个e限制最紧的l,转动这组l e
注意精度控制eps
c[e]>eps
还有找l的时候a[i][e]>eps才行
【对偶原理】:
1.原始线性规划 对偶线性规划
2.对于
最大化 cx
满足约束 Ax<=b x>0
对偶问题为
最小化 bx
满足约束 ATx>=c x>0 (AT为A的转置)
可以转化很多问题来避免初始解不可行
我来秀智商了…… 说从前有个线性规划 min c x^T Ax = b x >= 0 这里面A是矩阵,x、b、c都是向量 x^T表示转置 啊……我们假设以上出现的所有元素都是整数…… 那么Ax = b要是恰好方程数等于未知数数,且解出来恰好为正数是不是就超开心? (假设是线性无关的) 根据那个啥法则,x_i = det(A_i) / det(A) det(A)表示A的行列式 A_i表示把A的第i列换为b之后的矩阵 如果det(A_i)恰好是det(A)的倍数那不就超开心?这样 但是现实是残酷的,往往这家伙会除出来小数,然后整数规划就坑爹了。 但是人类的智慧是无穷的! 我们现在还是假定“恰好方程数等于未知数数,且解出来恰好为正数” 我们再加个限制:det(A) = 1或-1 就233了吧。 解一定是整数了。 于是可以顺利变为整数规划。我们把det(A) = 1, -1的矩阵称为幺模矩阵。 但是现实是残酷的,“恰好方程数等于未知数数,且解出来恰好为正数”往往不满足。 但是其实没关系。由于每个方程都对应着一个平面,所以解的空间是单纯形,最优解一定会出现在顶点上。 何为顶点?就是平面的交点。 何为平面?一共m + n个:Ax = b是m个方程,x = 0是n个方程。(本来是x >= 0,我们只靠虑切割空间的平面……) 要是顶点都是整点不是超开心?等价于从这m + n个方程中任取n个方程把它解掉得到的解是整数解。 通过前面的分析我们知道,如果恰好选取的这n个方程的系数矩阵的行列式值为1,-1就超开心了。当然要是行列式值为0对应着无解或无穷多解的情况,它又不是顶点管它做甚…… 考察系数矩阵 一个是A,好大好大 另一个是x = 0的系数,易知就是单位矩阵I 你从I中选哪几行……由于行列式的性质……一行*k加到另一行上去行列式的值不变,那么对应的未知数就会被干掉…… 所以等价于…… 从A中任意选取是一个子方阵,它的行列式的值只可能为1, -1, 0。 这样的矩阵我们称为全幺模矩阵。 番外篇: 1. 必要不充分:只含1,-1,0。因为单个元素可以看作行列式…… 2. 充分必要:对它进行高斯消元的主元操作……(好像叫转轴?啊反正就是消别人的未知数……),得来的还是全幺模矩阵……这个是因为那个啥啥幺模矩阵组成了一个乘法群?用这个性质我们可以不用double。 3. 您可以手工屠矩阵用定义证它是全幺模! 4. 如果数学太差,您也可以写一个O(4^n * n^3)的强程序证它是全幺模! orzorzorz
附上一道题BZOJ 1061
1 #include<iostream>
2 #include<cstdio>
3 #include<cstring>
4 #include<algorithm>
5 #include<cmath>
6 using namespace std;
7 typedef long long ll;
8 const int M=10005,N=1005,INF=1e9;
9 const double eps=1e-6;
10 inline int read(){
11 char c=getchar();int x=0,f=1;
12 while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
13 while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();}
14 return x*f;
15 }
16
17 int n,m;
18 double a[M][N],b[M],c[N],v;
19 void pivot(int l,int e){
20 b[l]/=a[l][e];
21 for(int j=1;j<=n;j++) if(j!=e) a[l][j]/=a[l][e];
22 a[l][e]=1/a[l][e];
23
24 for(int i=1;i<=m;i++) if(i!=l&&fabs(a[i][e])>0){
25 b[i]-=a[i][e]*b[l];
26 for(int j=1;j<=n;j++) if(j!=e) a[i][j]-=a[i][e]*a[l][j];
27 a[i][e]=-a[i][e]*a[l][e];
28 }
29
30 v+=c[e]*b[l];
31 for(int j=1;j<=n;j++) if(j!=e) c[j]-=c[e]*a[l][j];
32 c[e]=-c[e]*a[l][e];
33
34 //swap(B[l],N[e])
35 }
36
37 double simplex(){
38 while(true){
39 int e=0,l=0;
40 for(e=1;e<=n;e++) if(c[e]>eps) break;
41 if(e==n+1) return v;
42 double mn=INF;
43 for(int i=1;i<=m;i++)
44 if(a[i][e]>eps&&mn>b[i]/a[i][e]) mn=b[i]/a[i][e],l=i;
45 if(mn==INF) return INF;//unbounded
46 pivot(l,e);
47 }
48 }
49
50 int main(){
51 n=read();m=read();
52 for(int i=1;i<=n;i++) c[i]=read();
53 for(int i=1;i<=m;i++){
54 int s=read(),t=read();
55 for(int j=s;j<=t;j++) a[i][j]=1;
56 b[i]=read();
57 }
58 printf("%d",(int)(simplex()+0.5));
59 }
附上几道题的题号,练习学习一下:
BZOJ 3550
BZOJ 3112
BZOJ 3265
BZOJ 1061
BZOJ 1061
POJ 1275
1 #include<iostream>
2 #include<cstdio>
3 #include<cstring>
4 #include<algorithm>
5 #include<cmath>
6 using namespace std;
7 typedef long long ll;
8 const int N=25;
9 const double eps=1e-8,INF=1e15;
10 inline int read(){
11 char c=getchar();int x=0,f=1;
12 while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
13 while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();}
14 return x*f;
15 }
16 int n,m,type;
17 double a[N][N],ans[N];
18 int id[N<<1];
19 int q[N];
20 void Pivot(int l,int e){
21 swap(id[n+l],id[e]);
22 double t=a[l][e]; a[l][e]=1;
23 for(int j=0;j<=n;j++) a[l][j]/=t;
24 for(int i=0;i<=m;i++) if(i!=l && abs(a[i][e])>eps){
25 t=a[i][e]; a[i][e]=0;
26 for(int j=0;j<=n;j++) a[i][j]-=a[l][j]*t;
27 }
28 }
29 bool init(){
30 while(true){
31 int e=0,l=0;
32 for(int i=1;i<=m;i++) if(a[i][0]<-eps && (!l||(rand()&1))) l=i;
33 if(!l) break;
34 for(int j=1;j<=n;j++) if(a[l][j]<-eps && (!e||(rand()&1))) e=j;
35 if(!e) {puts("Infeasible");return false;}
36 Pivot(l,e);
37 }
38 return true;
39 }
40 bool simplex(){
41 while(true){
42 int l=0,e=0; double mn=INF;
43 for(int j=1;j<=n;j++)
44 if(a[0][j]>eps) {e=j;break;}
45 if(!e) break;
46 for(int i=1;i<=m;i++) if(a[i][e]>eps && a[i][0]/a[i][e]<mn)
47 mn=a[i][0]/a[i][e],l=i;
48 if(!l) {puts("Unbounded");return false;}
49 Pivot(l,e);
50 }
51 return true;
52 }
53 int main(){
54 freopen("in","r",stdin);
55 srand(317);
56 n=read();m=read();type=read();
57 for(int i=1;i<=n;i++) a[0][i]=read();
58 for(int i=1;i<=m;i++){
59 for(int j=1;j<=n;j++) a[i][j]=read();
60 a[i][0]=read();
61 }
62 for(int i=1;i<=n;i++) id[i]=i;
63 if(init() && simplex()){
64 printf("%.8lf\n",-a[0][0]);
65 if(type){
66 for(int i=1;i<=m;i++) ans[id[n+i]]=a[i][0];
67 for(int i=1;i<=n;i++) printf("%.8lf ",ans[i]);
68 }
69 }
70 }
充分条件:
任何最大流、最小费用最大流的线性规划都是全幺模矩阵