窥探深度优先搜索(DFS)算法与Java内部类的精妙结合:解决全排列问题

一、引言: 在AcWing做到这道题:823. 排列

        

        在这道题目中,给定了一个整数 n,要求将数字 1 到 n 排成一排,并按照字典序输出所有的排列方案。这道题主要考察全排列和字典序的应用,通过深度优先搜索(DFS)算法来解决。

        全排列是指从给定的一组元素中,选取一些或全部元素,按照一定顺序进行排列,从而得到不同的排列方式。而字典序则是按照字母或数字在字典中的顺序进行排列。在解决这道题时,我们需要找到所有的排列方案,并按照字典序输出。

        为了实现这个目标,我们可以使用深度优先搜索算法。DFS是一种常用的搜索算法,它从初始状态开始,逐步向前探索,直到达到目标状态或无法继续前进为止。在全排列问题中,我们需要遍历每个数字,将其放入当前位置,然后递归地对剩下的数字进行排列。通过回溯法,我们可以找到所有的排列方案。

        此外,Java内部类的概念在解决这道题中也起到了重要作用。内部类是定义在另一个类内部的类,它具有访问外部类私有成员的能力,并且可以被当做外部类的成员使用。在解决这道题时,我们可以使用内部类来保存当前的排列情况,并将其传递给下一层递归。这样,我们就可以方便地访问和修改当前的排列状态。

        综上,通过深度优先搜索算法和Java内部类的结合运用,我们可以解决这道题目,找到所有的排列方案,并按照字典序输出。接下来,我们将详细讲解DFS算法的原理和Java内部类的使用方法,并给出完整的代码实现,帮助读者更好地理解和掌握这两个知识点。


二、深度优先搜索(DFS)

        深度优先搜索(DFS)是一种重要的图遍历算法,它可以用于求解许多图论问题。它的搜索过程类似于树的先序遍历。在算法实现中,我们通过递归的方式进行遍历,并在每一步尽可能深入地探索图中的分支,直到无法继续为止。

        1.基本原理

DFS算法的基本原理是从一个起点开始,依次访问其所有邻居节点(也就是与这个节点有连接的其他节点),并将这些邻居节点标记为已访问过。接着,对于每个已经访问过的邻居节点,我们再递归访问它们的邻居节点,直到所有节点都被访问过为止。

在实现DFS算法时,可以使用递归或栈的方式进行遍历。如果使用递归的方式,我们需要在进入递归之前,将当前节点标记为已被访问过。如果使用栈的方式,我们需要在进栈之前,将当前节点标记为已被访问过。

        2.应用场景

DFS算法可以用于求解许多图论问题,例如:

  • 遍历图中的所有节点
  • 判断图是否连通
  • 求解最短路径
  • 求解欧拉回路和哈密顿回路

此外,DFS算法还可以用于求解一些其他类型的问题,例如:

  • 在排列、组合等问题中搜索所有可能的解
  • 在数独、八皇后等问题中搜索所有可能的解

三、Java内部类

        这里将详细讲解java内部类的概念和用法,并针对题目中的内部类示例进行说明。

Java内部类是指定义在另一个类内部的类。与常规的独立类不同,内部类具有访问外部类私有成员的能力,并且可以被当做外部类的成员使用。内部类提供了更好的封装性和代码组织性,可以在某些情况下提供更清晰和优雅的代码实现。

1.内部类的概念

Java内部类主要分为四种类型:成员内部类静态内部类局部内部类匿名内部类。这里,我们将重点介绍 成员内部类 的 概念 和 用法

成员内部类是定义在另一个类内部的普通类,它可以访问外部类的所有成员(包括私有成员),并且可以被外部类及其它类使用。成员内部类通过持有一个外部类的引用,实现了对外部类的访问。

2.内部类的用法

在题目中的内部类示例中,我们可以使用内部类来保存当前的排列情况,并将其传递给下一层递归。这样,我们就可以方便地访问和修改当前的排列状态。

以下是一个示例代码,演示了如何使用内部类来实现排列方案的生成:

 static class PermutationSolver {
        private int num;
        private int[] nums;
        private boolean[] vis;
        private BufferedWriter bw;

        public PermutationSolver(int num, int[] nums, boolean[] vis, BufferedWriter bw) {
            this.num = num;
            this.nums = nums;
            this.vis = vis;
            this.bw = bw;
        }

        public void solve() throws IOException {
            dfs(0);
        }

        private void dfs(int u) throws IOException {
            if (u == num) {
                for (int i = 0; i < num; i++) {
                    bw.write((nums[i] + 1) + " ");
                }
                bw.write("\n");
            } else {
                for (int i = 0; i < num; i++) {
                    if (!vis[i]) {
                        vis[i] = true;
                        nums[u] = i;
                        dfs(u + 1);
                        vis[i] = false;
                    }
                }
            }
        }
    }

在上面的代码示例中,我们使用了成员内部类来展示Java内部类的用法。

内部类是嵌套在其他类内部的类。它具有以下特点:

  1. 内部类可以访问外部类的私有成员:在示例代码中,PermutationSolver内部类可以直接访问外部类 PermutationDemo 中的私有成员变量 numnumsvisbw

  2. 外部类可以访问内部类的私有成员:在示例代码中,外部类 PermutationDemo 可以创建 PermutationSolver 内部类的实例,并调用其中的方法。

  3. 内部类可以访问外部类的方法参数和局部变量:在示例代码的 solve 方法中,PermutationSolver 内部类可以访问 dfs 方法的参数 u

  4. 内部类可以拥有与外部类相同的变量名:在示例代码中,内部类的 PermutationSolver 类有与外部类相同名称的变量 num,但它们代表不同的含义。

