dfs——深度优先搜索 详解

本文探讨了深度优先搜索算法,包括其工作原理、在数字排列、n皇后问题和迷宫问题中的应用,以及优化方法——剪枝。
摘要由CSDN通过智能技术生成

dfs——深度优先搜索

前言:

介绍: 深度优先搜索,又称dfs,是一种图的搜索方法。算法的基本思想是:从图的某一个节点出发,沿着某一条路径一直往下搜索,直至该条路径的所有节点均被访问,则向上回溯,寻找没有被访问的节点。这个算法的思想有点类似于我们之前学过的暴力求解法,即枚举问题的所有可能的情况,对每一种情况进行讨论,然后找到符合问题的解。

算法优点: 思维量小,不需要费脑筋。也不挑题型,几乎所有的题型均可用dfs(但是有些题并不适用dfs)。申明一下,可用和适用是两回事。

算法缺点: 时间复杂度较高,只适用于问题规模较小的问题。对于输入规模较大的问题,dfs算法将不再适用。(这里的不适用,并不是不能求解,而是求解问题所需要的时间太久)。

为什么要学? 深度优先搜索虽然是图的一种搜索方式,但是它的算法思想常常被我们用来解决生活中一些其他的问题。例如,一些问题的解没有规律,需要讨论问题的每一种情况来求解,如果使用其他算法,问题的解的准确率得不到保证。这时候我们就可以采用dfs。

入门案例——数字排列和组合

问题描述: 现有五张卡牌,上面分别有数字1、2、3、4、5。让你从从中选出三张卡牌,组成一个三位数,且每个三位数的大小不同,问你能组成多少个三位数?

输出示例:

  1  2  3
  1  2  4
  1  2  5
  1  3  2
  1  3  4
  1  3  5
  1  4  2
  1  4  3
  1  4  5
  1  5  2
  1  5  3
  1  5  4
  2  1  3
  2  1  4
  2  1  5
  2  3  1
  2  3  4
  2  3  5
  2  4  1
  2  4  3
  2  4  5
  2  5  1
  2  5  3
  2  5  4
  3  1  2
  3  1  4
  3  1  5
  3  2  1
  3  2  4
  3  2  5
  3  4  1
  3  4  2
  3  4  5
  3  5  1
  3  5  2
  3  5  4
  4  1  2
  4  1  3
  4  1  5
  4  2  1
  4  2  3
  4  2  5
  4  3  1
  4  3  2
  4  3  5
  4  5  1
  4  5  2
  4  5  3
  5  1  2
  5  1  3
  5  1  4
  5  2  1
  5  2  3
  5  2  4
  5  3  1
  5  3  2
  5  3  4
  5  4  1
  5  4  2
  5  4  3

代码示例:

#include<bits/stdc++.h>
using namespace std;
int visit[6];//标识数组,若数字 i 已经被选择了,则arr[i]=1,反之,arr[i]=0 
int arr[4];// 用存放每次找到三位数的结果 ,arr[1]、arr[2]、arr[3]表示三个盒子 
void dfs(int num)
int main(){
	dfs(1);// 先找一号盒子中的数字 
	return 0;
} 
void dfs(int num){
	if(num>3){
		// 打印结果 
		for(int i=1;i<=3;i++){
			cout<<setw(3)<<arr[i];
		}
		cout<<endl;
		return ;
	}
	
	for(int i=1;i<=5;i++){
		// 先判断数字 i 是否被已经被选择了 
		if(!visit[i]){
			// 如果没被选择,将数字 i 标识为已选择,即visit[i]=1 
			visit[i]=1;
			// 把数字 i 存进结果数组arr中 
			arr[num]=i;
			// 寻找下一个盒子中的数字 
			dfs(num+1);
			// 回溯 
			visit[i]=0;
		}
	}
}

思路分析:

需要从五张卡牌中选出三张,我们不妨假设有三个盒子来存放这三张卡牌。先找一张卡牌放入①号盒子,然后再找②盒子中的卡牌,再找③号盒子中的卡牌。如图所示,以①号盒子中放的卡牌号码是 1 为例:

在这里插入图片描述

假设①号盒子为1,②号盒子为2,当找到③号盒子的中一个解时,我们就可以回溯到②号盒子,重新来找满足③号盒子的解。同理,当找完所有满足③号盒子的解时,我们可以回溯到①号盒子,来找另一个满足②号盒子的解,以此类推,找出满足条件的每一种情况。

