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 全排列内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java使用Hutool实现AES、DES加密解密的方法

    Java使用Hutool实现AES、DES加密解密的方法

    本篇文章主要介绍了Java使用Hutool实现AES、DES加密解密的方法,具有一定的参考价值,有兴趣的可以了解一下
    2017-08-08
  • Spring内置任务调度如何实现添加、取消与重置详解

    Spring内置任务调度如何实现添加、取消与重置详解

    任务调度是我们日常开发中经常会碰到的,下面这篇文章主要给大家介绍了关于Spring内置任务调度如何实现添加、取消与重置的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧。
    2017-10-10
  • SpringMVC常用注解载入与处理方式详解

    SpringMVC常用注解载入与处理方式详解

    这篇文章主要介绍了SpringMVC常用注解载入的方式和处理的方式,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-09-09
  • Spring中ApplicationListener的使用解析

    Spring中ApplicationListener的使用解析

    这篇文章主要介绍了Spring中ApplicationListener的使用解析,ApplicationContext事件机制是观察者设计模式的实现,通过ApplicationEvent类和ApplicationListener接口,需要的朋友可以参考下
    2023-12-12
  • 详解java中float与double的区别

    详解java中float与double的区别

    这篇文章主要介绍了JAVA中float与double的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04
  • 详解Java如何在CompletableFuture中实现日志记录

    详解Java如何在CompletableFuture中实现日志记录

    这篇文章主要为大家详细介绍了一种slf4j自带的MDC类,来记录完整的请求日志,和在CompletableFuture异步线程中如何保留链路id,需要的可以参考一下
    2023-04-04
  • 关于在IDEA热部署插件JRebel使用问题详解

    关于在IDEA热部署插件JRebel使用问题详解

    这篇文章主要介绍了关于在IDEA热部署插件JRebel使用问题详解,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • idea注入mapper报错报红的几种解决方案

    idea注入mapper报错报红的几种解决方案

    相信大家在使用idea的时候一定会遇到这样的问题,就是在service里注入mapper的时候,明明代码没有问题,也可以运行,但是idea它就是给你报个错,有个红色的波浪线在下面,所以本文将给大家介绍了idea注入mapper报错报红的几种解决方案,需要的朋友可以参考下
    2023-12-12
  • Java SPI 机制知识点总结

    Java SPI 机制知识点总结

    在本篇文章里小编给大家整理的是一篇关于Java SPI 机制知识点总结内容,需要的朋友们可以参考下。
    2020-02-02
  • Mybatis如何使用@Mapper和@MapperScan注解实现映射关系

    Mybatis如何使用@Mapper和@MapperScan注解实现映射关系

    这篇文章主要介绍了Mybatis使用@Mapper和@MapperScan注解实现映射关系,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10

最新评论