飘云阁

 找回密码
 加入我们

QQ登录

只需一步,快速开始

查看: 9626|回复: 13

《算法三部曲》 之 搜索算法(中篇)——首发于飘云阁

[复制链接]

该用户从未签到

发表于 2011-2-8 12:28:59 | 显示全部楼层 |阅读模式
本帖最后由 Knuth学徒 于 2011-2-10 10:45 编辑

注:此贴首发于飘云阁

Content 目录:

搜索算法(上篇):https://www.chinapyg.com/viewthread.php?tid=61759
搜索算法(下篇):https://www.chinapyg.com/viewthr ... &extra=page%3D1


        上一节里我介绍了 深度优先搜索算法,以及它在N皇后问题中发挥的作用,这一节我讲深入讲解,引入迷宫问题的例子,并且引出第二种搜索算法——广度优先搜索。
         并说明两种算法在用途上的不同。     
         
         这里先回顾一下我 之前讲的深度优先搜索: 首先,在做搜索题的时候,脑子里要建立一棵搜索树,就是说树中的任意一个节点都表示一个状态,然后每个状态它的子状态都可以确定的表示出来,所谓的子状态就是指一步就能够达到的下一个状态的集合。

         然后采用 树的先序遍历的 方法递归的遍历整棵树,直到找到表示目标状态的节点。(注意这里的先序遍历并不仅仅是二叉树了,也许每个节点都有N个子节点,但是也没关系,只要一个循环就可以)

        这里,我先引入迷宫问题: 给你一个 大小为 N*M 的迷宫地图,并且给你一个起点和终点,让你找到一条从起点通往终点的路径,并且输出这条路径,如果起点和终点不连通,则输出“不连通”

        按照搜索题的分析方法,我们先建立一棵搜索树:
     
      
