深度优先搜索与广度优先搜索

mevincent 6年前
   <p>今天做笔试题遇到两个很有代表性的题目,分别用到了广度优先搜索和深度优先搜索,可以记录并分析一下。</p>    <h2>广度优先搜索</h2>    <p>首先来看一下题目:</p>    <pre>  <code class="language-cpp">题目描述  定义一个二维数组N*M(其中2<=N<=10;2<=M<=10),如5 × 5数组下所示:   int maze[5][5] = {            0, 1, 0, 0, 0,            0, 1, 0, 1, 0,            0, 0, 0, 0, 0,            0, 1, 1, 1, 0,            0, 0, 0, 1, 0,    };  它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,  不能斜着走,要求编程序找出从左上角到右下角的最短路线。  入口点为[0,0],既第一空格是可以走的路。  Input  一个N × M的二维数组,表示一个迷宫。数据保证有唯一解,不考虑有多解的情况,即迷宫只有一条通道。  Output  左上角到右下角的最短路径,格式如样例所示。  Sample Input  0 1 0 0 0  0 1 0 1 0  0 0 0 0 0  0 1 1 1 0  0 0 0 1 0  Sample Output  (0, 0)  (1, 0)  (2, 0)  (2, 1)  (2, 2)  (2, 3)  (2, 4)  (3, 4)  (4, 4)  </code></pre>    <p>从題意来看的话因为要搜索的是最短路径,所有应该是用广度优先搜索算法没跑了。</p>    <p>广度优先搜索(Breadth First Search, BFS),又叫宽度优先搜索,是一种穷举的搜索算法,算法把所有展开的节点放入一个先进先出的队列中,依次搜索解,因此,如果算法有解的话,一定是最优解或最优解之一。BFS常用队列或链表实现。</p>    <p>直接上代码:</p>    <pre>  <code class="language-cpp">#include <iostream>  #include <list>  #include <vector>  using namespace std;    typedef struct node  {   int x;   int y;   int id; // 节点ID   int patent; // 父节点ID  }node;    inline bool isvalid(int x, int y, int m, int n)  {   if (x >= 0 && x < m &&    y >= 0 && y < n)    return true;   return false;  }    int BFS(vector<vector<int>> &maze, vector<node> &node_table, int start_x, int start_y, int end_x, int end_y)  {   // 基本思想,广度优先,找最短路径   int m, n, id = 0,    new_x, new_y;   n = maze.size();   m = maze[0].size();   // open table   list<node> open = { node_table[id] };   id++;   // close table   vector<vector<bool>> visited(n, vector<bool>(m, false));   visited[0][0] = true;   // 四个方向   vector<pair<int, int>> dir = { { -1, 0 },{ 0, -1 },{ 1, 0 },{ 0, 1 } };   // 广度优先   while (!open.empty())   {    node cur = open.front(); // 每次从open表取出一个节点    open.pop_front();    // 已经找到    if (cur.x == end_x && cur.y == end_y) return cur.id;    // 四个方向扩展子节点    for (int i = 0; i < 4; ++i)    {     new_x = cur.x + dir[i].first;     new_y = cur.y + dir[i].second;     if (isvalid(new_x, new_y, n, m) && maze[new_x][new_y] != 1 && !visited[new_x][new_y])     {      node &tmp = node_table[id];      tmp.x = new_x;      tmp.y = new_y;      tmp.id = id;      tmp.patent = cur.id;      open.push_back(tmp);      visited[new_x][new_y] = true;      id++;     }    }   }   return -1;  }    // 递归输出路径  void print_path(vector<node> &node_table, int id)  {   if (node_table[id].patent != -1)    print_path(node_table, node_table[id].patent);       cout << "(" << node_table[id].x << "," << node_table[id].y << ")" << endl;  }    int main(void)  {   int n, m, id;   while (cin >> n >> m)   {    vector<vector<int>> maze(n, vector<int>(m, 0));    // node 表    vector<node> node_table(m * n, { 0, 0, 0, -1 });    for (int i = 0; i < n; ++i)    {     for (int j = 0; j < m; ++j)     {      cin >> maze[i][j];     }    }    id = BFS(maze, node_table, 0, 0, n - 1, m - 1);    print_path(node_table, id);   }   return 0;  }  </code></pre>    <p>可以看到代表并不是很复杂,BFS算法都在BFS这个函数中,而node这个结构主要是用来记录路径用的,便于找到路径之后通过递归展开输出路径。BFS函数中的思路也比较清晰,不断从open表头出去节点,并把扩展的子节点加入到open表尾部,即先进先出的思想。</p>    <p>由于BFS中已经被访问过的节点如果第二次被访问,那么第二次访问时候的路径长度必然大于(或等于)第一次访问时的路径长度,因此就第二次访问的时候就不需要考虑该节点了,所以我们只需要记录被访问过的节点,而不用撤销访问(和DFS有所区别)。</p>    <h2>深度优先搜索</h2>    <p>如果说广度优先搜索适合搜索最优解,那么深度优先搜索就是适合搜索是否存在解。还是先来看问题:</p>    <pre>  <code class="language-cpp">请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。  路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。  如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。   例如:  abce  sfcs  adee   矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,  因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。  </code></pre>    <p>从题意可以看出,需要找到是否包含该路径,也就是找到是否存在解,所以此处用深度优先搜索比较合适。深度优先搜索展开子节点后把子节点加入到open表的头部,因此适合用递归来实现。</p>    <p>直接上代码:</p>    <pre>  <code class="language-cpp">#include <iostream>  #include <list>  #include <vector>  using namespace std;    class Solution {  public:   bool hasPath(char* matrix, int rows, int cols, char* str)   {    this->matrix = matrix;    this->str = str;    this->rows = rows;    this->cols = cols;    // visited    vector<vector<bool>> visited(rows, vector<bool>(cols, false));    // 寻找迷宫起点    for (int i = 0; i < rows * cols; ++i)    {     if (matrix[i] == str[0])     {      visited[i / cols][i % cols] = true;      if (DFS(i / cols, i % cols, 0, visited)) return true;      visited[i / cols][i % cols] = false;     }    }    return false;   }     bool isvalid(int x, int y)   {    if (x >= 0 && x < rows && y >= 0 && y < cols)     return true;    return false;   }     bool DFS(int x, int y, int str_index, vector<vector<bool>> &visited)   {    // 四个方向    static pair<int, int> dir[] = { {-1, 0}, {0, -1}, {1, 0}, {0, 1} };    int new_x, new_y;      if (str[str_index] == matrix[x * cols + y])    {     if (str[str_index + 1] == '\0') return true;     for (int i = 0; i < 4; ++i)     {      new_x = x + dir[i].first;      new_y = y + dir[i].second;      if (isvalid(new_x, new_y) && !visited[new_x][new_y])      {       visited[new_x][new_y] = true;       if (DFS(new_x, new_y, str_index + 1, visited))        return true;       visited[new_x][new_y] = false;      }     }    }    return false;   }  private:   int rows;   int cols;   char *matrix;   char *str;  };    int main(void)  {   Solution sss;   cout << sss.hasPath("abcesfcsadee", 3, 4, "abcd");   return 0;  }  </code></pre>    <p>可以看到,DFS的代码和BFS的代码十分类似。说一下区别:1. DFS采用递归来实现; 2. 访问表在进入函数之间置位,而在退出函数之后需要复位。原因是因为DFS在访问的时候有一个回溯的过程,如果子节点没有得到需要的解,那么就需要回溯的父节点,并扩展父节点的其他子节点来搜索解,因此原来被子节点访问过的位置现在应该撤销访问。</p>    <h2>总结</h2>    <p>BFS和DFS是两种十分重要的搜索算法,BFS适合查找最优解,DFS适合查找是否存在解(或者说能找到任意一个可行解)。</p>    <p> </p>    <p>来自:http://yosef-gao.github.io/2016/08/06/bfs-and-dfs/</p>    <p> </p>