深度优先搜索(DFS)
1.1 定义
先说什么是搜索。搜索算法是利用计算机的高性能来有目的的穷举一个问题解空间的部分或所有的可能情况,从而求出问题的解的一种方法。在忽略效率的情况下,没有什么是搜索解决不了的(?
深度优先搜索(DFS)是基于递归的搜索。
想象你在走迷宫:
1、 这个方向有路可走,我没走过
2、 往这个方向前进
3、 是死胡同(到达了已经走过的地方),往回走,回到上一个路口
4、 重复第一步,直到找着出口
通过这种方式,只要地点的数量是有限的,你就一定可以在有限的时间找到迷宫的出口(或者确定这个迷宫没有出口)。
只要可以前进就往前走,这就是深度优先搜索的思路流程。
1.2 例题
1.2.1 全排列
题目:
输出1~n所有的全排列。
如1~3的全排列:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
排列:一般地,从n个不同元素中取出m(m≤n)个元素,按照一定的顺序排成一列,叫做从n个元素中取出m个元素的一个排列(permutation)。特别地,当m=n时,这个排列被称作全排列(all permutation)。
思路:
搜索。
代码:
#include <stdio.h>
#define N 1001
int v[N],a[N],n;
void dfs(int t)
{
if (t==n+1)
{
for (int i=1;i<=n;++i) printf("%d ",a[i]);
printf("\n");
return;
}
for (int i=1;i<=n;++i)
if (!v[i])
{
a[t]=i,v[i]=1;
dfs(t+1);
v[i]=0;
}
}
int main()
{
scanf("%d",&n);
dfs(1);
}
分析:
为什么用搜索来完成这项工作?
假设n=3,我们可以用循环来简单的完成这份代码:
#include <stdio.h>
int n;
int main()
{
for (int i=1;i<=3;++i)
for (int j=1;j<=3;++j)
if (i!=j)
for (int k=1;k<=3;++k)
if (k!=i&&k!=j)
printf("%d %d %d\n",i,j,k);
}
那么当n=9时,我们虽然还是可以这样完成工作,但反正我不想写一写(
然而n是未知数,我们需要完成许多分这样功能上几乎没什么差异的代码。这非常不优美,也没有充分利用计算机擅长进行重复性工作的特长。而利用递归式的深搜算法就可以简单的实现1-n的全排列。
下面利用例2.2.1的代码对搜索算法的代码流程进行分析:
- 递归的终止条件。
我们的递归不能无限制的进行下去,需要一个终止条件,就像迷宫的出口(或者已经遍历了所有的地点)。在这个题中,递归的终止条件即已经确定了n个数。
当我们已经确定了n个数时,我们输出一个排列,并停止继续寻找,返回上一层。- 遍历待选集合。
当我们执行到第十二行时,我们所知道的信息有:已经有t-1位数字确定了(同样有t-1个数字已经被使用过了),第t位数字一定是1~n中的某一个,而且不能是已经使用过的数字。v[i]存储了数字i是否已经被使用过的信息。
我们需要确定第t位数字是谁。 在1到n中找到没有用过的数字i,i可以被放在位置t上。- 进入下一层递归。
把i放在第t个位置上。 给i打上用过了的标记。 进入下一层递归dfs(t+1)。
?- 回溯。
即还原回未前往改点时的状态。 取消i用过了的标记。 清空第t个位置。(在这个题里没有影响)
这样我们就可以得到搜索算法的结构框架:
1.2.2 细胞
题目:
一nm的矩形阵列由数字 0到 9组成(n和m不超过1000),数字 1到 9代表细胞,细胞的定义为沿细胞数字上下左右若还是细胞数字则为同一细胞,求给定矩形阵列的细胞个数。如以下410的矩阵中,共有4个细胞(已用不同底纹标明)。
思路:
搜索。
一种非常常见的搜索题型,矩阵的遍历。周黑鸭那场比赛和我们两次半月赛的搜索题都是这种题型。即扫描整张地图,遇到符合要求的点就进行dfs拓展,覆盖所有通路,统计答案。
代码:
#include <stdio.h>
int m,n,t=0,b[1001][1001];
int dfs(int x,int y)
{
b[x][y]=0;
if (b[x+1][y]==1) dfs(x+1,y);
if (b[x-1][y]==1) dfs(x-1,y);
if (b[x][y+1]==1) dfs(x,y+1);
if (b[x][y-1]==1) dfs(x,y-1);
}
int main()
{
scanf("%d%d",&n,&m);