搜索与回溯

概论

搜索与回溯算法,就像是在迷宫里不断寻找正确前进路径的方式。
这里写图片描述
搜索与回溯也是信息学竞赛中常会有的算法(algorithm)。于是,诸位作为新生代的大佬,哪里能不好好学习一下搜索回溯算法呐?!!!

这里讲的是深度搜索策略(dfs);

dfs就是一条路走到黑,不撞南墙不回头!!!
就类似迷宫问题: 进入迷宫后,先随意选择一个前进方向,一步步向前试探前进,如果碰到死胡同,说明前进方向已无路可走,这时,首先看其它方向是否还有路可走,如果有路可走,则沿该方向再向前试探;如果已无路可走,则返回一步,再看其它方向是否还有路可走;如果有路可走,则沿该方向再向前试探。按此原则不断搜索回溯再搜索直到找到新的出路或从原路返回入口处无解为止。
递归回溯法算法框架[一]

int Search(int k)
{
 for (i=1;i<=算符种数;i++)
  if (满足条件)
     {
    保存结果
    if (到目的地) 输出解;
              else Search(k+1);
    恢复:保存结果之前的状态{回溯一步}
     }
}

递归回溯法算法框架[二]

int Search(int k)
{
   if  (到目的地) 输出解;
   else
    for (i=1;i<=算符种数;i++)
    if  (满足条件) 
   {
    保存结果;
       Search(k+1);
    恢复:保存结果之前的状态{回溯一步}
   }
}

以上是两个框架,就是搜索回溯算法的框架,看不懂吗?没关系,结合着它一起看看题目和代码。

例题:

例题一

设有n个整数的集合{1,2,…,n},从中取出任意r个数进行排列(r<=n),试列出所有的排列。
输入:一行,n和r。
输出:所有的排列,每种占一行。
输入样例:

3 3

输出样例:

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
6

我们分析一下这个题哈:首先,你要从1开始搜,搜啊搜,搜啊搜,当1的情况全部搜完事时,再从2开始搜········
以下是参考程序:

#include<iostream>
#include<cstdio>
#include<iomanip>
//库文件名 
using namespace std;
int num=0,a[10001]={0},n,r;
//先定义一下全局变量 ,b数组是bool类型、用于判断,以防止搜重 
bool b[100001]={0};
int search(int);
int print();
//以上是函数声明 
int main()
{
    cin>>n>>r;
    //取到n,r得值 
    search(1);
    //开始搜索,从"1"开始 
    cout<<num<<endl;
}
int search(int k){
    int i;
    for(i=1;i<=n;i++)//n个数,从一到n都搜索一遍,for循环内的是搜索以ni为开头的数 (定义ni是第i个数) 
      if(!b[i]){
        a[k]=i;
        //自己手动模拟,你就明白了 
        b[i]=1;
        //防止搜重 
        if(k==r) print();
          else search(k+1);
         //没到目的地,那就继续搜 
        b[i]=0;
        //回溯,很关键,特别关键,超超级关键(重要的词说三遍; 

      }
}
int print()
//这是一个输出含数 
{
    num++;
    //看输出了多少个排列 
    for(int i=1;i<=r;i++)
       cout<<setw(3)<<a[i];
       //setw(3)是以空三个格形式输出 
    cout<<endl;
}

这里是这个程序运行情况
这里写图片描述

注意:搜索与回溯这一方面的算法,是需要大量的模拟 的;
模拟模拟模拟,特别重要哦!!!!!

例题二:

任何一个大于1的自然数n,总可以拆分成若干个小于n的自然数之和。
输入:n
输出:n这个数的拆分方式,并输出总共有多少种拆分方式。
输入样例:

7

输出样例:

7=1+1+1+1+1+1+1
7=1+1+1+1+1+2
7=1+1+1+1+3
7=1+1+1+2+2
7=1+1+1+4
7=1+1+2+3
7=1+1+5
7=1+2+2+2
7=1+2+4
7=1+3+3
7=1+6
7=2+2+3
7=2+5
7=3+4
total=14

以下为参考程序;

#include<iostream>
#include<fstream>

