DFS算法的应用

DFS算法一般步骤

void dfs(int step)
{
	if(边界成立)
	{
		走到最深处
		。。。。。。
		return;
	}
	for(尝试每一种可能的状态)
	{
		if(如果这种状态可行){  //剪枝
            把这种可能的状态标记,表示走过 
            继续下一步dfs(step+1)   //状态转移
            把这种标记去除  //回溯
		}
	}
}

数独

你一定听说过“数独”游戏。
如下图所示,玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个同色九宫内的数字均含1-9,不重复。
数独的答案都是唯一的,所以,多个解也称为无解。
本图的数字据说是芬兰数学家花了3个月的时间设计出来的较难的题目。但对会使用计算机编程的你来说,恐怕易如反掌了。
本题的要求就是输入数独题目,程序输出数独的唯一解。我们保证所有已知数据的格式都是合法的,并且题目有唯一的解。
格式要求,输入9行,每行9个数字,0代表未知,其它数字为已知。
输出9行,每行9个数字表示数独的解。
输入:

005300000
800000020
070010500
400005300
010070006
003200080
060500009
004000030
000009700

程序应该输出:

145327698
839654127
672918543
496185372
218473956
753296481
367542819
984761235
521839764

再例如,输入:

800000000
003600000
070090200
050007000
000045700
000100030
001000068
008500010
090000400

程序应该输出:

812753649
943682175
675491283
154237896
369845721
287169534
521974368
438526917
796318452

package DFS;

import java.util.Scanner;

/**
 * @author: DreamCode
 * @file: 数独.java
 * @time: 2022年3月27日-下午2:53:20
 * @思路: DFS遍历每一种情况,对不满足约束条件的情况进行剪枝
 */
public class 数独 {
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		char[][] arr = new char[9][9];
		for (int i = 0; i < 9; i++) {
			arr[i] = scanner.nextLine().toCharArray();
		}
		DFS(arr, 0, 0);
	}

	private static void DFS(char[][] arr, int x, int y) {
		// TODO DFS遍历,x,y为当前遍历位置的坐标
		if (x == 9) { // 出口,二维表是从左到右,从上到下遍历
			print(arr);// 打印二维表
			return;
		}
		if (arr[x][y] == '0') { // 当前位置为0,需要进行填数
			for (int i = 1; i <= 9; i++) {
				if (check(arr, x, y, i)) { // 填入当前数合规
					arr[x][y] = (char) (i + '0');
					DFS(arr, x + (y + 1) / 9, (y + 1) % 9); // x与y的取值范围都是[0~8]
				}
			}
			arr[x][y] = '0'; // 回溯
		} else {// 当前位置不为0,直接进入下一层;
			DFS(arr, x + (y + 1) / 8, (y + 1) % 8); // x与y的取值范围都是[0~8]
		}
	}

	private static void print(char[][] arr) {
		// TODO 打印字符二维表
		for (int i = 0; i < arr.length; i++) {
			for (int j = 0; j < arr[i].length; j++) {
				System.out.print(arr[i][j]);
			}
			System.out.println();
		}

	}

	private static boolean check(char[][] arr, int x, int y, int num) {
		// TODO 检查当前填入的数是否合规
		// 行检查
		for (int i = 0; i < 9; i++) {
			if ((arr[x][i] - '0') == num) {
				return false;
			}
		}
		// 列检查
		for (int i = 0; i < 9; i++) {
			if ((arr[i][y] - '0') == num) {
				return false;
			}
		}
		// 方格检查
		for (int i = (x / 3) * 3; i < (x / 3 + 1) * 3; i++) {
			for (int j = (y / 3) * 3; j < (y / 3 + 1) * 3; j++) {
				if ((arr[i][j] - '0') == num) {
					return false;
				}
			}
		}
		return true;
	}

}

部分和

给定整数序列a1,a2,…,an,判断是否可以从中选出若干数,使它们的和恰好为k. 1≤n≤20,-108≤ai≤108,-108≤k≤108

