如何实现这样的分组和排列组合?

假定有3个人 有3种职业和姓名

String[] nameArray = {"小王", "小张", "小赵"};

String[] occupationArray = {"经商", "学生", "士兵"};

然后有一个Person的对象 里面包含了name 和 occupation
我现在想把所有可能性都列出来 并且分组
因为每个人只能有一个名字和一种职业 而且别人不能重复 我发现这样的双重for循环好像很难实现
List<List<Person>> 如何最终能保存成我想要的结果

想要的结果大概是这样:

[

[{"name":"小王","occupation":"经商"},{"name":"小张","occupation":"学生"},{"name":"小赵","occupation":"士兵"}],//这是第一组

[{"name":"小王","occupation":"学生"},{"name":"小张","occupation":"经商"},{"name":"小赵","occupation":"士兵"}],//这是第二组

....

]

如果2组中的姓名和职业是一样的 不管位置 只能算一组 因为位置在这里是要忽略的因素


回答:

public class Test {

static class Person {

String name;

String occupation;

}

public static void main(String[] args) {

String[] nameArray = {"小王", "小张", "小赵"};

String[] occupationArray = {"经商", "学生", "士兵"};

assert nameArray.length == occupationArray.length;

List<List<String>> nameLists = permutation(ListUtil.toList(nameArray));

List<List<Person>> personLists = new ArrayList<>();

for (int i = 0; i < nameLists.size(); i++) {

List<Person> personList = new ArrayList<>();

List<String> nameList = nameLists.get(i);

for (int j = 0; j < nameList.size(); j++) {

Person person = new Person();

person.name = nameList.get(j);

person.occupation = occupationArray[j];

personList.add(person);

}

personLists.add(personList);

}

for (List<Person> personList : personLists) {

System.out.println(personList.stream().map(p -> p.name + ":" + p.occupation).collect(Collectors.joining(",")));

}

}

public static List<List<String>> permutation(List<String> list) {

Stream<List<String>> hashSetStream = list.stream().distinct().map((str) -> new ArrayList<>(Collections.singleton(str)));

for (int n = 1; n < list.size(); n++) {

hashSetStream = hashSetStream.flatMap(arrayList -> list.stream()

.filter((temp) -> !arrayList.contains(temp))

.map((v) -> {

List<String> newList = new ArrayList<>(arrayList);

newList.add(v);

return newList;

}));

}

return hashSetStream.collect(Collectors.toList());

}

}

尝试一下该用法。

思路是将 occupation 分别排列组合,再将排练组合的结果与 原始的 name 合并成 person。


回答:

主要是对职业全排列

不含重复项的全排列:全排列
含重复项的全排列:全排列 II

下面假设不含重复职业

方法一:选择法,基于回溯(深搜) / 广搜

最常见的做法

所有职业组成一个集合,每次从中拿走一个放到列表末尾,拿完就得到一个排列,然后回溯,把刚放在列表末尾的职业拿走放回集合中,再拿下一个重复操作即可
使用回溯法模拟该过程,或者使用广搜维护每一层操作的所有状态:

graph LR

empty[ ] --> 经商

empty --> 学生

empty --> 士兵

经商 --> 经商,学生 --> 经商,学生,士兵

经商 --> 经商,士兵 --> 经商,士兵,学生

学生 --> 学生,经商 --> 学生,经商,士兵

学生 --> 学生,士兵 --> 学生,士兵,经商

士兵 --> 士兵,经商 --> 士兵,经商,学生

士兵 --> 士兵,学生 --> 士兵,学生,经商

维护集合方法很多,只要保证不重复拿即可,可用 暴力判断 / 标记数组 / 哈希表 / 交换法
不考虑输出顺序的话,推荐交换法,性能最佳,代码最简

交换法+回溯法 参考代码:

import java.util.*;

public class Main {

public static void main(String[] args) {

backtrack(Arrays.asList("经商", "学生", "士兵"), 0);

}

private static <T> void backtrack(List<T> list, int first) {

int last = list.size() - 1;

if (first == last) {

System.out.println(list);

return;

}

for (int i = first; i <= last; ++i) {

Collections.swap(list, first, i);

backtrack(list, first + 1);

Collections.swap(list, first, i);

}

}

}

[经商, 学生, 士兵]

[经商, 士兵, 学生]

[学生, 经商, 士兵]

[学生, 士兵, 经商]

[士兵, 学生, 经商]

[士兵, 经商, 学生]

方法二:放置法,基于回溯(深搜) / 广搜

