关于dfs的剪枝

深度优先搜索是一种搜索策略,但是他是通过递归(或循环)来实现的,那么时间复杂度便不会低。通过dfs,我们可以得到一颗搜索树,但实际上这棵树的许多枝条是不需要的,那么我们没必要对这个分支进行搜索,砍掉这个子树,便是剪枝。

1.可行性剪枝

   如果当前条件不合法就不再继续搜索,直接return。这是非常好理解的剪枝。

    问题:给定n个数,要求选出k个数,使得选出的数和为sum。

代码如下:

import java.util.Scanner;

public class L1 {

	static int n, k, sum, ans = 0;
	static int[] a = new int[1005];

	static void dfs(int i, int cnt, int s) {
		if (cnt > k)// 限制条件1,选的个数超过k
			return;
		if (s > sum)// 限制条件2,和大于sum
			return;
		if (i == n) {
			if (cnt == k && s == sum) {
				ans++;
			}
			return;
		}
		dfs(i + 1, cnt, s);
		dfs(i + 1, cnt + 1, s + a[i]);
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		k = sc.nextInt();
		sum = sc.nextInt();
		for (int i = 0; i < n; i++)
			a[i] = sc.nextInt();

		dfs(0, 0, 0);
		System.out.println(ans);
	}

}

2.最优性剪枝

如果当前条件所创造出的答案必定比之前的答案大,那么剩下的搜索就毫无必要,甚至可以剪掉。我们利用某个函数估计出此时条件下答案的‘下界’,将它与已经推出的答案相比,如果不比当前答案小,就可以剪掉。尤其是求解迷宫最优解时。

题目:有一个n x m大小的迷宫,字符 'S'表示起点,字符'T'表示终点, '*'表示墙壁,字符'.'表示平地。求S到T的最少步数。

代码如下:

import java.util.Scanner;

public class L9 {

	static int n, m, ans = Integer.MAX_VALUE;
	static char[][] map = new char[110][110];
	static boolean[][] vis = new boolean[110][110];
	static int[][] dir = { { -1, 0 }, { 0, -1 }, { 1, 0 }, { 0, 1 } };

	static boolean in(int x, int y) {
		return 0 <= x && x < n && 0 <= y && y < m;
	}

	static void dfs(int x, int y, int step) {
		if (step >= ans) {// 如果步数已经超过当前最优解,那么直接剪掉
			return;
		}
		if (map[x][y] == 'T') {
			ans = step;
			return;
		}
		vis[x][y] = true;
		for (int i = 0; i < 4; i++) {
			int tx = x + dir[i][0];
			int ty = y + dir[i][1];
			if (in(tx, ty) && !vis[tx][ty] && map[tx][ty] != '*') {
				dfs(tx, ty, step + 1);
			}
		}
		vis[x][y] = false;
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		m = sc.nextInt();
		for (int i = 0; i < n; i++) {
			map[i] = sc.next().toCharArray();
		}
		int x=0,y=0;
		for(int i=0;i<n;i++) {
			for(int j=0;j<m;j++) {
				if(map[i][j]=='S') {
					x=i;
					y=j;
				}
			}
		}
		dfs(x,y,0);
		System.out.println(ans);
	}

}

3.重复性剪枝

对于某一些特定的搜索方式,一个方案会被搜索很多次,这样是没有必要的。

对于第一题,通过每次选取一个数,下次再选另外的数的思路,可以通过规定选取数的次序使用重复性剪枝。

代码如下:

import java.util.Scanner;

public class L2 {

	static int n, k, sum, ans;
	static int[] a = new int[40];
	static boolean[] xuan = new boolean[40];

	static void dfs(int s, int cnt, int pos) {
		if (s > sum || cnt > k)
			return;
		if (s == sum && cnt == k) {
			ans++;
			return;
		}
		for (int i = pos; i < n; i++) {
			if (!xuan[i]) {
				xuan[i] = true;
				dfs(s + a[i], cnt + 1, i + 1);
				xuan[i] = false;
			}
		}

	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		k = sc.nextInt();
		sum = sc.nextInt();
		for (int i = 0; i < n; i++)
			a[i] = sc.nextInt();
		dfs(0, 0, 0);
		System.out.println(ans);
	}

}

4.奇偶性剪枝

先看题目:有一个n x m 的迷宫,其中字符 ‘S’代表起点,字符‘D’代表终点,字符‘X’代表墙壁,字符‘.’代表平地,从S到D,每次行动消耗1时间,走过的路都会坍塌,因此不能原地不动或回头,现在一直大门会在T时间打开,判断在0时间能否逃离迷宫。

我们只需要dfs每条路径,只搜到T时间就可以了,但是还可以继续剪枝。

代码如下:

import java.util.Scanner;

public class L10 {

	static int n, m, T;
	static char[][] map = new char[10][10];
	static boolean[][] vis = new boolean[10][10];
	static int[] dx = { 0, 0, -1, 1 };
	static int[] dy = { 1, -1, 0, 0 };
	static boolean ok;

	static void dfs(int x, int y, int t) {
		if (ok)
			return;
		if (t == T) {
			if (map[x][y] == 'D')
				ok = true;
			return;
		}
		vis[x][y] = true;
		for (int i = 0; i < 4; i++) {
			int tx = x + dx[i];
			int ty = y + dy[i];
			if (tx < 0 || tx >= n || ty < 0 || ty >= m || map[tx][ty] == 'X' || vis[tx][ty])
				continue;
			dfs(tx, ty, t + 1);
		}
		vis[x][y] = false;
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		m = sc.nextInt();
		T = sc.nextInt();
		for (int i = 0; i < n; i++) {
			map[i] = sc.next().toCharArray();
		}
		int sx = 0, sy = 0, ex = 0, ey = 0;
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < m; j++) {
				if (map[i][j] == 'S') {
					sx = i;
					sy = j;
				}
				if (map[i][j] == 'D') {
					ex = i;
					ey = j;
				}
			}
			if ((sx + sy + ex + ey + T) % 2 != 1) {// 奇偶性剪枝
				System.out.println("NO");
			} else {
				ok = false;
				dfs(sx, sy, 0);
				if (ok) {
					System.out.println("Yes");
				} else {
					System.out.println("No");
				}
			}
		}
	}

}

猜你喜欢

转载自blog.csdn.net/qq_41921315/article/details/88026712
今日推荐