算法学习-回溯、分支限界法

回溯法

基本概念

  • 解空间树:子集树,排序树

子集树是m叉树,每个非叶子节点出度都是m。
排序树可以看作是子集树的子树,也就是子集树减枝过后的树,非叶子节点的出度随着深度的增加而减小

  • 剪枝(constraint)
  • 限界(bound)
  • 回溯
  • 可以设置全局变量然后手动回溯
  • 也可以设置形参,利用栈进行自动回溯
  • 问题类型:所有解、最优解

递归回溯解题框架:

  • 首先只考虑递归体部分,也就是弄清楚节点是怎么分支的,以及如何回溯
  • 然后是递归出口
  • 约束
  • 限界

子集树:
在这里插入图片描述
排列树
在这里插入图片描述

框架

子集树递归框架

int x[n];  //解向量
void backtrack(int i){
	if(i>n){
		递归出口;
		return ;
	}
	" 遍历所有子节点(可能不用for-loop而直接枚举,并且每个子节点的间址和约束可能不同)"
	for(j=下界;j<=上界;j++){
		"节点预处理"
		x[i]=j;
		if(constraint() && bound())
			backtrace(i+1);
		"可能的额外回溯"
	}
}

子集树非递归框架

int x[n];	"解向量"
int m; "节点子节点个数"
fill(x, x+n, 0);
void backtrace(){
	int i=0;
	while(i>=0){
		if(i>=n){
			判断是否得解
			i--;		回溯
			continue;
		}
		x[k]++;
		if(x[i]<m){
			if(constraint)	
				i++;
			else
				continue;
		}
		else { "回溯"
			x[i] = 0;
			i--;
		}
	}
}

排列数递归框架

int x[n];
void backtrack(int i){
	if(i>n)
		递归出口
	for(j=i;j<=n;j++){
		...
		swap(x[i],x[j])
		if(constraint(i) && bound(i))
			backtrace(i+1);
		swap(x[i],x[j]);
	}
}

分支限界法

概念

  • 和回溯法的不同
  • 通常的求解目标不同
    回溯法常用来寻找解空间树中满足约束条件的所有解,而分支限界法常用来寻找一个解或最优解
  • 解向量的存储要求不同
    回溯法只有一条线,所以解向量的存储只有一份
    分支限界法有多条线,需要使用双亲节点指针或在每个节点存一份解向量
  • 数据结构
    • 优先队列
    • 队列
  • 解向量
    • 双亲节点指针
    • 在每个子节点中创建一个解向量

使用 STL 中的队列作为数据结构的时候,由于push过后数据无法保留,所以为每个节点分配一个id,并且单独建立两个数组保存节点的动作和双亲节点。
int actions[maxN];
int pres[maxN];

节点数据结构

  • 深度: i
  • 状态:如w, v
  • 界限: lb, ub
  • 缓存:解向量(x[], 表示顺序) , 哨兵向量(alloc[], 表示任务是否已分配)

例题

  • 0/1背包
// 0/1 背包 优先队列 分支限界法  使用双亲节点替代在子节点中存储的解向量 
#include<queue>
#include<iostream>
using namespace std;
struct Node{
	int no;
	int i;
	int w;	// 当前节点总重量 
	int v;	// 当前节点总价值 
	bool operator<(const Node&e)const{
		return w<e.w;
	}
};

int wws[] = {16,15,15};
int vs[] = {45, 25, 25};
int maxv = -1;
int W=30;
int n=3;
int bestX[3];

int actions[100]={0}; 
int pres[100];
int nodeid=0;
 
void bfs();
void enQueue(Node t, priority_queue<Node>&qu);
int upBound(int i);
int main(){
	
	bfs();
	cout<<maxv<<endl;
	for(int i=0;i<n;i++){
		cout<<bestX[i];
	}
	cout<<endl;
	return 0;
}

void bfs(){
	Node t, t1, t2;
	t.i = -1;
	t.w = t.v = 0;
	t.no = nodeid++;
	pres[t.no] = -1;
	actions[t.no] = -1;
	
	priority_queue<Node>qu;
	qu.push(t);
	while(!qu.empty()){
		t = qu.top();
		qu.pop();
		
		if(t.w+wws[t.i+1] <= W){
			t1 = t;
			t1.i = t.i+1;
			t1.w = t.w + wws[t1.i];
			t1.v = t.v + vs[t1.i];
			
			t1.no = nodeid++;
			actions[t1.no] = 1;
			pres[t1.no] = t.no;
			
			enQueue(t1, qu);			
		}

		if(t.v + upBound(t.i+2) > maxv){
			t2 = t;
			t2.i++;
			t2.no = nodeid++;
			actions[t2.no]=0;
			pres[t2.no] = t.no;
			enQueue(t2,qu);
		}
	}
}

void enQueue(Node t, priority_queue<Node>&qu){
	if(t.i== n-1){
		if(t.v > maxv){
			maxv = t.v;
			int k = t.no, i=n-1;
			while(pres[k]!=-1){
				bestX[i--] = actions[k];
				k = pres[k];
			}
		}
	}else{
		qu.push(t);
	}
}

int upBound(int i){
	int up=0;
	for(int j=i; j<3;j++){
		up+=vs[j];
	}
	return up;
}
发布了56 篇原创文章 · 获赞 2 · 访问量 504

猜你喜欢

转载自blog.csdn.net/qq_41956860/article/details/102782038