通过使用内部类,我们可以实现更好的封装和组织代码结构。内部类通常用于实现与外部类紧密相关的辅助功能或实现某个接口。


补充:

内部类的四种类型介绍:

  1. 成员内部类(Member Inner Class):成员内部类是嵌套在外部类中,与外部类的成员变量和方法有着紧密的联系。它可以访问外部类的私有成员,并且可以被外部类直接访问。

  2. 静态内部类(Static Inner Class):静态内部类是使用 static 关键字修饰的内部类。它与外部类的实例无关,可以像普通类一样被创建和访问,不依赖于外部类的实例。

  3. 方法内部类(Method Local Inner Class):方法内部类是定义在方法内部的类。它的作用域限定在所在方法内部,只能在方法内部被访问。方法内部类可以访问外部方法的参数和局部变量,但这些变量必须声明为 final 或事实上的 final

  4. 匿名内部类(Anonymous Inner Class):匿名内部类是没有具体类名的内部类。它通常用于实现某个接口或继承某个类,并且只能创建一个实例。匿名内部类一般在需要时立即定义,并且可以覆盖父类的方法或实现接口的方法。

内部类与外部类之间的访问权限如下:

  1. 外部类可以直接访问内部类的成员,即使是私有成员。

  2. 内部类可以访问外部类的所有成员,包括私有成员。

  3. 外部类想要访问内部类的成员,需要通过内部类的实例来访问。

内部类的优点和使用场景包括:

  1. 封装:内部类可以访问外部类的私有成员,提供了更好的封装性。

  2. 访问外部类的成员:可以轻松访问外部类的成员变量和方法。

  3. 实现多重继承:内部类可以实现多个接口,从而实现多重继承。

  4. 代码组织和可读性:内部类可以将相关的代码组织在一起,提高代码的可读性和维护性。

不同类型的内部类的区别和用法如下:

  1. 成员内部类常用于描述外部类的特定功能,与外部类之间有着密切的联系。

  2. 静态内部类可以独立于外部类创建和访问,适合满足某个特定功能的类的定义。

  3. 方法内部类通常用于在方法内部创建一个辅助类,实现某个特定功能。

  4. 匿名内部类可以简化代码,特别适用于只需要创建一个实例的情况。

创建内部类的对象并访问其方法和成员的方式如下:

  1. 成员内部类的实例化方式:外部类实例.new 内部类(),例如:Outer outer = new Outer(); Outer.Inner inner = outer.new Inner();

  2. 静态内部类的实例化方式:外部类直接.new 内部类(),例如:Outer.Inner inner = new Outer.Inner();

  3. 方法内部类的实例化方式:在方法内部直接使用方法内部类的构造器创建实例。

  4. 匿名内部类的实例化方式:通常使用匿名内部类的父类或接口的引用变量来创建实例。

内部类与外部类的关系和生命周期:

  1. 内部类与外部类之间存在着强关联,内部类可以访问外部类的成员和方法。

  2. 内部类的生命周期与外部类的对象绑定,只能在外部类的实例中创建内部类的实例。


四、题目解析及代码实现

    依据题目分析: ”递归搜索树“

解题思路:使用深度优先搜索算法(DFS)求解。从下标为 0 开始,不断枚举未选择过的数,并递归向下搜索。当下标 u 等于 n 时,说明已经完成了一种排列,将该排列输出即可。

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Scanner;

public class Main {
    private static int[] path;  // 已选过的数
    private static boolean[] st;  // 标记数组
    private static int n;  // 选择的数的个数
    private static BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

    public static void main(String[] args) throws IOException {
        Scanner sca = new Scanner(System.in);
        n = sca.nextInt();
        path = new int[n];
        st = new boolean[n];
        dfs(0);  // 从下标 0 开始搜索
        bw.flush();  // 刷新缓存区
    }

    private static void dfs(int u) throws IOException {
        if (u == n) {  // 如果已经选满 n 个数,则输出该排列
            for (int i = 0; i < n; i++) {
                bw.write(path[i] + " ");
            }
            bw.write("\n");
        } else {
            for (int i = 0; i < n; ++i) {  // 尝试枚举未选择过的数
                if (!st[i]) {
                    st[i] = true;
                    path[u] = i + 1;  // 将第 i 个数加入排列中
                    dfs(u + 1);  // 递归向下搜索
                    st[i] = false;  // 回溯(撤销选择)
                }
            }
        }
    }
}

结合java内部类实现:
在本题中,我们使用局部内部类来定义一个名为 Inner 的类,在其中实现 DFS 函数。

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Scanner;

public class Main {
    /**
     * 使用内部类代替静态变量的方式
     */
    public static void main(String[] args) throws IOException {
        Scanner sca = new Scanner(System.in);
        int num = sca.nextInt();
        int[] nums = new int[num];
        boolean[] vis = new boolean[num];
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        class Inner {
            public void dfs(int u) throws IOException {
                if (u == num) {  // 如果已经选满 num 个数,则输出该排列
                    for (int i = 0; i < num; i++) {
                        bw.write((nums[i]) + " ");
                    }
                    bw.write("\n");
                } else {
                    for (int i = 0; i < num; i++) {  // 尝试枚举未选择过的数
                        if (!vis[i]) {
                            vis[i] = true;
                            nums[u] = i;  // 将第 i 个数加入排列中
                            dfs(u + 1);  // 递归向下搜索
                            vis[i] = false;  // 回溯(撤销选择)
                        }
                    }
                }
            }
        }

        Inner inner = new Inner();  // 创建 Inner 类的对象
        inner.dfs(0);  // 调用 dfs 函数
        bw.flush();  // 刷新缓存区
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值