【算法分析与设计】第九章-分枝限界法

一、什么是分枝限界法

分枝限界法的本质是BFS + 剪枝,也是通过搜索状态空间树来找出问题的解。

分枝限界法的分类

按活结点表的存储方式分为三种:

  1. FIFOBB:用队列作为活结点表
  2. LIFOBB:用栈作为活结点表(D-搜索)
  3. 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;
} 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值