zoukankan      html  css  js  c++  java
  • (数学建模)整数规划

    定义

    整数规划是指规划中的变量(全部或部分)限制为整数,若在线性模型中,变量限制为整数,则称为整数线性规划。所流行的求解整数规划的方法往往只适用于整数线性规划。 在线性规划问题中,有些最优解可能是分数或小数,但对于某些具体问题,常要求某些变量的解必须是整数。例如,当变量代表的是机器的台数,工作的人数或装货的车数等。为了满足整数的要求,初看起来似乎只要把已得的非整数解舍入化整就可以了。实际上化整后的数不见得是可行解和最优解,所以应该有特殊的方法来求解整数规划。在整数规划中,如果所有变量都限制为整数,则称为纯整数规划;如果仅一部分变量限制为整数,则称为混合整数规划。整数规划的一种特殊情形是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,直到得到最优解
      • 注意事项:在对两分支进行分解时,优先选择目标函数值最小的分支进行分解。
      分支定界法中,通过定界进而进行剪枝,对分支进行了筛选,使我们仅在一部分可行解中寻求最优解,而不是全部穷举出来再寻找,其求解效率更高。

    原理示例

    截屏2020-02-25下午2.26.50 v2-3a347e761776e076c13ff90567c052c6_hd v2-74b50a27fa1a178f7e5befe9d9f987f0_hd v2-9dd2dfa8d61cbc1af38e33f450a18e22_hd

    代码示例

    • 建立主函数
      % 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的,所以可以枚举所有取值求得极大/极小值。例如,求解: 1044022-20180718110337871-472326624
    • (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代码过于凶残。。。 例如:31162637-ce07c6e893ff4e53b4544036e5786b55 31162651-76f74b5ac39942c99c20abb9632e5bfe 截屏2020-02-25下午3.04.07
    • 1. 行归约: 每行元素减去该行的最小元素 截屏2020-02-25下午3.05.08

    • 2. 列归约: 截屏2020-02-25下午3.06.52

    • 3. 试指派: (1)找到未被画线的含0元素最少的行列,即,遍历所有未被画线的0元素,看下该0元素所在的行列一共有多少个0,最终选取最少个数的那个0元素。 (2)找到该行列中未被画线的0元素,这就是一个独立0元素。对该0元素所在行和列画线。 截屏2020-02-25下午3.07.58 (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元素的位置 截屏2020-02-25下午3.09.28 (1)对没有独立0元素的行打勾、 (2)对打勾的行所含0元素的列打勾 (3)对所有打勾的列中所含独立0元素的行打勾 (4)重复(2)(3)直到没有不能再打勾 (5)对打勾的列和没有打勾的行画画线,这就是最小盖0线。 截屏2020-02-25下午3.10.27

    • 5. 更新矩阵: (1)对没有被线划到的数中,找到最小的数。 (2)对没有被线划到的数中,减去最小的数。 (3)对被2条线划到的数中,加上最小的数 截屏2020-02-25下午3.11.14

    • 6.重复3-5直到成功。 截屏2020-02-25下午3.13.57

    蒙特卡洛法

    所谓蒙特卡洛法(随机取样),是指对于计算量过大的问题,通过随机取样计算部分,而非整体,来降低计算量的方法。这通常是近似解,但概率统计的方法证明,这是可靠的。

    应用举例

    • (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)一个非线性规划蒙特卡洛求解实例 1044022-20180718110341868-638415910
      使用:
      产生随机数种子:为了防止相同状态开始会产生相同的伪随机数(特别是程序中有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
      

    参考文章:

    https://zhuanlan.zhihu.com/p/40825420 https://zhuanlan.zhihu.com/p/27976866 https://www.cnblogs.com/duye/p/9327955.html https://blog.csdn.net/yyywww666/article/details/42805645 https://www.ilovematlab.cn/thread-204089-1-1.html https://blog.csdn.net/qq_43271202/article/details/93769141 https://www.cnblogs.com/chenyg32/p/3293247.html
  • 相关阅读:
    react log
    valueOf()、toString()、toLocaleString()三个方法的区别
    git 多账号配置 mac和windows10 记录一下
    js执行顺序,Promise和async中的立即执行
    js事件冒泡及event的target和currentTarget的区别
    canvas 旋转时 中心点的坑
    uni app 在组件中操作 canvas 已踩坑
    js 预编译原理
    mixins 在装饰器 vue-property-decorator 中的使用
    js 面向对象和函数式编程
  • 原文地址:https://www.cnblogs.com/pteromyini/p/12374871.html
Copyright © 2011-2022 走看看