第十二周周结(搜索与回溯)

这周主要学习的是搜索与回溯,二分是上周的内容,但由于上周在做背包的题目,所以就没有总结二分,相较于二分,我感觉搜索与回溯更加复杂一点,所以这周就先总结搜索与回溯。

搜索与回溯

深度优先搜索:深度优先搜索相当于是一条直线从问题的最初找到最后,必须把问题的答案找出来;
广度优先搜索:与深度对立,它是从横向来找答案,把每种情况的子情况都考虑一遍。

首先介绍深度优先搜索。
深度优先搜索与递归分不开,我们也可以把这种算法叫做递归回溯法。这种算法的基本思想是:为求得问题的解,先选择某一种可能的情况进行探索,在探索过程中,一旦发现原来的选择是错误的,就退回一部重新选择,继续想先探索,如此反复进行,直至得到解或证明无解。
只看文字不好理解,下面给出算法框架.
int search(int k)
{
for(i=1;i<=算符种数;i++)
if(满足条件)
{
保存结果;
if(到目的地)输出解;
else search(k+1);
恢复:保存结果之前的状态{回溯一步}
}
}

下面通过素数环来具体看一下这个框架。
素数环:将1到20这20个数摆成一个环,要求相邻的两个数之和必须是素数。
算法分析:我们可以给1到20这20个位置填数,这里的位置很重要,不然算法程序中的k没办法初始化。每个空位有20种可能,第20个空还要与第一个进行判断。

#include <bits/stdc++.h>

using namespace std;
int a[21]={0};
int total=0;
bool b[21]={0};
bool prime(int ,int);
int print();
int search(int t)
{
    for(int i=1;i<=20;i++)
        if(prime(a[t-1],i)&&(!b[i]))//判断该数是否与前面的数构成素数且该数是否可用
           {
               a[t]=i;
               b[i]=1;
               if(t==20){if(prime(a[20],a[1]))print();}//满足条件
               else search(t+1);//寻找下一步
               b[i]=0;//回溯
           }
}
int print()
{
    total++;//找到了一个满足条件的素数环
    cout<<"<"<<total<<">";
    for(int j=1;j<=20;j++)
        cout<<a[j]<<" ";
    cout<<endl;
}
bool prime(int x,int y)//判断素数
{
    int k=2,i=x+y;
    while(k<=sqrt(i)&&(i%k!=0))k++;
    if(k>sqrt(i))return 1;
    return 0;
}
int main()
{
    search(1);//从第一个位置开始
    cout<<total<<endl;//输出总数
    return 0;
}

这里做搜索的题目有一个技巧,我们在定义变量的时候定义全局变量,就像上面的total,我们定义全局变量,不管怎么调用,最后结果都是对的。

看完素数环,下面在介绍几个搜索的题目,来感受一下搜索该怎么做。
n个数取出r个进行排列(r<n),求所有的排列的问题
这道题与素数环基本一样,比素数环还要简单点,这次我们只需要判断第i个数是否可用就行了,不用再去判断是否为素数。

int search(int k)
{
  for(int i=1;i<=n;i++)
  {
     if(!b[i])//判断第i个数是否可用
     {
        a[k]=i;         //保存结果
        b[i]=1;
        if(k==r)print();//满足条件,打印结果
        else search(k+1);//寻找下一步
        b[i]=0;//回溯
     }
  }
}     

整数划分问题
这道题可以用完全背包做,之前的博客中有讲解。对于搜索,我们先来看一组具体的数。比如n=4,则总共有4种分解方式。
4=1+1+1+1
4=1+1+2
4=1+3
4=2+2
我们可以让i从小到大循环,然后用n减去i直到n=0结束

int search(int s,int k)//s代表要求的数,
{ 
  for(int i=a[k-1];i<=s;i++)//这里要小于s,而不是n,因为s是能选的最大值
  if(i<n)   //当前的数i要大于等于前一个数,且不超过n
  {
    a[k]=i;//保存当前拆分的数i
    s-=i;
    if(s==0)print(t);//满足,打印
    else search(s,t+1);//寻找下一步
    s+=i;//回溯
  }
}

八皇后问题
在国际象棋中放置八个皇后,使两两之间不能相吃。(皇后能吃同一行,同一列,同一对角线的任意棋子)
问题的关键在于判皇后所在的行,列,对角线上是否还有别的皇后。如果在同一行,行号相同;在同一列,列好相同;在/对角线上,行列值之和相同;在\对角线上,行列之差相同。用a[i]=j来表示皇后在第i行第j列,用b[1…8]代表每一列只能有一个皇后,c[1…16],d[-7…7]控制同一对角线只能有一个皇后。

int search(int i)
{
  for(int j=1;j<=8;j++)
    if((!b[j])&&(!c[i+j])&&(!d[i-j+7]))//寻找皇后的位置
                                       //由于数组下不能小于0,所以这里+7
       {
         a[i]=j;//记录皇后的位置,便于在print中输出
         b[j]=1;
         c[i+j]=1;
         d[i-j+7]=1;//宣布皇后占领位置
         if(i==8)print();
         else search(i+1);
         b[j]=0;
         c[i+j]=0;
         d[i-j+7]=0;//回溯
       }
}