根节点: 起点位置

        根节点的子节点: 起点位置的上下左右的位置(如果有墙挡住就不算)

        任意一个节点:  这个节点表示的位置

        任意一个节点的子节点: 该节点的上下左右的位置(如果有墙挡住就不算)

        目标状态节点:终点的位置


        搜索树是搜索问题的骨架,所以建立好之后就可以一直用了,这棵搜索树对于以后的广度优先搜索也有用,请务必仔细体会。

        接着的算法就是先序遍历这棵树了,但是我们用什么数据结构来实现它呢?    众所周知算法是建立在数据结构上的,一个好的算法需要有一个好的数据结构来实现。

        我这里采用的数据结构是:

        一个判断之前是否已经访问过的标记数组:
        bool  hash[N][M];

        以及一个矩阵来存储迷宫的地图:

        char map[N][M];           //如果是墙壁则 map = #,  如果是空地则map = '  '

        最后一个栈,用来保存路径
        struct node{
              int x,y;
        }
        于是,我们可以得到:对于起点位置,扩展它的四个方向位置,判断它们是墙还是空地,若是墙壁则不搜索。     如果是空地,则判断该位置以前是否已经访问过,如果访问过则不搜索,反之搜索这个子节点。


        实现代码:
  1. #include<cstdio>
  2. #include<stack>
  3. #include<cstring>
  4. #include<cstdlib>
  5. using namespace std;

  6. #define M  8
  7. #define N  8

  8. bool  hash[N][M];
  9. char  map[N][M];  
  10. struct Node{
  11.       int x,y;         //节点的横纵坐标
  12. };

  13. int EndX,EndY;  //定义终点坐标为全局变量

  14. stack<Node> s;   //保存路径的栈

  15. const int direction[4][2] = {(0,-1) , (0, 1) , (-1, 0), (1, 0 )} // 四个方向的数组,分别为"上下左右"

  16. bool  is_legal( int x , int y )  //判断位置是否越界或者是否是墙壁
  17. {
  18.       if( x < 0 || x >= N || y < 0 || y >= M) //越界
  19.       {
  20.              return false;
  21.       }
  22.       else if( map[x][y] == '#' ){   //墙壁
  23.              return false;
  24.       }
  25.       else
  26.       {
  27.              return true;
  28.       }
  29. }

  30. bool  is_end( int x , int y , int endX, int endY ) //判断是否终点
  31. {
  32.        return ( (x == endX) && (y == endY) );
  33. }

  34. void input(){      //输入迷宫地图
  35.        for(int i = 0; i < N ; ++i )
  36.             for(int j = 0; j < M ; ++j){
  37.                  scanf("%c", &map[i][j]);
  38.             }
  39. }

  40. bool  Depth_First_Search( int x , int y )  
  41. //深度优先搜索,如果能够找到路径返回 true,并且把路径保存在栈中。
  42. {
  43.        if( is_end ( x , y , EndX, EndY) ) //终点
  44.        {
  45.               Node tmp;
  46.               tmp.x = x;  tmp.y = y;
  47.               s.push(tmp);    //保存该位置作为路径
  48.               return true;
  49.        }
  50.        if(hash[x][y]) { return false; }

  51.        hash [x][y] = true;  
  52.        bool flag(false);
  53.        for(int k = 0; k < 4 ; ++k )  //扩展4个方向
  54.        {
  55.                int tempX = x + dirction[k][0];      
  56.                int tempY = y + dirction[k][1];

  57.                if( is_legal ( tempX, tempY) )  //该子节点位置合法
  58.                {
  59.                        flag = flag || Depth_First_Search(tempX, tempY);  
  60.                        
  61.                        if(flag){     //如果该子节点能够连到出口
  62.                                Node temp;
  63.                                temp.x = x;  temp.y = y;
  64.                                s.push(temp);    //将该父节点压栈作为路径保存
  65.                                return true;
  66.                        }
  67.                }
  68.         }
  69.         if(!flag){                  
  70.         //如果该位置的四个位置都不连终点,那么该位置也不连终点
  71.                return false;
  72.         }
  73. }

  74. void output()
  75. {
  76.         printf("入口->");
  77.         while(!s.empty())
  78.         {
  79.                 Node node = s.top();
  80.                 s.pop();
  81.                 printf("( %d , %d ) -> ", node.x , node.y );
  82.          }
  83.          printf("出口\n");
  84. }

  85. int main()
  86. {
  87.         int StartX,StartY;
  88.         input();
  89.         memset(hash,false,sizeof(hash));
  90.         printf("再输入起点坐标\n");
  91.         scanf("%d %d", &StartX,&StartY);
  92.         printf("再输入终点坐标\n");
  93.         scanf("%d %d",&EndX, &EndY);
  94.         if(Depth_First_Search(StartX,StartY))
  95.         //判断起点能否连通到终点,能连通就输出路径
  96.         {
  97.               output();
  98.         }
  99.         else
  100.         {
  101.               printf("不连通\n");
  102.         }
  103.         system("pause");
  104.         return 0;
  105. }
复制代码
以上的代码注释算是详细了,如果要理解深搜的话,建议仔细体味。

      这样,迷宫问题的路径问题就解决了。   
      深度优先搜索介绍到此为止。

