单纯形算法详细解析

线性规划(Linear Programming,LP)是非常经典的算法之一,而解决该问题的最常用方法是单纯形法。本博文致力于用最简单、最详细的语言一步步解释单纯形算法的过程并加以详细的解释。

中学课程里,我们都简单地接触过线性规划,那时候一般都是分析每个约束,在二维平面上画出直线,得到可行域,然后以固定斜率作出目标函数直线,在可行域内移动直线,在y轴上的截距就是最优解。而往往最优解的地方是通过(凸)可行域的顶点。就像下面这个例子:

\[
\begin{equation}
\begin{split}
\max& \quad x_3+x_4 \s.t.& \quad -x_3+2x_4\leq 2 \&\quad 3x_3-2x_4\leq 6 \&\quad x_3, x_4\geq 0
\end{split}
\end{equation}
\]

蓝色区域是可行域,红色直线是固定斜率的,当上移到(4,3)点时目标函数取最大值。

而我们后面将证明,最优解一定是可行域(凸超几何体)的顶点之一。那么,我们先假定这成立,就使用”改进-停止“(improve-stopping)的套路,即给定可行域的一个顶点,求值,沿一条边到达下一个顶点,看是否能改善解,直到达到停止要求。

这里就要问几个问题了:为什么最优解一定在顶点处?怎么得到顶点?怎么实现沿着一条边到下一个顶点?什么时候停止?接下来,我们将一一解答这些问题,当解答完这些问题,单纯形法也就显而易见了。

1、凸多边形最优解在顶点

考虑最小化问题,目标函数\(\mathbf{c}^T \mathbf{x}\),有一个在可行域内部的最优解\(\mathbf{x}^{(0)}\),因为凸多边形内部任一点都可以表示成顶点的线性组合,即对于顶点\(\mathbf{x}^{(k)}, k=1,2,\cdots, n\),有

\[
\mathbf{x}^{(0)}=\sum_{k=1}^{n}\lambda_k\mathbf{x}^{(k)},
\]

\[
\sum_{k=1}^n \lambda_k=1
\]

假设\(\mathbf{x}^{(i)}\)是所有顶点中使得\(\mathbf{c}^T \mathbf{x}\)最小的顶点,那么有

\[
\begin{equation}
\begin{split}
\mathbf{c}^T\mathbf{x}^{(0)} &= \sum_{k=1}^{n}\lambda_k\mathbf{c}^T\mathbf{x}^{(k)} \&\geq \sum_{k=1}^{n}\lambda_k\mathbf{c}^T\mathbf{x}^{(i)} \&= \mathbf{c}^T\mathbf{x}^{(i)}
\end{split}
\end{equation}
\]

因此,总有一个顶点,他的目标函数值不比内部点差。

2、怎样得到一个凸多边形的顶点?

下面要说明的就是这样一个定理:对于可行域方程组\(\mathbf{Ax=b}\),该方程确定的凸多边形的任意一个顶点对应\(\mathbf{A}\)的一组基。

2.1 顶点对应一组基

上面这个例子是松弛形式的约束,原来的变量有三个,加上后面4个后变成等式,形成的可行域如上图所示。我们取一个顶点(0,2,0)分析,代入约束中,可以算出一个完整解\(x=(0,2,0,2,2,3,0)\)。取出矩阵\(\mathbf{A}\)中对应的\(x\)不为0的列,即图中标蓝的几列(用\(\mathbf{a}_2\),\(\mathbf{a}_4\),\(\mathbf{a}_5\),\(\mathbf{a}_6\)表示),那么这几列就是线线性无关的,考虑\(m<n\)(约束数目小于松弛后的变量个数),那么自由解有\(n-m\)维,因此挑出来的列必有\(m\)个,构成一组基。下面主要说明他们为什么线性无关。假设他们线性相关,即存在一组\(\lambda\neq\mathbf{0}\),使得\(\lambda_2\mathbf{a}_2+\lambda_4\mathbf{a}_4+\lambda_5\mathbf{a}_5+\lambda_6\mathbf{a}_6=0\),也就是说,可以构造\(\lambda=[0, \lambda_2, 0, \lambda_4, \lambda_5, \lambda_6, 0]\),使得\(\mathbf{A}\lambda=0\)。那么还可以再构造两个异于\(x\)的解:\(x‘=x+\theta\lambda\)和\(x‘‘=x-\theta\lambda\)。他们都满足\(\mathbf{Ax=b}\)。并且可以通过控制\(\theta\)取很小的正值,使得这两个解满足都大于0的约束。由此这两个解都在凸多面体内,并且有\(x=\frac{1}{2}(x‘+x‘‘)\),但是这是有问题的,因为一个凸多面体的顶点是不能被内部点线性表示的,因此这几列是构成一组基的。

