Java 全排列的几种实现方法

 更新时间:2024年11月26日 11:14:50   作者:飞滕人生TYF  
本文详细介绍了Java中全排列问题的几种实现方法,包括回溯法、字典序排列法和迭代法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

全排列问题是一个经典的算法问题,指的是对一个序列中的所有元素生成不重复的排列组合。以下是全排列问题在 Java 中的详细实现和讲解。

1. 全排列问题定义

输入: 给定一个序列或数组,找出所有元素的排列。
输出: 返回所有可能的排列。

示例:

输入:[1, 2, 3]

输出:

[1, 2, 3]
[1, 3, 2]
[2, 1, 3]
[2, 3, 1]
[3, 1, 2]
[3, 2, 1]

2. 常用算法

全排列的常见实现方法:

  • 回溯法(Backtracking)
  • 字典序排列法(Lexicographic Order)
  • 迭代法(非递归实现)

3. 使用回溯法解决全排列

回溯法是一种基于递归的搜索算法,它通过不断尝试并撤销之前的选择来生成所有可能的解。

3.1 回溯法实现(基础版)

适用于数组中无重复元素的全排列。

import java.util.ArrayList;
import java.util.List;

public class Permutations {
    public static List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        boolean[] used = new boolean[nums.length]; // 标记是否使用过
        List<Integer> current = new ArrayList<>();
        backtrack(nums, used, current, result);
        return result;
    }

    private static void backtrack(int[] nums, boolean[] used, List<Integer> current, List<List<Integer>> result) {
        // 终止条件:当前排列的大小等于数组长度
        if (current.size() == nums.length) {
            result.add(new ArrayList<>(current)); // 将当前排列加入结果
            return;
        }
        // 遍历每个元素
        for (int i = 0; i < nums.length; i++) {
            if (used[i]) { // 跳过已使用的元素
                continue;
            }
            // 做选择
            current.add(nums[i]);
            used[i] = true;
            // 递归
            backtrack(nums, used, current, result);
            // 撤销选择
            current.remove(current.size() - 1);
            used[i] = false;
        }
    }

    public static void main(String[] args) {
        int[] nums = {1, 2, 3};
        List<List<Integer>> permutations = permute(nums);
        System.out.println(permutations);
    }
}

输出结果

对于输入 [1, 2, 3]

[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]

3.2 回溯法(不使用标记数组)

通过交换数组中的元素,可以避免使用标记数组。

import java.util.ArrayList;
import java.util.List;

public class PermutationsSwap {
    public static List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        backtrack(nums, 0, result);
        return result;
    }

    private static void backtrack(int[] nums, int start, List<List<Integer>> result) {
        if (start == nums.length) {
            List<Integer> permutation = new ArrayList<>();
            for (int num : nums) {
                permutation.add(num);
            }
            result.add(permutation);
            return;
        }
        for (int i = start; i < nums.length; i++) {
            swap(nums, start, i); // 交换元素
            backtrack(nums, start + 1, result);
            swap(nums, start, i); // 撤销交换
        }
    }

    private static void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }

    public static void main(String[] args) {
        int[] nums = {1, 2, 3};
        List<List<Integer>> permutations = permute(nums);
        System.out.println(permutations);
    }
}

3.3 回溯法处理重复元素

如果数组中包含重复元素(例如 [1, 1, 2]),我们需要对结果去重。可以通过对数组排序并在递归时跳过重复元素来实现。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class PermutationsWithDuplicates {
    public static List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        Arrays.sort(nums); // 排序以便跳过重复元素
        boolean[] used = new boolean[nums.length];
        backtrack(nums, used, new ArrayList<>(), result);
        return result;
    }

    private static void backtrack(int[] nums, boolean[] used, List<Integer> current, List<List<Integer>> result) {
        if (current.size() == nums.length) {
            result.add(new ArrayList<>(current));
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            if (used[i]) {
                continue;
            }
            // 跳过相邻重复的元素
            if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
                continue;
            }
            current.add(nums[i]);
            used[i] = true;
            backtrack(nums, used, current, result);
            current.remove(current.size() - 1);
            used[i] = false;
        }
    }

    public static void main(String[] args) {
        int[] nums = {1, 1, 2};
        List<List<Integer>> permutations = permuteUnique(nums);
        System.out.println(permutations);
    }
}

输出结果

对于输入[1, 1, 2]

[[1, 1, 2], [1, 2, 1], [2, 1, 1]]

4. 字典序排列法

字典序法生成全排列的步骤:

  • 找到当前排列的“最后一个升序对”。
  • 从右侧找到比升序对中较小值大的最小值,交换这两个值。
  • 将右侧部分反转。

适用于需要按字典序输出排列的情况。

import java.util.Arrays;

