回溯法与分支界限的区别
回溯法:
- 定义解空间树
- 深度优先
- 遍历结构是栈(递归/回溯)
- 通过约束函数和界限函数剪枝
分支界限
- 定义解空间树
- 广度优先、广度优先结合深度优先遍历
- 遍历方式可以是队列和最优队列
- 通过约束函数和界限函数剪枝
当采用队列时,与回溯一样,可以一定程度的剪掉一部分的解空间子树。此时遍历方式为广度优先
当采用最优队列时,可以最大程度的“剪掉”更可能多的解空间子树,此事遍历方式为广度优先结合深度优先
原因:
- 最大问题:界限函数为上限,虽然本层节点的界限函数 大于或等于
下层该节点的子节点的界限函数(上界),但不确本层其他节点和下层节点的界限函数大小关系,所以采用最优队列取出的节点(当前的最大值),可能是当前层的也可能是下层的。所以解空间的遍历方式是广度优先结合深度优先。
但是值得注意,由于其他层的父节点的上限大于各自的儿子节点和孙子节点等的上限,所以只要最底下一层被取出一个节点,就代表这是整棵解空间树的最大的上限的解,也等效最大装载量的解,最底层再也没有比这个节点大的上限,哪怕它还没被加入优先队列。
- 同理,最小问题:界限函数为下限,采用最优队列取出的节点(当前的最小值),可能是当前层的也可能是下层的。所以解空间的遍历方式也是广度优先结合深度优先
下面以0/1背包问题、最大装载问题、旅行商问题、流水线调度问题比较回溯算法及分治界限算法的两种形式。
最大装载问题
给定n个货物,及其对应的重量w1,w2…wn-1,wn,再载给定一个最大限重量W。求在不超过限重量的情况下最大装载量。
(1)回溯算法:
//r是全局变量
BackTrackLoading(int i)
{
if( i == n+1){
if(cw > bestw){
bestw = cw;
for(int j = 0; j<n; j++) //记录货物装载情况(1:装 0:不装)
bestx[ j ] = x[ j ];
}
}
else{
r = r - w[ i ];
if (cw + w[i] <= W){
x[ i ] = 1;
cw = cw + w[i];
BackTrackLoading( i );
cw = cw + w[i];
}
if(cw + r > bestw ){
x[ i ] = 0;
BackTrackLoading( i );
}
r = r + w[ i ];
}
}
(2)分治算法
void AddLiveNode(Q, E, B , ch, i ) //添加活结点
{
b.parent = E;
b.ch = ch;
N.up_weight = B;
N.level = i ;
N.ptr = b;
Insert( Q, E);
}
int MaxCostLoading()
{
i = 1;
r[ n ]= 0;
for( int j=n-1; j>=1; j++) s[j] = s[j+1] + r[j+1];
while( i != n){
if ( cw + w[i] < W)
AddLiveNode(Q, E, cw + w[i] + r[i], 1, i );
AddLiveNode(Q, E, cw+ r[i], 0, i );
N = ExtractMax(Q);
i = N.level + 1;
E = N.ptr;
cw = N.weight - r[i-1];
}
for(int j=n; j >= 1; j++){
bestx[j] = E.Lchild;
E = E.parent;
}
return cw;
}
(2)完整代码:
#include<stdio.h>
#include<queue>
#include<vector>
using namespace std;
#define N 20
#define NUM 100000
typedef struct LiveNode
{
int up_weight;
int level;
LiveNode *parent;
char ch;
}LiveNode, *pNode;
int n;
int W;
int w[N];
int bestx[N];
LiveNode buffer[NUM]; /*!!为节点提供内存!!*/
int head = 0;
struct comp {
comp() {}
~comp() {}
bool operator()(pNode x, pNode y) {
return x->up_weight < y->up_weight;
}
};
priority_queue<pNode, vector<pNode>, comp> Q;
/*!!其中,第二个参数为容器类型。第三个参数为比较函数。*/
void scanff();
void AddLiveNode(pNode pE, int B , char ch, int i); //添加活结点
int MaxCostLoading();
int main()
{
scanff();
int bestw = MaxCostLoading();
printf("最大最大装载量是:%d\n",bestw);
return 0;
}
void scanff()
{
printf("货物数量/装载量:");
scanf("%d%d",&n,&W);
printf("装载信息:\n");
for(int i=1;i<=n;i++)
scanf("%d",&w[i]);
}
int MaxCostLoading()
{
int i = 1,cw = 0;
pNode pE = NULL;
int r[N];
r[ n ]= 0;
for( int j=n-1; j>=1; j--){
r[j] = r[j+1] + w[j+1];
printf("r[%d] = %d\n",j,r[j]);
}
/*!!思考为什么只要取出解空间树最下一层的一个节点就可以停止了*/
while( i != n+1){
if ( cw + w[i] <= W)
AddLiveNode(pE, cw + w[i] + r[i], 1, i );
AddLiveNode(pE, cw+ r[i], 0, i );
/*!!注意这里的”剪枝“,是通过优先队列取最大,取出最大即停止来实现的。*/
pE = Q.top();
Q.pop();
i = pE->level + 1;
cw = pE->up_weight - r[i-1];
//printf("%d->\n",i) ;
}
for(int j=n; j >= 1; j--){
bestx[j] = pE->ch;
pE = pE->parent;
}
return cw;
}
void AddLiveNode(pNode pE, int B , char ch, int i ) //添加活结点
{
pNode pN = &buffer[head++];
pN->up_weight = B;
pN->level = i ;
pN->parent = pE;
pN->ch = ch;
Q.push(pN);
}