样例:

输入

n=4
a={1,2,4,7}
k=13

输出:

Yes (13 = 2 + 4 + 7)

package DFS;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Scanner;

/**
 * @author: DreamCode
 * @file: 部分和.java
 * @time: 2022年3月27日-下午3:19:53
 * @思路: 思路1:遍历其非空子集列表,如果子集满足则输出;思路2:DFS遍历所有组合情况,按照约束条件进行剪枝
 */
public class 部分和 {
    
	static List<Integer> container = new ArrayList<>(); // 记录当前选择的数字
	static int K;

	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		int n = scanner.nextInt();
		int k = scanner.nextInt();
		K = k;
		Integer[] arr = new Integer[n];
		for (int i = 0; i < n; i++) {
			arr[i] = scanner.nextInt();
		}
		// 对数组arr从小到大排序,保证结果是按照字典序从小打到输出各数
		Arrays.sort(arr);
		DFS(arr, k, 0); // 深搜算法
		SubSet(arr, k);

	}

	private static void SubSet(Integer[] arr, int k) {
		// TODO 采用二进制求非空子集解决部分和问题
		Arrays.sort(arr, new Comparator<Integer>() {

			@Override
			public int compare(Integer o1, Integer o2) {
				return o1 < o2 ? -1 : 1;
			}
		});
		int len = arr.length;
		ArrayList<ArrayList<Integer>> container = new ArrayList<>();
		for (int i = (int) Math.pow(2, arr.length) - 1; i > 0; i--) { // 获取每位的二进制数
			ArrayList<Integer> subSet_container = new ArrayList<>();
			for (int j = len - 1; j >= 0; j--) { // 分别获取数字的每个二进制数字
				if (((i >> j) & 1) == 1) { // 该二进制位为1,表示选择
					subSet_container.add(arr[j]);
				}
			}
			container.add(subSet_container);
		}
		FindResult(container, k); // 获取正确结果
	}

	private static void FindResult(ArrayList<ArrayList<Integer>> container, int k) {
		// TODO 从非空子集中找到满足约束条件的结果
		for (int i = container.size() - 1; i >= 0; i--) { // 从后往前遍历
			int sum = 0;
			for (int j = container.get(i).size() - 1; j >= 0; j--) {
				sum += container.get(i).get(j);
			}
			if (sum == k) {
				System.out.print("yes (" + K + " = ");
				for (int j = container.get(i).size() - 1; j >= 0; j--) {
					System.out.print(j == 0? container.get(i).get(j) : container.get(i).get(j) + " + ");
				}
				System.out.println(")");
			}
		}
	}

	private static void DFS(Integer[] arr, int k, int i) {
		// TODO 判断第i位数是否需要选择,两种分支,要么选,要么不选
		if (i == arr.length || k < 0) { // 出口:如果当前已经选完了一遍所有数或者K已经不足以支持继续选择
			if (k == 0) { // 满足越苏条件
				System.out.print("yes (" + K + " = ");
				for (int j = 0; j < container.size(); j++) {
					System.out.print(j == container.size() - 1 ? container.get(j) : container.get(j) + " + ");
				}
				System.out.println(")");
			}
			return;
		}
		container.add(arr[i]);
		DFS(arr, k - arr[i], i + 1); // 选择第i位数
		container.remove(container.indexOf(arr[i])); // 回溯
		DFS(arr, k, i + 1); // 不选择第i位数

	}

}

水洼数目

有一个大小为 N*M 的园子,雨后积起了水。八连通的积水被认为是连接在一起的。请求出
园子里总共有多少水洼?(八连通指的是下图中相对 W 的的部分)

***
*W*
***

限制条件

N, M ≤ 100

样例:

输入
N=10, M=12

