求解目标:回溯法的求解目标是找出解空间树中满足约束条件的所有解,而分支限界法的求解目标则是找出满足约束条件的一个解,或是在满足约束条件的解中找出在某种意义下的最优解。
搜索方式的不同:回溯法以深度优先的方式搜索解空间树,而分支限界法则以广度优先或以最小耗费优先的方式搜索解空间树。
在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所需的解或活结点表为空时为止。
选择下一个E结点的方法如下:
1)先进先出(FIFO):从活结点表中取出结点的顺序与加入结点的顺序相同。
后进先出(LIFO):从活结点表中取出结点的顺序与加入结点的顺序相反
2)优先队列式分支限界法
按照优先队列中规定的优先级选取优先级最高的节点成为当前扩展节点。
1️⃣ 在 e_结点估算沿着它的各儿子结点搜索时,目标函数可能取得的“界”,
2️⃣ 把儿子结点和目标函数可能取得的“界”,保存在优先队列或堆中,
3️⃣ 从队列或堆中选取“界”最大或最小的结点向下搜索,直到叶子结点,
4️⃣ 若叶子结点的目标函数的值,是结点表中的最大值或最小值,则沿叶子结点到根结点的路径所确定的解,就是问题的最优解,否则转 3 继续搜索
0-1背包问题
考虑实例n=4,w=[3,5,2,1],v=[9,10,7,4],C=7。
定义问题的解空间
该实例的解空间为(x1,x2,x3,x4),xi=0或1(i=1,2,3,4)。
确定问题的解空间组织结构
该实例的解空间是一棵子集树,深度为4。
搜索解空间
约束条件
限界条件 cp+rp>bestp
cp初始值为0;rp初始值为所有物品的价值之和;bestp表示当前最优解,初始值为0。
当cp>bestp时,更新bestp为cp。
优先级:活结点代表的部分解所描述的装入背包的物品价值上界,该价值上界越大,优先级越高。活结点的价值上界up=cp+r‘p。
约束条件:同队列式
限界条件:up=cp+r‘p>bestp。
r‘p 剩余物品装满背包的价值
在下图所给的有向图G中,每一边都有一个非负边权。要求图G的从源顶点s到目标顶点t之间的最短路径。
下图是用优先队列式分支限界法解有向图G的单源最短路径问题产生的解空间树。其中,每一个结点旁边的数字表示该结点所对应的当前路长。
解单源最短路径问题的优先队列式分支限界法用一极小堆来存储活结点表。其优先级是结点所对应的当前路长。
算法从图G的源顶点s和空优先队列开始。结点s被扩展后,它的儿子结点被依次插入堆中。此后,算法从堆中取出具有最小当前路长的结点作为当前扩展结点,并依次检查与当前扩展结点相邻的所有顶点。如果从当前扩展结点i到顶点j有边可达,且从源出发,途经顶点i再到顶点j的所相应的路径的长度小于当前最优路径长度,则将该顶点作为活结点插入到活结点优先队列中。这个结点的扩展过程一直继续到活结点优先队列为空时为止。
static float[][]a //图G的邻接矩阵
static float []dist //源到各顶点的距离
static int []p //源到各顶点的路径上的前驱顶点
HeapNode //最小堆元素
{
int i; //顶点编号
float length;//当前路长
……
}
while (true) {
//搜索问题的解空间
for (int j = 1; j <= n; j++)
if ((a[enode.i][j]<Float.MAX_VALUE)&&
(enode.length+a[enode.i][j]<dist[j])) {
//顶点i和j间有边,且此路径长小于原先从原点到j的路径长
// 顶点i到顶点j可达,且满足控制约束
dist[j]= enode.length+c[enode.i][j];
p [j]= enode.i;
// 加入活结点优先队列
HeapNode node=new HeapNode(j,dist[j]);
heap.put(node);
}
// 取下一扩展结点
if ( heap.isEmpty( ) ) break;
else enode=(HeapNode)heap.removeMin();
}
}
解答参考https://www.it610.com/article/1296236014334976000.htm
给定n种物品和一个背包。物品i的重量是wi,其价值为vi,背包的容量为c。
应如何选择装入背包的物品,使得装入背包中物品的总价值最大?
在选择装入背包的物品时,对每种物品i只有2种选择,即装入背包或不装入背包。不能将物品i装入背包多次,也不能只装入部分的物品i。
仅当右儿子结点满足上界函数约束时,才将它加入子集树和活结点优先队列。
假设有4个物品,其重量分别为(4, 7, 5, 3),价值分别为(40, 42, 25, 12),背包容量W=10。将给定物品按单位重量价值从大到小排序,结果如下:
物品 | 重量(w) | 价值(v) | 价值/重量(v/w) |
---|---|---|---|
1 | 4 | 40 | 10 |
2 | 7 | 42 | 6 |
3 | 5 | 25 | 5 |
4 | 3 | 12 | 4 |
上界计算
先装入物品1,剩余的背包容量为6,只能装入物品2的6/7(即42*(6/7)=36
)。 即上界为40+6*6=76
已第一个up为例:40+6*(10-4)=76
打x的部分因为up值已经小于等于bestp了,所以没必要继续递归了。
template<class Typew, class Typep>
Typep Knap<Typew, Typep>::Bound(int i)
{
// 计算上界
Typew cleft = c - cw; // 剩余容量
Typep b = cp;
// 以剩余物品单位重量价值递减序装入物品
while (i <= n && w[i] <= cleft) {
cleft -= w[i];
b += p[i];
i++;
}
// 装满背包
if (i <= n) b += p[i]/w[i] * cleft;
return b;
}
static class Bbnode{
BBnode parent; //父结点
boolean leftChild; //左儿子结点标志
…}
static class HeapNode implements Comparable{
BBnode liveNode; //活结点
double upperProfit; //结点的价值上界
double profit; //结点所相应的价值
double weight; //结点所相应的重量
int level; //活结点在子集树中所处的层序号
详情参考https://blog.csdn.net/qq_40801709/article/details/90439784
n 个操作员以 n 种不同时间完成 n 种不同作业。要求分配每位操作员完成一项工作,使完成 n 项工作的总时间最少操作员编号为 0,1,…n-1,作业也编号为 0,1,…n-1, 矩阵 c 描述每位操作员完成每个作业时所需的时间,元素 ci,j 表示第 i 位操作员完成第 j 号作业所需的时间 向量 x 描述分配给操作员的作业编号,分量 xi 表示分配给第 i 位操作员的作业编号。
1)从根结点开始,每遇到一个扩展结点,就对它的所有儿子结点计算其下界,把它们登记在结点表中。
2)从表中选取下界最小的结点,重复上述过程。
3)当搜索到一个叶子结点时,如果该结点的下界是结点表中最小的,那么,该结点就是问题的最优解。
4)否则,对下界最小的结点继续进行扩展
搜索深度为 0 时,把第 0 号作业分配给第 i 位操作员所需时间至少为第 i 位操作员完成第 0 号作业所需时间,加上其余 n-1个作业分别由其余 n-1 位操作员单独完成时所需最短时间之和,有:
例:4个操作员完成4个作业所需的时间表如下:
把第 0 号作业分配给第 0 位操作员时,所需时间至少不小于 3 + 7 + 6 + 3 = 19 ,把0号作业1 位操作员时,所需 时间至少不会小于9+7+4+3…
搜索深度为 k 时,前面第0,1,…,k-1号作业已分别分配 给编号为i0,i1,…,ik-1的操作员。 S={0,1,…,n-1}表示所有操作员的编号集合;
mk-1={i0,i1,…ik-1}表示作业已分配的操作员编号集合。当把第k号作业分配给编号为ik的操作员时, i k ∈ S − m k − 1 i_k\in S-m_{k-1} ik∈S−mk−1, 所需时间至少为:
则上式为把第k号作业分配给编号为ik的操作员时的下界
#include
using namespace std;
#define MAX_NUM 99999
const int n = 4;
float c[n][n];//n个操作员分别完成n项作业所需时间
float bound = MAX_NUM;//当前已搜索可行解的最优时间
struct ass_node {
int x[n];//分配给操作员的作业
int k;//搜索深度
float t;//当前搜索深度下,已分配作业所需时间
float b;//本节点所需的时间下界
struct ass_node* next;//优先队列链指针
};
typedef struct ass_node* ASS_NODE;
//把xnode所指向的节点按所需时间下界插入优先队列qbase中,下界越小,优先性越高
void Q_insert(ASS_NODE qbase, ASS_NODE xnode) {
ASS_NODE temp = qbase->next;
ASS_NODE temp2 = qbase;
while (temp != NULL) {
if (xnode->b < temp->b) {
break;
}
temp2 = temp;
temp = temp->next;
}
xnode->next = temp2->next;
temp2->next = xnode;
}
//取下并返回优先队列qbase的首元素
ASS_NODE Q_delete(ASS_NODE qbase) {
//ASS_NODE temp = qbase;
ASS_NODE rt = new ass_node;//只是一个node
if (qbase->next != NULL)
*rt = *qbase->next;
else
rt = NULL;
qbase->next = qbase->next->next;
return rt;
}
//分支限界法实现
float job_assigned(float (*c)[n], int n, int* job) {
int i, j, m;
ASS_NODE xnode,ynode=NULL;
ASS_NODE qbase = new ass_node;
qbase->next = NULL; qbase->b = 0;//空头节点
float min, bound = MAX_NUM;
xnode = new ass_node;
for (i = 0;i < n;i++)
xnode->x[i] = -1;//-1表示尚未分配
xnode->t = xnode->b = 0;
xnode->k = 0;
//非叶子节点,继续向下搜索
while (xnode->k != n) {
//对n个操作员分别判断处理
for (i = 0;i < n;i++) {
if (xnode->x[i] == -1) {
//i操作员未分配工作
ynode = new ass_node;//为i操作员建立一个节点
*ynode = *xnode;//把父节点数据复制给它
ynode->x[i] = ynode->k;//作业k分配给操作员i
ynode->t += c[i][ynode->k];//已分配作业累计时间
ynode->b = ynode->t;
ynode->k++;//该节点下一次搜索深度
ynode->next = NULL;
for (j = ynode->k;j < n;j++) {
//未分配作业最小时间估计
min = MAX_NUM;
for (m = 0;m < n;m++) {
if ((ynode->x[m] == -1) && c[m][j] < min)
min = c[m][j];
}
ynode->b += min;//本节点所需时间下界
}
if (ynode->b < bound) {
Q_insert(qbase, ynode);//把节点插入优先队列
if (ynode->k == n)//得到一个可行解
bound = ynode->b;//更新可行解的最优下界
}
else delete ynode;//大于可行解最优下界
}
}
delete xnode;//释放节点xnode的缓冲区
xnode = Q_delete(qbase);//取下队列首元素xnode
}
min = xnode->b;
for (i = 0;i < n;i++)//保存最优方案
job[i] = xnode->x[i];
while (qbase->next) {
xnode = Q_delete(qbase);
delete xnode;
}
return min;
}
int main() {
c[0][0] = 3;c[0][1] = 8;c[0][2] = 4;c[0][3] = 12;
c[1][0] = 9;c[1][1] = 12;c[1][2] = 13;c[1][3] = 5;
c[2][0] = 8;c[2][1] = 7;c[2][2] = 9;c[2][3] = 3;
c[3][0] = 12;c[3][1] = 7;c[3][2] = 6;c[3][3] = 8;
int* job = new int[n];
for (int i = 0;i < n;i++)
job[i] = -1;
float result = job_assigned(c, n, job);
for (int i = 0;i < n;i++)
cout << job[i] << " ";
cout << endl;
cout << result << endl;
system("pause");
return 0;
}