
本文深入探讨了在Java中如何正确生成并逐一处理数组的所有排列,以解决一个经典的模拟雇佣问题,并计算特定事件(如雇佣恰好两次)的概率。文章分析了原始代码中将排列扁平化处理的常见错误,并提供了基于 List<List> 结构,逐个遍历并应用业务逻辑的正确解决方案,确保概率计算的准确性。
1. 引言:排列组合与概率计算
在计算机科学和算法设计中,我们经常需要处理数据的各种排列。一个典型的应用场景是模拟实验或计算概率。本教程将以一个经典的“雇佣问题”为例,展示如何生成一个集合的所有排列,并对每个排列应用特定的逻辑,最终计算出满足特定条件的排列所占的比例(即概率)。
问题的核心在于:给定一组按排名顺序的候选人(例如1到n),我们按随机顺序面试他们。每面试一个候选人,如果他比之前面试过的所有候选人都优秀(排名更低),我们就雇佣他。我们的目标是计算在所有可能的面试顺序(即所有排列)中,恰好雇佣了两次的概率。
原始代码尝试通过生成所有排列来解决此问题,但在将这些排列传递给核心的雇佣逻辑时,出现了一个常见的错误:将所有排列扁平化处理,导致无法对单个排列进行独立分析。
2. 核心组件与雇佣策略解析
为了理解并修正代码,我们首先分析其核心组件:
2.1 hireAssistant1 方法:模拟雇佣过程
这是模拟雇佣逻辑的关键方法。它接收一个 int[] 数组作为候选人序列(一个排列),并返回雇佣的次数。
立即学习“Java免费学习笔记(深入)”;
public static int hireAssistant1(int[] arr, int n) { ArrayList hired = new ArrayList(); // 第一个候选人总是被雇佣 int best = arr[0]; hired.add(best); // 遍历后续候选人 for (int i = 1; i < n; i++) { // 如果当前候选人比目前为止的最佳候选人更优秀(排名更低),则雇佣 if (arr[i] < best) { best = arr[i]; hired.add(best); } } return hired.size(); // 返回雇佣的总人数}
该方法的核心思想是:始终雇佣第一个面试的候选人,之后只雇佣比当前已雇佣的最佳候选人更优秀的候选人。
2.2 permute 方法:生成所有排列
此方法及其辅助方法 permuteHelper 负责生成给定数组的所有可能排列。它返回一个 List<List>,其中每个内部 List 代表一个独立的排列。
public List<List> permute(int[] arr) { List<List> list = new ArrayList(); permuteHelper(list, new ArrayList(), arr); return list;}private void permuteHelper(List<List> list, List resultList, int[] arr) { // 递归终止条件:当resultList的长度等于原始数组长度时,表示一个完整的排列已生成 if (resultList.size() == arr.length) { list.add(new ArrayList(resultList)); // 将当前排列添加到结果列表中 } else { // 遍历原始数组中的每个元素 for (int i = 0; i < arr.length; i++) { // 如果当前元素已在resultList中,则跳过(避免重复) if (resultList.contains(arr[i])) { continue; } resultList.add(arr[i]); // 将元素添加到当前排列中 permuteHelper(list, resultList, arr); // 递归调用 resultList.remove(resultList.size() - 1); // 回溯:移除最后一个元素,尝试其他路径 } }}
2.3 辅助方法
makeArray(int n): 创建一个包含 1 到 n 的有序数组。factorial(int n): 计算 n 的阶乘,用于确定总排列数。toIntArray(List list): 将 List 转换为 int[]。listToList(List<List> list): 这是一个有问题的辅助方法,它将 List<List> 扁平化为一个 List。
3. 原始代码的问题剖析:扁平化陷阱
原始 main 方法中的核心问题在于对 permute 方法返回结果的处理:
public static void main(String[] args) { Assignment8 pa = new Assignment8(); int n = 6; List<List> p = pa.permute(makeArray(n)); // p 包含所有独立的排列 List list = listToList(p); // 错误:将所有排列扁平化为一个大列表 System.out.println("N = 6"); methodThreePerm(list, n); // 将扁平化列表传递给处理方法}
这里的 listToList(p) 调用是问题的根源。例如,如果 n=3,permute 可能会返回 [[1,2,3], [1,3,2], [2,1,3], …]。经过 listToList 后,list 可能会变成 [1,2,3,1,3,2,2,1,3,…]。
随后,methodThreePerm 接收这个扁平化的 List,并将其转换为一个巨大的 int[] arr。当 hireAssistant1(arr, n) 被调用时,它会将这个巨大的数组当作一个单一的、长度为 n 的序列来处理,但实际上这个数组的长度远大于 n,且包含了多个排列的拼接。这导致 hireAssistant1 无法正确地模拟单个排列的雇佣过程,从而使最终的概率计算结果完全错误。
hireAssistant1 方法期望的输入是一个独立的排列(例如 [1,2,3] 或 [3,1,2]),而不是所有排列拼接在一起的巨大序列。
4. 解决方案:逐个排列处理
解决这个问题的关键在于,我们应该直接利用 permute 方法返回的 List<List> 结构,逐个遍历每个独立的排列,并对其应用 hireAssistant1 逻辑。
正确的处理流程如下:
生成所有排列,得到 List<List>。初始化一个计数器 hiresTwoCount,用于统计雇佣次数恰好为2的排列数量。遍历 List<List> 中的每一个内部 List(即每一个独立的排列)。对于每一个独立的排列:将其转换为 int[] 数组。调用 hireAssistant1 方法,获取该排列的雇佣次数。如果雇佣次数等于2,则 hiresTwoCount 加1。计算最终概率:hiresTwoCount / factorial(n)。
5. 代码实现与验证
以下是修正后的 main 方法示例,它直接在 permute 的结果上进行迭代,确保每个排列都被正确处理:
import java.util.ArrayList;import java.util.List;import java.util.stream.Collectors; // For listToList, though it's removed in solutionpublic class PermutationHiring { // 辅助方法:创建包含1到n的数组 public static int[] makeArray(int n) { int arr[] = new int[n]; for (int i = 0; i < arr.length; i++) { arr[i] = i + 1; } return arr; } // 雇佣模拟方法 public static int hireAssistant1(int[] arr, int n) { // ArrayList hired = new ArrayList(); // 实际上不需要存储被雇佣者,只需计数 int hires = 0; if (n == 0) return 0; // 空数组没有雇佣 if (n == 1) return 1; // 只有一个候选人,必然雇佣一次 int best = arr[0]; hires = 1; // 第一个候选人总是被雇佣 for (int i = 1; i < n; i++) { if (arr[i] < best) { best = arr[i]; hires++; } } return hires; } // 计算阶乘 public static int factorial(int n) { if (n < 0) throw new IllegalArgumentException("Factorial is not defined for negative numbers."); if (n == 0 || n == 1) return 1; int result = 1; for (int i = 2; i <= n; i++) { result *= i; } return result; } // List 转换为 int[] static int[] toIntArray(List list) { int[] ret = new int[list.size()]; for (int i = 0; i < ret.length; i++) ret[i] = list.get(i); return ret; } // 生成所有排列的核心逻辑 public List<List> permute(int[] arr) { List<List> list = new ArrayList(); permuteHelper(list, new ArrayList(), arr); return list; } private void permuteHelper(List<List> list, List resultList, int[] arr) { if (resultList.size() == arr.length) { list.add(new ArrayList(resultList)); } else { for (int i = 0; i < arr.length; i++) { if (resultList.contains(arr[i])) { continue; } resultList.add(arr[i]); permuteHelper(list, resultList, arr); resultList.remove(resultList.size() - 1); } } } public static void main(String[] args) { PermutationHiring ph = new PermutationHiring(); // 实例化类以调用非静态方法 int n = 6; // 生成所有排列 List<List> allPermutations = ph.permute(makeArray(n)); int hiresTwoCount = 0; // 统计雇佣次数恰好为2的排列数 int totalPermutations = factorial(n); // 总排列数 // 遍历每一个独立的排列 for (List permutation : allPermutations) { int[] currentPermutationArray = toIntArray(permutation); int hires = hireAssistant1(currentPermutationArray, n); if (hires == 2) { hiresTwoCount++; } } double probability = (double) hiresTwoCount / totalPermutations; System.out.println("N = " + n); System.out.println("雇佣次数恰好为2的排列数: " + hiresTwoCount); System.out.println("总排列数 (n!): " + totalPermutations); System.out.println("雇佣次数恰好为2的概率: " + probability); // 原始答案中提供的Method 1,用于计算不同的概率(例如,期望雇佣次数的某个变体) // 注意:这个方法计算的结果与“雇佣恰好两次”的概率不同。 // static void methodOne
以上就是Java中处理全排列:模拟雇佣问题与概率计算的正确姿势的详细内容,更多请关注创想鸟其它相关文章!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/103620.html
微信扫一扫
支付宝扫一扫