园子如下图('W’表示积水, '.'表示没有积水)

W........WW.
.WWW.....WWW
....WW...WW.
.........WW.
.........W..
..W......W..
.W.W.....WW.
W.W.W.....W.
.W.W......W.
..W.......W.

输出

3

package DFS;

import java.util.Scanner;

/**
 * @author: DreamCode
 * @file: 水洼数目.java
 * @time: 2022年3月27日-下午4:14:17
 * @思路: DFS问题,如果当前为W,则将当前W变为 .
 * 同时向其8个方向中是W的进行状态转移,每一次遍历能活动一个水洼,也能消除一个水洼。直到没有W,当前的遍历次数即为水洼数目
 */
public class 水洼数目 {

	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		int N = scanner.nextInt();
		int M = scanner.nextInt();
		char[][] arr = new char[N][M];
		for (int i = 0; i < N; i++) {
			arr[i] = scanner.next().toCharArray();
		}
		int count = 0;
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < M; j++) {
				if (arr[i][j] == 'W') {
					DFS(arr, i, j);
					count++;
				}
			}
		}
		System.out.println(count);

	}

	private static void DFS(char[][] arr, int x, int y) {
		// TODO DFS
		arr[x][y] = '.';
		for (int i = -1; i < 2; i++) {
			for (int j = -1; j < 2; j++) {
				if ((x + i) >= 0 && (x + i) < arr.length && (y + j) >= 0 && (y + j) < arr[x].length
						&& arr[x + i][y + j] == 'W') { // 该方向可以扩展
					DFS(arr, x + i, y + j);
				}
			}
		}

	}

}

n皇后问题

请设计一种算法,解决著名的n皇后问题。这里的n皇后问题指在一个n*n的棋盘上放置n个棋子, 使得每行每列和每条对角线上都只有一个棋子,求其摆放的方法数。给定一个int n,请返回方法数,保证n小于等于15

package DFS;

import java.util.Iterator;
import java.util.Scanner;

/**
 * @author: DreamCode
 * @file: n皇后问题.java
 * @time: 2022年3月28日-下午8:42:30
 * @思路: DFS+剪枝,这里的剪枝主要是:同行不能出现两个,同列不能出现两个,正负对角线不能出现两个(正对角线y-x相同,负对角线x+y相同)
 */
public class n皇后问题 {

	static int ans;
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		int n = scanner.nextInt();
		int[][] arr = new int[n + 1][n + 1];
		DFS(arr, 1); // 从第一行开始选择
		System.out.println(ans);
	}

	private static void DFS(int[][] arr, int row) {
		// TODO DFS+剪枝
		if(row==arr.length) {
			ans++;
			print(arr);
			return;
		}
		for (int i = 1; i < arr[row].length; i++) { // 从1到n列总共n中列选择
			if (check(arr, row, i)) {// 当前位置可以放个皇后
				arr[row][i] = 1;
				DFS(arr, row + 1);
				arr[row][i] = 0;// 回溯	
			}
		}
	}

	private static void print(int[][] arr) {
		// TODO 打印棋盘
		for(int i=1;i<arr.length;i++) {
			for(int j=1;j<arr[i].length;j++) {
				System.out.print(arr[i][j]);
			}
			System.out.println();
		}
		System.out.println();
		
	}

	private static boolean check(int[][] arr, int row, int col) {
		// TODO 剪枝
		for (int i = 1; i < arr.length; i++) {// 检查当前列
			if (arr[i][col] == 1 && i != row) {
				return false;
			}
		}
		for (int i = 1; i < arr[row].length; i++) {// 检查当前列
			if (arr[row][i] == 1 && i != col) {
				return false;
			}
		}
		for (int i = 1; i < arr.length; i++) {// 检查对角线
			for (int j = 1; j < arr[i].length; j++) {
				if ((i - j) == (row - col) && arr[i][j] == 1) {// 正对角线
					return false;
				}
				if ((i + j) == (row + col) && arr[i][j] == 1) {// 副对角线
					return false;
				}
			}
		}
		return true;
	}

}

素数环

输入正整数n,对1-n进行排列,使得相邻两个数之和均为素数,输出时从整数1开始,逆时针排列。同一个环应恰好输出一次。n<=16

