DFS模板及讲解 深度优先遍历(DFS)也叫深度优先搜索。它的定义是:不断地沿着顶点的深度方向遍历。顶点的深度方向是指它的邻接点方向。
DFS的实现步骤:
1、从顶点出发。
2、访问顶点,也就是根节点。
3、依次从顶点的未被访问的邻接点出发,进行深度优先遍历;直至和顶点有路径相通的顶点都被访问。
4、若此时尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到所有顶点均被访问过为止。
图解
文章图片
文章图片
搜索过程:假设起点是A,访问顺序是A->B->C->F,由于节点F没有相邻节点,所以回溯到节点C,节点C也没有相邻节点,继续回溯,到节点B,发现B节点有相邻节点,于是访问B->E->G->D,由于D的相邻节点已经被访问过,所以回溯到节点G,G有相邻节点,访问顺序G->H->I,…最后回溯到节点A。
核心代码
void dfs()//参数用来表示状态
{
if(到达终点状态)
{
...//根据题意添加
return;
}
if(越界或者是不合法状态)
return;
if(特殊状态)//剪枝
return ;
for(扩展方式)
{
if(扩展方式所达到状态合法)
{
修改操作;
//根据题意来添加
标记;
dfs();
(还原标记);
//是否还原标记根据题意
//如果加上(还原标记)就是 回溯法
}
}
}
在这里介绍一下剪枝:
剪枝是一种优化方式,对于不同的问题有着不同的方法。通常分为以下两类:
1.最优化剪枝
2.可行性剪枝
举个例子:N个城市,编号1到N。城市间有R条单向道路。每条道路连接两个城市,有长度和过路费两个属性。Bob只有K块钱,他想从城市1走到城市N。问最短共需要走多长的路。如果到不了N,输出-1
2<=N<=100
0
每条路的长度 L, 1 <= L <= 100
每条路的过路费T , 0 <= T <= 100
剪枝一:如果当前已经找到的最优路径长度为L ,那么在继续搜索的过程中,总长度已经大于等于L的走法,就可以直接放弃,不用走到底了
剪枝二:如果当前到达城市的路费已大于k,或者等于k且没有到达终点,就可以直接放弃。
DFS形参:一般情况下,什么在变化,就把什么设置成形参
简单的例题
- 最最最最简单的全排列问题
#include
#include
using namespace std;
int n, a[10086];
bool vis[10086];
void dfs(int x)
{
if (x == n + 1)
{
for (int i = 1;
i <= n;
i++)
cout << a[i]<<" ";
cout << endl;
return;
}
for (int i = 1;
i <= n;
i++)
{
if (!vis[i])
{
vis[i] = true;
a[x] = i;
dfs(x + 1);
vis[i] = false;
}
}
}
int main()
{
while (cin >> n)
{
memset(a, 0, sizeof(a));
memset(vis, 0, sizeof(vis));
dfs(1);
}
return 0;
}
- 组合的输出
题目描述
排列与组合是常用的数学方法,其中组合就是从n个元素中抽出r个元素(不分顺序且r < = n),我们可以简单地将n个元素理解为自然数1,2,…,n,从中任取r个数。
现要求你不用递归的方法输出所有组合。
例如n = 5 ,r = 3 ,所有组合为:
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5
输入
一行两个自然数n、r ( 1 < n < 21,1 < = r < = n )。
输出
所有的组合,每一个组合占一行且其中的元素按由小到大的顺序排列,所有的组合也按字典顺序。
int n, r;
int a[50];
bool vis[50];
void dfs(int x)
{
for (int i = a[x-1]+1;
i <= n;
i++)
{
if (!vis[i])
{
vis[i] = true;
a[x] = i;
if (x == r)
{
for (int j = 1;
j <= r;
j++)
{
cout << a[j] << " ";
}
cout << endl;
}
dfs(x + 1);
vis[i] = false;
}
}
}
int main()
{
while (scanf_s("%d %d",&n,&r)!=EOF)
{
dfs(1);
}
return 0;
}
- 组合加判断素数
题目描述
已知 n 个整数b1,b2,…,bn
以及一个整数 k(k<n)。
从 n 个整数中任选 k 个整数相加,可分别得到一系列的和。
例如当 n=4,k=3,4 个整数分别为 3,7,12,19 时,可得全部的组合与它们的和为:
3+7+12=223+7+19=297+12+19=383+12+19=34。
现在,要求你计算出和为素数共有多少种。
例如上例,只有一种的和为素数:3+7+19=29。
第一行两个整数:n , k (1<=n<=20,k<n)
第二行n个整数:x1,x2,…,xn (1<=xi<=5000000)
输出
一个整数(满足条件的方案数)。
样例输入
4 3
3 7 12 19
样例输出
1
int n, k,sum,ans;
int a[50],p[50];
bool vis[50];
bool isprime(int x)
{
for (int i = 2;
i <= sqrt(x);
i++)
if (x%i == 0)
return false;
return true;
}
void dfs(int x)
{
if (x == k + 1)
{
if (isprime(sum))
{
ans++;
}
return;
}
for (int i = 1;
i <= n;
i++)
{
if (!vis[i]&&i>p[x-1])
{
p[x] = i;
vis[i] = true;
sum = sum + a[i];
dfs(x + 1);
vis[i]=false;
sum = sum - a[i];
}
}
}
int main()
{
while (scanf_s("%d %d", &n, &k) != EOF)
{
for (int i = 1;
i <= n;
i++)
{
scanf_s("%d", &a[i]);
}
ans = 0;
dfs(1);
printf("%d\n", ans);
}
return 0;
}
最最最最经典例题:N皇后问题
题目描述
会下国际象棋的人都很清楚:皇后可以在横、竖、斜线上不限步数地吃掉其他棋子。如何将8个皇后放在棋盘上(有8 * 8个方格),使它们谁也不能被吃掉!这就是著名的八皇后问题。
输入
一个整数n( 1 < = n < = 10 )
输出
每行输出对应一种方案,按字典序输出所有方案。每种方案顺序输出皇后所在的列号,相邻两数之间用空格隔开。如果一组可行方案都没有,输出“no solute!”
样例输入
4
样例输出
2 4 1 3
3 1 4 2
int n,a[10];
bool vis[10],had;
void dfs(int x)
{
for (int i = 0;
i < x ;
i++) {
for (int j = i + 1;
j < x;
j++)
{
if (i - j == a[i] - a[j] || a[i] + i == j + a[j])
return;
}
}
if (x == n)
{
for (int i = 0;
i < n;
i++)
{
printf("%d ", a[i]+1);
}
printf("\n");
had = false;
return;
}
for (int i = 0;
i < n;
i++)
{
if (!vis[i])
{
vis[i] = true;
a[x] = i;
dfs(x + 1);
vis[i] = false;
}
}
}
int main()
{
while (scanf_s("%d", &n) != EOF)
{
had = true;
dfs(0);
if (had)
{
printf("no solute!\n");
}
}
return 0;
}
,约翰的农场中中积水汇聚成一个个不同的池塘,农场可以用 N x M (1 <= N <= 100; 1 <= M <= 100) 的矩形来表示。农场中的每个格子可以用’W’或者是’.'来分别代表积水或者土地,约翰想知道他的农场中有多少池塘。八连通的积水被认为是一块积水。
给你约翰农场的航拍图,确定有多少池塘?
Sample Input
10 12
W…WW.
.WWW…WWW
…WW…WW.
…WW.
…W…
…W…W…
.W.W…WW.
W.W.W…W.
.W.W…W.
…W…W.
Sample Output
3
#include
using namespace std;
int n,m;
char a[100][100];
bool vis[100][100];
int dis[8][2]={{-1,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1}};
int sum=0;
void dfs(int bx,int by)
{
for(int i=0;
i<8;
i++)
{
int nx=bx+dis[i][1];
int ny=by+dis[i][0];
if(nx<0||nx>=n||ny<0||ny>=m||vis[nx][ny]==true||a[nx][ny]!='W')
continue;
vis[nx][ny]=true;
dfs(nx,ny);
}
}
void solve()
{
for(int i=0;
i>n>>m;
for(int i=0;
i>a[i];
solve();
cout<
初学dfs:先做最最最基础的题: http://codeup.cn/contest.php?cid=100000608 |
---|
做完了,你就入门了(绝对没有比这更基础的题了) |
写过这些入门题后,我们可以将DFS题分为两大类:
1 . 地图型:这种题型将地图输入,要求完成一定的任务。因为地图的存在。使得题意清楚形象化,容易理清搜索思路。
AOJ 869-迷宫(遍历地图,四向搜索)
HDU 1035-Robot Motion(指定方向搜索,迷路(循环)判断)
HDU 1045-Fire Net(check函数,回溯)
HDU 1010-Tempter of the Bone(奇偶剪枝,回溯)
2 . 数据型:这种题型没有给定地图,一般是一串数字或字母,要求按照一定的任务解题。相对于地图型,这种题型较为抽象,需要在数据中进行搜索。数据以数组的形式存储,那么只要将数组也当作一张图来进行搜索就可以了。
HDU 1016-Prime Ring Problem(回溯、素数筛)
HDU 1258-Sum It Up(双重DFS递归,去重技巧)
HDU 1015-Safecraker(回溯,字符处理)
HDU 2676-Sudoku(抽象,回溯)
DFS讲解的差不多了,开始BFS
BFS讲解 广度优先算法(Breadth-First Search),同广度优先搜索,又称作宽度优先搜索,或横向优先搜索,简称BFS,是一种图形搜索演算法。简单的说,BFS是从根节点开始,沿着树的宽度遍历树的节点,如果发现目标,则演算终止。
BFS实现步骤
1.选择一个起始点放入一个先进先出的队列中;
2. 如果队列不为空,弹出一个队列首元素,记为当前结点,执行3;否则算法结束;
3. 将与 当前结点 相邻并且尚未被访问的结点的信息进行更新,并且全部放入队列中,继续执行2;
图解
文章图片
文章图片
搜索过程:假设A是起点,访问与他距离为一的节点,A->B->D->E,B节点相邻有C节点B->C,D节点相邻节点G,节点E没有相邻任何未被访问过的节点…搜索路径A->B->D->E->C->G->F->H->I。
核心代码
bool BFS(Node& Vs, Node& Vd){
queue Q;
Node Vn, Vw;
int i;
//初始状态将起点放进队列Q
Q.push(Vs);
hash(Vw) = true;
//设置节点已经访问过了!
while (!Q.empty()){//队列不为空,继续搜索!
//取出队列的头Vn
Vn = Q.front();
//从队列中移除
Q.pop();
while(Vw = Vn通过某规则能够到达的节点){
if (Vw == Vd){//找到终点了!
//把路径记录,这里没给出解法
return true;
//返回
}
if (isValid(Vw) && !visit[Vw]){
//Vw是一个合法的节点
Q.push(Vw);
//加入队列Q
hash(Vw) = true;
}
}
}
return false;
//无解
}
迷宫问题
定义一个二维数组:
int maze[5][5] = {
0, 1, 0, 0, 0,
0, 1, 0, 1, 0,
0, 0, 0, 0, 0,
0, 1, 1, 1, 0,
0, 0, 0, 1, 0,
};
它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的最短路线。Input一个5 × 5的二维数组,表示一个迷宫。数据保证有唯一解。Output左上角到右下角的最短路径,格式如样例所示。
Sample Input
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
Sample Output
(0, 0)
(1, 0)
(2, 0)
(2, 1)
(2, 2)
(2, 3)
(2, 4)
(3, 4)
(4, 4)
#include
using namespace std;
typedef struct node{
int x;
int y;
int pre;
}q;
q queue[50];
int front=0;
int rear=0;
int dis[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
bool vis[5][5];
int a[5][5];
void bfs(int x1,int y1,int x2,int y2)
{
queue[0].x=x1;
queue[0].y=y1;
queue[0].pre=-1;
rear=rear+1;
vis[x1][y1]=true;
while(front4||y3<0||y3>4||a[x3][y3]==1||vis[x3][y3]==true)
{continue;
}
queue[rear].x=x3;
queue[rear].y=y3;
queue[rear].pre=front;
rear++;
vis[x3][y3]=true;
if(x3==x2&&y3==y2)
return ;
}
front++;
}
}
void print(q qq)
{
if(qq.pre==-1)
cout<<"("<>a[i][j];
bfs(0,0,4,4);
print(queue[rear-1]);
return 0;
}
DFS和BFS区别
二、区别
广度优先搜索算法(Breadth-First-Search,缩写为 BFS),是一种利用队列实现的搜索算法。简单来说,其搜索过程和 “湖面丢进一块石头激起层层涟漪” 类似。
深度优先搜索算法(Depth-First-Search,缩写为 DFS),是一种利用递归实现的搜索算法。简单来说,其搜索过程和 “不撞南墙不回头” 类似。
BFS 的重点在于队列,而 DFS 的重点在于递归。这是它们的本质区别。
举个典型例子,如下图,灰色代表墙壁,绿色代表起点,红色代表终点,规定每次只能走一步,且只能往下或右走。求一条绿色到红色的最短路径。
文章图片
对于上面的问题,BFS 和 DFS 都可以求出结果,它们的区别就是在复杂度上存在差异。我可以先告诉你,该题 BFS 是较佳算法。
DFS示意图
文章图片
DFS会沿着一个方向直到可以到达要求点或者是到达一定的条件才会结束,把DFS想成一个“铁头娃”,不撞南墙不回头。
BFS示意图
文章图片
BFS就像是石头砸向湖面激起的涟漪一样,从中心一点,向四周延伸;也可以想成你的眼镜掉在地上,你趴在地上进行寻找,你总是先寻找最近的地方,如果没有你,向远处在寻找…直到找到目标为止。
实现方法 | 基本思想 | 解决问题 | 规模 | |
---|---|---|---|---|
DFS | 栈/递归 | 回溯法,一次访问一条路,更接近人的思维方式 | 所有解问题,或连通性问题 | 不能太大,<=200 |
BFS | 队列 | 分治限界法,一次访问多条路,每一层需要存储大量信息 | 最优解问题,如最短路径 | 可以比较大,因为可以用队列解决,<=1000 |
BFS一般应用在寻找一条单一的最短路径,而DFS则是寻找符合条件的所有解,而且DFS寻找到的不一定是最优解,需要配合高效的剪枝算法才可以。
想要熟练的掌握DFS和BFS,需要多多练题,多多练题,多多练题。
ps:第一次写博客,可能不是很好,望大家多多包涵,凑活着看。