/////////////////////////分割线///////////////////////////

      广度优先搜索开始:

            介绍广搜,还是引用迷宫问题的例子,只是这下我改变问法:
            前提条件还是不变,我这次改问,从起点到终点至少要走多少步,并输出最小步数。

            对比深搜,这次问的是最少步数,我们用深搜只求出了一条路径,但是这条路径的步数并不是最小的,那么我们如果不借助广搜的方法,是否要用深搜把所有路径全部求出来,然后取一个步数最小值,显然这个耗时非常的多。

            那么就要用到 基于 “层次遍历” 的广度优先搜索。

            讲解广搜,还是得用到我们先前建立的 搜索树,不知道有多少朋友还记得,不记得的话可以往上翻,这里就不再鳌述了。

            因为广搜是层次遍历,所以根据搜索树的性质,我们可以发现,广搜最先访问的是 起点,然后把起点周围的位置搜索一圈,然后把起点周围第2层再搜索一边,接着再搜索起点周围的第3层。。。。逐层展开。

           这样,由于是逐层展开,所以第一次搜索到终点的层数,就是最少的步数!~

           如果熟悉二叉树遍历的话,我们知道,层次遍历需要借助一个数据结构 ——“队列”,关于队列的详细介绍,我也在飘云阁写过,见链接:https://www.chinapyg.com/viewthr ... &extra=page%3D1

           那么,这里开个头,关于广搜的具体算法,将在 搜索算法(下篇)深入讨论。

//////////////////////////////后记////////////////////////////////////////////////

           我在写算法三部曲的第一部曲时,发现帖子变成热门帖子一类了,感谢大家的阅读,我旨在传播算法的重要性,以及一些基本算法,我尽可能的把算法抽象成模型,帮助大家理解。

           我相信面试过大公司的朋友,一定会发现,那些IT公司对于应聘者的算法功底的要求非常高,经常会出一些算法的问题,所以我就因此打算写“算法三部曲”,同时也希望大家能够借此提高算法功底。

           转载请注明出处。
           谢谢
//////////////////////////////////////////////////////////////////////////////////////////////////////////

评分

参与人数 2威望 +48 飘云币 +48 收起 理由
whypro + 8 + 8 PYG有你更精彩!
MOV + 40 + 40 很好继续噢

查看全部评分

PYG19周年生日快乐!

该用户从未签到

 楼主| 发表于 2011-2-8 12:31:18 | 显示全部楼层
自己先顶一下。。。
PYG19周年生日快乐!

该用户从未签到

发表于 2011-2-8 22:05:51 | 显示全部楼层
拜读了,代码还没读明白/:017
PYG19周年生日快乐!

该用户从未签到

 楼主| 发表于 2011-2-8 22:15:58 | 显示全部楼层
回复 3# tianfire


    /:014  其实如果完全理解搜索树的想法和 递归的思想的话,应该可以看到一部分了。

     当然如果以前没写过这些,一下子要全看懂也许有点困难。
PYG19周年生日快乐!

该用户从未签到

 楼主| 发表于 2011-2-10 10:40:54 | 显示全部楼层
将 搜索(上)(中)(下)放在一起,免得人家看了上忘了下
PYG19周年生日快乐!
  • TA的每日心情
    郁闷
    2019-4-14 18:28
  • 签到天数: 5 天

    [LV.2]偶尔看看I

    发表于 2011-2-11 09:25:04 | 显示全部楼层
    学习了。谢谢
    PYG19周年生日快乐!
  • TA的每日心情
    郁闷
    2015-10-3 22:28
  • 签到天数: 1 天

    [LV.1]初来乍到

    发表于 2011-2-11 14:01:46 | 显示全部楼层
    学习下.....
    PYG19周年生日快乐!
  • TA的每日心情
    慵懒
    2019-3-12 17:25
  • 签到天数: 3 天

    [LV.2]偶尔看看I

    发表于 2011-2-11 18:44:02 | 显示全部楼层
    过来膜拜一下,算法的力量。
    PYG19周年生日快乐!

    该用户从未签到

    发表于 2011-2-13 18:14:00 | 显示全部楼层
    看不懂 楼主牛/:good
    PYG19周年生日快乐!
  • TA的每日心情
    无聊
    2024-1-11 16:32
  • 签到天数: 5 天

    [LV.2]偶尔看看I

    发表于 2011-3-10 15:49:15 | 显示全部楼层
    我真的没有看明白 很是郁闷
    PYG19周年生日快乐!
    您需要登录后才可以回帖 登录 | 加入我们

    本版积分规则

    快速回复 返回顶部 返回列表