成为dfs高手(一)

憧憬成为dfs高手

我对于dfs的理解可以分为三种,一种是在图/二维数组里对于路径的搜索的暴力;第二种是在树里去用dfs暴力拿一些路径;最后一种就是全排列对于做事顺序的暴力排列。这里主要讲后者。

dfs是一种暴力,搜索的艺术,而且做题也可以很公式化。

以最常见的全排列为例子

java 复制代码
import java.util.Scanner;

public class Main {
    static int n, a[] = new int[10005], f[] = new int[10005],jg[]=new int[10005];

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        dfs(0);
    }
    //wz是第几个数字
    static void dfs(int wz) {
        if(wz>n)return;
        if (wz == n) {
            solve();
        }
        for (int i = 0; i < n; i++) {
            if (f[i] == 0) {
                f[i] = 1;
                jg[wz]=i;
                dfs(wz + 1);
                jg[wz]=0;
                f[i] = 0;
            }
        }
    }
    //调用临界解决问题的方法
    static void solve() {
        for (int i = 0; i < n; i++) {
            System.out.printf("%d ",jg[i]);
        }
        System.out.println();
    }
}

可以看到这里其实就已经可以抽象为模板,利用fi作为标记,jgi存数字来枚举全排列。

那么在这个基础还做到什么呢,其实靠着这个全排列就已经解决很多问题了,这个就是最基础的暴力,我们可以把这个全排列的数字来作为ai的索引再在solve函数里做一些逻辑判断来解决问题。

对了这里的全排列是不考虑结果递增顺序的所以会有很多顺序变了,但是实际结果可能意指的,比如不考虑操作顺序的动作里 12 3和 2 1 3是一样的,这个时候要考虑产生递增的操作顺序结果。

java 复制代码
import java.util.Scanner;

public class Main {
    static int n, a[] = new int[10005], f[] = new int[10005],jg[]=new int[10005];

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        dfs(0,0);
    }
    //wz是第几个数字
    static void dfs(int wz,int ks) {
        if(wz>n)return;
        if (wz == n) {
            solve();
        }
        for (int i = ks; i < 10; i++) {
            if (f[i] == 0) {
                f[i] = 1;
                jg[wz]=i;
                dfs(wz + 1,i+1);
                jg[wz]=0;
                f[i] = 0;
            }
        }
    }
    //调用临界解决问题的方法
    static void solve() {
        for (int i = 0; i < n; i++) {
            System.out.printf("%d ",jg[i]);
        }
        System.out.println();
    }
}

上面的这个是在10个数字里选3个递增的数字,主要借助ks这个保存上一次的初始值,可以做到顺序不一项结果的暴力枚举。其实这里因为确定了起点fi去掉也没问题了,但是好习惯还是写上。

比如n皇后使用dfs来枚举所有棋子的坐标,这就等价于dfs来实现无法完成的n重for循环,因为实际写代码做不到写n重for循环。

java 复制代码
import java.util.Scanner;

public class Main {
    static int n, a[] = new int[10005], f[] = new int[10005],jg[]=new int[10005],sl;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        sl=n*n;
        dfs(0,0);
    }
    //wz是第几个数字
    static void dfs(int wz,int qs) {
        if (wz == n) {
            solve();
        }
        for (int i = qs; i < sl; i++) {
            if (f[i] == 0) {
                f[i] = 1;
                jg[wz]=i;
                dfs(wz + 1,i+1);
                f[i] = 0;
            }
        }
    }
    //调用临界解决问题的方法
    static void solve() {
        for (int i = 0; i < n; i++) {
            System.out.printf("%d %d\n",jg[i]/n,jg[i]%n);
        }
        System.out.println();
    }
}

我们只需要修改 for (int i = 0; i < sl; i++)这里就完成了n重for循环,再借助qs就完成了对n*n枚举n个递增的数再映射为坐标,接下里只需要对 System.out.printf("%d %d\n",jgi/n,jgi%n);就能实现得到每个皇后的x,y坐标。这只是完成了暴力对所有皇后位置的放,还需要在solve里进行横竖左斜右斜的判断来看这个结果是否符合,不过这样通常是会时间复杂度用的更多,其实更理想的是在

jkava 复制代码
  for (int i = 0; i < sl; i++) {
            if (f[i] == 0) {
                f[i] = 1;
                jg[wz]=i;
                dfs(wz + 1);
                f[i] = 0;
            }
        }

这个期间就进行剪枝判断,而不是全枚举完再判断,这里主要是提供思路。

那我们接下来看一下组合总和:

给定一个无重复元素 的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。
candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是不同的。

可以发现区别是一个数字可以重复选还有判断条件不再是根据数字的数量而是总和。那么我们先看数字重复选怎么实现,这里要在fi上动手。

java 复制代码
import java.util.Scanner;

public class Main {
    static int n, a[] = new int[10005], f[] = new int[10005], jg[] = new int[10005];

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        dfs(0, 0);
    }

    //wz是第几个数字
    static void dfs(int wz, int ks) {
        if(wz>n)return;
        if (wz == n) {
            solve();
        }
        for (int i = ks; i < n; i++) {
            jg[wz] = i;
            dfs(wz + 1, i );
            jg[wz] = 0;
        }
    }

    //调用临界解决问题的方法
    static void solve() {
        for (int i = 0; i < n; i++) {
            System.out.printf("%d ", jg[i]);
        }
        System.out.println();
    }
}

去掉fi,再把qs不加1传参就可以得到有重复数字的递增序列了。

那么针对这道问题,需要把递归临界从数量改为总和。

java 复制代码
import java.util.Scanner;

public class Main {
    static int n, a[] = new int[10005],mub,f[] = new int[10005], jg[] = new int[10005],f1;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        mub=sc.nextInt();
        for (int i = 0; i < n; i++) {
            a[i]=sc.nextInt();
        }
        dfs(0, 0);
    }

    static void dfs(int sum, int ks) {
        if(sum>mub)return;
        if (sum == mub) {
            solve();
        }
        for (int i = ks; i < n; i++) {
            jg[f1++] = a[i];
            dfs(sum+a[i], i );
            jg[f1--] = 0;
        }
    }

    //调用临界解决问题的方法
    static void solve() {
        for (int i = 0; i < f1; i++) {
            System.out.printf("%d ", jg[i]);
        }
        System.out.println();
    }
}

这样就完成了,遇到问题的时候思路不要乱,大胆去想。

相关推荐
Brilliantwxx2 分钟前
【算法从零到千】【55-58】哈希位图+常见数学运算 接口
算法
空堂与归1 小时前
用户分群找不到规律?用K-Means聚类算法自动发现数据模式
算法·机器学习·kmeans·聚类
动词ing1 小时前
【C语言】自定义函数+指针入门
c语言·开发语言·算法
重生之后端学习2 小时前
283. 移动零[简单]✅
开发语言·数据结构·算法·leetcode·职场和发展
一只小小的芙厨2 小时前
基础数论总结
笔记·学习·算法
夜不会漫长4 小时前
C++入门(1)
开发语言·c++·算法
wen_zhufeng4 小时前
IndexTTS 2.5 技术报告
人工智能·算法·机器学习
大熊背5 小时前
树莓派相机自动白平衡详解(四)
算法·树莓派·白平衡·isppipeline
一米阳光86615 小时前
软考(中级)软件设计师核心笔记(9)算法——背包问题
笔记·算法·职场发展·软考·软件设计师·中级职称
晚风醉蝶5 小时前
1-11-奇偶排序-OddEvenSort
java·数据结构·算法