在这里,我们还可以对每一组解,都将\(\mathbf{A}\)的列重新排列一下,将解向量也排列一下,写成分块矩阵的形式,那么就会有\(\mathbf{x}_{\mathbf{B}}=\mathbf{B}^{-1}\mathbf{b}\)和\(\mathbf{c^Tx=c_B^TB^{-1}b}\)。这是两个很有用的式子,在后面单纯形算法的理解上很有帮助,这里先记下。

2.2 一组基对应一个基可行解(顶点)

有了上面的知识,给定一组基\(\mathbf{B}\),我们直接构造\(\mathbf{x}=[\mathbf{B^{-1}b}, \mathbf{0}]^T\),只要说明他不能被两个异于他的内部点线性表示即可。假设有两个内部点\(\mathbf{x}_1, \mathbf{x}_2\),满足\(\mathbf{x}=\lambda_1\mathbf{x}_1+\lambda_2\mathbf{x}_2\),由于\(\mathbf{x_N}=0\),并且这些解的元素都大于等于0,因此\(\mathbf{x_1}_{\mathbf{N}}=\mathbf{x_2}_{\mathbf{N}}=0\)。而又因为\(\mathbf{Ax_1=Ax_2=b}\),因此\(\mathbf{x_1}_{\mathbf{B}}=\mathbf{x_2}_{\mathbf{B}}=\mathbf{B^{-1}b}\)。即这两个解和\(\mathbf{x}\)相同,因此\(\mathbf{x}\)是顶点。

3 如何从一个顶点沿着边到另一个顶点?

这里是要研究怎么改善一个解,我们需要知道怎么从一个顶点出发沿着边找到另一个顶点。前面我们知道了一个顶点对应一组基,而且一个矩阵的基有多个,那么是否可以通过基的变换从而使得顶点变换呢?先来看一个例子。

图中红色点对应一个完全解\(\mathbf{x}=[2,0,0,2,0,3,6]\),对应的基是\(\mathbf{B}=\{\mathbf{a}_1,\mathbf{a}_4,\mathbf{a}_6,\mathbf{a}_7\}\),考虑向量\(\mathbf{a}_3\),即绿色那列,他可以表示成:

\[
\mathbf{a}_3=0\mathbf{a}_1+1\mathbf{a}_4+1\mathbf{a}_6+1\mathbf{a}_7
\]

将式子补全,就会有

\[
0\mathbf{a}_1+0\mathbf{a}_2-1\mathbf{a}_3+1\mathbf{a}_4++0\mathbf{a}_5+1\mathbf{a}_6+1\mathbf{a}_7=0
\]

把系数写出来:\(\lambda=[0,0,-1,1,0,1,1]\),他就是对应上图中的绿色向量(相反方向)。因此,只有沿着这个方向走适合的步长\(\theta\),就能到达下一个顶点。即新的顶点和旧的顶点关系为:

\[
\mathbf{x‘}=\mathbf{x}-\theta\mathbf{\lambda}(\theta>0)
\]

