回溯法:
是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走。
解题步骤:
- 定义问题解空间
- 确定易于搜索并且符合问题的解空间向量(子集树、排列数、m叉树)
- 根据约束函数和限界函数确定剪枝函数,避免无效搜索,降低问题复杂度
1.装载问题
问题描述:有一批共n个集装箱要装上2艘载重量分别为c1和c2的轮船,其中集装箱i的重量为wi,且 ∑ i = 1 n w i \sum_{i=1}^nw_i ∑i=1nwi ≤ \leq ≤c1+c2。装在问题要求确定,是否有一个合理的装载方案将这n个集装箱装上这2艘轮船。
基本思路:首先将第一艘船尽量装满,将剩余的集装箱装上第二艘轮船,并且满足以下条件(约束函数与限界函数)
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
//最优装载方案
//首先将第一艘轮船尽量装满,将剩余的集装箱装上第二艘轮船
//用排列树表示解空间
//i<=n,当cw+w[i]<=c,进入左子树,x[i]=1;当cw+w[i]>c,剪枝操作
int n; //集装箱数
int cw=0; //当前载重量
int bestw=0; //最优载重量
int r=0; //剩余集装箱重量
int c1; //第一艘载重量
int c2; //第二艘载重量
int x[100]; //当前解
int bestx[100]; //当前最优解
int w[100]; //集装箱重量数组
void BackTrack(int i){
if(i>n){
if(cw>bestw){
for(int i=1;i<=n;i++)
bestx[i]=x[i];
bestw=cw;
}
return;
}
r-=w[i];
if(cw+w[i]<=c1){
cw+=w[i];
x[i]=1;
BackTrack(i+1);
x[i]=0;
cw-=w[i];
}
if(cw+r>bestw){
x[i]=0;
BackTrack(i+1);
}
r+=w[i];
}
int main(){
int restweight=0;
scanf("%d",&n);
scanf("%d%d",&c1,&c2);
for(int i=1;i<=n;i++){
scanf("%d",&w[i]);
r+=w[i];
}
BackTrack(1);
for(int i=0;i<=n;i++){
if(bestx[i]==0)
restweight+=w[i];
}
if(restweight>c2)
cout<<"不能装入!"<<endl;
else{
printf("船1装入的货物为:");
for(int i=1;i<=n;i++)
if(bestx[i]==1)
printf("%d ",i);
printf("\n船2装入的货物为:");
for(int i=1;i<=n;i++)
if(bestx[i]!=1)
printf("%d",i);
}
return 0;
}
2.批处理作业调度
问题描述:给定n个作业的集合J={J1,J2,…Jn}。每个作业Ji都有两项任务分别在两台机器上完成。每个作业必须先由机器1处理,再由机器2处理。作业Ji需要机器j处理的时间为Jji(i=1,2,…,n;j=1,2)。对于一个确定的作业调度,设FJij是作业i在机器j上完成处理的时间,则所有的作业在机器2上完成处理的时间和f= ∑ i = 1 n F 2 i \sum_{i=1}^nF_2i ∑i=1nF2i称为该作业调度的完成时间和。
基本思路:设x={1,2…,n}是n个作业,则排列树为由x[1:n]的排列所构成
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1000;
//批作业处理调度:回溯法搜索排列树
int m[3][maxn]; //各个作业所需的处理时间
int x[maxn]; //当前作业调度
int bestx[maxn];//最优作业调度
int f2[maxn]; //机器二完成处理时间
int f1=0; //机器一完成处理时间
int f=0; //完成时间和
int n; //作业数
int bestf=0x3f3f3f; //当前最优值
void swap(int &a,int &b){
int temp=a;
a=b;
b=temp;
}
void backtrack(int i){
if(i>n){ //到达叶子结点
for(int j=1;j<=n;j++) //维护数组,更新最优值
bestx[j]=x[j];
bestf=f;
}else{
for(int j=i;j<=n;j++){
f1+=m[x[j]][1]; //机器一不需要等待作业
f2[i]=(f2[i-1]>f1?f2[i-1]:f1)+m[x[j]][2]; //需要比较机器二上一个作业完成时间与机器一当前作业完成时间
f+=f2[i];
if(f<bestf){
swap(x[i],x[j]); //交换两个作业的位置,把选择出的原来在x[j]位置上的任务调到当前执行的位置x[i]
backtrack(i+1);
swap(x[i],x[j]);
}
f1-=m[x[j]][1];
f-=f2[i];
}
}
}
int main(){
cout<<"输入作业数:"<<ends;
cin>>n;
cout<<"输入作业在各机器作业时间"<<endl;
for(int i=1;i<=n;i++)
for(int j=1;j<=2;j++)
cin>>m[i][j];
for(int i=1;i<=n;i++){
x[i]=i;
f2[i]=0;
}
backtrack(1);
cout<<"最优调度方案:"<<endl;
for(int i=1;i<=n;i++)
cout<<bestx[i]<<" "<<ends;
cout<<endl;
cout<<"最优调度时间:"<<bestf<<endl;
return 0;
}
3.符号三角形问题
问题描述:在一般情况下,符号三角形的第一行有n个符号。符号三角形问题要求对于给定的n,计算有多少不同的符号三角形,使’+‘的数量和’-'的数量相同。
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=1000;
int n; //第一行符号个数
int half; //n*(n+1)
int count=0;//当前'+'的个数
int p[maxn][maxn]={0}; //矩阵
long long sum=0; //计数器
void Backtrack(int t){
if((count>half)||(t*(t-1)/2)-count>half) //'+'大于'-'数量
return;
if(t>n){ //输出
sum++;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
cout<<p[i][j]<<" ";
}
cout<<endl;
}
cout<<endl;
}else{
for(int i=0;i<2;i++){
p[1][t]=i;
count+=i;
for(int j=2;j<=t;j++){
p[j][t-j+1]=p[j-1][t-j+1]^p[j-1][t-j+2];
count+=p[j][t-j+1];
}
Backtrack(t+1);
for(int j=2;j<=t;j++)
count-=p[j][t-j+1];
count-=i;
}
}
}
int main(){
cout<<"输入第一行符号个数:\n";
cin>>n;
half=n*(n+1)/2;
if(half%2==1)
return 0;
half=half/2;
Backtrack(1);
cout<<sum<<endl;
return 0;
}
4.N皇后问题
问题描述
在n*n的棋盘上放置n个皇后,使他们互不攻击,皇后可以攻击与之处在同一行或同一列或同一条斜线上的其他皇后
参考代码
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=1000;
int c[maxn];
int n,tot=0;
void backtrack(int cur){
if(cur==n) tot++;
else for(int i=0;i<n;i++){
int ok=1;
c[cur]=i;
for(int j=0;j<cur;j++){
if(c[cur]==c[j]||abs(cur-j)==abs(c[cur]-c[j])){
ok=0;
break;
}
}
if(ok) backtrack(cur+1);
}
}
int main(){
cin>>n;
backtrack(0);
cout<<tot<<endl;
}