深度优先搜索入门

简介:

深度优先搜索(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  维数组来表示一个迷宫

  1. S##.
  2. . . . .
  3. ###T
‘S’表示起点,‘T’表示终点,‘#’表示墙壁,‘ . ‘表示平地。你需要从‘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(1m100000000)n(1n30)k(1kmin(8,n))

接下来一行输入 n 个整数,表示每本书的价格pi (1pi100000000)

输出格式

如果小明能 刚好 用 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;
			}
		}
	}
}


八皇后问题

我们再来看一道很经典的问题—八皇后问题,这也是检验有一个人有没学会回溯法的问题。
小明在和朋友下国际象棋,下的时候突发奇想,在国际象棋棋盘的每个格子上写下 1 到 99 内的数字,又拿出了珍藏已久的 8 个皇后棋子。国际象棋中的皇后可以将同一行、同一列和同一对角线上的对方棋子吃掉。小明在想,怎么摆放这 8 个皇后的位置才能让她们不能互相攻击,同时这 8 个皇后占的格子上的数字总和最大。
小明来求助热爱算法的你了,你能帮他算出答案吗?

输入格式

每个棋盘有 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;
			}
		}
	}
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值