那么多大的\(\theta\)合适呢?我们很容易知道\(\mathbf{x‘}\)能够满足\(\mathbf{Ax=b}\),因为\(\mathbf{A\lambda=0}\),现在要保证的就是\(\mathbf{x‘}\)的各个分量大于等于0。对于\(\lambda_i\leq0\)的项,相减后大于0,没问题。但是对于\(\lambda_i>0\)的项,就要小心了,为了保证相减后仍然大于等于0,我们设置

\[
\theta=\min\limits_{\mathbf{a}_i\in \mathbf{B},\lambda_i>0}\frac{x_i}{\lambda_i}
\]

就能保证\(\mathbf{x‘}\geq0\)。在上面的例子中,\(\theta=2\),新解是\(\mathbf{x‘}=[2,0,2,0,0,1,4]\),对应的基是\(\mathbf{B‘}=\{\mathbf{a}_1,\mathbf{a}_3,\mathbf{a}_6,\mathbf{a}_7\}\),到这里,一切看上去很完美,我们找到了运动到下一个顶点的方法,也就是先找一个非基向量,将他写成用基向量表示的形式,提出系数,确定步长,得到新解。但是还有一个小问题,我们看到实际上\(\mathbf{B‘}\)和\(\mathbf{B}\)差了一个向量,相当于把\(\mathbf{a_4}\)换出去,把\(\mathbf{a}_3\)换进来。我们称这个过程为换基,后面算法实现部分叫pivot。那么,怎么保证换了个向量之后,仍然是基呢?证明一下:

证明:\(\mathbf{B‘}=\mathbf{B}-\{\mathbf{a}_l\}+\{\mathbf{a}_e\}\)仍然是基。(l表示leave,e表示enter)

假设\(\mathbf{B‘}\)线性相关,那么存在\(<d_1,d_2,\ldots,d_{l-1},d_{l+1},\ldots,d_m, d_e>\neq0\),使得\(\sum_{k}d_k\mathbf{a}_k=0\)。而\(\mathbf{a}_e=\sum_{i=1}^m \lambda_i\mathbf{a_i}\),代入得:

\[
(d_1+d_e\lambda_1)\mathbf{a_1}+\ldots+(d_e\lambda_l)\mathbf{a}_l+\ldots+(d_m+d_e\lambda_m)\mathbf{a}_m=0
\]

这里是证明的关键之处:我们在设置\(\theta\)时的做法,假如最终选出来的使得\(\frac{x_i}{\lambda_i}\)最小的那一项下标为\(p\),那么得到的新解的第\(p\)项必然为0,相当于把\(\mathbf{a}_p\)换了出去。在上面这个例子重\(p=4\)。而因为我们只考虑\(\lambda_i>0\)的基向量,因此被换出去的基\(\mathbf{a}_l\)对应的\(\lambda_l>0\),因此上式中有\(d_1=d_2=\ldots=d_m=d_e=0\),和原假设矛盾,因此\(\mathbf{B‘}\)也是线性无关的。

截止到目前,我们回答了三个问题,基本上单纯形算法呼之欲出了,单纯形算法就是通过反复的基变换(通过向量的进出)来找顶点,从而找到达到最优值的顶点。但是还是有些细节需要琢磨,比如,怎么选入基向量?改善的过程什么时候停止?

4 入基向量的选择及停止准则

以最小化问题为标准,我们的最终目标是最小化\(\mathbf{c^Tx}\),因此一个很自然的贪心想法是每步的改善都尽可能地大,因此可以计算一下更新的目标函数值和原来的差值,取使得变化大的顶点继续下一步迭代。那么这个差值怎么能够向量化地计算呢?只有向量化地计算,才能避免一个一个地计算比较,提高效率。

