DFS深度优先遍历经典例题总结

DFS的大概模板

void dfs(int x)//关于传入参数问题,根据题意而定,看在题目运行的过程中,哪些是在变得
{
    if(满足输出条件)
    {
        输出解;
        return ;
    }
    if(目前已经没有必要进行下去的条件){
        return ;
    }//剪枝操作
    //如果传入的条件,还需要继续搜下去,分析每一种情况后面跟哪些情况,然后循环,每个情况(注意前提:得符合题意)都深搜一下
     for(int i=1;i<=每种情况数;i++)
            if(满足进一步搜索条件)//判断是否合理
            {
                为进一步搜索所需要的状态打上标记;//是需要的状态
                dfs(t+1);
                恢复到打标记前的状态;//也就是说的{回溯一步}
            }
    }
}

拿最经典的八皇后问题解释dfs,正常情况下,如果有一个6*6的棋盘,在第一行,选择落子,不考虑规则限制,我们有6种落子方式。
第二行,有1 2 3 4 5 6,6种落子方式。以此类推,6的6次方种情况。
但现在由于规则限制,跟bfs一样根据流程想出一个树出来。指需要按照规则取舍一下那些结点有,哪些没有即可,搜索方式上采取递归+回溯
从最终结果上,呈现出来的是从根节点一条路走到底,然后撤回底点的上一个结点,撤回后能走另一个,就走另一个
DFS传入的参数一定要能表示出每个情况的状态

例题1

P1219 八皇后 题目链接
一个如下的 6×6 的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。
如图

上面的布局可以用序列 2 4 6 1 3 5 来描述,第 ii 个数字表示在第 ii 行的相应位置有一个棋子,如下:

行号 1 2 3 4 5 6

列号 2 4 6 1 3 5

这只是棋子放置的一个解。请编一个程序找出所有棋子放置的解。
并把它们以上面的序列方法输出,解按字典顺序排列。
请输出前 33 个解。最后一行是解的总个数。

输入格式
一行一个正整数 nn,表示棋盘是 n \times nn×n 大小的。

输出格式
前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。

输入输出样例
输入
6
输出
2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
4
说明/提示
【数据范围】
对于 100%100% 的数据,6 ≤n≤13。
思路
就是深搜加回溯,技巧就是对于斜对角线上面是否有结点的判别,算一下就知道,左下到右上斜对角线i+j=固定值,左上到右下i-j+n=固定值
按照深搜的思想进行分析,在题目运行过程中,所脑补出的树形结构,什么在改变。开始一个棋盘上面,什么都没放,在第一行上可以放1 2 3 4 5 6位置。
如果选取1,对1深搜结果如下,选择2,3都一样。
下一步,第二行,1已经被放过,可以放2 3 4 5 6,放2位置不行,3位置可以
下一步,从3行2列开始。。。。。。。
过程中可以发现,改变是行数,第一行每个可能深搜第二行,第二行每个可能深搜第三行。
AC代码

#include<bits/stdc++.h>
using namespace std;
int p[100];//储存路径
int visy[100]={0};//记录列是否访问
int vxie1[100]={0};//记录左下到右上斜线是否访问
int vxie2[100]={0};//左上到右下
int n;//n*n棋盘
int total=0;//记录搜索到结果总数
void dfs(int x){
	if(x==n){
		if(total<=2)
		for(int i=0;i<n;i++) printf("%d ",p[i]+1);
		if(total<2) printf("\n");
		total++;
		return ;
	}//如果X=n表示已经搜到最后一行,total++,一条路走到尽头
	//否则这一行,有n种情况,如果符合题意,每个情况继续深搜
	for(int j=0;j<n;j++){
		if(!visy[j]&&!vxie1[x+j]&&!vxie2[x-j+n]){
			p[x]=j;//储存路径,表示x行选择j列
			visy[j]=1;vxie1[x+j]=1;vxie2[x-j+n]=1;//标记这一行,列,斜线
			dfs(x+1);//深搜下一行
			visy[j]=0;vxie1[x+j]=0;vxie2[x-j+n]=0;//深搜结束
			//回到这一行初始状态就是回溯,进行下一种情况深搜
		}
	}
}
int main(){
	    cin>>n;
		dfs(0);
		printf("\n%d",total); 
}

例题2

试题 历届试题 分考场

http://lx.lanqiao.cn/problem.page?gpid=T457

资源限制
时间限制:1.0s 内存限制:256.0MB
问题描述
  n个人参加某项特殊考试。
  为了公平,要求任何两个认识的人不能分在同一个考场。
  求是少需要分几个考场才能满足条件。
输入格式
  第一行,一个整数n(1<n<100),表示参加考试的人数。
  第二行,一个整数m,表示接下来有m行数据
  以下m行每行的格式为:两个整数a,b,用空格分开 (1<=a,b<=n) 表示第a个人与第b个人认识。
