《关于dfs》 一

在之前https://blog.csdn.net/K08e_824?spm=1000.2115.3001.5343中,大致讲解了dfs搜索,

这里还可以再来几道习题

比如:https://www.luogu.com.cn/problem/P1605

 

这个迷宫其实是比较经典的dfs,(如果将其再包装一下,要求最短路径,就成了bfs,这都是后话...)

可以设想一下,如下的状态空间:

 红色代表起点,绿色代表终点,从起点往下搜索,搜索方式是上下左右,上下左右...直到遇到终点,其实这是比较靠谱的搜索方式。

当然,这一题里面还有障碍物等等附加条件,这都是后话

对于一个题,我们确定了它是dfs,就需要确定以下问题:

1、搜索方式是什么?

这一题很明显就是上下左右;所以可以定义两个数组

const int dx[]={1,-1,0,0};
const int dy[]={0,0,-1,1};

for(int i = 0;i < 4;++i)

遍历dx和dy,得到的dx[i] 和 dy[i]恰好可以组成上/下/左/右,并且不重复

2、递归出口是什么?

如果对于单独的迷宫问题,递归出口就是

if(map[x][y] == 终点)

        路径数++;

        return;

但这一题还有障碍物,所以需要再加一个

if(map[x][y] == 障碍)

        return;

所以这一题大致就解决了;

#include<bits/stdc++.h>
using namespace std;
const int dx[] = {1,-1,0,0};
const int dy[] = {0,0,1,-1};
const int N = 5;//题目中有 1<= N , M <= 5
bool vis[N][N];//标记数组 
int n,m,t;//设置为全局变量,方便函数调用,减少参数 

bool in(const int& x,const int& y)
{
	return x >= 1 && y >= 1 && x <= n && y <= m;
}

void dfs(const vector<vector<int>>& maze,const int& x,const int& y,const int& fx,const int& fy,int& count)
{
	vis[x][y] = true;
	//遍历到的地方先打上标记
	if(maze[x][y] == 1)//遇到障碍就回溯 
		return;
	if(x== fx && y == fy && maze[x][y] != 1)//到达终点,路径数++,回溯,但不能直接退出,因为还不能确定是不是所有路径都搜完了 
	{
		count++;
		vis[x][y] = false;//必须要有这一步,不然找到一条路径终点一直是true就没法搜了 
		return;
	}
	
	for(int i = 0;i < 4;++i)
	{
		int tx = x + dx[i];
		int ty = y + dy[i];
		//实现上下左右 
		if(vis[tx][ty] != true && in(tx,ty))
		{
			dfs(maze,tx,ty,fx,fy,count); 
		}
	}
	vis[x][y] = false;//回溯 
}
int main()
{
	cin>>n>>m>>t;
	//n行 m列 t个障碍
	
	int sx,sy,fx,fy;
	cin>>sx>>sy>>fx>>fy; 
	//起点终点坐标 
	
	vector<vector<int>> maze(n + 1,vector<int>(m + 1));//地图,且默认初始化为0 
	//这里根据题意可知,都是从第一行开始计数而不是第零行,所以可以多开一行; 
	for(int i = 0;i < t;++i)
	{
		int x,y;
		cin>>x>>y;
		//障碍坐标
		
		maze[x][y] = 1;//将障碍处标记为1
	} 
	int count = 0;
	dfs(maze,sx,sy,fx,fy,count);
	cout<<count;
	return 0;
}

切记不要忘记回溯操作,,初学的时候因为忘记回数操作导致很多题都没做对!

二、八皇后

这是一道非常经典的dfs,我认为这题写十遍都不为过;

https://www.luogu.com.cn/problem/P1219

这题其实难点比较多,我们之前见到的dfs题大多设置一个 判断是否遍历过的数组 都是看这一题有多少状态空间来设置的,但是这一题不一样,

之前是到哪一个点,那么那一个点就打个标记,但是这个涉及到很多点,同一行的,同一列的,同一对角线的...

这就需要多个数组来判断,如何更简单的设置这些数组也是一门学问。比如我可以再设置一个vis[N][N],然后将同一行同一列,同一对角线都打上标记,这确实是可行的,但是会有很多冗余量;

其实这一题的状态空间大概如下:

 

