装载问题 回溯法剪枝过程详解 cw + r > bestw

因为我参考的文章也是转,暂时没找到真正的作者,如有知原地址,望告之,再加以修正。

1.问题描述:
     有一批共有 n 个集装箱要装上两艘载重量分别为 c1 和 c2 的轮船,其中集装箱 i 的重量为 w[i], 且重量之和小于(c1 + c2)。装载问题要求确定是否存在一个合理的装载方案可将这 n 个集装箱装上这两艘轮船。如果有,找出一种装载方案。
    例如,当n=3,c1=c2=50,且w=[10,40,40]时,可将集装箱1和集装箱2装上一艘轮船,而将集装箱3装在第二艘轮船;如果w=[20,40,40],则无法将这3个集装箱都装上轮船。

下面是摘抄内容:

    没有原图,我根据自己理解随后画了一张,仅供参考:装载问题 回溯法剪枝过程详解 cw + r > bestw_第1张图片

假定n= 4,w= [ 8 , 6 , 2 , 3 ],c1 = 1 2。解空间树为图1 6 - 2的树再加上一层节点。搜索从根A开始且c w= 0。若移动到左孩子B则c w= 8,c w≤c1 = 1 2。以B为根的子树包含一个可行的节点,故移动到节点B。从节点B不能移动到节点D,因为c w+w2 >c1。移动到节点E,这个移动未改变c w。下一步为节点J,c w= 1 0。J的左孩子的c w值为1 3,超出了c1,故搜索不能移动到J的左孩子。


可移动到J的右孩子,它是一个叶节点。至此,已找到了一个子集,它的c w= 1 0。xi 的值由从A到J的右孩子的路径获得,其值为[ 1 , 0 , 1 , 0 ]。

回溯算法接着回溯到J,然后是E。从E,再次沿着树向下移动到节点K,此时c w= 8。移动到它的左子树,有c w= 11。既然已到达了一个叶节点,就看是否c w的值大于当前的最优c w 值。结果确实大于最优值,所以这个叶节点表示了一个比[ 1 , 0 , 1 , 0 ]更好的解决方案。到该节点的路径决定了x 的值[ 1 , 0 , 0 , 1 ]。从该叶节点,回溯到节点K,现在移动到K的右孩子,一个具有c w= 8的叶节点。这个叶节点中没有比当前最优cw 值还好的cw 值,所以回溯到K , E , B直到A。从根节点开始,沿树继续向下移动。算法将移动到C并搜索它的子树。


通过不移动到不可能包含比当前最优解还要好的解的右子树,能提高函数m a x L o a d i n g的性能。令b e s t w为目前最优解的重量, Z为解空间树的第i 层的一个节点, c w的定义如前。以Z为根的子树中没有叶节点的重量会超过c w + r,其中r=n ?j = i + 1w[ j ] 为剩余货箱的重量。因此,当c w + r≤b e s t w时,没有必要去搜索Z的右子树。

 令n, w, c1 的值与上例中相同。用新的限界函数,搜索将像原来那样向前进行直至到达第一个叶节点J(它是J的右孩子)。bestw 被置为1 0。回溯到E,然后向下移动到K的左孩子,此时b e s t w被更新为11。我们没有移动到K的右孩子,因为在右孩子节点c w = 8,r = 0,c w + r≤b e s t w。回溯到节点A。同样,不必移动到右孩子C,因为在C点c w = 0,r = 11且c w + r≤b e s t w。加强了条件的限界函数避免了对A的右子树及K的右子树的搜索


以上就是我搜集理解的剪枝详细过程,再附上一份可以运行的代码:

#include 
using namespace std;
int c1, c2;         //分别载重量
int n;              //集装箱数量
int w[100];         //集装箱重量
int cw;             //c1当前载重量
int bestw;          //c1当前最优载重量
int r;              //剩余集装箱重量
int x[100];         //当前解
int bestx[100];      //当前最优解
void Backtrack(int i)
{
    if(i > n)
    {
        //当前解由于最优解,更新之
        if(cw > bestw)
        {
            for(int j = 1; j <= n; j++)
                bestx[j] = x[j];
            bestw = cw;
        }
        return;
    }

    //搜索子树,放入或不放入
    r -= w[i];              //剩余容量集合去掉w[i]
    if(cw + w[i] <= c1)     //可放入,且放入
    {
        x[i] = 1;           //放入
        cw += w[i];
        Backtrack(i+1);

        cw -= w[i];
    }
	//如果当前c1的载重量+当前节点的子树的最大重量都没有betsw(最优载重量)大,不放入当前的重量
	//就不再向当前节点的右子树移动了,即剪枝。
    if(cw + r > bestw)      //有可能有新的最优载重量
    {
        x[i] = 0;//向右子树移动
        Backtrack(i+1);
    }
    r += w[i];              //回溯
}
void main()
{
    while((cin >> c1 >> c2 >> n) && n)
    {
        cw = 0;             //每组样例初始化
        r = 0;
        bestw = 0;
        for(int i = 1; i <= n; i++)
        {
            cin >> w[i];
            r += w[i];
        }
        Backtrack(1);
        if(r-bestw <= c2)
            cout << "Yes" << endl;
        else
            cout << "No" << endl;
    }
}



你可能感兴趣的:(基于c++的数据结构)