搜索入门略谈

<前言>

我惊讶的发现,其实我每天写的博客和每日总结也没区别了???

然而,今天讲到了dfs、bfs,那么…………

就让我来略扯一下吧!

haha~,胡cingger多有得罪啦!!

以下内容大部分来自PPT“dfs入门”与“bfs入门”;


DFS:

先看看什么是dfs吧,Dfs是“深度优先搜索”的英文简称,深度优先搜索是一种搜索方式,代码实现的载体基本上是递归函数,要求是对递归的结构必须完全理解。

基本上是按黑皮书的三个阶段来讲dfs:递归->回溯->dfs,递归会讲一些,但不会细讲,重讲回溯和dfs;


153. 拍卖

       题目描述
 一般情况下,拍卖行的拍卖师在拍卖商品的时候都是从低价开始起拍,由买方报价,最后谁出的价格高,商品就归谁所有。但海亮有个毛毛拍卖行,拍卖师毛毛在拍卖商品时正好相反:总是从高价开始起拍,如果没有人举成交牌就降价,而且毛毛在降价时还有规律:假如第i次报价为w元,那么第i+1次报价为w-a或者w-b元,如果降到p元时,你认为价格合适,赶快第一个举成交牌,你就花p元买下了商品。
 任务:毛毛把商品从w元降到p元的方法总数。 
 输入格式
   文件第一行有两个正整w 和p ,第二行有有两个正整a 和b. 1 ≤ w,p ≤ 10^6  , 2 ≤ a,b ≤ 10000, a不等于b. 
 输出格式
   文件只有一行,即所求得的方法总数。注意:测试数据中方法总数不超过MAXlongint. 
 样例数据
  10 3 2 3 
  3
 数据规模与约定
  保证2≤a,b≤100002≤a,b≤10000
 时间限制:1s1s
 空间限制:256MB

      题目描述的问题请问乔治;

如题意,从w元有很多方法可以降成p元,但不变的是每一次只能降a元或b元,所以,我们可以定义一个递归函数search(Int start ,int end),start代表当前的拍卖金额状态,所以start一开始就是w,end代表结束时的金额,即为p。End不定义也可以,定义了方便理解。

    void search(int start,int end)
    {
      if(start==end)
      {
        tot++;
      }
      else
      {
        if(start<end)
        {
          return ;
        }
        else
        {
          search(start-a,end);
          search(start-b,end);
        }
      }
    }

然后,这就是我们所谓的爆搜代码了,好吧,这就是爆搜代码,是会超时的,我们必须对代码进行优化,

其实加个记忆化就行了

那就加记忆化吧!

记忆化

顾名思义,就是对一些计算后的结果进行记录,然后直接调用即可,避免重复计算。也是空间换时间的思想。后面(明天肯定会讲到的)会讲的;

  int search(int start,int end)
{
    if(start==end)return 1;
    if(start<end)return 0;
    if(tot[start]>0)return tot[start];
    tot[start]=search(start-a,end)+search(start-b,end);
    return tot[start];
}

下面给出AC代码:

  #include<bits/stdc++.h>
using namespace std;
long long w,p,a,b,tot[1300000];
int search(int start,int end)
{
    if(start==end)return 1;
    if(start<end)return 0;
    if(tot[start]>0)return tot[start];
    tot[start]=search(start-a,end)+search(start-b,end);
    return tot[start];
}
int main()
{
    cin>>w>>p>>a>>b;
    search(w,p);
    cout<<tot[w]<<endl;
    return 0;
}

好的,那么我们dfs的第一步递归就算过了!(前路漫漫啊)

回溯例题:

#8mark问题(哈我居然翻出这题了)
    题目描述
    在一个n×n的棋盘上放置n个mark,要求所有的mark之间都不形成攻击。请你给出所有可 能的排布方案数。(注:Mark会攻击和他同行、同列、同对角线的其他生物)
    输入格式     一个整数n
    输出格式     一个整数表示方案数
    样例数据
    input
     4 
    output
     2 
   数据规模与约定
    n<=8 
   时间限制:1s
   空间限制:256MB

这是一个经典的回溯搜索题,就是对标记过的东西重新解除标记,以达到搜索全局所有可能性。

显然,这道题的思路就是尝试在每一行的每一个位置放下mark,然后对不能放的位置进行标记,每一次尝试放mark的时候检测当前位置有没有标记即可。

