DFS(深度优先搜索)、BFS(广度优先搜索)

DFS 

dfs是一种思想,并不是一种固定的算法,它不仅仅只在图论的问题中出现。有些时候,一些非图论的题的问题也可以转化成dfs问题。要掌握dfs必须见许多的题。这里只以最简单的题目为例,阐述dfs的思想,以及给出例题的题解。

dfs的思想是什么?

dfs思想的重点在于回溯,与递归类似。它会先将某一条路走到穷尽,然后换另一条路走,当某一个节点的方向全部走完后,回溯到上一个节点,重复上述过程,直到满足条件或者穷尽所有可能的路径。相信初学者根本没看懂这到底说的什么意思。dfs用文字描述起来特别抽象,所以需要例题和图示。

提醒一下,下面将要讲的例子只不过是一种很简单的dfs,弄懂了例题并不代表弄懂了dfs。只能说是学会dfs的基本思想。想要深入弄懂dfs,必须见各种题,慢慢来吧,实际上我现在遇到的dfs题也远远不够,dfs需要长期实践才能彻底参透。

直接以例题为切入点开始。

例题-棋盘问题

原本是想用八皇后作为例题,但是hduoj不对外开放了,所以换成了棋盘问题,这两个例题都是典型的dfs例题。

 题目连接: 1321 -- 棋盘问题 (poj.org)

题目描述:

 题目大意:. 的地方不能放棋子,#的地方必须保证同行同列只能有一个棋子,问摆放k个棋子的方案个数。

例如:假设现有以下3*3棋盘:

要求放入3个棋子,问有几种方案?

对于这道题,每个格子只有放或者不放两种可能。所以一次回溯即回到该格子没有放置棋子的状态。然后在该状态下寻找其他满足条件的格子放置棋子。如果这一行上所有满足条件的格子都放置过了,则再次回溯到上一行。重复此过程。

简单做了这个例子完整dfs的流程gif。

 这是这道题的dfs完整版。但是在写代码的时候有很多可以简化的地方。

比如,每一行只能放一个,所以我们只需要一个一维数组来保存列即可。每一次dfs代表进入下一行,一个全局一维数组代表每一列的状态,这样就在计算时将二维数组压缩成了一维数组。(当然最开始存棋盘还是得用二维数组存)

整个详细过程和细节写在代码里了。这里直接上AC代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
using namespace std;
typedef long long int ll;
const int MAX_N = 11;
char map[MAX_N][MAX_N];//存放最初的棋盘
int vis[MAX_N];//列状态,每个下标代表一列,0代表该列无棋子,1代表有棋子
int cnt;//棋盘上的棋子数量
ll ans;//最终答案,即方案数
int n,m;//n为地图大小,m为需要放置棋子个数

//初始化函数
void init(){
    cnt = 0;
    ans = 0;
    memset(map,0,sizeof(map));
    memset(vis,0,sizeof(vis));
}

void dfs(int now)//now代表所在第几行
{
    if(cnt == m){//如果棋子全部放置完毕
        ans ++;//方案数加一
        return;//返回去准备回溯
    }
    if(now > n){//如果没有放置完棋子并且已经没有行可以放棋子了
        return;//返回去准备回溯
    }
    dfs(now+1);//不填该行,直接进入下一行
    for(int i = 1;i<=n;i++){//扫描列
        if(map[now][i] == '#'&&vis[i] == 0){//如果有位置可以填
            cnt++;//放一颗棋子
            vis[i] = 1;//该列状态置为1,即该列有棋子了
            dfs(now+1);//下一行
            vis[i] = 0;//回溯
            cnt--;
        }
    }
}

int main()
{
    char c;//c是用来吞/n的
    while(scanf("%d%d",&n,&m) && n!=-1&&m!=-1){
        c = getchar();
        init();//记得初始化
        for(int i = 1;i<=n;i++){
            for(int j = 1;j<=n;j++){
                scanf("%c",&map[i][j]);
            }
            c = getchar();
        }
        dfs(1);//参数表示所在行,第一行序号为1
        printf("%lld\n",ans);
    }
    return 0;
}

BFS

BFS,广度优先算法,以源点为圆心,向周围一层层扩散开去的搜索算法。也可以形象的将它比作为正在扩散的水波。该算法运用了队列。本身不难。

