文章目录
1. 分支界限法
1.1 基本思想
对有约束条件的最优化问题的所有可行解(数目有限)空间进行搜索。该算法在具体执行时,把全部可行的解空间不断分割为越来越小的子集(称为分支),并为每个子集内的解的值计算一个下界或上界(称为界限)。在每次分支后,对凡是界限超出已知可行解值的那些子集不在做进一步分支。这样就缩小了搜索范围。这一过程一直进行到找出可行解为止,该可行解的值不大于任何子集的界限。
1.2 搜索策略
在扩展结点处,先生成其所有的儿子结点(分支),然后再从当前的活结点表中选择下一个扩展结点。为了有效地选择下一扩展结点,加速搜索的进程,在每一个活结点处,计算一个函数值(限界),并根据函数值,从当前活结点表中选择一个最有利的结点作为扩展结点,使搜索朝着解空间上有最优解的分支推进,以便尽快地找出一个最优解。
1.3 队列式
按照队列先进先出(FIFO)原则选取下一个节点为扩展节点。
1.4* 优先队列式
按照优先队列中规定的优先级选取优先级最高的节点成为当前扩展节点。
2. 典型案例
2.1 装载问题
2.1.1 问题描述
集装箱装载问题要求确定在不超过轮船载重量的前提下,将尽可能多的集装箱装上轮船。
2.1.2 约束函数和限界条件
约束函数:当 Ew+wi > c 对扩展结点的左子树剪枝
限界函数:当 Ew+r <= bestw 对扩展结点的右子树剪枝
2.1.3 队列式分支界限法求解(案例解释)
轮船的载重量为c=80、集装箱个数n=4、重量分别为18 7 25 36。
定义一个先进先出(FIFO)队列Q,初始化队列时,在尾部增加一个 -1 标记。这是一个分层的标志,当一层结束时,在队列尾部增加一个 -1 标志。
定义扩展结点相应的载重量为Ew,剩余集装箱的重量为r,当前最优载重量为bestw。
没到一个结点就要计算bestw、r、Ew,然后进行约束函数和限界函数的判断,如果不满足约束函数则对左子树剪枝,不满足限界函数对右子树进行剪枝。例如下图中的结点M,此时bestw=50,r=7+36=43,r<bestw,则结点M不入队。结点O也是同样。
2.1.4 案列二
- 基于队列式的分支限界法解决
2.1. 代码实现
#include "iostream"
#include "queue"
using namespace std;
#define NUM 100
int n; //集装箱的数量
int c; //轮船的载重量
int w[NUM]; //集装箱的重量数组
int MaxLoading()
{
queue<int> Q;
Q.push(-1);
int i = 0;
int Ew = 0;
int bestw = 0;
int r = 0;
for(int j=1; j<n; j++)
r += w[j];
//搜索子空间树
while (true) {
//检查左子树
int wt = Ew + w[i];
if (wt <= c) { //检查约束条件
if (wt > bestw) bestw = wt;
//加入活结点队列
if (i < n - 1) Q.push(wt);
}
//检查右子树
//检查上界条件
if (Ew + r > bestw && i < n - 1)
Q.push(Ew);
//从队列中取出活结点
Ew = Q.front();
Q.pop();
if (Ew == -1) { //判断同层的尾部
if (Q.empty()) return bestw;
//同层结点尾部标志
Q.push(-1);
//从队列中取出活结点
Ew = Q.front();
Q.pop();
i++;
r -= w[i];
}
}
return bestw;
}
int main(){
cin >> c;
cin >> n;
for (int i = 0; i < n; ++i) {
cin >> w[i];
}
cout << MaxLoading();
return 0;
}
2.2 0-1背包问题(基于优先队列)
2.2.1 队列的进出过程
每到一个结点就计算其上界,判断该上界值是否小于当前最优解,如果小于就减去,如果不小于就按照优先级(上界大小),按次序插入队列。
2.2.2 上界值的计算
采用 贪心算法 计算结点的上界值:将剩余物品依其单位重量价值排序,然后依次装入物品,直至装不下时,再装入该物品的一部分而装满背包时的价值。
3. 回溯法与分支限界法异同
- 相同点:
都是一种在问题的解空间树T中搜索问题解的算法。
- 不同点:
(1)求解目标不同
(2)搜索方式不同
(3)对扩展结点的扩展方式不同
(4)存储空间的要求不同
搜索方式 | 求解目标 | 扩展方式 | 存储空间 | |
---|---|---|---|---|
回溯法 | 深度优先 | 满足条件的所有解 | 当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。 | 栈 |
分支限界法 | 广度优先 | 满足条件一个解或者特定意义的最优解 | 每个结点只有一次成为活结点的机会一旦成为扩展结点,就一次性产生所有儿子结点,并舍弃不可行解或非最优解的子结点。 | 队列、优先队列 |
c++中 friend的用法
- 普通的非成员函数友元
这类友元函数最常见,通常是操作符。
- 类作为友元
类作为友元需要注意的是友元类和原始类之间的相互依赖关系,如果在友元类中定义的函数使用到了原始类的私有变量,那么就需要在友元类定义的文件中包含原始类定义的头文件。但是在原始类的定义中(包含友元类声明的那个类),就不需要包含友元类的头文件.
另外,不需要在类定义前去声明友元类,因为友元类的声明自身就是一种声明。
- 类成员函数作为友元函数
这个稍微有点复杂,因为你要类成员函数作为友元,你在声明友元的时候要用类限定符,所以必须先定义包含友元函数的类,但是在定义友元的函数时候,又必须事先定义原始类。通常的做法先定义包含友元函数的类,再定义原始类,这个顺序不能乱。
员函数作为友元,你在声明友元的时候要用类限定符,所以必须先定义包含友元函数的类,但是在定义友元的函数时候,又必须事先定义原始类。通常的做法先定义包含友元函数的类,再定义原始类,这个顺序不能乱。