今天做笔试题遇到两个很有代表性的题目,分别用到了广度优先搜索和深度优先搜索,可以记录并分析一下。
广度优先搜索 首先来看一下题目:题目描述
定义一个二维数组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)
从題意来看的话因为要搜索的是最短路径,所有应该是用广度优先搜索算法没跑了。 广度优先搜索(Breadth First Search, BFS),又叫宽度优先搜索,是一种穷举的搜索算法,算法把所有展开的节点放入一个先进先出的队列中,依次搜索解,因此,如果算法有解的话,一定是最优解或最优解之一。BFS常用队列或链表实现。
直接上代码:#include <iostream>
#include <list>
#include <vector>
using namespace std ;
typedef struct node
{
int x;
int y;
int id;
int patent;
}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();
list <node> open = { node_table[id] };
id++;
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.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 ));
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 ;
}
可以看到代表并不是很复杂,BFS算法都在BFS这个函数中,而node这个结构主要是用来记录路径用的,便于找到路径之后通过递归展开输出路径。BFS函数中的思路也比较清晰,不断从open表头出去节点,并把扩展的子节点加入到open表尾部,即先进先出的思想。 由于BFS中已经被访问过的节点如果第二次被访问,那么第二次访问时候的路径长度必然大于(或等于)第一次访问时的路径长度,因此就第二次访问的时候就不需要考虑该节点了,所以我们只需要记录被访问过的节点,而不用撤销访问(和DFS有所区别)。
深度优先搜索 如果说广度优先搜索适合搜索最优解,那么深度优先搜索就是适合搜索是否存在解。还是先来看问题:请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。
路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。
如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。
例如:
abce
sfcs
adee
矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,
因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
从题意可以看出,需要找到是否包含该路径,也就是找到是否存在解,所以此处用深度优先搜索比较合适。深度优先搜索展开子节点后把子节点加入到open表的头部,因此适合用递归来实现。
直接上代码:#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;
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 ;
}
可以看到,DFS的代码和BFS的代码十分类似。说一下区别:1. DFS采用递归来实现; 2. 访问表在进入函数之间置位,而在退出函数之后需要复位。原因是因为DFS在访问的时候有一个回溯的过程,如果子节点没有得到需要的解,那么就需要回溯的父节点,并扩展父节点的其他子节点来搜索解,因此原来被子节点访问过的位置现在应该撤销访问。
总结 BFS和DFS是两种十分重要的搜索算法,BFS适合查找最优解,DFS适合查找是否存在解(或者说能找到任意一个可行解)。