初始化一个临时数组,开始时全部位置空着,每次把职业放到一个空位置,全部放完就得到了一个排列,然后回溯,移到下一个空位置重复操作即可
一样可用回溯法模拟该过程,或者使用广搜维护每一层操作的所有状态

回溯法 参考代码:

import java.util.*;

public class Main {

public static void main(String[] args) {

backtrack(new String[] { "经商", "学生", "士兵" }, 0, new String[3]);

}

private static <T> void backtrack(T[] items, int i, T[] tmp) {

int n = items.length;

if (i == n) {

System.out.println(List.of(tmp));

return;

}

T item = items[i++];

for (int j = 0; j < n; ++j) {

if (tmp[j] != null) continue;

tmp[j] = item;

backtrack(items, i, tmp);

tmp[j] = null;

}

}

}

[经商, 学生, 士兵]

[经商, 士兵, 学生]

[学生, 经商, 士兵]

[士兵, 经商, 学生]

[学生, 士兵, 经商]

[士兵, 学生, 经商]

方法三:迭代

nextPermutation() 用于获取下一个字典序更大的排列,参考:下一个排列
反复调用即可得到全排列

输出按索引字典序排列的全排列 参考代码:

import java.util.*;

import java.util.stream.*;

public class Main {

public static void main(String[] args) {

List<String> list = Arrays.asList("经商", "学生", "士兵");

List<Integer> indices = IntStream.range(0, list.size()).boxed().collect(Collectors.toList());

int count = 1;

for (int i = list.size(); i > 1; count *= i--);

for (;;) {

System.out.println(indices.stream().map(list::get).collect(Collectors.toList()));

if (--count == 0) break;

nextPermutation(indices);

}

}

public static <T extends Comparable<T>> void nextPermutation(List<T> list) {

int n = list.size(), i = n - 1, j;

for (;;) {

j = i--;

if (i < 0) break;

if (list.get(i).compareTo(list.get(j)) < 0) {

int k = n;

while (list.get(i).compareTo(list.get(--k)) >= 0);

Collections.swap(list, i, k);

break;

}

}

Collections.reverse(list.subList(j, n));

}

}

[经商, 学生, 士兵]

[经商, 士兵, 学生]

[学生, 经商, 士兵]

[学生, 士兵, 经商]

[士兵, 经商, 学生]

[士兵, 学生, 经商]


回答:

要想明白这个问题,其实需要先在数学上弄清楚。

这个问题可以转换为 全排列的问题,其实可以固定人或者职业(那个数量少固定那个),看另外一个有哪些排列可能。比如这里可以是职业数大于人数的。

这样对人数和职业数不同也可以处理了,只是前面先取组合而已。

而排列有很多处理方法,找一个即可。

以3人(甲、乙、丙),4职业为例(A、B、C、D)
比如把人(固定为对应位置),这里有3个就是 0,1,2 位置(从左到右),
把职业原始数据对应为索引编号0,1,2,3
则从4个职业中取3个来排列有P(4,3)可能(共24)。分别为

0 1 2 -> 甲A 乙B 丙C

0 2 1 -> 甲A 乙C 丙B

1 0 2 -> 甲B 乙A 丙C

1 2 0 -> 甲B 乙C 丙A

2 0 1 -> 甲C 乙A 丙B

2 1 0 -> 甲C 乙B 丙A

0 1 3 -> 甲A 乙B 丙D

0 3 1 -> 甲A 乙D 丙B

1 0 3 -> 甲B 乙A 丙D

1 3 0 -> 甲B 乙D 丙A

3 0 1 -> 甲D 乙A 丙B

3 1 0 -> 甲D 乙B 丙A

0 2 3 -> 甲A 乙C 丙D

0 3 2 -> 甲A 乙D 丙C

2 0 3 -> 甲C 乙A 丙D

2 3 0 -> 甲C 乙D 丙A

3 0 2 -> 甲D 乙A 丙C

3 2 0 -> 甲D 乙C 丙A

1 2 3 -> 甲B 乙C 丙D

1 3 2 -> 甲B 乙D 丙C

2 1 3 -> 甲C 乙B 丙D

2 3 1 -> 甲C 乙D 丙B

3 1 2 -> 甲D 乙B 丙C

3 2 1 -> 甲D 乙C 丙B

所以问题降为获取职业数的排列情况。即有N个不重复元素的数组,选M个进行排列的情况。
即求A(N,M),A(N,M)=N!/(N-M)!,A(4,3)=24

以上是 如何实现这样的分组和排列组合? 的全部内容, 来源链接: utcz.com/p/944685.html

回到顶部