关于深度优先搜索与宽/广度优先搜索

在解决一些较复杂的问题时候,只会一些很简单的算法如:贪心,简单枚举,模拟,分治...是远远不够的,还需要了解一些除此之外的算法,这篇文章将带你了解搜索基础:dfs(下面简称深搜)与bfs(下面简称广搜)。

什么是深度优先搜索与宽/广度优先搜索

深搜和广搜都是以一定的顺序遍历整张图的算法,算法上的搜索主要包括两类:深搜与广搜,狭义的深搜还包括 A*(A Star) 和 IDA* ,深搜的搜索思想是从一个结点开始不断下探,直到无路可走在进行回溯,而广搜则是将此层遍历完后再搜索先一层,例如下面这棵树:

s

使用深搜的遍历顺序是 1-2-5-3-6-7-4
而使用广搜则是 1-2-3-4-5-6-7

深度优先搜索

深度优先搜索本质是枚举的一种,和普通枚举的区别是普通枚举有明确的层次,而搜索则没有这一点,因此需要用递归实现,深搜的模板如下:

void dfs(需要的数据)
{
    if(符合条件)//剪枝
    {
        return;
    }
    if(找到了想要的数据)
    {
        处理数据
        return;
    }
    for(遍历所有情况)
    {
        if(符合条件)
        {
            标记已搜索
            dfs(...) //继续搜索
            回溯
         }
    }
例题: 骑士的移动

题目描述:

输入8*8的国际象棋棋盘上的2个格子(列:a-h,行:1-8),求马至少多少步从起点(键盘输入的第一个位置)跳到终点(键盘输入的第二个位置)。

解析:

从题目中可以看出是棋盘问题,棋盘类的题目一般用搜索或者动态规划来解决,这一题则应使用搜索来解决,但最短路问题最好使用bfs,但用dfs更能体现简直所在。

首先需要解决输入的为字符串的问题,字母可以使用: 字母 -'a'+1 的方式转换为数字,数字则用: 数字-'0' 来解决。

接着套上模板写出代码:

错误代码示范(超时)

#include<bits/stdc++.h>
using namespace std;
string a,b;
int ans=1000000,ax,ay,bx,by;
int ne[8][2]={{-2,1},{-1,2},{1,2},{2,1},{2,-1},{1,-2},{-1,-2},{-2,1}};
int vis[25][25]={0};
void dfs(int x,int y,int n)
{
	if(x==bx&&y==by)
	{
		ans=min(ans,n);
		return;
	}
	for(int i =0;i<8;i++)
	{
		int nx=x+ne[i][0];
		int ny=y+ne[i][1];
		if(nx>=1&&nx<=8&&ny>=1&&ny<=8&&vis[nx][ny]==0)
		{
			vis[nx][ny]=1;
			dfs(nx,ny,n+1);
			vis[nx][ny]=0; 
		}
	}
}
int main()
{
	while(cin>>a>>b) 
	{
		memset(vis,0,sizeof(vis));
		ans=10000000;
		ax=a[0]-'a'+1;
		ay=a[1]-'0';
		bx=b[0]-'a'+1;
		by=b[1]-'0';
        dfs(ax,ay,0);
        cout<<"To get from "<<a<<" to "<<b<<" takes "<<ans<<" knight moves."<<endl;

	}
	return 0;
}

超时原因是因为这一题用dfs时间复杂度太高,优化不到位,可以这样解决:如果当前找到的答案大于已找到的答案则直接返回:

正确代码示例:

#include<bits/stdc++.h>
using namespace std;
string a,b;
int ans=1000000,ax,ay,bx,by;
int ne[8][2]={{-2,1},{-1,2},{1,2},{2,1},{2,-1},{1,-2},{-1,-2},{-2,-1}};
int vis[25][25]={0};
void dfs(int x,int y,int n)
{
	if(x>=1&&y<=8&&y>=1&&y<=8&&n<ans)
	{
		if(x==bx&&y==by)
		{
			ans=n;
		}
		
		for(int i =0;i<8;i++)
	    {
		    int nx=x+ne[i][0];
	    	int ny=y+ne[i][1];
		    if(nx>=1&&nx<=8&&ny>=1&&ny<=8&&vis[nx][ny]==0)
	    	{
			    vis[nx][ny]=1;
			    dfs(nx,ny,n+1);
			    vis[nx][ny]=0; 
		    }
	    }
	}
	
}
int main()
{
	while(cin>>a>>b) 
	{
		memset(vis,0,sizeof(vis));
		ans=10000000;
		ay=a[0]-'a'+1;
		ax=a[1]-'0';
		by=b[0]-'a'+1;
		bx=b[1]-'0';
		
        dfs(ax,ay,0);
        cout<<"To get from "<<a<<" to "<<b<<" takes "<<ans<<" knight moves."<<endl;
	}
	return 0;
}

完美AC!

广度优先搜索

一般涉及到最单路问题是,使用bfs的效率远高于dfs,因为广度优先搜索是一层一层的遍历,所以不需要回溯。
bfs靠队列实现,其原理是,遍历这一层的所有点,当符合条件时,入队将数据存储起来.

什么是队列

队列是 STL 中一种先进先出的数据结构 ,使用时应带上头文件<queue>.
队列主要有以下操作:
1.建立队列: queue<数据类型> 队列名,如 queue<int> q;
2.获取队首元素 : 队列名.front(),如 q.front();
3.弹出对手元素:队列名.pop(),如 q.pop();
4.判断队列是否为空:队列名.empty(),如 q.empty();

下面是bfs的模板:

void dfs(int x)
{
    queue<int> q;
    q.push(x);
    int u;
    标记起始位置已访问
    while(!q.empty())
    {
        u=q,front();
        q.pop();
        处理数据
        for(遍历所有情况)
        {
            if(符合条件)
            {
                入队
                标记已访问
            }
        }
    }
}

例题:填涂颜色

题目描述:

由数字 0 组成的方阵中,有一任意形状闭合圈,闭合圈由数字 1 构成,围圈时只走上下左右 4 个方向。现要求把闭合圈内的所有空间都填写成 2

解析:

很明显的一道bfs 题目(也可以用dfs),只需要找到封闭的换里面的一个 0 然后进行 广搜 就可以了,但要怎么去找呢? 通过观察样例可以看出,按照从上到下,从左到右的顺序遍历矩阵,第一个 1 右下角的 0 就是环里面的数,这样一来代码就很容易实现了:

#include<bits/stdc++.h>
using namespace std;
int n,m[35][35],vis[35][35]={0},a[35][35];
int ne[4][2]={{1,0},{0,1},{-1,0},{0,-1}};
void bfs(int x,int y)
{
	queue<int> q;
	q.push(x);
	q.push(y);
	vis[x][y]=1;
    m[x][y]=2;
	int u,v; 
	while(!q.empty())
	{
		u=q.front();
		q.pop();
		v=q.front();
		q.pop();
		m[u][v]=2;
		for(int i = 0;i<=3;i++)
		{
			int a=u+ne[i][0];
			int b=v+ne[i][1]; 
			if(a>=1&&a<=n&&b>=1&&b<=n&&vis[a][b]==0&&m[a][b]==0)
			{
				q.push(a);
				q.push(b);
				vis[a][b]=1;
			}
		}
		
	}
}
int main()
{
	scanf("%d",&n);
	for(int i = 1;i<=n;i++)
	for(int j = 1;j<=n;j++)
	{
		cin>>m[i][j];
	}
	
	for(int i  = 1;i<=n;i++)
	{
		for(int j = 1;j<=n;j++)
	    {
		    if(m[i][j]==1)
		    {
			    bfs(i+1,j+1);
			    break;
		    }
	    }
	    break; 
	}
	
	for(int i = 1;i<=n;i++)
	{
		for(int j = 1;j<=n;j++)
	    {
		    cout<<m[i][j]<<" ";
	    }
	    cout<<endl;
	}
	
	return 0;
 } 
posted @ 2023-07-24 17:18  全角的!与半角的!  阅读(43)  评论(0编辑  收藏  举报