DFS介绍
深度优先搜索(Depth First Search,简称DFS) 属于图算法的一种。其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次。
基本模板:
对于一个标准的DFS模板而言,其包括了以下的内容:
void dfs(int step){
判断边界{
相应操作
}
尝试每一种可能{
满足check条件
标记
继续下一步dfs(step+1)
恢复初始状态(回溯的时候要用到)
}
}
bool check(参数){
if(满足条件)
return true ;
return false;
}
练习题一:数独问题
根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个同色九宫内的数字均含1-9,不重复。
代码:
1. 伪代码
def(table,x,y){
// 出口
if(x==9){
print(table);
System.exit(0); // 因为只存在一种解,找到即可退出整个程序,而不是退出递归调用。
}
if(tablee[x][y]=='0'){
// 选1-9之间合法的数字填到x,y这个位置
for(int i = 1; i <= 9; i++){
// teble二维数组,x,y用于定位,i表示当前填写的数字,用于检查是否重复。
boolean res = check(table, x,y,i);
if(res){
// 转移到下一个状态
table[x][y] = (char)('0'+i);
// 这个x,y的遍历很巧妙
dfs(table, x+(y+1)/9, (y+1)%9);
}
}
// 回溯
table[x][y]='0';
}else{
// 继续找下一个需要处理的位置
dfs(table, x+(y+1)/9, (y+1)%9);
}
}
// teble二维数组,x,y用于定位,i表示当前填写的数字,用于检查是否重复。
check(table, x, y, i){
}
2. 完整java代码
import java.util.Scanner;
public class Sudoku{
public static void input(){
Scanner scanner = new Scanner(System.in);
char[][] table = new char[9][];
// for (int i = 0; i <= 9; i++) {
// table[i] = scanner.nextLine().toCharArray();
// }
table[0] = "005300000".toCharArray();
table[1] = "800000020".toCharArray();
table[2] = "070010500".toCharArray();
table[3] = "400005300".toCharArray();
table[4] = "010070006".toCharArray();
table[5] = "003200080".toCharArray();
table[6] = "060500009".toCharArray();
table[7] = "004000030".toCharArray();
table[8] = "000009700".toCharArray();
dfs(table,0,0);
scanner.close();
}
private static void dfs(char[][] table, int x, int y) {
if (x==9){
print(table);
// 唯一解,直接退出
System.exit(0);
}
if (table[x][y]=='0'){
for (int i = 1; i <= 9; i++) {
if (check(table, x, y, i)){
table[x][y] = (char)('0'+i);
dfs(table, x+(y+1)/9, (y+1)%9);
}
}
table[x][y] = '0';
}else {
dfs(table, x+(y+1)/9, (y+1)%9);
}
}
private static boolean check(char[][] table, int x, int y, int i) {
// 检查同行和同列
for (int j = 0; j < 9; j++) {
if (table[x][j]==(char)('0'+i))
return false;
if (table[j][y]==(char)('0'+i))
return false;
}
// 检查小九宫格,仔细琢磨,十分巧妙。
for (int j = (x/3)*3; j < (x/3+1)*3; j++) {
for (int k = (y/3)*3; k <(y/3+1)*3; k++) {
if (table[j][k]==(char)('0'+i))
return false;
}
}
return true;
}
private static void print(char[][] table) {
for (int i = 0; i < table.length; i++) {
for (int j = 0; j < table[0].length; j++) {
System.out.print(table[i][j]+" ");
}
System.out.println();
}
}
public static void main(String[] args) {
input();
}
}
练习题二:部分和问题
给定一个整数 k k k 和一个整数数列 a 1 , a 2 , ⋯ , a n {a_1},{a_2}, \cdots ,{a_n} a1,a2,⋯,an, 判断是否可以从中选出若干个数,使得它们的和恰好为 k k k。
样例:
输入:
n
n
n=4
a
a
a={1,2,4,7}
k
k
k=13
输出: Yes(13 = 2 + 4 + 7)
题目分析:
一看到本题,让我最先想到的是利用二进制思想,即每个数有两种状态,要么选要么不选,查看每种情况是否等于 k k k,如果有返回Yes,并与之对应的下标。此外我们还可以利用DFS解题。
Java代码:
import java.util.ArrayList;
import java.util.Scanner;
public class SubSum{
public static int kk = 0;
public static void input(){
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] arr = new int[n];
for (int i = 0; i < arr.length; i++) {
arr[i] = scanner.nextInt();
}
int target = scanner.nextInt();
kk = target;// 备份一下
dfs(arr,target,0, new ArrayList<Integer>());
scanner.close();
}
/**
* 部分和
* @param arr 待求和的数组
* @param target 和的目标值
*/
private static void dfs(int[] arr, int target, int cur, ArrayList<Integer> ans) {
if (target==0){
System.out.print("Yes ( "+kk+" = ");
for (int i = 0; i < ans.size(); i++) {
// System.out.print(ans.get(i)+(i==(ans.size()-1)?"":"+"));
System.out.print(ans.get(i));
if (i==(ans.size()-1)){
System.out.print("");
}else {
System.out.print(" + ");
}
}
System.out.println(" ) ");
System.exit(0);
}
if ((target<0) || (cur==arr.length)){
return;
}
dfs(arr,target,cur+1, ans);
ans.add(arr[cur]);
dfs(arr,target-arr[cur],cur+1, ans);
// 回溯
ans.remove(ans.size()-1);
}
public static void main(String[] args) {
input();
}
}
练习题三:水洼数目
有一个大小为N*M的园子,雨后起了积水,八连通的积水被认为是连接在一起的。请求出园子里总共有多少水洼。如下列字符组成的图,可以看出有两个水洼。
...WW
WWWW.
...W.
W....
WWW..
题目分析:
使用深度优先搜索(DFS),在某一处水洼,从8个方向查找,直到找到所有连通的积水。再次指定下一个水洼,直到没有水洼为止。
Java代码:
import java.util.Scanner;
public class Dfs3 {
static int count = 0;
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][];
// for (int i = 0; i < N; i++) {
// arr[i] = "W........WW.".toCharArray();
// }
arr[0] = "W........WW.".toCharArray();
arr[1] = ".WWW.....WWW".toCharArray();
arr[2] = "....WW...WW.".toCharArray();
arr[3] = ".........WW.".toCharArray();
arr[4] = ".........W..".toCharArray();
arr[5] = "..W......W..".toCharArray();
arr[6] = ".W.W.....WW.".toCharArray();
arr[7] = "W.W.W.....W.".toCharArray();
arr[8] = ".W.W......W.".toCharArray();
arr[9] = "..W.......W.".toCharArray();
int row = arr.length-1;
int col = arr[0].length-1;
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
if (arr[i][j]=='W'){
dfs(arr, i, j, row, col); // 清除一个水洼
count++;
}
}
}
System.out.println(count);
scanner.close();
}
private static void dfs(char[][] arr, int i, int j, int row, int col) {
arr[i][j]='.';
// 八个方向的检查,特别经典,牢记!!!
for (int k = -1; k <= 1; k++) {
for (int l = -1; l <= 1; l++) {
if (k==0 && l==0)
continue;
if (k+i>=0 && i+k<=row && j+l>=0 && j+l<=col){
if (arr[i+k][j+l]=='W'){
dfs(arr,i+k, j+l, row, col);
}
}
}
}
// // 上方
// if (i>0 && arr[i][j]=='W')
// dfs(arr, i-1, j);
// // 下方
// if (i<row && arr[i+1][j]=='W')
// dfs(arr, i+1, j);
// // 左方
// if (j>0 && arr[i][j-1]=='W')
// dfs(arr, i, j-1);
// // 右方
// if (j<col && arr[i][j+1]=='W')
// dfs(arr, i, j+1);
// // 左上方
// if (i>0 && j>0 && arr[i-1][j-1]=='W')
// dfs(arr, i-1, j-1);
// // 右上方
// if (i>0 && j<col && arr[i-1][j+1]=='W')
// dfs(arr, i-1, j+1);
// // 左下方
// if (i<row && j>0 && arr[i+1][j-1]=='W')
// dfs(arr, i+1, j-1);
// // 右下方
// if (i<row && j<col && arr[i+1][j+1]=='W')
// dfs(arr, i+1, j+1);
}
}
练习题一:N皇后问题
在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击 (同一行、同一列、同一斜线上的皇后都会自动攻击), 问有多少种摆法?
代码:
1. 伪代码
int count;
int n;
int[] rec; //下标代表行,值代表某一列
dfs(rec, row){
if(row==n+1){
count++;
return;
}
for(col from 1 to n){
// 检查是否合法
if(check(rec, row, col)){
rec[row] = col;
dfs(rec, row+1)
}
// 回溯
rec[row] = 0;
}
}
check(rec,x,y){
for(int i=0; i<rec.length; i++){
// 列
if(rec[i]==y){
return false;
}
// 负对角线
if(rec[i]+i == x+y){
return false;
}
// 主对角线
if(i-rec[i] == x-y){
return false;
}
}
return true;
}
2. 完整java代码
public class Quene{
public static int N = 4;
public static int count = 0;
public static int[] rec = new int[N];
public static void dfs(int row){
if (row==N){
count++;
return;
}
// 依次尝试在某一列上放一个皇后
for (int col = 0; col < N; col++) {
boolean flag = true;
for (int i = 0; i < row; i++){
if (rec[i]==col|| i+rec[i]==row+col||i-rec[i]==row-col){
flag = false;
break;
}
}
if (flag){
rec[row] = col;
dfs(row+1);
}
// 回溯
// rec[row]=0;
}
}
public static void main(String[] args) {
dfs(0);
System.out.println(count);
}
}
练习题五:素数环问题
输入一个正整数,对 1 ∼ n 1 \sim n 1∼n 进行排列,使得相邻的两个数之和均为素数;头尾相加也要为素数。输出时从整数1开始,逆时针排序。同一个输出一次。
输入:6
输出:
1 4 3 2 5 6
1 6 5 2 3 4
代码:
1. 伪代码
// 开辟一个长度为n的数组,存放n个元素。
int[] rec = new int[n];
// 第一个元素固定了,为1。
rec[0] = 1;
// 参数从第二个元素开始。
dfs(k){
if(k==n && [rec[0]+rec[n-1])]和是素数){
print(rec);
return;
}
// i表示待填写的数字
for(i from 2 to n){
// i没有在rec中出现
// i+rec[i-1]的和是素数
if(check(rec, i)){
rec[k] = i;
dfs(k+1);
rec[k] = 0;
}
}
}
2. 完整java代码
import java.util.Arrays;
import java.util.Scanner;
public class PrimeCircle{
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] rec = new int[n];
rec[0] = 1;
// 初始从第二个元素开始
dfs(rec, n, 1);
scanner.close();
}
private static void dfs(int[] rec, int n, int cur) {
// 填完最后一个元数,并且首尾相加为素数。
if (cur==n && isP(rec[0]+rec[n-1])){
print(rec);
return;
}
for (int i = 2; i <= n; i++) {
if (check(rec, i, cur)){
rec[cur] = i;
dfs(rec, n, cur+1);
rec[cur] = 0;
}
}
}
private static boolean check(int[] arr, int n, int cur) {
for (int ele: arr) {
if (ele==n || !isP(arr[cur-1]+n))
return false;
}
return true;
}
private static void print(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+(i == (arr.length-1)? "":" "));
}
System.out.println();
// System.out.println(Arrays.toString(arr));
}
private static boolean isP(int num) {
// 默认返回true
boolean flag = true;
for (int i = 2; i*i <= num; i++) {
if (num % i == 0){
flag = false;
return flag;
}
}
return flag;
}
}