高效的java版排列组合算法

高效的Java版排列组合算法

前言

排列组合是数学中的一种常见问题,例如给定数列[1,2,3],对其进行排列组合可以得到以下六种可能:

  • 1 2 3
  • 1 3 2
  • 2 1 3
  • 2 3 1
  • 3 1 2
  • 3 2 1

在Java中,我们可以使用递归和循环等方式来实现排列组合,但是如果数列过长,将会十分耗时,因此我们需要一种高效的实现方式。

算法基础

排列

排列的基本概念是:一组数据中,任意取出几个(也可以是全部),按照一定的顺序排列起来,叫做这组数据的一个排列。

在Java中,我们可以通过递归函数来生成排列。

public static void permutation(int[] nums, int start, List<List<Integer>> res) {
    if (start >= nums.length) {
        List<Integer> list = new ArrayList<>();
        for (int n : nums) {
            list.add(n);
        }
        res.add(list);
    } else {
        for (int i = start; i < nums.length; i++) {
            swap(nums, i, start);
            permutation(nums, start + 1, res);
            swap(nums, i, start);
        }
    }
}

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

上面的递归函数permutation接收一个整数数组nums,一个整数start和一个List> res,表示从nums[start]开始生成一个排列,并将结果保存在res中。我们先将nums[start]与nums[start+1]到nums[nums.length-1]依次交换,然后递归调用函数permutation,直到start >= nums.length。

组合

组合的基本概念是:一组数据中,任意取出几个(不能重复取出),不考虑顺序排列起来,叫做这组数据的一个组合。

在Java中,我们可以通过递归函数来生成组合。

public static void combination(int[] nums, int start, int k, List<List<Integer>> res) {
    if (k == 0) {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < nums.length; i++) {
            if ((start & (1 << i)) != 0) {
                list.add(nums[i]);
            }
        }
        res.add(list);
    } else {
        for (int i = start; i < (1 << nums.length); i++) {
            if (Integer.bitCount(i) != k) {
                continue;
            }
            combination(nums, i + 1, k - 1, res);
        }
    }
}

上面的递归函数combination接收一个整数数组nums,一个整数start,一个整数k和一个List> res,表示从nums[start]开始选择k个数,生成一个组合,并将结果保存在res中。我们通过循环枚举[0,2^nums.length)范围内的数,如果这个数中1的个数不等于k,说明选中的数个数不足k个,因此直接跳过。否则,递归调用combination函数,将选中的数添加到结果中。

算法优化

虽然上面的排列组合算法已经可以完成任务,但是在处理长数列的时候,时间开销仍然很大。我们可以通过剪枝和非递归方式等方式优化算法,以提高算法效率。

排列算法优化

剪枝:在对nums数组进行交换的时候,可以首先检查当前元素是否与之前交换过,避免重复计算。

非递归方式:使用while循环代替递归实现,只需要保存下标,因此实现简单,效率高。

public static List<List<Integer>> permute(int[] nums) {
    List<List<Integer>> res = new ArrayList<>();
    int n = nums.length;
    int[] indexes = new int[n];
    for (int i = 0; i < n; i++) {
        indexes[i] = 0;
    }
    int i = 0;
    while (i < n) {
        if (indexes[i] < i) {
            swap(nums, i % 2 == 0 ? 0 : indexes[i], i);
            List<Integer> list = new ArrayList<>();
            for (int num : nums) {
                list.add(num);
            }
            res.add(list);
            indexes[i]++;
            i = 0;
        } else {
            indexes[i] = 0;
            i++;
        }
    }
    return res;
}

组合算法优化

剪枝:组合算法本身就包含了去重的操作,因此不需要再进行剪枝。

非递归方式:使用while循环代替递归实现,只需要保存下标,因此实现简单,效率高。

public static List<List<Integer>> combine(int n, int k) {
    List<List<Integer>> res = new ArrayList<>();
    if (n < k || n == 0) {
        return res;
    }
    int[] indexes = new int[k];
    for (int i = 0; i < k; i++) {
        indexes[i] = i;
    }
    while (indexes[0] < n - k + 1) {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < k; i++) {
            list.add(indexes[i] + 1);
        }
        res.add(list);
        int t = k - 1;
        while (t >= 0 && indexes[t] == n - k + t) {
            t--;
        }
        if (t < 0) {
            break;
        }
        indexes[t]++;
        for (int i = t + 1; i < k; i++) {
            indexes[i] = indexes[i - 1] + 1;
        }
    }
    return res;
}