如输入:6

输出:

1 4 3 2 5 6

1 6 5 2 3 4

package DFS;

import java.util.Scanner;

/**
 * @author: DreamCode
 * @file: 素数环.java
 * @time: 2022年3月29日-下午2:43:27
 * @思路: 方法1:全排列结合逐个判断;方法2:DFS结合剪枝
 */
public class 素数环 {

	static int[] rec;
	public static void main(String[] args) {
		Scanner scanner=new Scanner(System.in);
		int n=scanner.nextInt();
		int arr[]=new int[n];
		rec=new int[n];
		rec[0]=1;
		DFS(1);

	}
	private static void DFS(int k) {
		if(k==rec.length) {
			if(isPrime(rec[k-1]+rec[0])) {
				printArr();
				return;
			}
		}
		for(int i=2;i<=rec.length;i++) {  //从第k位可以填的数未2~n
			if(check(k,i)) {
				rec[k]=i;
				DFS(k+1);
				rec[k]=0;
			}
			
		}
		
	}
	private static void printArr() {
		// TODO 打印rec数组
		for(int i:rec) {
			System.out.print(i+" ");
		}
		System.out.println();
		
	}
	private static boolean check(int k, int num) {
		// TODO 检查第k位置放数字num是否符合要求
		if(!isPrime(num+rec[k-1])) {
			return false;
		}
		for(int i=0;i<k;i++) {  //是否出现过
			if(rec[i]==num) {
				return false;
			}
		}
		return true;
	}
	private static boolean isPrime(int num) {
		// TODO 判断num是否为素数
		for(int i=2;i<=Math.sqrt(num);i++) {
			if(num%i==0) {
				return false;
			}
		}
		return true;
	}

}

困难的串

问题描述:如果一个字符串包含两个相邻的重复子串,则称它为容易的串,其他串称为困难的串,如:BB,ABCDACABCAB,ABCDABCD都是容易的,A,AB,ABA,D,DC,ABDAB,CBABCBA都是困难的。

输入正整数n,L,输出由前L个字符(大写英文字母)组成的,字典序第n小的困难的串。
例如,当L=3时,前7个困难的串分别为:

A,AB,ABA,ABAC,ABACA,ABACAB,ABACABA
n指定为4的话,输出ABAC

package DFS;

import java.util.Scanner;

/**
 * @author: DreamCode
 * @file: 困难的串.java
 * @time: 2022年3月29日-下午7:45:11
 * @思路: DFS遍历+回溯+剪枝;困难串的判定方法:从尾到头,按照字符长度由1到n/2逐渐遍历,判断是否有重复字串,
 *      例如ABA,当前需要判断加字母C是否为困难的串,则先判断A与C,再判断AB与AC,结果都为false,所以为困难的串
 */
public class 困难的串 {

	static int L;
	static int n;
	static int count=0;
	public static void main(String[] args) {
		Scanner scanner=new Scanner(System.in);
		n=scanner.nextInt();
		L=scanner.nextInt();
		DFS("");

	}
	private static void DFS(String prefix) {
		// TODO 当前字符串的前缀
		if(count==n) {
			System.out.println(prefix);
			System.exit(0);
		}
		for(int i=0;i<L;i++) {
			char c = (char)(i+'A');
			if(isHard(prefix,c)) {
				count++;
				DFS(prefix+c);
			}
		}
		
	}
	private static boolean isHard(String prefix, char c) {
		// TODO 判断当前前缀prefix加如字符c以后是否是困难的串
		int count=0;  //记录当前有重复字符的长度
		for(int j=prefix.length();j>0;j-=2) {
			String s1 = prefix.substring(j-1,prefix.length()-count);
			String s2 = prefix.substring(prefix.length()-count)+c;
			if(s1.equals(s2)) {
				return false;
			}else {
				count+=1;
			}
		}
		return true;
	}

}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦码城

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值