前置知识

队列:队列是一种数据结构,特点为先入先出,就像排队时一样,先排队的人先离开,后来的人后离开。这里不会讲队列的实现原理。代码中均使用C++ stl的queue。可以自行搜索使用方法。

优先队列:BFS有时会与优先队列联合使用。这里暂时不提,但是到最短路时会有一个Dijkstra堆优化,这里贴一个之前写的优先队列的连接,本篇中不作要求。

 c++ STL 优先队列_issey的博客-CSDN博客

例题

 与之前写的DFS一样,通过简单的例题说明BFS的思路和使用方法。

题目连接:1979 -- Red and Black (poj.org)

题目描述:

题目大意:有一个人站在黑块的位置,可以上下左右移动,但是白块不能踩,问能踩到的最大黑块数目是多少。输入地图时,.为黑块,#为白块,@为这个人起始位置。(注意@也是黑块)。


思路:典型的BFS题,现在以@为圆心,假想以传波的形式遍历地图,其中#会阻挡波,看能走多少个 . 。(注意答案还要加上起点本身)  

思路很简单,问题是我们要怎么模拟这个所谓的“波”。队列的特点在于先进先出,而波的扩散是从里到外。在这道题中,小人只能以上下左右的方向行动,所以先依次将距离源点最近的上下左右中满足条件(即 . )的四个点放入队列尾部。然后读出队列头部(最先加入队列的点),再以该点为圆心,将距离最近且没有访问过的点放入队列尾部。循环上述过程,直到队列为空,结束计算。

假设现在地图如下:

 以黑格点为起点,遍历整张地图。

这是每一轮的队列流程图,一个个对照着地图看一看应该就能懂。

再在代码里加上#以及是否越界的判定条件即可。对于BFS的思想写了这道题就差不多明白了。

AC代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int MAX_SIZE = 30; 
typedef struct Node{
	int x,y;
}Node;
int Cnt;//能走的方块个数 
int vis[MAX_SIZE][MAX_SIZE];//是否已经访问过 
char map[MAX_SIZE][MAX_SIZE];//存地图
int X[4] = {0,-1,1,0};
int Y[4] = {1,0,0,-1};//方向

//判断某一点坐标在不在地图内
bool judge(int x,int y,int n,int m)
{

	if(x>=0&&x<n&&y>=0&&y<m)
	{
		return true;
	}else return false;
}

//检测每一轮的队列
inline void check_queue(queue<Node> Q)
{
    printf("当前队列:");
    while(!Q.empty()){
        printf("(%d,%d)     ",Q.front().x,Q.front().y);
        Q.pop();
    }
    printf("\n");
}


int main()
{
	int n,m;
	int i,j;

	while(scanf("%d %d",&m,&n)&&n!=0&&m!=0)
	{
		Node now,next,start;
		queue<Node> Q; //队列
		memset(map,0,sizeof(map));
		memset(vis,0,sizeof(vis));
		Cnt = 0;
		for(i = 0;i<n;i++)//输入地图,设置起点
		{ 
			getchar();
			for(j = 0;j<m;j++)
			{
				scanf("%c",&map[i][j]);
				if(map[i][j] == '@')
				{
					start.x = i;
					start.y = j;
					vis[i][j] = 1;
					Cnt = 1;
				}
			}
		}
		Q.push(start);//将起点加入队列
		while(!Q.empty())//若队列不为空
		{
			now = Q.front();//取出队列头部
			Q.pop(); //把队列头部丢掉
			for(i = 0;i<4;i++)
			{//四个方向
				if(judge(now.x+X[i],now.y+Y[i],n,m)&&!vis[now.x+X[i]][now.y+Y[i]]&&map[now.x+X[i]][now.y+Y[i]]=='.')
				{//如果下一个方块满足条件 
					next.x = now.x+X[i];
					next.y = now.y+Y[i];
					Cnt++;//找到一个,Cnt++; 
					Q.push(next);//将下一个点放入队列
					vis[now.x+X[i]][now.y+Y[i]] = 1;//将下一个点置为已访问
				}
			}
            //检测每一轮的队列
            //check_queue(Q);
		} 
		printf("%d\n",Cnt);
	}
	return 0;
}

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Twilight Sparkle.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值