• 《啊哈算法》——搜索


      这篇文章我们将通过一些实例来初步理解两种搜索算法:dfs、bfs。

      按照惯例,我们依然首先给出一个具体问题来导入:给出一个n x m的矩阵迷宫map,人在迷宫上的某个点map[i][j],可以上下左右移动,但是一些点标记为不可经过。那么现在给出起点和终点的矩阵下的坐标,我们能否找到一条起点到终点的路径?需要移动多少步?

      深度优先搜索:

      我们先通过之前一个我们曾经见到过的较为简单的“生成全排列”问题来初步认识一下dfs的思想。

      我们将问题更加的形象化,即我们假设生成全排列是一个将1~n个数字放到A~N个桶里的过程,这里以n=4为例吧,假设一次我们放数字的过程得到了2143这个全排列,在将3放入D盒中,我们期望能够继续生成其余的全排列,于是我们将3从D中拿出来,看有没有其余选择,发现并没有得到另外一种全排列的可能性,那么我们再将4从C盒中取出,这时,我们便可以将3放入C中,4放入D中来得到一个新的全排列。

      上面其实描述了一个简单的回溯过程,我们更加抽象的来理解dfs过程,即它将一个问题分成n个步骤,第i个步骤有a[i]个选择,那么我们在完成第i个步骤时,先任选a[i]中的一个,随后开始第i+1个步骤,一直到第n个步骤,我们枚举玩完第n个步骤的a[n]个方法后,开始向上回溯,即我们在进行第n-1个步骤有a[n-1]-1个没有遍历到的方法当中的一个,然后再进行第n个步骤,枚举a[n]中方法,很显然,这种算法能够搜索到完成这个问题所采取的n个步骤的不同方法组合的所有情况。

      那么针对生成全排列,我们尝试用dfs的思路进行重写。

      简单的参考代码如下。

    #include<cstdio>
    using namespace std;
    int a[10] , book[10] , n;
    
    void dfs(int step)//给第step箱子填充数字
    {
         int i;
         if(step == n + 1)//生成了一种全排列,输出结果
         {
             for(i = 1;i <= n;i++)
                  printf("%d",a[i]);
    
             printf("
    ");
                return;
         }
    
          for(i = 1;i <= n;i++)//当前情况的方法
          {
                if(book[i] == 0)
                {
                      a[step] = i;//选择一种方法
                      book[i] = 1;//标记这个数字已经使用过
    
                      dfs(step + 1);//进行深度优先,即开始给第step + 1个箱子填数
                      book[i] = 0;//回溯到填充第step箱子的步骤,清除标记
                }
          }
            return;
    
    }
    
    int main()
    {
         scanf("%d",&n);
         dfs(1);
    }

      那么基于这层铺垫,我们来看一看如何用dfs处理我们在文章一开始提出的问题。

      我们从起点开始,每一步其实有四个选择,即上、下、左、右,当然这里排除走出边界的、有障碍物的情况,假设这里我们从map[sx][sy]开始,(map是用于储存图的邻接矩阵)每当走了一步,假设我们向右走了一步,到达map[i][j+1],我们深度优先,继续从map[i][j+1]开始继续往下走...直到走到某个点map[i'][j'],发现四处无路可走,那么便开始回溯到路径中上一个点,去走那些没有走过的方向。这里需要注意的一个问题是,我们从map[i][j]走到了map[i][j+1],那么再从map[i][j+1]开始走的时候,会面临又回到map[i][j]的情况,这里只需在深搜的时候标记已经走过的点便可以轻松处理了。

      简单的参考代码如下。

    #include<cstdio>
    using namespace std;
    int n,m,p,q,Min = 9999999;
    int a[51][51],book[51][51];
    void dfs(int x,int y,int step)
    {
         int next[4][2] = {{0,1},{1,0},{0,-1},{-1,0}};
         int tx,ty,k;
         if(x == p && y == q)
         {
              if(step < Min)
                   Min = step;
              return;
         }
    
         for(k=0;k<=3;k++)//四个方向
         {
             tx=x+next[k][0];
             ty=y+next[k][1];
    
             if(tx < 1 || tx>n ||ty<1||ty>m)//越界
                  continue;
             if(a[tx][ty]==0&&book[tx][ty] == 0)
             {
                  book[tx][ty] = 1;  //标记访问过
                  dfs(tx,ty,step+1);//深度优先的搜索
                  book[tx][ty] = 0;
             }
         }
    
         return;
    }
    
    int main()
    {
         int i , j , startx , starty;
         scanf("%d%d",&n,&m);
         for(i = 1;i <= n;i++)
              for(j=1;j<=m;j++)
                scanf("%d",&a[i][j]);
    
         scanf("%d %d %d %d",&startx,&starty,&p,&q);
    
         book[startx][starty] = 1;
         dfs(startx,starty,0);
    
         printf("%d",Min);
    }

      宽度优先搜索:

      其实通过前面对深度优先搜索的介绍,这里我们对比来看就很容易理解宽度优先搜索的思路。

      简单的说,假设我们完成搜索需要进行n个步骤,记第i个步骤有a[i]个方法。

      深搜给出的思路是,先使用a[i]给出的完成第i个步骤的一个方法,然后紧接着去完成第i+1个步骤,对于那些没有用到的方法,深搜在搜到底部之后没有其余方法后,会回溯回来再重新a[i]中的其余方法,以此来构造出新的方法。

      而宽搜给出的思路是,完成第i个步骤,即将a[i]种方法全部列出来,这显示出当前(进行i个步骤)的所有情况,然后进行第i+1个步骤,采用相同的策略。一直到第n个步骤,宽搜将列举出所有的可能情况,然后完成搜索。

      那么我们现在面临的一个问题,如何编码来实现宽搜的这一系列操作呢?我们可以看到,我们在完成对第i个步骤的a[i]种情况的列举之后,之前i-1个步骤所出现的情况似乎已经与我们没有关系了,因为我们在进行第i+1个步骤的时候,仅仅需要基于完成第i个步骤后所有结果即可。想一想,以这种尾部添加元素,头部删除元素的操作......对,就是我们在前面文章曾经介绍过的队列结构。

      基于这种宽搜过程,我们只需判断第j个步骤之后,是否出现我们想要的结果。可以看到,每宽搜一次,都要删除头部元素,可能添加尾部元素。而当头部元素一直删除而尾部却没有添加多少,即队列为空的时候,依然没有出现我们想要的结果,那么这显然表明我们在图中找不到这样一个从起点到达终点的路径。

      基于上面的算法分析,我们进行简单的编程实现。

    #include<cstdio>
    using namespace std;
    
    struct note
    {
        int x;
        int y;
        int f;
        int s;
    };
    
    int main()
    {
         struct note que[2501];
         int a[51][51] = {0},book[51][51] = {0};
    
         int next[4][2] = {{0,1},     //四个方向
                           {1,0},
                           {0,-1},
                           {-1,0}};
         int head , tail;
         int i , j , k , n , m , startx , starty , p , q , tx , ty , flag;
    
         scanf("%d %d",&n,&m);
         for(i = 1;i <= n;i++)//读入map
              for(j = 1;j <= m;j++)
                  scanf("%d",&a[i][j]);
    
         scanf("%d%d%d%d",&startx,&starty,&p,&q);
    
          head = 1;
          tail = 1;//初始化队列
          que[tail].x = startx;
          que[tail].y = starty;
          que[tail].f = 0;
          que[tail].s = 0;
           tail++;
           book[startx][starty] = 1;
          while(head < tail) //bfd算法核心部分
          {
                 for(k = 0;k <= 3;k++)
                 {
                     tx = que[head].x + next[k][0];
                     ty = que[head].y + next[k][1];
    
                     if(tx < 1 || tx > n || ty < 1 || ty > m)
                           continue;
                     if(a[tx][ty] == 0 && book[tx][ty] == 0)//入队操作
                     {
                          book[i][j] = 1;
                          que[tail].x = tx;
                          que[tail].y = ty;
                          que[tail].f = head;
                          que[tail].s = que[head].s + 1;
                          tail++;
                     }
                   if(tx == p && ty == q)
                      {
                        flag = 1;
                        break;
                      }
                 }
    
    
    
               if(flag == 1) 
                   break;
               head++;   //弹出队首元素
          }
    
          printf("%d",que[tail-1].s);
    
          return 0;
    }
  • 相关阅读:
    hdu2818 Building Block
    struct2面试准备
    Spring mvc 面试
    Spring 面试详解
    Java面试必备Springioc上
    redis高级命令4 持久化机制 、事务
    redis高级命令3哨兵模式
    redis高级命令2
    redis高级命令1
    redis基础二----操作set数据类型
  • 原文地址:https://www.cnblogs.com/rhythmic/p/5520036.html
Copyright © 2020-2023  润新知