八皇后问题难在很难取在矩阵中判断行,列,对角线。把这三项判断好后,八皇后问题就会很简单,就是最普通的回溯问题。

马的遍历(只允许向右跳)
在限制条件下,马每次只有四种跳法在这里插入图片描述
这道题目与八皇后一样,也是难在坐标的表示,其实感觉挺难,理清思路后还是很好表示的,我们无非是要判断马向这四个方向跳跃后不越界罢了。首先,我们定义二维数组a[100][3](这里之所以定义二维数组,是我们可以用一个数组就表示矩阵的两个坐标)用来表示马的位置。a[i][1]代表横坐标,a[i][2]代表纵坐标。初始化我们从(0,0)开始,所以a[1][1]=0,a[1][2]=0,这是第一步,我们从第二步开始遍历,所以最初主函数里面是search(2);

int x[4]={2,1,-1,-2};
int y[4]={1,2,2,1};//这里x,y对应跳的方式,这里只要能对应起来就行
.....
.....
int search(int i)
{
  for(int j=0;j<=3;j++)//四种跳法
    if(if(a[i-1][1]+x[j]>=0)&&(a[i-1][1]+x[j]<=4)
    &&(a[i-1][2]+y[j]>=0)&&(a[i-1][2]+y[j]<=8))//判断马不越界
    {
      a[i][1]=a[i-1][1]+x[j];
      a[i][2]=a[i-1][2]+y[j];//保存当前马的位置
      if(a[i][1]==4&&a[i][2]==8)print(i);
      else search(i+1);
      //这里就不需要再回溯了,因为题目没有强调每个位置只能到达一次
    }
}

广度优先搜索
在介绍广搜前,需要先把队列的只是给弄明白,队列是解决广搜问题的一个很好用的容器。
通过一道题来理解广搜
题意:给出一个整数n,(1 <= n <= 200)。求出任意一个它的倍数m,要求m必须只由十进制的’0’或’1’组成。

看到这道题,给我的感觉是不难,不就是找出用0,1构成的数,然后判断一下是不是n的倍数不就完了吗?但是在实现的时候发现思路虽然简单,但是我们怎么才能储存所有用0,1组成的数且不能遗漏呢?在学习了广搜后,这道题是广搜中最好理解的一道题目了。这时,队列就发挥了他独特的优势,先进先出,这样就能从小到大遍历,不会出现遗漏的情况。总结不动了,直接看代码吧。

long long bfs()
{
    queue<long long> q;
     while(!q.empty()) q.clear();//如果队列不空,清空
    q.push(1);//放入第一个数
    while(!q.empty())
    {
    if(q.front()%n==0) return q.front();//找到,返回
         long long p=q.front();
        q.pop();
        q.push(p*10);//放入多一个0的数
        q.push(p*10+1);//放入多一个1的数
    }
    return 0;
 
}

总之,这道题还是非常典型的,通过这个例子,我们不难发现深搜与广搜的区别,深搜的复杂度要比广搜大,广搜不需要必须执行到低,有可能他的第一步就是问题的的答案,深搜牵扯到一个递归的问题,所以会显得比较麻烦,理解起来也比广搜繁琐些。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
牙科就诊管理系统利用当下成熟完善的SSM框架,使用跨平台的可开发大型商业网站的Java语言,以及最受欢迎的RDBMS应用软件之一的Mysql数据库进行程序开发。实现了用户在线查看数据。管理员管理病例管理、字典管理、公告管理、药单管理、药品管理、药品收藏管理、药品评价管理、药品订单管理、牙医管理、牙医收藏管理、牙医评价管理、牙医挂号管理、用户管理、管理员管理等功能。牙科就诊管理系统的开发根据操作人员需要设计的界面简洁美观,在功能模块布局上跟同类型网站保持一致,程序在实现基本要求功能时,也为数据信息面临的安全问题提供了一些实用的解决方案。可以说该程序在帮助管理者高效率地处理工作事务的同时,也实现了数据信息的整体化,规范化与自动化。 管理员在后台主要管理病例管理、字典管理、公告管理、药单管理、药品管理、药品收藏管理、药品评价管理、药品订单管理、牙医管理、牙医收藏管理、牙医评价管理、牙医挂号管理、用户管理、管理员管理等。 牙医列表页面,此页面提供给管理员的功能有:查看牙医、新增牙医、修改牙医、删除牙医等。公告信息管理页面提供的功能操作有:新增公告,修改公告,删除公告操作。公告类型管理页面显示所有公告类型,在此页面既可以让管理员添加新的公告信息类型,也能对已有的公告类型信息执行编辑更新,失效的公告类型信息也能让管理员快速删除。药品管理页面,此页面提供给管理员的功能有:新增药品,修改药品,删除药品。药品类型管理页面,此页面提供给管理员的功能有:新增药品类型,修改药品类型,删除药品类型。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值