算法——分支限界法(装载问题)

对比回溯法

  • 回溯法的求解目标是找出解空间中满足约束条件的所有解,想必之下,分支限界法的求解目标则是找出满足约束条件的一个解,或是满足约束条件的解中找出使某一目标函数值达到极大或极小的解,即在某种意义下的最优解。
  • 另外还有一个非常大的不同点就是,回溯法以深度优先的方式搜索解空间,而分支界限法则以广度优先的方式或以最小耗费优先的方式搜索解空间。

分支限界法的搜索策略

在当前节点(扩展节点)处,先生成其所有的儿子节点(分支),然后再从当前的活节点(当前节点的子节点)表中选择下一个扩展节点。为了有效地选择下一个扩展节点,加速搜索的进程,在每一个活节点处,计算一个函数值(限界),并根据函数值,从当前活节点表中选择一个最有利的节点作为扩展节点,使搜索朝着解空间上有最优解的分支推进,以便尽快地找出一个最优解。分支限界法解决了大量离散最优化的问题。

选择方法

1.队列式(FIFO)分支限界法

队列式分支限界法将活节点表组织成一个队列,并将队列的先进先出原则选取下一个节点为当前扩展节点。

2.优先队列式分支限界法

优先队列式分支限界法将活节点表组织成一个优先队列,并将优先队列中规定的节点优先级选取优先级最高的下一个节点成为当前扩展节点。如果选择这种选择方式,往往将数据排成最大堆或者最小堆来实现。


例子:装载问题

有一批共n个集装箱要装上2艘载重量分别为c1,c2的轮船,其中集装箱i的重量为wi,且要求确定是否有一个合理的装载方案可将这n个集装箱装上这2艘轮船。
可证明,采用如下策略可以得到一个最优装载方案:先尽可能的将第一艘船装满,其次将剩余的集装箱装到第二艘船上。

代码如下:

 

//分支限界法解装载问题

//子函数,将当前活节点加入队列
template<class Type>
void EnQueue(Queue<Type> &Q, Type wt, Type &bestw, int i, int n) 
{
    if(i == n)     //可行叶结点
    {     
        if(wt>bestw) bestw = wt ;
    }
    else Q.Add(wt) ; //非叶结点
}

//装载问题先尽量将第一艘船装满
//队列式分支限界法,返回最优载重量
template<class Type>
Type MaxLoading(Type w[],Type c,int n)
{
    //初始化数据
    Queue<Type> Q;    //保存活节点的队列
    Q.Add(-1);    //-1的标志是标识分层
    int i=1;    //i表示当前扩展节点所在的层数
    Type Ew=0;    //Ew表示当前扩展节点的重量
    Type bestw=0;    //bestw表示当前最优载重量
    
    //搜索子集空间树
    while(true)
    {
        if(Ew+w[i]<=c)    //检查左儿子
            EnQueue(Q,Ew+w[i],bestw,i,n);    //将左儿子添加到队列
        
        //将右儿子添加到队列 即表示不将当前货物装载在第一艘船
        EnQueue(Q,Ew,bestw,i,n);
        Q.Delete(Ew);    //取下一个节点为扩展节点并将重量保存在Ew
        if(Ew==-1)    //检查是否到了同层结束
        {
            if(Q.IsEmpty()) return bestw;    //遍历完毕,返回最优值
            Q.Add(-1);    //添加分层标志
            Q.Delete(Ew);    //删除分层标志,进入下一层
            i++;
        }
    }
}

 

算法MaxLoading的计算时间和空间复杂度为O(2^n).

上述算法可以改进,设r为剩余集装箱的重量,当Ew+r<=bestw的时候,可以将右子树剪去。因为最优值不可能出现在下面了。

改进代码如下:

 

分支限界法解装载问题的改进
//分支限界法解装载问题

//装载问题先尽量将第一艘船装满
//队列式分支限界法,返回最优载重量
template<class Type>
Type MaxLoading(Type w[],Type c,int n)
{
    //初始化数据
    Queue<Type> Q;    //保存活节点的队列
    Q.Add(-1);    //-1的标志是标识分层
    int i=1;    //i表示当前扩展节点所在的层数
    Type Ew=0;    //Ew表示当前扩展节点的重量
    Type bestw=0;    //bestw表示当前最优载重量
    
    //搜索子集空间树
    while(true)
    {
        //检查左儿子
        Type wt=Ew+w[i]; //wt为左儿子节点的重量
        if(wt<=c)    //若装载之后不超过船体可承受范围
            if(wt>bestw)    //更新最优装载重量
            {
                bestw=wt;
                if(i<n) Q.Add(wt);    //将左儿子添加到队列
            }
        
        //将右儿子添加到队列
        if(Ew+r>bestw&&i<n)
            Q.Add(Ew);
        Q.Delete(Ew);    //取下一个节点为扩展节点并将重量保存在Ew
        if(Ew==-1)    //检查是否到了同层结束
        {
            if(Q.IsEmpty()) return bestw;    //遍历完毕,返回最优值
            Q.Add(-1);    //添加分层标志
            Q.Delete(Ew);    //删除分层标志,进入下一层
            i++;
            r-=w[i];    //剩余集装箱重量
        }
    }
}

 

用处

分支限界法解决了大量离散最优化问题。

 

posted @ 2016-04-15 17:18  天涯逐梦  阅读(17756)  评论(0编辑  收藏  举报