所以,我们需要三个标记数组,分别记录列,左斜线,右斜线。行通过递归函数的形参枚举即可。

记录斜对角线时我们可以发现,对角线行列号的差或和是不变的,所以标记数组的下标为行列号的差或和即可,对于左下和右上的对角线用le数组下标行列号求和标记,右下和坐上的对角线用ri数组下标行列号求差标记,避免负下标统一加n即可。

代码应该好实现吧,写完之后思考一下这题代码与上题的区别?

这就是回溯了,在搜索时,直接顺序搜索是无法得到所有解的。我们需要对刚刚进行的操作进行“撤回”,再试试其他位置能否有解。这句“相反”的,看似无用的语句一定要好好理解。

这样就没问题了!!

拓展:尝试打印每一组解(这还是我们13皇后时应该做的)

代码献上:

    #include<bits/stdc++.h>
using namespace std;
int s=0,n;
bool zong[1001]={},you[1001]={},zuo[1001]={};
void digui(int ceng){
    if(ceng==n){
        s++;return;
    }
    for(int i=0;i<n;i++){
        if(!(zong[i]||you[ceng-i+1]||zuo[ceng+i])){
            zong[i]=you[ceng-i+1]=zuo[ceng+i]=1;
            digui(ceng+1);
            zong[i]=you[ceng-i+1]=zuo[ceng+i]=0;
        } 
    } 
}
int main()
{
    cin>>n;
    digui(0);
    cout<<s;
    return 0;
}

回溯就算讲完了吧(我的代码很渣是吧)

dfs搜索经典例题:

#解救Zero(迷宫)
    题目描述
     给定一个N*M方格的迷宫,迷宫里有T处障碍,障碍处不可通过。给定起点坐标和终点坐标,
问每个方格最多经过1次,有多少种从起点坐标到终点坐标的方案,这样才能解救Zero。在迷宫中
移动有上下左右四种方式。保证起点上没有障碍。
   输入格式
    第一行N、M和T,N为行,M为列,T为障碍总数。 第二行起点坐标SX,SY,终点坐标FX,FY。 
接下来T行,每行为障碍的坐标。
   输出格式
    给定起点坐标和终点坐标,问每个方格最多经过1次,从起点坐标到终点坐标的方案总数。
   样例数据
   input
    2 2 1
    1 1 2 2
    1 2
   output
    1
   数据规模与约定
    1<=N,M<=5。
   时间限制:1s1s
   空间限制:256MB

这题可是个大工程啊,我们先得制定搜索的策略,可想而知,需要模拟的是每一步在合法的情况下如何走,搜索完每一种路线,统计方案总数即可。这也就是本题的大致思路。

首先我们又要用到这狗血无比的方向数组了:

    dx[]={-1,0,1,0}
    dy[]={0,-1,0,1}

还是讲讲方向数组吧:将两个数组dx和dy的同下标数字对应起来看,其实就是模拟了走迷宫四个方向走法发生在坐标系中的x,y轴变化请况,这样就可以用for循环快速简洁的对迷宫进行模拟遍历搜索了。这也是在众多遍历搜索题中必须要掌握的一点。

接下来就是边界的判断了:

停止搜索的三个条件:(1)越界,超出数组或n,m范围;(2)到达终点;(3)走到已走过的点(已标记)

可以将1),2)写在函数开头判断,3)写在遍历下一步时判断,再添上标记的语句和回溯取消标记的语句,就能写出搜索函数代码了。

那么,如果没问题就直接给代码了!!

    #include<bits/stdc++.h>
using namespace std;
struct rode{
    int x,y;
}st,en,x;
int n,m,s=0,vis[10][10]={},t;
void huisu(int x,int y){
    if(x==en.x&&y==en.y){s++;return ;}
     else{
         vis[x][y]=1;
         if(!vis[x][y-1]&&y-1>0)huisu(x,y-1);
         if(!vis[x][y+1]&&y+1<=m)huisu(x,y+1);
         if(!vis[x-1][y]&&x-1>0)huisu(x-1,y);
         if(!vis[x+1][y]&&x+1<=n)huisu(x+1,y);
         vis[x][y]=0;
     }
    return ;
}
int main()
{
    scanf("%d%d%d",&n,&m,&t);
    scanf("%d%d%d%d",&st.x,&st.y,&en.x,&en.y);
    for(int i=1;i<=t;i++){
        scanf("%d%d",&x.x,&x.y);
        vis[x.x][x.y]=1;
    }
    huisu(st.x,st.y);
    printf("%d",s);
    return 0;
}