这样一个长宽都为n的正方形,

为了避免打标记的数组成为二维数组,我们可以将列/行看成一个单独变量,具体的意思就是我们可以从上往下依次放,这样是十分高效而且不会出错的,

比如我是用行做单独变量,我们在一行放完之后,立马到下一行寻找到可以放的地方,

因为一行中只能出现一个,所以我放完之后立马去下一行寻找是没有问题的,

这就解决了二维数组的产生的问题;

那么对角线该如何解决??

从这一条 走向为右上的对角线 来说(下标从0开始)

 

我们可以看到对角线上的点 横纵坐标相加最大为 2(n-1),最小为0,而且是在[0,2n-2]这个区间中,每个值都会出现的,所以我们可以将这个点的横纵坐标的和 作为 判断有没有在对角线上出现过的标准

所以我们可以开一个大小为 2n的数组,每次搜索到一个点都将这个点的横纵坐标加起来,从将数组中下标为这个值的点 置为true 代表被遍历过了 这条对角线不可用了;

那再以这条 走向为右下的 对角线为例:

这些对角线上的点都有一个很明显的特征:它们的差值为固定值

在一个长宽都为n的正方形中,x坐标的取值范围是[0,n-1],y也是[0,n-1];

所以 x-y的取值范围是[1-n,n-1];

那这个怎么用数组实现呢?

其实仔细看它的 最大值减去最小值 还是 2(n-1),我们不妨再开一个大小为2n的数组

然后我们把每次 横纵坐标的差值 + n 的结果 当作是否被搜索过的标准;

这里觉得绕是很正常的,但其实也很好理解,既然每条对角线上的点 x-y都是相同的 而且 独一无二的 那么将这个x-y加上n之后还是相同的而且独一无二的,这些特性都没有改变过,所以这是一个比较巧妙的技巧;

那么,题前准备做好了,接下来应该就是那两个问题了;

1、搜索策略是什么?

很明显,我在上面说过,这么搜索是按照行数从上往下,一行一行搜,搜到一个可以放的,放完立刻下一行;

2、递归出口是什么?

我们既然决定了搜索策略是  从第0行 一行一行的来,那么如果n-1行我们已经放过了,再次进入递归,这个时候 行数应该是 == n的 所以这个时候直接return

那么这一题也就变得十分容易:

#include<bits/stdc++.h>
using namespace std;
int n;
void dfs(const vector<vector<int>>& maze,vector<bool>& vis1,vector<bool>& vis2,vector<bool>& vis3,const int& x,int& count,vector<int>& res,int& num)
{
	if(x == n)
	{
		if(num)
		{
			--num;
			for(int i = 0;i < res.size();++i)
			{
				cout<<res[i] <<" ";
			}
			cout<<endl;
			
		}
		count++;	
		return;
	}//如果 x可以进行到 n-1行 下一次递归进来就是 x== n 那么代表 n-1行已经放进元素了,这就是一种成功的策略
	
	else
	{
		for(int y = 0;y < n;++y)
		{
			if(vis1[y] != true && vis2[x + y] != true && vis3[x - y + n] != true)
			{
				res[x] = y + 1;
				vis1[y] = true;
				vis2[x + y] = true;
				vis3[x - y + n] = true;
                //如果这些都符合条件,代表未被搜索过,我们现在给他打上标记,然后往下一行搜索
				dfs(maze,vis1,vis2,vis3,x + 1,count,res,num);	
                   
                //回溯 千万不能忘记
				vis1[y] = false;
				vis2[x + y] = false;
				vis3[x - y + n] = false;
			}
		}
		
	} 
}
int main()
{
	cin >> n;
	vector<int> res(n);//答案数组 
	vector<vector<int>> maze(n,vector<int>(n));//地图 
	vector<bool> vis1(n);//标记列 
	vector<bool> vis2(2 * n);//标记对角线1
	vector<bool> vis3(2 * n);//标记对角线2
	int count = 0;
	int num = 3;
//输出前三个答案
//这里num和count都必须传 引用,因为递归中如果传值,会一直重置值;
	dfs(maze,vis1,vis2,vis3,0,count,res,num);
	cout<<count;
	return 0;
}

希望这两个题对大家有所帮助!敬礼~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值