提升案例——n皇后问题

问题描述: 在n*n的方格棋盘中,放置n个皇后,使得它们不能相互攻击(即任意2个皇后不能在同一行、同一列,并且不能在一条对角线上),对于给定的n,请问有多少种放置方法?

输入格式: 一个正整数n,棋盘的大小,皇后的个数

输出格式: 一个正整数,放置方法的数量

输入示例:

5

输出示例:

10

代码示例:

#include<bits/stdc++.h>
using namespace std;
int n;// 棋盘的规模,也是皇后的个数 
int visit[11][11];// 标识数组,如果棋盘的第i行,第j列放置了皇后,则visit[i][j]=1,反之visit[i][j]=0;
void dfs(int line,int num);
int sum=0; // 放置的方法的数量 
bool checkout(int x,int y);// 检验函数,判断该位置是否能放置皇后 
int main(){
	cin>>n;
	dfs(1,0);
	cout<<sum<<endl;
	return 0;
} 
void dfs(int line,int num){ // line为行数,num为已经放置皇后的个数
	if(line>n){
		// 因为需要放置n个皇后,所以当放置了n个皇后后,就找到了一种放置方法 
	    if(num==n){
	       sum+=1;
		}
		num=0;
		return ;
	}
	
	// 从第一行开始,一行放置一个皇后 
	for(int i=1;i<=n;i++){
		if(checkout(line,i)){
			visit[line][i]=1;
			num+=1;
			dfs(line+1,num);
			num-=1;
			visit[line][i]=0;
		}
	}
}
bool checkout(int x,int y){
	bool flag=true;
	for(int i=1;i<=n;i++){
		// 如果该位置的同一行或者同一列有放置皇后,则该位置不能放置皇后,将flag赋值为false 
		if(visit[x][i]==1||visit[i][y]==1){
			flag=false;
		}
	}
	
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			// 如果该位置的对角线上有放置皇后,则该位置不能再放置皇后
			// 利用斜率来判断是否在对角线上,在对角线上斜率必然相同 
			if(abs(i-x)==abs(j-y)&&visit[i][j]==1){
				flag=false;
			}
		}
	}
	return flag;
}

思路分析:

每一行必须放置一个皇后,我们可以从第一行开始,先放置第一行的皇后,然后以此类推放置每一行的皇后。在放置每一行的皇后时,从第一列开始判断,如果满足条件就放置皇后,将该位置的标记为已访问,即visit [i] [j] =1。当准备放置皇后且行数为n时,并且放置皇后后,如果已经放置了的皇后的个数为n,则放置皇后的方法数加1。

基本思想就是判断每一行的每一列,如果该位置满足条件,就放置皇后。

不同行、不同列,只要满足不在同一行、同一列就行。不在对角线上,可以通过判断其斜率是否相同,若斜率相同,则必然处于对角线上。

下面是4行4列的表格:0为皇后放置的位置

1234
10
20
30
40
综合案例——迷宫问题

问题描述: 给定一个 n * m 大小的迷宫,其中迷宫位置上为 1 表示可以通行,为 0 表示不能通行。迷宫的入口坐标为(0,0),给定出口坐标,只能横向移动和纵向移动,不能斜着移动。求入口坐标到出口坐标的最少步骤。(其中 n和m 小于等于10)

输入格式: 正整数n和m,n为迷宫的行数,m为迷宫的列数。输入n行数据,每行有m个数字(0或者1)。最后一行输入迷宫出口坐标a,b。

输出格式: 一个正整数,表示最少的步骤数

输入示例:

5 5
1 1 0 0 1
1 1 1 1 1
1 1 0 0 1
1 1 0 1 1
1 1 1 1 0
3 3

输出示例:

8

代码示例:

