考虑一个小例子:假设我有1234,我想生成一个四位数的所有排列,并且每次检查它是否等于1234。
我的第一个想法是实现一个递归函数来生成排列。但考虑到规模,调用栈会溢出。
有没有一种高效的方法来生成所有排列?鉴于Java拥有大量的库,请问如何实现?
如果我理解正确,您需要生成一个64字节数组的所有64!排列,即:
64!= 126886932185884164103433389335161480802865516174545192198801894375214704230400000000000000 种排列!
如果每个排列和比较都需要一毫秒(最差情况下的时间)来计算,那么您需要:
4023558225072430368576654912961741527234446859923426946943236123009091331506849.3150684931506849315 年在一台机器上计算出所有结果!(如果每个排列只需要1/1000毫秒,则缩短该时间为100年)。
因此,您应该通过应用一些启发式方法来减少问题的搜索空间,而不是简单地列出所有可能的解决方案。
当你将搜索空间缩小到一个较小的可处理数量,例如:14!(在“一毫秒”情况下需要2年的计算时间),你可以使用Factoradics(这里有一个实现here)将你的计算分布到几台机器上,以计算每台机器的起始和结束排列,然后在每个节点上使用以下代码(Knuth's L-algorithm的实现)在每台机器中搜索解决方案:
public class Perm {
private static byte[] sequenceToMatch;
private static byte[] startSequence;
private static byte[] endingSequence;
private static final int SEQUENCE_LENGTH = 64;
public static void main(String... args) {
final int N = 3;
startSequence = readSequence(args[0]);
endingSequence = readSequence(args[1]);
sequenceToMatch = readSequence(args[2]);
permutations();
}
private static boolean sequencesMatch(byte[] s1, byte[] s2) {
for (int i = 0; i < SEQUENCE_LENGTH; i++) {
if (s1[i] != s2[i]) {
return false;
}
}
return true;
}
private static byte[] readSequence(String argument) {
String[] sBytes = argument.split(",");
byte[] bytes = new byte[SEQUENCE_LENGTH];
int i = 0;
for (String sByte : sBytes) {
bytes[i++] = Byte.parseByte(sByte, 10);
}
return bytes;
}
private static void swap(byte[] elements, int i, int j) {
byte temp = elements[i];
elements[i] = elements[j];
elements[j] = temp;
}
/**
* Reverses the elements of an array (in place) from the start index to the end index
*/
private static void reverse(byte[] array, int startIndex, int endIndex) {
int size = endIndex + 1 - startIndex;
int limit = startIndex + size / 2;
for (int i = startIndex; i < limit; i++) {
// swap(array, i, startIndex + (size - 1 - (i - startIndex)));
swap(array, i, 2 * startIndex + size - 1 - i);
}
}
/**
* Implements the Knuth's L-Algorithm permutation algorithm
* modifying the collection in place
*/
private static void permutations() {
byte[] sequence = startSequence;
if (sequencesMatch(sequence, sequenceToMatch)) {
System.out.println("Solution found!");
return;
}
// For every possible permutation
while (!sequencesMatch(sequence, endingSequence)) {
// Iterate the array from right to left in search
// of the first couple of elements that are in ascending order
for (int i = SEQUENCE_LENGTH - 1; i >= 1; i--) {
// If the elements i and i - 1 are in ascending order
if (sequence[i - 1] < sequence[i]) {
// Then the index "i - 1" becomes our pivot index
int pivotIndex = i - 1;
// Scan the elements at the right of the pivot (again, from right to left)
// in search of the first element that is bigger
// than the pivot and, if found, swap it
for (int j = SEQUENCE_LENGTH - 1; j > pivotIndex; j--) {
if (sequence[j] > sequence[pivotIndex]) {
swap(sequence, j, pivotIndex);
break;
}
}
// Now reverse the elements from the right of the pivot index
// (this nice touch to the algorithm avoids the recursion)
reverse(sequence, pivotIndex + 1, SEQUENCE_LENGTH - 1);
break;
}
}
if (sequencesMatch(sequence, sequenceToMatch)) {
System.out.println("Solution found!");
return;
}
}
}
}
关于非递归,这个答案可能会有所帮助:Java无递归排列算法
至于简单的例子,这里是我想出的递归解决方案:
public class Solution {
public List<List<Integer>> permute(int[] num) {
boolean[] used = new boolean[num.length];
for (int i = 0; i < used.length; i ++) used[i] = false;
List<List<Integer>> output = new ArrayList<List<Integer>>();
ArrayList<Integer> temp = new ArrayList<Integer>();
permuteHelper(num, 0, used, output, temp);
return output;
}
public void permuteHelper(int[] num, int level, boolean[] used, List<List<Integer>> output, ArrayList<Integer> temp){
if (level == num.length){
output.add(new ArrayList<Integer>(temp));
}
else{
for (int i = 0; i < num.length; i++){
if (!used[i]){
temp.add(num[i]);
used[i] = true;
permuteHelper(num, level+1, used, output, temp);
used[i] = false;
temp.remove(temp.size()-1);
}
}
}
}
}
编辑:使用递归方法,最大输入为10,需要在合理时间内完成。
如果给定一个长度为10的输入数组:
Permutation execution time in milliseconds: 3380
编辑: 我快速搜索了可能的实现方式,并找到了一个算法,它是作为回答另一个问题的一部分建议的。
下面是由https://stackoverflow.com/users/2132573/jon-b开发的代码,供您方便使用。 它正确地创建并打印整数列表的所有排列。您可以轻松地使用相同的逻辑来处理数组(鸣谢jon-b!!)。
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class HelloWorld {
public static int factorial(int x) {
int f = 1;
while (x > 1) {
f = f * x;
x--;
}
return f;
}
public static List<Integer> permute(List<Integer> list, int iteration) {
if (list.size() <= 1) return list;
int fact = factorial(list.size() - 1);
int first = iteration / fact;
List<Integer> copy = new ArrayList<Integer>(list);
Integer head = copy.remove(first);
int remainder = iteration % fact;
List<Integer> tail = permute(copy, remainder);
tail.add(0, head);
return tail;
}
public static void main(String[] args) throws IOException {
List<Integer> list = Arrays.asList(4, 5, 6, 7);
for (int i = 0; i < 24; i++) {
System.out.println(permute(list, i));
}
}
}
我已经在http://www.tutorialspoint.com/compile_java8_online.php上测试过,它可以正常工作。
希望这有所帮助!