假设\(\mathbf{B}=\{\mathbf{a}_1, \mathbf{a}_2,\ldots, \mathbf{a}_m\}\),那么对于任何一个非基向量\(\mathbf{a}_e\),都有\(\mathbf{a}_e=\lambda_1\mathbf{a}_1+\ldots+\lambda_m\mathbf{a}_m\)。将\(\lambda\)写完整:\(\lambda=[\lambda_1,\lambda_2,\ldots,\lambda_m,0,0,\ldots,-1,\ldots,0]^T\),差值\(\mathbf{c^Tx‘-c^Tx=c^T(-\theta\lambda)=\theta(c_e-\sum_{a_i\in B}\lambda_ic_i)}\),因此我们要选使得这个值的绝对值最大的\(\mathbf{a}_e\)。那么什么时候表示找到最优值应该停止呢?很明显,就是对于所有\(\mathbf{a}_e\),这个差值都大于等于0,即目标函数不再减小。因此,每次迭代,先计算差值,如果存在小于0的,就选一个使得差值绝对值最大的作为入基向量。

嗯,接下来就是要考虑向量化操作。首先我们看一下\(\lambda\)能不能向量化表示:由于\(\mathbf{B}\lambda=\mathbf{a}_e\)(\(\lambda\)只取基系数部分),因此\(\lambda=\mathbf{B^{-1}}\mathbf{a}_e\),如果对整个矩阵\(\mathbf{A}\)左乘\(\mathbf{B^{-1}}\),这就很有意思了,所有的非基列将变成该非基向量用基向量表示的系数,而所有的基列将变成\(e_k\),即合起来成为一个单位阵的形式。这是很关键的一步,在单纯形算法实现中也涉及到,先记下。进一步,我们取\(\mathbf{c}\)的基部分\(\mathbf{c_B}\),这样\(\mathbf{c_B^TB^{-1}A}\)就等于了上式中的\(\sum_{\mathbf{a}_i\in \mathbf{B}}\lambda_i\mathbf{c}_i\)的向量化形式(对所有的非基向量一同操作)。然后再加上一部分,变成\(\mathbf{c^T-c_B^TB^{-1}A}\),这就是最终的形式,称之为检验数\(\bar{\mathbf{c}}\)。很容易验证,基向量对应的检验数都是0,我们的目标就是通过迭代,使得\(\bar{\mathbf{c}}\geq0\),这时对于任何一个可行解\(\mathbf{y}\)(\(\mathbf{Ay=b,y\geq0}\)),都有\(\mathbf{c^Ty}\geq\mathbf{c_B^TB^{-1}Ay=c_B^TB^{-1}b=c_B^Tx_B=c^Tx}\),即\(\mathbf{x}\)就是最优的。

5 单纯性算法核心:单纯形表

终于,一系列的讲解结束,单纯性算法也就顺理成章了。要将上面的一堆东西整理成一个简短高效的可行算法并不简单。先贴上算法伪代码:

再献上一个非常漂亮的单纯形表:

现在,我们来对算法进行分析,将算法的每个操作和我们上面的介绍对应起来。

  • SIMPLEX算法一开始调用INITIALIZESIMPLEX找到一个初始基可行解,这在某些情况下很简单,当\(\mathbf{b}\geq0\)时,他就是一个初始基可行解,否则,可能要用到两阶段法、大M法等求,这不是重点。
  • WHILE循环内,只要找到一个\(c_e<0\),就继续迭代。第10到16行就是通过设定\(\theta\)找到出基向量。
  • 最关键最有意思的就是PIVOT算法,他巧妙地将我们介绍的繁杂操作使用一个简单的高斯行变换就实现了。而这个算法的载体就是单纯形表,如上图,左上角是目标函数值相反数\(-z\),第一行是检验数\(\bar{\mathbf{c}}\),左下角是基对应的部分解(其他部分是0,不用写出),右下角是矩阵\(\mathbf{A}\)。他始终被分块成两部分,基向量部分始终以单位阵的形式存在。注意左边的部分解每个分量都是严格对应着一个基向量,即他们是有顺序的。
  • 一行一行地看PIVOT算法。输入参数告诉我们下标为\(l\)的向量被换出,因此找到他对应的那行,暂称为第\(l\)行,这一行对应的基的下标要被换成\(e\),那么为什么更新后对应的解是\(\frac{b_l}{a_{le}}\)呢,要注意,其实这个值就是\(\theta\),\(b_l\)就是旧的\(x_i\),\(a_{le}\)就是\(\lambda_i\)(上面已经解释了乘上\(\mathbf{B^{-1}}\)后每一列都是系数)。那么为什么更新后是\(\theta\)呢?我们回到式子\(\mathbf{x‘=x-\theta\lambda}\),由于\(b_l\)现在对应的新向量不是\(\mathbf{x}\)对应的基向量,因此\(\mathbf{x}\)在该位置的值是0,而我们知道\(\lambda\)在入基向量对应的位置的值是-1,因此\(0-(-1)\theta=\theta\)。
  • 第3到4行,将第\(l\)行除以\(a_{le}\),目的就是将\(a_{le}\)变成1,因为要始终保持基是以单位阵的形式存在。
  • 第8行,就是在执行\(\mathbf{x‘=x-\theta\lambda}\)的操作,得到新解。
  • 第10行,高斯行变换,你会发现这样操作完后,入基列就变成和刚才出基列一样,高斯行变换保证了矩阵的性质。
  • 第14行,我们知道\(-z=0-\mathbf{c_B^TB^{-1}b}\),由于旧有的基对应的\(c\)都是0,而只有新换入的向量对应的\(c_e\)不为0,具体写一下,减掉的那部分就只有\(c_e\)和他对应的解\(b_l\)的乘积了。同理,第16行,\(\mathbf{c^T-c_B^TB^{-1}A}\),由于也是只有\(c_e\)不为0,因此就和他对应的\(\mathbf{A}\)的第\(l\)行相乘了。

到此,终于介绍完了单纯形算法。其他还有一些要注意的地方,比如一定要注意检验数和原目标函数的\(\mathbf{c}\)是完全不一样的概念,在原约束为不等式,需要加松弛变量的情况下,他们可能相等,但心里一定要区分它们,同时,这种情况下,基很容易找,就是松弛变量的那几列构成的单位阵。但是如果原约束是等式,就需要自己找基,并且这时检验数往往就和目标函数参数不同了。

最后,本文所用的截图均来自中科院计算所B老师的课程PPT,本人在学习该课程时也受益良多,对单纯形算法也钻研了比较长的时间,因此撰写本文,希望给大家一个学习参考!其中可能有错误之处,欢迎指正讨论。

原文地址:https://www.cnblogs.com/Kenneth-Wong/p/8451343.html

时间: 2024-10-08 12:19:45

单纯形算法详细解析的相关文章

算法面试课程笔记000 玩转算法面试 leetcode题库分门别类详细解析

算法面试课程笔记 =============================================================================== 本文地址 : =============================================================================== liuyubobobo老师 <<玩转算法面试 leetcode题库分门别类详细解析>> 为了面试,更为了提升你的算法思维 http:/

算法核心——空间复杂度和时间复杂度超详细解析

算法核心——空间复杂度和时间复杂度超详细解析 一.什么是算法 算法: 一个有限指令集 接受一些输入(有些情况下不需要收入) 产生输出 一定在有限步骤之后终止 每一条指令必须: 有充分明确的目标,不可以有歧义 计算机能处理的范围之内 描述应不依赖于任何一种计算机语言以及具体的实现手段 其实说白了,算法就是一个计算过程解决问题的方法.我们现在已经知道数据结构表示数据是怎么存储的,而“程序=数据结构+算法”,数据结构是静态的,算法是动态的,它们加起来就是程序. 对算法来说有输入,有输出,相当于函数有参

Mahout机器学习平台之聚类算法详细剖析(含实例分析)