public class PermutationsLexicographic {
    public static void nextPermutation(int[] nums) {
        int i = nums.length - 2;
        while (i >= 0 && nums[i] >= nums[i + 1]) { // 找到升序对
            i--;
        }
        if (i >= 0) {
            int j = nums.length - 1;
            while (j >= 0 && nums[j] <= nums[i]) {
                j--;
            }
            swap(nums, i, j); // 交换
        }
        reverse(nums, i + 1); // 反转后面的部分
    }

    private static void reverse(int[] nums, int start) {
        int i = start, j = nums.length - 1;
        while (i < j) {
            swap(nums, i, j);
            i++;
            j--;
        }
    }

    private static void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }

    public static void main(String[] args) {
        int[] nums = {1, 2, 3};
        System.out.println(Arrays.toString(nums));
        for (int i = 0; i < 6; i++) {
            nextPermutation(nums);
            System.out.println(Arrays.toString(nums));
        }
    }
}

5. 总结

常见方法对比

方法适用场景特点
回溯法全排列(通用)最常用,适合生成所有排列,可以处理重复元素,通过递归和剪枝实现。
交换法无重复全排列不需要额外空间标记数组,通过交换实现排列,但对重复元素需要额外处理。
字典序排列法字典序输出排列按字典序生成排列,适合序列化输出;需要对输入序列进行排序作为初始状态。
迭代法生成排列的特定场景使用循环代替递归,但实现起来较为复杂,适合排列生成问题中的迭代优化。

性能分析

  • 时间复杂度: O ( n ! ) O(n!) O(n!),每种方法
    的时间复杂度都与排列数量成正比。
  • 空间复杂度
    • 使用标记数组的回溯法: O ( n ) O(n) O(n)
    • 使用交换法: O ( 1 ) O(1) O(1)(不包括递归栈)

全排列是算法中基础而重要的问题。回溯法是最常用的解决方式,而在实际开发中,根据不同的需求可以选择合适的方法来实现高效的排列生成。

到此这篇关于Java 全排列的几种实现方法的文章就介绍到这了,更多相关Java 全排列内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringMVC拦截器失效问题及解决方法

    SpringMVC拦截器失效问题及解决方法

    这篇文章主要介绍了SpringMVC 拦截器和异常处理器,以及SpringMVC拦截器失效分析解决方案,文中补充介绍了SpringMVC 拦截器和异常处理器的相关知识,需要的朋友可以参考下
    2024-01-01
  • 字节码调教入口JVM 寄生插件javaagent

    字节码调教入口JVM 寄生插件javaagent

    这篇文章主要介绍了字节码调教入口JVM 寄生插件javaagent方法示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • 使用原生JDBC动态解析并获取表格列名和数据的方法

    使用原生JDBC动态解析并获取表格列名和数据的方法

    这篇文章主要介绍了使用原生JDBC动态解析并获取表格列名和数据,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-08-08
  • 详解Java子线程异常时主线程事务如何回滚

    详解Java子线程异常时主线程事务如何回滚

    如果主线程向线程池提交了一个任务,如果执行这个任务过程中发生了异常,如何让主线程捕获到该异常并且进行事务的回滚?本篇文章带给你答案
    2022-03-03
  • Mybatis如何获取insert新增数据id值

    Mybatis如何获取insert新增数据id值

    这篇文章主要介绍了Mybatis如何获取insert新增数据id值问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-05-05
  • 深入解析Java中volatile关键字的作用

    深入解析Java中volatile关键字的作用

    Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块 和 volatile 关键字机制
    2013-09-09
  • Java.toCharArray()和charAt()的效率对比分析

    Java.toCharArray()和charAt()的效率对比分析

    这篇文章主要介绍了Java.toCharArray()和charAt()的效率对比分析,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-10-10
  • Java三种获取redis的连接及redis_String类型演示(适合新手)

    Java三种获取redis的连接及redis_String类型演示(适合新手)

    这篇文章主要介绍了Java三种获取redis的连接及redis_String类型演示(适合新手),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • JavaGUI菜单栏与文本和密码及文本域组件使用详解

    JavaGUI菜单栏与文本和密码及文本域组件使用详解

    这篇文章主要介绍了JavaGUI菜单栏与文本和密码及文本域组件使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2023-03-03
  • Java中集合遍历的方法示例代码展示

    Java中集合遍历的方法示例代码展示

    在 Java 编程中,集合(Collection)是用于存储和操作一组对象的重要工具,无论是数组、列表(List)、集合(Set),还是映射(Map),它们都提供了在不同场景下灵活使用的数据结构,这篇文章主要介绍了Java中集合遍历的方法示例代码展示,需要的朋友可以参考下
    2024-08-08

最新评论