嘿,好像相比之下是我的代码通俗易懂一点(^∨^)!!

呼,dfs终于完了,那么……我们继续bfs!!

BFS

bfs:这是啥?

宽度优先搜索算法(又称广度优先搜索)是最简便的图的搜索算法之一,这一算法也是很多重要的图的算法的原型。Dijkstra单源最短路径算法和Prim最小生成树算法都采用了和宽度优先搜索类似的思想。其别名又叫BFS,属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到 找到结果为止。

一般可以用它做什么呢?

一个最直观经典的例子就是走迷宫,我们从起点开始,找出到终点的最短路程,很多最短路径算法就是基于广度优先的思想成立的。

与深度优先搜索的对比

深度优先搜索 用栈(stack)来实现,整个过程可以想象成一个倒立的树形:

1、把根节点压入栈中。

2、每次从栈中弹出一个元素,搜索所有在它下一级的元素,把这些 元素压入栈中。并把这个元素记为它下一级元素的前驱。

3、找到所要找的元素时结束程序。

4、如果遍历整个树还没有找到,结束程序。

广度优先搜索 使用队列(可用数组代替)来实现,整个过程也可以看作一个倒立的树形:

1、把根节点放到队列的末尾。

2、每次从队列的头部取出一个元素,查看这个元素所有的下一级元素,把它们放到队列的末尾。并把这个元素记为它下一级元素的前驱。

3、找到所要找的元素时结束程序。

4、如果遍历整个树还没有找到,结束程序。

好的以上内容都来自度娘,能理解多少就多少

bfs毫无花哨,来让我们直接开始吧:

    #Mark坐电梯(奇怪的电梯):
    题目描述:
     Mark有一天做了一个梦,他梦见他坐在一个电梯里。这个电梯很奇怪,它只能按当前楼层所对应的按钮,每个按钮上有一个数字,表示按下这个按钮可以上升或下降几层楼。当然,如果不能满足要求,相应的按钮就会失灵。     例如:3 3 1 2 5代表了Ki(K1=3,K2=3,……),从一楼开始。在一楼,按“上”可以到4楼,按“下”是不起作用的,因为没有-2楼。现在Mark在A楼,他想要到B楼去,请问最少需要按几次按钮。   输入样例:
     5 1 5 //分别表示:有5层楼,Mark在1楼,他想到5楼
     3 3 1 2 5
   输出样例:
     3

这已经是bfs的模板--了,就是入门的;

思路:

电梯只能上下移动

So?

我们先判断在当前楼层上下移动时,所到达的楼层是否合法(即不超过n层楼并且不低于1楼)。接着将所能到达的合法楼层进队,再去搜索它们。

想一想:我们要不要像dfs那样,判断当前楼层是否到达过?

答案是:要!!!但不用回溯;

