简介:
深度优先搜索(depth-first-search)简称DFS,其特点的就是一条路走到黑,不撞南墙不回头。在算法竞赛中DFS算法应用算是最广泛。它从某个状态开始,不断地转移状态直到无法转移,然后回退到前一步的状态,继续转移到其他的状态,(可以利用这个特点进行提前返回无法搜到的路,这个也叫做剪枝,这也是避免DFS算法超时的技巧)如此不断重复,直到找到最终的解。
深度优先搜索应采用递归实现比较简单。
DFS一般框架:
void dfs(int step){
if(step == n){//判断边界
return ;//返回
}
for(int i = 0; i<= n; i++){//尝试每一种可能
dfs(step + 1); //继续下一步
}
}
dfs 和递归的区别是,dfs 是一种算法,注重的是思想,而递归是编程语言的一种写法。我们通过递归的写法来实现 dfs。下面我们通过一个实际问题来理解 dfs 到底干了什么。
迷宫问题
用 2 维数组来表示一个迷宫
- S##.
- . . . .
- ###T
我们尝试用dfs 来求解这个问题。先找到起点,每个点按照左,下,右,上的顺序尝试,从起点‘S’开始,走到下一个点以后,把这个点再当做起点‘S’继续按照顺序尝试,如果某个点上下左右都尝试走过了以后,便把起点回到走到这个点的点。继续尝试其他方向。直到所有点都尝试走了上下左右。好比你自己去走这个迷宫,你也要一个方向一个方向的尝试着走,如果这条路不行,就回头,尝试下一条路,现在由程序来完成这个过程。
废话不多说,直接上代码。
实现代码
import java.util.*;
public class Main {
static String[] mat = new String[11]; // 地图信息
static int ans, n, m; // ans 用来记录最后答案 n, m 表示行列
static int dir[][] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}}; //对应的上下左右4各方向
static boolean vis[][] = new boolean[11][11];// 用来标记是否访问
static void dfs(int x, int y) {
vis[x][y] = true;
if(mat[x].charAt(y) == 'T'){
ans ++;
return;
}
int next_x = 0;int next_y = 0;
for(int i = 0; i< dir.length; i++){
next_x = x+dir[i][0];
next_y = y+dir[i][1];
if(next_x<0||next_x>=n||next_y<0||next_y>=m) continue;
if(vis[next_x][next_y] == false&&mat[next_x].charAt(next_y)!='#'){
vis[next_x][next_y] = true;
dfs(next_x,next_y);
vis[next_x][next_y] = false;
}
}
vis[x][y] = false;
}
public static void main(String[] args) {
Scanner cin = new Scanner(System.in);
n = cin.nextInt();
m = cin.nextInt();
for (int i = 0; i < n; ++i) {
mat[i] = cin.next();
}
int x = 0, y = 0;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (mat[i].charAt(j) == 'S') {
x = i;
y = j;
}
}
}
ans = 0;
dfs(x, y);
System.out.println(ans);
}
}
抽象式的dfs
前面用的 dfs 算法都是比较形象的,很容易想象搜索过程,接下来我们看看一些需要抽象成 dfs 的问题。实际上,我们所遇到的大多数问题都是需要抽象成 dfs 的形式才能应用 dfs 进行搜索的。看下面一个问题。
给出 n 个整数,要求从里面选出 k 个整数,使的选出来的数的和为 S。
我们可以用 dfs 来抽象这样的枚举过程。在第一层 dfs 的时候,我们可以选择是否加上第一个数,如果加上第一个数,和值加上第一个数,dfs 进入到下一层,否则 dfs 直接进入到下一层,在第二层,对第二个数做同样的处理,dfs 的过程中记录已经选取的数的个数,如果已经选取了 k 个数,判断和值是否是 S。对于每一层,我们都有 2 个择,选和不选,不同的选择,都会使得搜索进入 2 个完全不同的分支进行搜索。
还是以一道题为例
买书
小明去书店买书,他有 m 元钱,书店里面有 n 本书,每本书的价格为 pi 元。小明很爱学习,想把身上钱都用来买书,并且刚好买 k 本书。请帮小明计算他是否能刚好用 m 元买 k 本书
输入格式
第一行输入 3 个整数m(1≤m≤100000000),n(1≤n≤30),k(1≤k≤min(8,n))
接下来一行输入 n 个整数,表示每本书的价格pi (1≤pi≤100000000)。
输出格式
如果小明能 刚好 用 m 元买 k 本书,输入一行“Yes” 否则输出“No”。
样例输入
10 4 4
1 2 3 4
样例输出
Yes
实现代码
import java.util.Scanner;
public class Main{
static int box [] = new int [40];
static int a [] = new int [40];
static int map [] = new int [40];
private static int k;
private static long m;
private static int n;
public static void main(String[] args) {
Scanner cin = new Scanner(System.in);
m = cin.nextLong(); n = cin.nextInt();k = cin.nextInt();
for (int i = 0; i <n; i++) {
a[i] = cin.nextInt();
}
dfs(a,0,0,0);
System.out.println("No");
}
static void dfs(int [] a, int step,long sum,int pos){
if(step>k||sum>m)return;
if(step == k && sum == m){
System.out.println("Yes");
System.exit(0);
return;
}
for (int i = pos; i <n; i++) {
if(box[i] == 0){
box[i] = 1;
map[step] = a[i];
dfs(a,step+1,sum+a[i],i+1);
box[i] = 0;
}
}
}
}
八皇后问题
我们再来看一道很经典的问题—八皇后问题,这也是检验有一个人有没学会回溯法的问题。输入格式
每个棋盘有 64 个数字,分成 8行 8 列输入,就如样例所示。棋盘上每一个数字均小于 100。
输出格式
输出一个最大的总和
样例输入
1 2 3 4 5 6 7 8
9 10 11 12 13 14 15 16
17 18 19 20 21 22 23 24
25 26 27 28 29 30 31 32
33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48
48 50 51 52 53 54 55 56
57 58 59 60 61 62 63 64
样例输出
260
实现代码
import java.util.Scanner;
public class Main {
private static int n;
static int a[] = new int [100];
static int box [] = new int [100];
static int max = -1;
private static int map[][] = new int [8][8];
public static void main(String[] args) {
Scanner cin = new Scanner(System.in);
for (int i = 0; i <8; i++) {
for (int j = 0; j <8; j++) {
map[i][j] = cin.nextInt();
}
}
Dfs(0);
System.out.println(max);
}
static void Dfs( int step ){ //行
if(step == 8){
int sum = 0;
for (int i = 0; i <step; i++) {
sum+=map[i][a[i]];
}
if(sum>max) max = sum;
return;
}
for (int i = 0; i <8; i++) {//列
boolean isduijiaoxian = false;
for (int j = 0; j <step; j++) //行
if(Math.abs(a[j]-i)==Math.abs(j-step)){//判断是否在同一个对角线
isduijiaoxian = true;
break;
}
if(box[i] == 0&&isduijiaoxian == false){
box[i] = 1;
a[step] = i;
Dfs(step+1);
box[i] = 0;
}
}
}
}