回溯法
基本概念
- 解空间树:子集树,排序树
子集树是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;
}