第一部分: 学习Mahout必须要知道的资料查找技能: 学会查官方帮助文档: 解压用于安装文件(mahout-distribution-0.6.tar.gz),找到如下位置,我将该文件解压到win7的G盘mahout文件夹下,路径如下所示: G:\mahout\mahout-distribution-0.6\docs 学会查源代码的注释文档: 方案一:用maven创建一个mahout的开发环境(我用的是win7,eclipse作为集成开发环境,之后在Maven Dependencies中找到相应

微信消息体签名及加解密功能详细解析以及.net实现

原文:微信消息体签名及加解密功能详细解析以及.net实现 前言 微信消息体签名及加密功能已上线,明文传输确实存在安全风险,鉴于微信的用户范围使用之广泛,必定会成为众矢之的.所以大家还是尽快接入安全模式为好.仔细阅读官方接入指南,发现这次安全升级只是涉及到用户在微信对话窗口中与公众好消息交互,所以此次升级还是比较简单的.下面为大家一一道来. 一.功能解析 微信消息体签名及加密功能已上线,出于安全考虑,强烈建议您尽快接入消息加密功能,消除安全风险.详见公告.公众平台接口调试工具已经全面支持消息体加密

linux中的压缩命令详细解析(二)

我们在<Linux中的压缩命令详细解析(一)>中已经讲解了常见的三种压缩命令,下面我们开始讲解工作中最常用到的tar命令. 为了使压缩和解压缩变得简单,tar命令就应运而生了.那么究竟该如何使用呢? tar.gz格式: 压缩命令: tar -zcvf 压缩文件名 源文件名 举例: 把abc文件压缩成后缀为tar.gz格式的文件 tar -zcvf abc.tar.gz abc 解压缩命令: 举例:解压缩abc.tar.gz文件 tar -zxvf abc.tar.gz tar.bz2格式: 压

【转】UML中的几种关系详细解析

UML图中类之间的关系:依赖,泛化,关联,聚合,组合,实现 类与类图 1) 类(Class)封装了数据和行为,是面向对象的重要组成部分,它是具有相同属性.操作.关系的对象集合的总称. 2) 在系统中,每个类具有一定的职责,职责指的是类所担任的任务,即类要完成什么样的功能,要承担什么样的义务.一个类可以有多种职责,设计得好的类一般只有一种职责,在定义类的时候,将类的职责分解成为类的属性和操作(即方法). 3) 类的属性即类的数据职责,类的操作即类的行为职责 一.依赖关系(Dependence) 依

字符数组的定义与使用详细解析

1. 字符数组的定义: 用来存放字符量的数组称为字符数组. 形式数值数组相同.例如: char c[10]; 由于字符型和整型通用,也可以定义为int c[10],但这时每个数组元素占2个字节的内存单元. 字符数组也可以是二维或多维数组.例如: char c[5][10]; 即为二维字符数组. 2. 字符数组的初始化 第一种方法是分别对每一个元素进行赋值操作: 字符数组也允许在定义时作初始化赋值.例如: char c[10]={'c', '  ', 'p', 'r','o', 'g', 'r',

红黑树(一)之 原理和算法详细介绍---转帖

目录1 红黑树的介绍2 红黑树的应用3 红黑树的时间复杂度和相关证明4 红黑树的基本操作(一) 左旋和右旋5 红黑树的基本操作(二) 添加6 红黑树的基本操作(三) 删除 作者:Sky Wang    于 2013-08-08 概述:R-B Tree,又称为"红黑树".本文参考了<算法导论>中红黑树相关知识,加之自己的理解,然后以图文的形式对红黑树进行说明.本文的主要内容包括:红黑树的特性,红黑树的时间复杂度和它的证明,红黑树的左旋.右旋.插入.删除等操作. 请尊重版权,转

【剧透高亮】最最最完整剧透加剧情详细解析

在美国看的,IMAX大厅爆满!只能缩在角落里的位置看,但是还是不影响观影过程,被震撼到不行!看到最后黑洞的情节都快哭出来跪在地上了!Hans Zimmer的配乐太结棍了啊! 就像诺兰所有的电影一样,Interstellar是一部烧脑+解读人性+看完后需要阅读大量相关资料补课的大片!尤其是马修麦康纳那mumbling的口音简直听得人更加confused神烦啊!伐碍紧!我们来把剧情从头到尾理一遍! OK废话不多say了,直接上剧情解析   在未来的世界,由于科技太发达,人类对于能源的过度开发导致地球