输出格式
  一行一个整数,表示最少分几个考场。
样例输入
5
8
1 2
1 3
1 4
2 3
2 4
2 5
3 4
4 5
样例输出
4
样例输入
5
10
1 2
1 3
1 4
1 5
2 3
2 4
2 5
3 4
3 5
4 5
样例输出
5

思路

这题因为我比较笨,所以想了很久才想通。同样是分析题目进行过程脑补出树或图。
第一步。所有房间都是空的,放里面放人,放第一个人
第二步。放第二个人,如果与第一个房间所有人没关系,放第一个房间,否则新开一个房间,放进去。
第三步,放第三个人,如果1,2房间不能放,开一个房间放到3号房间,否则放在1或2房间
第四步。。。。
分析过程可以看出,每次房间数是在变化,每次放的人的编号也在变化。意思就是树的每个点,都包含我现在放入编号几,我用了多少个房间,在每个点里搜出最佳点。
AC代码

#include<bits/stdc++.h>
using namespace std;
int n,m,min_num;
int mp[102][102]={{0}};
int r[102][102]={{0}};
void dfs(int x,int num){//x表示开始安排编号X,已经用了num个房间
	if(num>=min_num) return ;//剪枝
	if(x>n){
		if(num<min_num) min_num=num;
	}//如果已经安排完,比较后更新最少房间数
	//八皇后一样到x行,都会有n种情况。对于此题到达分配编号x,有num个房间
	for(int i=1;i<=num;i++){
		int k=0;
		while(r[i][k]&&!mp[x][r[i][k]]) k++;//找到放在房间的那一个位置
		if(r[i][k]==0){
			r[i][k]=x;
			dfs(x+1,num);
			r[i][k]=0;
		}
	}//找不到合适的房间,就开一个新的房间继续深搜,代码就是把流程写了一遍
	r[num+1][0]=x;
	dfs(x+1,num+1);
	r[num+1][0]=0;
}
int main()
{
	cin>>n>>m;
	min_num=n;
	while(m--){
		int x,y;
		cin>>x>>y;
		mp[x][y]=mp[y][x]=1;
	}
	dfs(1,0);
	printf("%d\n",min_num);
}

例题3

P1605 迷宫题目地址
题目背景
给定一个N*M方格的迷宫,迷宫里有T处障碍,障碍处不可通过。给定起点坐标和终点坐标,问: 每个方格最多经过1次,有多少种从起点坐标到终点坐标的方案。在迷宫中移动有上下左右四种方式,每次只能移动一个方格。数据保证起点上没有障碍。

题目描述

输入格式
第一行N、M和T,N为行,M为列,T为障碍总数。第二行起点坐标SX,SY,终点坐标FX,FY。接下来T行,每行为障碍点的坐标。

输出格式
给定起点坐标和终点坐标,问每个方格最多经过1次,从起点坐标到终点坐标的方案总数。

输入输出样例
输入 #1 复制
2 2 1
1 1 2 2
1 2
输出 #1 复制
1
说明/提示
【数据规模】

1≤N,M≤5
思路
这道题也可以用bfs做,每个结点有四处走向,不断地扩散,找到终点,返回
但是记录能有几条路比较麻烦
用dfs思路和前两题一样,按照题目过程脑补出树
第一步,在起点,有4种情况,如果可以,上下左右都深搜一下(如果选择深搜右)
第二步,起点的右节点为起点,上下左右如果可以的话,都深搜(如果可以还选择右)
第三步,起点的右的右,上下左右,都深搜,。。。。还选右的话
如果题意允许,就一直讲走完到了终点,递归出口让total++就行
AC代码
就没有注释太多了,跟前面差不多

#include<bits/stdc++.h>
using namespace std;
int n,m,t,total=0;//n行,m列,t个障碍物
int sx,sy,x2,y2;//起点x,y,终点x,y
int mp[10][10]={{0}};
int vis[10][10]={{0}};
int dir[4][2]={{0,1},{0,-1},{1,0},{-1,0}};
void dfs(int x,int y){
	if(x==x2&&y==y2){
		total++;
		return ;
	}
	for(int i=0;i<4;i++){
		int xx=x+dir[i][0];
		int yy=y+dir[i][1];
		if(!vis[xx][yy]&&xx>=1&&xx<=n&&yy>=1&&yy<=m&&!mp[xx][yy]){
			vis[x][y]=1;
			dfs(xx,yy);
			vis[x][y]=0;
		}
	}
}
int main(){
	cin>>n>>m>>t;	
	cin>>sx>>sy>>x2>>y2;
	for(int i=0;i<t;i++){
		int zx,zy;
		cin>>zx>>zy;
		mp[zx][zy]=1;
	}
	dfs(sx,sy);
	printf("%d\n",total);
} 

总结完毕!

  • 11
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值