我们来假设一下:若当前楼层是第2次到达,且有比第1次选择更优的方案,那么为什么不在第1次就选择它呢?(重点重点!!~

因此重复来到过的楼层肯定不会是最优解,我们不必去考虑它。

直接给上代码:

    #include<bits/stdc++.h>
using namespace std;
int a[501]={},b[501]={},c[501]={};
bool f[501]={};
int s=0,n,m,x,y,xx,yy,k=1,t;
void bfs()
{
    int head=0,tail=1;
    a[1]=x;
    f[x]=true;
    while(head>=tail) 
    {
        head++;
        if(a[head]-b[a[head]]>0&&!f[a[head]-b[a[head]]])//判断能否向下走 
        {
            tail++;
            a[tail]=a[head]-b[a[head]];
            f[a[head]-b[a[head]]]=1;//将当前楼层标记为已访问过 
            c[a[head]-b[a[head]]]=c[a[head]]+1;
        }
        if(a[head]+b[a[head]]<=n&&!f[a[head]+b[a[head]]])//判断能否向上走 
        {
            tail++;
            a[tail]=a[head]+b[a[head]];
            f[a[head]+b[a[head]]]=1;//将当前楼层标记为已访问过 
            c[a[head]+b[a[head]]]=c[a[head]]+1;
        }
        if(f[y]) return;//到达目标楼层,退出 
    }
}
int main()
{
    scanf("%d%d%d",&n,&x,&y);
    for(int i=1;i<=n;i++)
    scanf("%d",&b[i]);
    bfs();
    if(!f[y]) 
        printf("-1\n");
    else printf("%d\n",c[y]);
    return 0;
}

啊,myc果然是大佬,你看看这代码,啧啧,条理清晰啊……(巴拉巴拉一大堆)

(请忽略上句话)仔细体会代码的写法,bfs重在、难在代码的理解与模拟上,这很重要,尤其是队列所起到的作用;

但我觉得昨天上午柴老师讲过了,(应该)都会吧!

下一题:

   #山峰与山谷
     题目描述:
      心畅特别喜欢爬山,在爬山的时候他就在研究山峰和山谷。为了满足心畅无敌的强迫症与好奇心(这会害死他),他想知道山峰和山谷的数量。      给定一个地图,为心畅想要旅行的区域,地图被分为n*n的网格,每个格子(i,j) 的高度w(i,j)是给定的。
      若两个格子有公共顶点,那么他们就是相邻的格子。(所以与(i,j)相邻的格子有(i-1, j-1),(i-1,j),(i-1,j+1),(i,j-1),(i,j+1),(i+1,j-1),(i+1,j),(i+1,j+1))。我们定义一个格子的集合S为山峰(山谷)当且仅当:      1.S的所有格子都有相同的高度。
      2.S的所有格子都联通。
      3.对于s属于S,与s相邻的s’不属于S。都有ws>ws’(山峰),或者ws<ws’(山谷)。
      你的任务是,对于给定的地图,求出山峰和山谷的数量,如果所有格子都有相同的高度,那么整个地图即是山峰,又是山谷输入样例:
5
5 7 8 3 1
5 5 7 6 6
6 6 6 2 8
5 7 2 5 8
7 1 0 1 7
输出样例:
3 3

图副:

|5| 7 8 3 |1|

|5 5| 7 6 6

6 6 6 2 8

7 2 5 8

7 1 |0| 1 7

注:斜体加粗的是山峰,||内的是山谷

先来理解一下题目的意思:

在这张图中,我们可以看到,3座山峰周围的高度都比山峰的高度要低,3座山谷周围的高度都比山谷的高度要高

山峰与山谷的判定都是八个方向的(即上下左右、左上左下、右上右下)

理解题目的意思后是不是很简单!!!

#include<bits/stdc++.h>
using namespace std;
int ans1,ans2;
int n,now,mn,mx;
int a[1005][1005];
int xx[1000005],yy[1000005];
int dx[8]={1,1,1,-1,-1,-1,0,0},dy[8]={1,0,-1,1,0,-1,1,-1};//方向数组 
bool f[1005][1005];
void bfs(int x,int y)
{
    int head=0,tail=1;
    xx[0]=x;yy[0]=y;
    while(head!=tail)
    {
        int x=xx[head],y=yy[head];
        head++;
        for(int i=0;i<8;i++)//搜索8个方向 
        {
            int nowx=x+dx[i],nowy=y+dy[i];//访问其中一个方向 
            if(nowx<1||nowy<1||nowx>n||nowy>n) continue;//判断是否越界 
            mx=max(mx,a[nowx][nowy]);
            mn=min(mn,a[nowx][nowy]);
            if(a[nowx][nowy]==now&&!f[nowx][nowy])//若可能属于同一个山峰或山谷 
            {
                f[nowx][nowy]=1;
                xx[tail]=nowx;
                yy[tail]=nowy;
                tail++;
            }
        }
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
     for(int j=1;j<=n;j++)
      scanf("%d",&a[i][j]);
    for(int i=1;i<=n;i++)
     for(int j=1;j<=n;j++)
      if(!f[i][j])
      {
         now=a[i][j];
         mn=now;
         mx=now;
         bfs(i,j);
         if(mn<=now&&mx<=now) ans1++;
         if(mn>=now&&mx>=now) ans2++;
      }
    printf("%d %d\n",ans1,ans2);
    return 0; 
}

那么这题就算过了哦

#282青铜莲花池请参考我的博客,专门有一篇的


<后记>

突然就有种为自己骄傲的感觉啊!!

这篇文章虽然没hzk大佬的大列表长,但字数大概是比他多了吧!

胡cingger你先别急着卧槽,我很不容易的,PPT上的不准复制代码,我都自己写的(还有用myc的)好不好,为了复制到字我多不容易

完结了!!!

猜你喜欢

转载自blog.csdn.net/qq_40900472/article/details/81025213