回溯法(经典题目详解)

回溯法

是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走。

解题步骤:

  1. 定义问题解空间
  2. 确定易于搜索并且符合问题的解空间向量(子集树、排列数、m叉树)
  3. 根据约束函数和限界函数确定剪枝函数,避免无效搜索,降低问题复杂度

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值