示例说明

下面是一个排列的例子,[1,2,3,4,5,6,7,8,9,10]这样一个长度为10的数列,我们需要从中生成其全部的排列组合。可以使用上面所介绍的算法进行计算。

int[] nums = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
List<List<Integer>> permutations = permute(nums);
List<List<Integer>> combinations = combine(10, 5);

通过使用上面的代码,我们可以得到nums的所有排列和长度为5的所有组合。

结语

排列组合算法是程序设计中常见的问题,通过使用Java中的递归和非递归方式,可以实现高效的排列组合算法。本篇文章介绍了基础的排列组合算法,以及如何通过剪枝和非递归方式等方式优化算法。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:高效的java版排列组合算法 - Python技术站

(0)
上一篇 2023年5月19日
下一篇 2023年5月19日

相关文章

  • java中 Set与Map排序输出到Writer详解及实例

    概述 Set 与 Map 都是 Java 中常用的集合类型,它们各自有不同的特点和用途。而排序则是对集合中的元素进行按照特定规则的排序,使得输出的结果更加具有可读性和便于理解。本篇文章将详细讲解如何对 Set 和 Map 进行排序,并将最终结果输出到 Writer 中。 Set排序输出到Writer的示例 下面是如何对 Set 进行排序,然后输出到 Writ…

    Java 2023年5月26日
    00
  • Java Timer与TimerTask类使程序计时执行

    要使用Java Timer与TimerTask类使程序计时执行,需要遵循以下步骤: 步骤一:导入相关类库 要使用Java Timer和TimerTask类,需要在代码中导入相关类库,例如: import java.util.Timer; import java.util.TimerTask; 步骤二:创建任务定时器 要使用Java Timer和TimerTa…

    Java 2023年6月1日
    00
  • Spring interceptor拦截器配置及用法解析

    下面是“Spring interceptor拦截器配置及用法解析”的完整攻略。 1. 什么是 Spring Interceptor Spring Interceptor是一个在Spring MVC框架中,拦截处理程序请求、处理程序响应或者处理程序处理过程中发生的事件。拦截器与过滤器类似,但是更加灵活。它们能够获取请求的详细信息,包括请求的URI、请求的方法等…

    Java 2023年5月31日
    00
  • Spring MVC中Ajax实现二级联动的简单实例

    Spring MVC中Ajax实现二级联动的简单实例 在 Spring MVC 中,我们可以使用 Ajax 实现二级联动。本文将详细讲解 Spring MVC 中 Ajax 实现二级联动的完整攻略,并提供两个示例说明。 1. 创建 Spring MVC 控制器 我们需要创建一个 Spring MVC 控制器,用于处理 Ajax 请求。下面是一个简单的示例: …

    Java 2023年5月18日
    00
  • 微信小程序实现无缝滚动

    准备工作 微信小程序的开发环境 基本的HTML、CSS、JavaScript知识 微信小程序开发文档 实现步骤步骤一:建立一个scroll组件 在wxml文件中使用scroll组件 <scroll-view scroll-x="{{scrollX}}" scroll-y="{{scrollY}}" style=&…

    Java 2023年5月23日
    00
  • Java 超详细讲解字符流

    Java 超详细讲解字符流 什么是字符流 在Java中,字节流常常用来处理二进制数据(如图片、音频等),而字符流则使用在处理文本数据(如txt文件等)。不同于字节流,字符流是基于16位Unicode编码的字符来处理数据的。 Java中提供了两类字符流:Reader和Writer。Reader用于读取字符流,Writer用于写入字符流。 字符流的工作方式 字符…

    Java 2023年5月20日
    00
  • 可能是全网最详细的springboot整合minio教程

    可能是全网最详细的 Spring Boot 整合 MinIO 教程 介绍 本教程将带领读者了解 Spring Boot 如何与 MinIO 对象存储进行整合。我们将使用 Spring Boot 的官方框架 spring-boot-starter-web、spring-boot-starter-test,以及本文作者写的 minio-spring-boot-s…

    Java 2023年5月19日
    00
  • JDBC使用游标实现分页查询的方法

    介绍 JDBC是Java Database Connectivity的简称,是Java语言中用于访问关系型数据库的API,是Java程序员以及开发人员必须掌握的技能之一。本文将讲解如何使用JDBC实现分页查询。 步骤 获取数据库连接 Connection conn = null; Statement stmt = null; ResultSet rs = n…

    Java 2023年5月20日
    00
合作推广
合作推广
分享本页
返回顶部