参考:http://www.cnblogs.com/ECJTUACM-873284962/
单纯形法是解决线性规划问题的一个有效的算法。线性规划就是在一组线性约束条件下,求解线性目标函数最优解的问题。
在约束条件下,寻找目标函数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
所以,通过选择不同的基变量,可以获得不同的可行域的顶点。
注意,有些书上,把定为0的变量叫非基变量,而通过等式约束求解出来的变量叫做基变量。
如前所述,基变量可由非基变量表示:
目标函数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
2 using namespace std;
3 vector > Matrix;
4 double Z;
5 set P;
6 size_t cn, bn;
7
8 bool Pivot(pair &p)//返回0表示所有的非轴元素都小于0
9 {
10 int x = 0, y = 0;
11 double cmax = -INT_MAX;
12 vector C = Matrix[0];
13 vector 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::iterator it = P.begin() ; it != P.end() ; it++)
47 {
48 if( Matrix[x][*it] != 0 )
49 {
50 //cout<<"erase "<<*it< 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 t;
98 while(1)
99 {
100
101 pnt();
102 if( Pivot(t) == 0 )
103 {
104 return;
105 }
106 cout<::iterator it = P.begin(); it != P.end() ; it++ )
108 {
109 cout<<*it<<" ";
110 }
111 cout<>cn>>bn;
121 for( size_t i = 0 ; i < bn ; i++ )
122 {
123 vector 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才行
上面默认已经有了一个基本可行解,才用单纯形法,实际中,如何得到这个初始的基本可行解呢?
另外,还要注意:
实际中,可能出现多次换基,得到相同的基本解,就是退化解,出现退化解,从而更新步长为0,但是,由于换基导致了单纯形方向发生变化,所以,可以无视退化,继续进行。但是,也有可能一直死循环下去。
所以,单纯形算法,一般情况下是收敛的,但在极端情况下,也可能不收敛。
最后,适合计算机实现的单纯形算法,一般是通过矩阵运算方式,在求逆,换基,基变量顺序等角度,做实现优化,起到加速作用,同时,实际使用时,也可以增加约束条件的上下限,从而起到加速收敛的作用。