#include<bits/stdc++.h>
using namespace std;
int n,m;// n为迷宫的行数,m为迷宫的列数 
int a,b;// a,b为迷宫出口的坐标 
int arr[10][10]; // arr 来存放迷宫的大小 
int visit[10][10];// 标识数组,如果该点被访问过,则visit[i][j]=1 
int sum=100000;// sum为到出口的步骤数,因为需要找最小的步骤数,所以初始值给大一点 
// 方向向量dx、dy,分别表示在x轴和y轴上的偏移量
// 例如向右移动,则x+1,y不变,用方向向量表示为x+dx[0]、y+dy[0] 
int dx[4]={1,0,-1,0};
int dy[4]={0,1,0,-1}; 
void dfs(int x,int y,int num);// x,y为当前位置的坐标,num为从入口到当前位置的步骤数 
int main(){
	cin>>n>>m;
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++){
			cin>>arr[i][j];
		}
	}
	cin>>a>>b;
	// 出口位置必然是可以通行的,arr[a][b]=1
	// 这里为什么需要再次赋值,这是因为我出题不严谨所导致的 ,这个影响不大 
	arr[a][b]=1;
	dfs(0,0,0);
	cout<<sum<<endl;
} 
void dfs(int x,int y,int num){
	if(x==a&&y==b){
		if(num<sum){
			sum=num;
		}
		num=0;
		return ;
	} 
	
	// 迷宫问题一般需要确定一个方向,根据一个方向依次向前尝试
	// 这里我规定的是顺时针方向 
	for(int i=0;i<4;i++){
		int l=x+dx[i];
		int c=y+dy[i];
		// 判断一下,数组下标是否越界,如果越界则跳过 
		if((l<0||l>n)&&(c<0||c>m)){
			continue;
		}
		if(arr[l][c]==1){
			if(!visit[l][c]){
			   visit[l][c]=1;
			   num+=1;
			   dfs(l,c,num);
			   // 这里注意一下,回溯的方法  
			   // 因为之前找到了这个位置, 步骤数num加了1,所以回溯的时候步骤数num-1 
			   num-=1; 
			   visit[l][c]=0;
			}	
		}
	}
}

思路分析:

迷宫问题首先需要确定一个遍历方向,因为题目规定只能横向或纵向移动,所以只有上、下、左、右四个方向可以选择。根据确定好的方向依次进行遍历判断。本题中,我是根据顺时针方向进行判断,先向右,再向下,再向左,最后再向上移动。到达一个位置,先判断其右方向是否可以通行,如果可以通行,则移动到该位置上,然后依次判断其他方向。如果碰到了各个方向不能通行的位置,则向后回溯,寻找其他可以通行的路径。

输入示例迷宫如图所示:

入口1001
11111
11001
110出口1
11110

结束语:

dfs ——万能解题法,它可以解决我们遇到的绝大多数问题,但是它的时间复杂度较高,所以我们应用时,要注意dfs的剪枝,提高算法效率。上面的案例中没有涉及到剪枝的操作,不同题型的剪枝方法有所不同,具体题型具体分析。剪枝可以减少多余情况的遍历,从而提高算法的效率。

  • 39
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
迷宫问题可以使用深度优先搜索算法来解决。深度优先搜索是一种用于遍历或搜索树或图的算法,它从根节点开始,沿着一个分支尽可能深入地搜索,直到搜索到叶子节点或者满足某个条件时回溯到上一层节点继续搜索。 在迷宫问题中,我们可以将迷宫表示为一个二维数组,其中0表示可以通过的路,1表示墙或者障碍物。我们从起点开始搜索,每次选择一个可行的方向,直到到达终点或者无法继续前进时回溯,尝试其他方向。 下面是一份迷宫问题深度优先搜索的 Python 代码示例: ```python def dfs(maze, visited, start, end): # 如果起点和终点相同,则返回 True if start == end: return True # 标记当前节点为已访问 visited[start[0]][start[1]] = True # 定义四个方向的偏移量 directions = [(0, 1), (0, -1), (1, 0), (-1, 0)] # 尝试四个方向 for d in directions: # 计算下一个节点的坐标 x = start[0] + d[0] y = start[1] + d[1] # 判断下一个节点是否越界或者已经访问过 if x < 0 or x >= len(maze) or y < 0 or y >= len(maze[0]) or visited[x][y] or maze[x][y] == 1: continue # 递归搜索下一个节点 if dfs(maze, visited, (x, y), end): return True # 回溯到上一层节点 return False # 调用函数进行搜索 maze = [[0, 1, 0, 0], [0, 0, 0, 1], [0, 1, 0, 0], [0, 1, 1, 0]] visited = [[False for _ in range(len(maze[0]))] for _ in range(len(maze))] start = (0, 0) end = (3, 3) print(dfs(maze, visited, start, end)) ``` 在上面的代码中,我们使用了一个二维数组 visited 来记录每个节点是否访问过,防止重复访问。我们也可以用一个 set 或者 dict 来实现 visited。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值