搜索算法-深搜与广搜

1、深搜与回溯法

*本文主要是供自己复习,或者做笔记总结使用,专业性有待考量,如果遇到不对的地方还请指出来。

什么是回溯法?枚举每一个填空的选项,然后判断这个选项是否合法。如果合法则继续填写下一个选项,然后继续,否则就不用枚举下去了,而是去尝试上一个填空的选项。这种算法通常被称之为回溯法,通常会使用深度优先搜索(DFS)来实现。

DFS(深度优先搜索),字如其意,以深度优先,不到头之前就不会回头,主要用于解决遍历及所有解的问题。在数据结构树中,DFS可用于查找目标结点,沿一条路径不断向下直到到头然后回溯,然后再沿另一条路径继续向下走然后会回溯,直到查找到目标结点。在图中与在树中也一样,沿着一条路不断走,走到头就掉头,直到把所有路都走完。DFS也不一定通过递归实现,也可以使用栈(stack)实现非递归DFS。在DFS的深度过高的情况下,选择非递归DFS要更好。

回溯算法(递归)一般形式:

/*
基本模型
void dfs(int step)
{
	if(所有空都填完了){
        判断最优解或者记录答案;
        return;
      {
    for(枚举所有的情况){

        if(该选项是合法的)
           记录下这个空;
           dfs(step+1);
           取消这个空
        }
}              
*/

基础经典例题:N皇后问题

对于一个N*N的棋盘,我们需要放置N个皇后棋,并且要保证这N个皇后不在同一行同一列,同一斜行,问一共有多少摆法。

(说是基础,不过我真觉得一开始学不好理解,当初学的时候累死了)

#include 
using namespace std;
const int N = 1000;
int n,res;
int a[N], b[N], c[N];//行,两个斜行,对于某一个点而言,
//其中一个斜行x+y固定,另一个行x-y固定;
void dfs(int step)
{
	if (step > n)
	{
		res++;
		return;
//因为棋子已经摆完了,所以回溯到上一种情况继续摆放。
	}

	for (int i = 1; i <= n; i++)
	{
		if (a[i] == 0 && b[step + i] == 0 && c[step - i + 20] == 0)
		{
			a[i] = 1; b[step + i] = 1; c[step - i + 20] = 1;
			dfs(step + 1);
			a[i] = 0; b[step + i] = 0; c[step - i + 20] = 0;
//如果没有满足上述情况的位置放置,则恢复现场。
		}
	}
}
int main()
{
	cin >> n;
	dfs(1);
	cout << res;
	return 0;
}

对于棋盘中,坐标为(x,y)的点而言,其其中一个斜行x-y的值是固定的,另一个斜行x+y的值是固定的,因而我们可以创建两个数组,当第(x,y)的位置放置了皇后之后,便在下标为x+y,及x-y的位置标记为1 ,表示这里已经不能再放其他的皇后棋子了。

但是对于x-y的值而言,其可能出现负数,所以在这里我加上了20,以保证其总是为正数。因为我这里是从第一行开始枚举的,所以这里只需要储存一个列的数组。

从第一行开始,这里的step表示第step行,i表示第i列。从第一行第一列开始搜索,如果后续无法再放置任何合法的棋子,或者棋子已经放完,则会回溯,继续进行搜索,直到所有情况枚举完毕。


广搜(BFS)

BFS(广度优先搜索),对于解决最短或最少问题特别有效。BFS从某个结点开始,访问距离自己状态最近的结点,直到把所有结点访问完毕。在图储存结构之中通过BFS查找目标结点,其通过某一个结点开始,寻找紧邻的、尚未访问的结点,直到查找到目标结点。而在树结构之中查找目标结点,其可简单理解为由根节点开始,一层一层的访问,直到找到目标结点,很符合BFS的另一个名称“宽度优先搜索”,毕竟一层一层的找嘛。

广搜一般形式如下。BFS可以使用queue来实现。

/*
   Q.push(初始状态);
   while(!Q.empty())
   {
     State  u  =Q.front();
	 Q.pop();
	 for(枚举所有的可能状态)
	    if(是合法的)
		   Q.push(v);
   }
*/

例题:节选自洛谷

P1443 马的遍历 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

BFS用于解决最短最少问题十分有效,本题采用BFS可以很轻松的解决。枚举马可到达的地方的坐标,并将其入列,然后使马到达的坐标(x,y)对应的数组ans[x][y]的数据在原来马所在的坐标(dx,dy)对应的数组ans[dx][dy]的基础上+1,如果该坐标不存在或者ans[x][y]!=-1已经被走过了,则直接continue跳过。最后当队列中为空后,ans数组中也就存储了最终的答案,最后按格式化输出即可。

#include 
using namespace std;
const int N = 1000;
int horse[8][2] = {
	{1,2},{2,1},{-1,2},{-2,1},{-1,-2},{-2,-1},{1,-2},{2,-1}
};
int ans[N][N];
queue> temp;
int main()
{
	memset(ans, -1, sizeof(ans));
	int n, m, dx, dy;
	cin >> n >> m >> dx >> dy;
	ans[dx][dy] = 0;
	temp.push({ dx,dy });
	while (!temp.empty()) 
	{
		pair Top = temp.front();//标记原点
		temp.pop();
		for (int i = 0; i < 8; i++) {
			int dx1 = Top.first + horse[i][0];
			int dy1 = Top.second + horse[i][1];
			if (dx1 < 1 || dx1 > n || dy1 < 1 || dy1 > m || ans[dx1][dy1] != -1)
				continue;
			int temp1 = ans[Top.first][Top.second];//继承原点的步数
			ans[dx1][dy1] = temp1 + 1;
			temp.push({ dx1,dy1 });
		}
	}
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
			printf("%-5d", ans[i][j]);
		printf("\n");
	}
	return 0;
}

总结

DFS通常用于解决所有解或者遍历之类的问题;

BFS通常用于解决最短或者最少问题。

你可能感兴趣的:(深度优先,算法,广度优先)