int a[10001]={1},n,total;
using namespace std;
ifstream fin ("in.in");
ofstream fout ("out.out");
int print(int t){
    //这是一个输出函数 
    fout<<n<<"=";
    for(int i=1;i<=t-1;i++) fout<<a[i]<<"+";
    fout<<a[t]<<endl;
    total++;    

}
int search(int s,int t){
    int i;
    for(i=a[t-1];i<=s;i++){
        if(i<n){
            a[t]=i;
            s-=i;
            if(s==0) print(t);
            //如果到达目的地的话,就输出 
            else search(s,t+1);
            //如果没到目的地的话,就在搜索下一条路。 
        s+=i;
        //回溯阶段,很重要很重要很重要 
        }
    }
}
int main(){
    fin>>n;
    search(n,1);
    //搜索 
    fout<<total;
}

这里是程序运行结果,用文件流方式写的
这里写图片描述

这两个的例题是很经典的,一定要自已模拟一下,手动模拟!!!!

八皇后问题:

这里写图片描述
要在国际象棋棋盘中放八个皇后,使任意两个皇后都不能互相吃。(提示:皇后能吃同一行、同一列、同一对角线的任意棋子。)

放置第i个(行)皇后的算法为:
int search(i);
 {
     int j;
   for (第i个皇后的位置j=1;j<=8;j++ )      //在本行的8列中去试
   if (本行本列允许放置皇后)
    {
     放置第i个皇后;
                  对放置皇后的位置进行标记;
     if (i==8) 输出                                 //已经放完个皇后
        else search(i+1);                //放置第i+1个皇后
     对放置皇后的位置释放标记,尝试下一个位置是否可行;
    }
 }

显然问题的关键在于如何判定某个皇后所在的行、列、斜线上是否有别的皇后;可以从矩阵的特点上找到规律,如果在同一行,则行号相同;如果在同一列上,则列号相同;如果同在/ 斜线上的行列值之和相同;如果同在\ 斜线上的行列值之差相同;从下图可验证:
这*里写图片描述
考虑每行有且仅有一个皇后,设一维数组A[1..8]表示皇后的放置:第i行皇后放在第j列,用A[i]=j来表示,即下标是行数,内容是列数。例如:A[3]=5就表示第3个皇后在第3行第5列上。判断皇后是否安全,即检查同一列、同一对角线是否已有皇后,建立标志数组b[1..8]控制同一列只能有一个皇后,若两皇后在同一对角线上,则其行列坐标之和或行列坐标之差相等,故亦可建立标志数组c[1..16]、d[-7..7]控制同一对角线上只能有一个皇后。
如果斜线不分方向,则同一斜线上两皇后的行号之差的绝对值与列号之差的绝对值相同。在这种方式下,要表示两个皇后I和J不在同一列或斜线上的条件可以描述为:A[I]<>A[J] AND ABS(I-J)<>ABS(A[I]-A[J]){I和J分别表示两个皇后的行号}
下面放代码:

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<iomanip>
using namespace std;
bool d[100]={0},b[100]={0},c[100]={0};
int sum=0,a[100];
int search(int);
int print();
int main()
{
   search(1);                                                          //从第1个皇后开始放置
}
int search(int i)
{
  int j;
  for (j=1;j<=8;j++)                                              //每个皇后都有8位置(列)可以试放 
    if ((!b[j])&&(!c[i+j])&&(!d[i-j+7]))                   //寻找放置皇后的位置
      //由于C++不能操作负数组,因此考虑加7
      {                                                                  //放置皇后,建立相应标志值
      a[i]=j;                                                          //摆放皇后
      b[j]=1;                                                         //宣布占领第j列
      c[i+j]=1;                                                      //占领两个对角线
      d[i-j+7]=1;
      if (i==8) print();                                           //8个皇后都放置好,输出
        else search(i+1);                                      //继续递归放置下一个皇后
      b[j]=0;                                                        //递归返回即为回溯一步,当前皇后退出
      c[i+j]=0;
      d[i-j+7]=0;
      }
}
int print()
{
    int i;
    sum++;                                                        //方案数累加1
    cout<<"sum="<<sum<<endl;
    for (i=1;i<=8;i++)                                         //输出一种方案
      cout<<setw(4)<<a[i];
    cout<<endl; 
}

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值