一、什么是分枝限界法
分枝限界法的本质是BFS + 剪枝
,也是通过搜索状态空间树来找出问题的解。
分枝限界法的分类
按活结点表的存储方式分为三种:
- FIFOBB:用队列作为活结点表
- LIFOBB:用栈作为活结点表(D-搜索)
- LCBB:用优先权队列(堆)作为活结点表
二、分枝限界法的使用场景
分枝限界法与回溯法
同回溯法,都用于搜索、枚举问题。但分枝限界法是按层遍历搜索,因此,对于同一棵状态空间树,如果问题的答案属于不定长的,用分枝限界法搜索的次数可能要比回溯法少。因为答案结点距离根结点的距离小。因此分枝限界法常用于求最优解或一个解
,比如走迷宫问题。而对于定长的答案问题,分枝限界法的效果可能不好,比如n皇后问题。因为无论你用回溯法还是分枝限界法答案的长度都是n,用fifobb分枝限界法要把最后一层之前的所有结点都访问一遍。
三、算法框架
void BranchBound(){
q.Append(root);
while(!q.empty()){
q.Serve(e); //出队
for (e的所有孩子 x) {
if (满足约束条件) {
if (x 是答案结点)
输出x到root代表的解,return
q.Append(x) //孩子入队
}
}
}
cout << "no answer" << endl;
}
四、典例
- FIFOBB算法求解n皇后问题
对于回溯法求n皇后,因为函数栈的存在不需要保存结点的前驱关系。而对于BFS而言,每个结点被访问过一次后就失效了,所以需要在孩子结点保存parent指针,以便于判断是否满足约束条件和输出答案。
求一个解
(当然也能求所有解)
#include <iostream>
#include <queue>
using namespace std;
int n;
struct Node{
int row, col;
Node *parent;
Node(int r,int c, Node *p){
row = r;
col = c;
parent = p;
}
};
bool place(Node *child){
Node *p = child->parent;
while(p->row >= 0){
if(p->col == child->col || abs(p->row - child->row) == abs(p->col - child->col))
return false;
p = p->parent;
}
return true;
}
void print_ans(Node *child){
if(child->row >= 0){
print_ans(child->parent);
cout << child->col + 1 << ' ';
if(child->row == n - 1)
cout << endl;
}
}
void fifobb(Node *t) {
queue<Node *> q;
Node *E = t;
q.push(E);
while(!q.empty()) {
E = q.front();
q.pop();
for(int i = 0; i < n; i ++) { // 对于每个孩子
Node *child = new Node(E->row + 1, i, E);
if(place(child)) { // 满足约束条件
if(child->row == n - 1) // 是答案结点
print_ans(child), exit(0);
q.push(child);
}
}
}
}
int main(){
cin >> n;
Node *t = new Node(-1,-1,NULL);
fifobb(t);
return 0;
}
- 引入上下界估计函数的带时限作业问题
FIFOBB求最优解
#include <iostream>
#include <queue>
using namespace std;
const int N = 20;
int p[N], d[N], t[N];
int x[N];
struct Node{
int j;
int d;
int prof;
int loss;
Node* pre;
Node(int i,int dd, int p, int l, Node *pr){
j = i, d = dd, prof = p, loss = l, pre = pr;
}
Node(){}
};
int n, U, tot;
Node *res = NULL;
void fifobb(){
Node *e, *child;
queue<Node*> qu;
e = new Node(-1,0,0,0,NULL);
qu.push(e);
U = tot + 1;
while(qu.size()){
e = qu.front();
auto ep = *e;
qu.pop();
if(ep.loss >= U) continue;
int loss = ep.loss;
for(int i = ep.j + 1; i < n; i ++){
if(ep.d + t[i] <= d[i] && loss < U){ // 能否扩展孩子
child = new Node(i, ep.d + t[i], ep.prof + p[i], loss, e); //产生孩子
qu.push(child);
if(U > tot - child->prof){
res = child;
U = tot - child->prof;
} // 更新上界
}
loss = loss + p[i]; //loss 更新
}
}
}
int main(){
cin >> n;
for(int i = 0; i < n; i ++) cin >> p[i], tot += p[i];
for(int i = 0; i < n; i ++) cin >> d[i];
for(int i = 0; i < n; i ++) cin >> t[i];
fifobb();
for(Node *p = res; p->j != -1; p = p->pre) x[p->j] = 1;
for(int i = 0; i < n; i ++) cout << x[i] << ' ';
//cout << endl << U;
return 0;
}