定义
整数规划是指规划中的变量(全部或部分)限制为整数,若在线性模型中,变量限制为整数,则称为整数线性规划。所流行的求解整数规划的方法往往只适用于整数线性规划。 在线性规划问题中,有些最优解可能是分数或小数,但对于某些具体问题,常要求某些变量的解必须是整数。例如,当变量代表的是机器的台数,工作的人数或装货的车数等。为了满足整数的要求,初看起来似乎只要把已得的非整数解舍入化整就可以了。实际上化整后的数不见得是可行解和最优解,所以应该有特殊的方法来求解整数规划。在整数规划中,如果所有变量都限制为整数,则称为纯整数规划;如果仅一部分变量限制为整数,则称为混合整数规划。整数规划的一种特殊情形是01规划,它的变数仅限于0或1。不同于线性规划问题,整数和01规划问题至今尚未找到一般的多项式解法。分类
如果不加特殊说明,一般指整数线性规划。对于整数线性规划的模型大致可分为:- 纯整数规划:所有决策变量都是整数的整数规划
- 混合整数规划:部分决策变量要求为整数的整数规划
- 纯0-1整数规划:所有决策变量均要求为0-1的整数规划
- 混合0-1整数规划:部分决策变量均要求为0-1的整数规划
整数规划的特点
原线性规划有最优解,当自变量限制为整数后,其整数规划解出现下述情况:
- 原线性规划最优解全是整数,则整数规划最优解与线性规划最优解一致。
- 整数规划无可行解。
- 有可行解(当然就存在最优解),但最优解值变差。
整数规划最优解不能按照实数最优解简单取整而获得。
整数规划求解方法
- (1)分枝定界法:可求纯或混合整数线性规划。
(2)割平面法:可求纯或混合整数线性规划。
(3)隐枚举法:用于求解0-1整数规划,有过滤法和分枝法。
(4)匈牙利法:解决指派问题(0-1规划特殊情形)。
(5)蒙特卡罗法:求解各种类型规划。
分支定界法
分支定界法的基本思想:分支定界法是一种分而治之的方法。思路是将整数规划分解为两个更简单的整数规划问题。
求解步骤
- 首先规定求解的整数规划问题为A,相应的线性规划问题为B
对问题B进行求解
- 若B无可行解,则A也无可行解,停止计算
- 若B有最优解,且符合整数条件,该最优解为A的最优解,停止计算
- 若B有最优解,但不符合整数条件,记它的目标函数值为z*,作为最优值的下界
- 找出问题A的一个整数可行解,其目标函数值作为最优解的上界
进行迭代:
- 分支,在B的最优解中任选一个不符合整数条件的变量$x_j$ ,其值为$b_j$ ,构造两个约束条件$x_jleq [b_j]、x_jgeq [b_j +1]$分别加入到问题B中,形成两个子问题$B_1,B_2$。不考虑整数条件求解这两个子问题。即分支。
- 定界。对每个后继问题表明其求解的结果,与其他问题进行比较,将最优目标函数值最小者(不包括问题B)作为新的下界,在已符合整数条件的各分支中,找出目标函数值最小者作为新的上界。
- 剪枝,将目标函数值不在上界、下界中的分支剪去。
- 重复1 2 3,直到得到最优解
- 注意事项:在对两分支进行分解时,优先选择目标函数值最小的分支进行分解。
原理示例
代码示例
- 建立主函数
% main.m % 分支定界法 % ----------------------------------------------- % 求解模型: % min z = f * x % A * x <= b % x >= 0,且为整数 % --------------------------------------------------- clear global; clear; clc; global result; % 存储所有整数解 global lowerBound; % 下界 global upperBound; % 上界 global count; % 用以判断是否为第一次分支 count = 1; f = [-40, -90]; A = [8, 7; 7, 20;]; b = [56; 70]; Aeq = []; beq = []; lbnd = [0; 0]; ubnd = [inf; inf]; BinTree = createBinTreeNode({f, A, b, Aeq, beq, lbnd, ubnd}); if ~isempty(result) [fval,flag]=min(result(:,end)); % result中每一行对应一个整数解及对应的函数值 Result=result(flag,:); disp('该整数规划问题的最优解为:'); disp(Result(1,1:end-1)); disp('该整数规划问题的最优值为:'); disp(Result(1,end)); else disp('该整数规划问题无可行解'); end
- 建立二叉树
% createBinTreeNode.m % 构建二叉树,每一结点对应一解 function BinTree = createBinTreeNode(binTreeNode) global result; global lowerBound; global upperBound; global count; if isempty(binTreeNode) return; else BinTree{1} = binTreeNode; BinTree{2} = []; BinTree{3} = []; [x, fval, exitflag] = linprog(binTreeNode{1}, binTreeNode{2}, binTreeNode{3}, ... binTreeNode{4}, binTreeNode{5}, binTreeNode{6}, binTreeNode{7}); if count == 1 % upperBound = 0; % 初始下界为空 lowerBound = fval; count = 2; end if ~IsInRange(fval) return; end if exitflag == 1 flag = []; % 寻找非整数解分量 for i = 1 : length(x) if round(x(i)) ~= x(i) flag = i; break; end end % 分支 if ~isempty(flag) lowerBound = max([lowerBound; fval]); OutputLowerAndUpperBounds(); lbnd = binTreeNode{6}; ubnd = binTreeNode{7}; lbnd(flag, 1) = ceil(x(flag, 1)); % 朝正无穷四舍五入 ubnd(flag, 1) = floor(x(flag, 1)); % 进行比较,优先选择目标函数较大的进行分支 [~, fval1] = linprog(binTreeNode{1}, binTreeNode{2}, binTreeNode{3}, ... binTreeNode{4}, binTreeNode{5}, binTreeNode{6}, ubnd); [~, fval2] = linprog(binTreeNode{1}, binTreeNode{2}, binTreeNode{3}, ... binTreeNode{4}, binTreeNode{5}, lbnd, binTreeNode{7}); if fval1 < fval2 % 创建左子树 BinTree{2} = createBinTreeNode({binTreeNode{1}, binTreeNode{2}, binTreeNode{3}, ... binTreeNode{4}, binTreeNode{5}, binTreeNode{6}, ubnd}); % 创建右子树 BinTree{3} = createBinTreeNode({binTreeNode{1}, binTreeNode{2}, binTreeNode{3}, ... binTreeNode{4}, binTreeNode{5}, lbnd, binTreeNode{7}}); else % 创建右子树 BinTree{3} = createBinTreeNode({binTreeNode{1}, binTreeNode{2}, binTreeNode{3}, ... binTreeNode{4}, binTreeNode{5}, lbnd, binTreeNode{7}}); % 创建左子树 BinTree{2} = createBinTreeNode({binTreeNode{1}, binTreeNode{2}, binTreeNode{3}, ... binTreeNode{4}, binTreeNode{5}, binTreeNode{6}, ubnd}); end else upperBound = min([upperBound; fval]); OutputLowerAndUpperBounds(); result = [result; [x', fval]]; return; end else % 剪枝 return; end end
- 判断目标函数是否在上下界中
% IsInRange.m % 判断分支问题的解是否在上下界的范围中,若不在,剪去 function y = IsInRange(fval) global lowerBound; global upperBound; if isempty(upperBound) & fval >= lowerBound y = 1; else if (fval >= lowerBound & fval <= upperBound) y = 1; else y = 0; end end
- 输出上下界
% 打印输出上下界 function y = OutputLowerAndUpperBounds() global lowerBound; global upperBound; disp("此时下界、上界分别为"); disp(lowerBound); if isempty(upperBound) disp(' 无上界') else disp(upperBound); end end
隐枚举法
解决0-1问题,因为变量的取值是取0-1的,所以可以枚举所有取值求得极大/极小值。例如,求解:- (1)试探一个可行解(x1,x2,x3)=(1,0,0),相应的目标函数值是3。暂做最优解。
(2)继续试探其他可行解。倘若目标函数值小于3则不考虑,这相当于增加了目标大于等于3的又一个约束。否则目标函数值大于3,则新的可行解暂做最优解,更新当前最优目标函数值,重复(2)。
(3)直到:枚举完所有可行解。
隐枚举的思想是首先枚举找到一个可行解,并得到目标函数值z0,之后的枚举若目标函数值没有z0优,那么就一定不是最优解。
固定费用问题
举例说明这类问题是:有三种产品投资方式,相应增加A产品投资会使得A的固定成本上升,而由于产品产量增加使得单个产品费用下降,问如何投资使得成本最低。 解决这个问题的一个方法可以是:列成本函数,引入0-1变量统一到一个规划问题中。具体求解不赘述。指派问题
实际中,会遇到这样的问题,有n项不同的任务,需要n个人分别完成其中的1项,每个人完成任务的时间不一样。于是就有一个问题,如何分配任务使得花费时间最少。 通俗来讲,就是n*n矩阵中,选取n个元素,每行每列各有1个元素,使得和最小。指派问题性质:
指派问题的最优解有这样一个性质,若从矩阵的一行(列)各元素中分别减去该行(列)的最小元素,得到归约矩阵,其最优解和原矩阵的最优解相同. 参照:%0-1规划求指派问题
function [y,fval]=zhipai(C)%C为指派n*n系数矩阵
C=C';
f=C(:);%生成一个列向量,作为目标函数系数,matlab默认以列排序
[m,n]=size(C);
Aeq=zeros(2*n,n*n);%2*n个等式约束,n*n个变量
for i=1:n %这里先生成的是后四个等式约束的左端项
Aeq(1:n,1+(i-1)*n:i*n)=eye(n,n);
end
for i=1:n %前四个等式约束左端项
Aeq(i+n,1+(i-1)*n:i*n)=ones(1,n);
end
beq=ones(2*n,1);
lb=zeros(n*n,1);
ub=ones(n*n,1);
x=linprog(f',[],[],Aeq,beq,lb,ub);%线性规划函数
y=reshape(x,n,n);%将上式求出的x值变成n阶矩阵
y=y';%上式生成的是按列排列的,所以转致一下
y=round(y);%对y元素取整,生成匹配矩阵
sol=zeros(n,n);
for i=1:n
for j=1:n
if y(i,j)==1
sol(i,j)=C(j,i);%匹配矩阵
end
end
end
fval=sum(sol(:));%极小值的目标函数值
匈牙利算法
指派问题同样可以使用匈牙利算法,emmm代码过于凶残。。。 例如:- 1. 行归约: 每行元素减去该行的最小元素
2. 列归约:
3. 试指派: (1)找到未被画线的含0元素最少的行列,即,遍历所有未被画线的0元素,看下该0元素所在的行列一共有多少个0,最终选取最少个数的那个0元素。 (2)找到该行列中未被画线的0元素,这就是一个独立0元素。对该0元素所在行和列画线。 (3)暂时不看被线覆盖的元素,重复(1)(2)直到没有线可以画。 (4)根据(2)找到的0元素个数判断,找到n个独立0元素则Success,小于n个则Fail.(本例子中,n=5,可以看到,第一次试指派之后,独立0元素有4个,不符合)
4. 画盖0线: 目标:做最少的直线数覆盖所有0元素,直线数就是独立0元素的个数。 注意:这跟3的线不同;不能用贪心法去画线,比如 1 0 0 1 1 0 1 0 1 若先画横的,则得画3条线,实际只需2条;若先画竖的,将矩阵转置后同理。 步骤3得出的独立0元素的位置 (1)对没有独立0元素的行打勾、 (2)对打勾的行所含0元素的列打勾 (3)对所有打勾的列中所含独立0元素的行打勾 (4)重复(2)(3)直到没有不能再打勾 (5)对打勾的列和没有打勾的行画画线,这就是最小盖0线。
5. 更新矩阵: (1)对没有被线划到的数中,找到最小的数。 (2)对没有被线划到的数中,减去最小的数。 (3)对被2条线划到的数中,加上最小的数
6.重复3-5直到成功。
蒙特卡洛法
所谓蒙特卡洛法(随机取样),是指对于计算量过大的问题,通过随机取样计算部分,而非整体,来降低计算量的方法。这通常是近似解,但概率统计的方法证明,这是可靠的。
应用举例
- (1)计算面积:计算y=x^2,y=12-x与x轴在第一象限围城的曲边三角形的面积。
方法:利用蒙特卡罗法。在矩形(0,0),(0,9),(12,9),(12,0)中随机生成n个点,统计落在曲边三角形内的点个数,计算频度即为曲边三角形与矩形的面积之比。
matlab代码:
clc,clear x = unifrnd(0,12,1,10000000); y = unifrnd(0,9,1,10000000); pinshu = sum(y<x.^2 & x<=3) + sum(x>3 & y <12-x); area = pinshu/10000000*12*9; area
- (2)一个非线性规划蒙特卡洛求解实例
实现:使用: 产生随机数种子:为了防止相同状态开始会产生相同的伪随机数(特别是程序中有loop) rand('state',sum(100*clock)):根据当前时间,已经不推荐使用 rand('twister',mod(floor(now*8640000),2^31-1)):也可以 rng命令
- 定义函数:
function [f,g] = mente(x) f=x(1)^2+x(2)^2+3*x(3)^2+4*x(4)^2+2*x(5)-8*x(1)-2*x(2)-3*x(3) -x(4)-2*x(5); g=[sum(x)-400 x(1)+2*x(2)+2*x(3)+x(4)+6*x(5)-800 2*x(1)+x(2)+6*x(3)-200 x(3)+x(4)+5*x(5)-200];
- 求解:
clc,clear rand('state',sum(clock)); p0 = 0; tic for i = 1:10^6 x = 99*rand(5,1); %rand(5,1)生成5行1列0-1上的均匀分布随机数 x1 = floor(x); x2 = ceil(x); [f,g] = mente(x1); if sum(g<=0) == 4 if p0 <= f x0 = x1; p0 = f; end end [f,g] = mente(x2); if sum(g<=0)==4 if p0<=f x0 = x2; p0 = f; end end end x0 p0 toc