递归、回溯-装载问题

版权声明:转载请注明出处。 https://blog.csdn.net/baidu_38304645/article/details/83824061

之前讨论了最优装载问题的贪心算法,这里讨论最优装载问题的一个变形。

1、问题描述:

有一批共n个集装箱要装上两艘载重量分别为c1和c2的轮船,其中集装箱为wi,且\sum_{i=1}^{n}wi <= c1+c2 

要求确是否有一个合理的装载方案可将这n个集装箱装上这2艘轮船?如果有,找出一种装载方案。

例如,当n=3,c1=c2=50,且w=[10,40,40]时,可将集装箱1和2装上第一艘轮船,而将集装箱3装上第二艘轮船:如果w=[20,40,40],则无法将这3个集装箱都装上轮船。

输入:集装箱的个数n,第一艘船的载重量c1,各个集装箱的重量wi

输出:第一艘船的最大载货量,各个集装箱是否转载,装载输出1,反之输出0.

运行结果:

容易证明,如果一个给定的装载问题有解,则采用下面的策略可以得到最优装载方案。

(1)首先将第一艘轮船尽可能装满;

(2)然后将剩余的集装箱装上第二艘轮船。

将第一艘轮船尽可能装满等价于选取全体集装箱的一个子集,使该子集中集装箱重量之和最接近c1,由此可知,装载问题等价于以下特殊的0-1背包问题:

max\sum_{i=1}^{n}wixi

s.t.\sum_{i=1}^{n}wixi <=c1

xi\in {0,1}, 1<=i<=n

2、算法设计

用回溯法解装载问题时,用子集树表示其解空间显然是最合适的。可行性约束函数可剪去不满足约束条件\sum_{i=1}^{n}wixi <= ci的子树。在子集树的第j+1层的结点Z处,用cw记当前的装载重量,即cw = \sum_{i=1}^{j}wixi,当cw > c1时,以结点Z为根的子树中所有结点都不满足约束条件,因而该子树中的解均为不可行解,故可将该子树剪去。

下面的解装载问题的回溯法中,算法MaxLoading返回不超过c的最大子集和。

算法MaxLoading调用递归函数Backtrack(1)实现回溯搜索。Backtrack(i)搜索子集树中第i层子树。类Loading的数据成员。记录子集树中结点信息,以减少传给Backtrack的参数。cw记录当前结点所对应的装载重量,bestw记录当前最大装载重量。

在算法Backtrack中,当i>n时,算法搜索至叶结点,其相应的装载重量为cw。如果cw>bestw,则表示当前解优于当前最优解,此时应更新bestw。

当i  <= n时,当前扩展结点Z是子集树中的内部结点。该结点有 x[i] = 1 和 x[i] = 0两个儿子结点。其左儿子结点表示x[i] = 1的情形,仅当cw+w[i]<=c时进入左子树,对左子树递归搜索。其右儿子结点表示x[i]=0的情形。由于可行结点的右儿子结点总是可行的。故进入右子树时不需检查可行性。

算法Backtrack动态的生成问题的解空间树。在每个结点处算法花费O(1)时间,子集树中结点个数为O(2^{n}).故Backtrack所需的计算时间为

O(2^{n})。另外Backtrack还需要额外的O(n)的递归栈空间。

3、上界函数

对于前面描述的算法Backtrack,可以引入一个上界函数,用于剪去不含最优解的子树,从而改进算法在平均情况下的运行效率,设Z是解空间树第i层上的当前扩展结点。cw是当前载重量,bestw是当前最优载重量;r是剩余集装箱的重量,即r = \sum_{j=i+1}^{n}wj。定义上界函数为cw+r。在以Z为根的子树中任一叶结点对应的载重量均不超过cw+r。因此,当cw+r <= bestw时,可将Z的右子树剪去。

在下面的改进算法中,引入类Loading的变量r,用于计算上界函数。引入上界函数后,在达到一个叶结点时就不必再检查该叶结点是否优于当前最优解。因为上界函数使算法搜索到的每个叶结点都是当前找到的最优解。虽然改进后的算法的计算时间复杂度仍为O(2^{n}).但在平均情况下改进后的算法检查的结点数较少。

4、构造最优解

为了构造最优解,必须在算法中记录与当前最优值相应的当前最优解。为此,在类Loading中增加两个私有数据成员x和bestx。x用于记录从根至当前结点的路径;bestx记录当前最优解。算法搜索到叶结点处,就修正bestx的值。.

template <class Type>
class Loading
{
    template <class T>                                        //类模板中友元函数,加上,T防止重名
    friend T MaxLoading(T*, T, int, int*);
private:
    void Backtrack(int t);
    int n,                                                   //集装箱数
        *x,                                                  //当前解
        *bestx;                                              //当前最优解
    Type *w,                                                 //集装箱数组,存放了每个集装箱重量
         c,                                                  //第一艘轮船的载重量
         cw,                                                 //当前载重量
         bestw,                                              //当前最优载重量
         r;                                                  //剩余集装箱重量
};
//核心函数 对解空间树回溯搜索,求得最优值
template <class Type>
void Loading<Type>::Backtrack(int t)
{
    int j;
    //搜索第t层结点
    if(t > n)                                                //到达叶节点,此时x[1,n]是一个满足约束条件的可行解
    {
        for(j = 1; j <= n; j++)
            bestx[j] = x[j];
        bestw = cw;
        return;
    }
    r -= w[t];
    //搜索子树,此时,t<=n时 当前扩展结点是子集树中的内部结点
    //该结点有x[i]=1和x[i]=0两个儿子结点
    if(cw + w[t] <= c)  //搜索左子树x[i]=1
    {
        x[t] = 1;
        cw += w[t];
        Backtrack(t+1);
        cw -= w[t];
    }

    if(cw + r > bestw)
    {
        x[t] = 0;
        Backtrack(t+1);    //搜索右子树x[i]=0
    }

    r += w[t];
}
//负责对类的私有变量初始化,调用递归函数Backtrack(1)实现回溯搜索并返回最优值
template <class Type>
Type MaxLoading(Type *w, Type c, int n, int *bestx)
{
    int i;
    Loading<Type> X;
    //初始化X
    X.x = new int[n+1];
    X.bestx = bestx;
    X.w = w;
    X.c = c;
    X.n = n;
    X.cw = 0;
    X.bestw = 0;
    //初始化r
    X.r = 0;
    for(i = 1; i <= n; i++)
        X.r += w[i];
    //计算最优载重量
    X.Backtrack(1);
    delete []X.x;
    //返回最优载重量
    return X.bestw;
}

猜你喜欢

转载自blog.csdn.net/baidu_38304645/article/details/83824061
今日推荐