C++知识点总结(36):深度优先搜索算法

一、概念

深度优先搜索(简称 DFS),其工作原理为不撞南墙不回头,能深则深,不能则退。最差情况下的时间复杂度为 O ( a n ) O(a^n) O(an) a a a 为可选条数。

二、典型题目

1. 题目

给定一个 n n n m m m 列的迷宫,有些格子可以走,有些有障碍物不能到达。每步可以走到上下左右的格子中。请你判断,是否能从左上角走到右下角。如果能走到输出 YES,否则输出 NO。迷宫中字符为 * 表示迷宫这个格子有障碍物,. 表示没有障碍物。

2. 分析

不能走的地方

  • 迷宫的边界
  • 遇到障碍物
  • 走回头路

DFS 的功能

在一个点遍历 4 4 4 个方向,如果这个方向上的点满足条件,去下一个点。

伪代码

dfs(x, y)
    if (x == n && y == m)
        stop
    if (isRoad(a[?][?]))
        dfs(?, ?);
    ...

3. 参考答案

#include <iostream>
using namespace std;

int n, m; // 迷宫大小
bool flag; // 是否有解 
char Map[25][25]; // 地形图
bool vis[25][25]; // 标记是否走过 
int dx[5] = {-1, 0, 1, 0}; // 四个方向的偏移量 
int dy[5] = {0, 1, 0, -1}; // 四个方向的偏移量 

void dfs(int x, int y)
{
	// 到终点 
	if (x == n && y == m)
	{
		flag = true;
		return;
	}
	
	// 遍历方向,判断是否满足条件
	for (int i = 0; i < 4; i++)
	{
		int tmpX = x + dx[i];
		int tmpY = y + dy[i];
		
		// 是通路
		if (Map[tmpX][tmpY] == '.')
		{
			// 未到边界
			if (tmpX >= 1 && tmpX <= n && tmpY >= 1 && tmpY <= m) 
			{
				// 未访问
				if (vis[tmpX][tmpY] == false) 
				{
					vis[tmpX][tmpY] = true;
					dfs(tmpX, tmpY);
				}
			}
		}
	}
}

int main()
{
	// 输入
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			cin >> Map[i][j];
		}
	}
	
	// dfs
	vis[1][1] = 1;
	dfs(1, 1);
	
	// 输出
	cout << (flag ? "YES" : "NO");
	return 0;
}

三、变形题目

1. 路径数量

1.1 审题

给定一个 n n n m m m 列的矩阵,可以向上下左右四个方向移动,找到能从 ( s x , s y ) (sx, sy) (sx,sy) 走到 ( e x , e y ) (ex, ey) (ex,ey) 的所有路径数。

1.2 思路

这个题目和典型题目非常类似,只是多了回溯 ( 1 ) ^{(1)} (1)的步骤。

回溯
通过逐步试探和撤销的方式来查找问题的解,这种算法就叫回溯法。
优点:

  • 简单直观
  • 适用于穷举搜索
  • 可以找到所有的解

缺点:

  • 效率低
  • 难以优化
  • 可能重复计算

2. 走迷宫的过程

2.1 审题

给定一个 n n n m m m 列的迷宫,有些格子可以走,有些有障碍物不能到达。每步可以走到上下左右的 4 4 4 个格子中。请你输出从左上角开始,走到右下角的路径。其中,从一个格子出发,优先出发的顺序为:上、右、下、左。输出的路径坐标之间使用空格隔开,由于路径可能有多条,每条路径之间使用换行隔开。

2.2 参考答案

#include <iostream>
using namespace std;

int n, m; // 迷宫大小
char Map[25][25]; // 地形图
bool vis[25][25]; // 标记是否走过 
int dx[5] = {-1, 0, 1, 0}; // 四个方向的偏移量 
int dy[5] = {0, 1, 0, -1}; // 四个方向的偏移量
int pos = 1; // 路线的长度
int ansx[85] = {1}; // 路线的 x 坐标
int ansy[85] = {1}; // 路线的 y 坐标

void dfs(int x, int y)
{
	// 到终点 
	if (x == n && y == m)
	{
		for (int i = 0; i <= pos-1; i++)
		{
		    cout << "(" << ansx[i] << "," << ansy[i] << ") ";
		}
		cout << endl;
		return;
	}
	
	// 遍历方向,判断是否满足条件
	for (int i = 0; i < 4; i++)
	{
		int tmpX = x + dx[i];
		int tmpY = y + dy[i];
		
		// 是通路
		if (Map[tmpX][tmpY] == '.')
		{
			// 未到边界
			if (tmpX >= 1 && tmpX <= n && tmpY >= 1 && tmpY <= m) 
			{
				// 未访问
				if (vis[tmpX][tmpY] == false) 
				{
				    // 标记已经走过
					vis[tmpX][tmpY] = true;
					
					// 存储路线
					ansx[pos] = tmpX;
					ansy[pos] = tmpY;
					pos++;
					
					// 递归
					dfs(tmpX, tmpY);
					
					// 回溯
					vis[tmpX][tmpY] = false;
					pos--;
				}
			}
		}
	}
}

int main()
{
	// 输入
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			cin >> Map[i][j];
		}
	}
	
	// dfs
	vis[1][1] = true;
	dfs(1, 1);
	return 0;
}
  • 10
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值