手撸代码系列(十一)--DFS专题

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 1n 进行排列,使得相邻的两个数之和均为素数头尾相加也要为素数。输出时从整数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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值