Советы по реализации алгоритма перестановок в Java
в рамках школьного проекта, мне нужно написать функцию, которая будет принимать целое число n и возвращает двумерный массив все перестановки массива {0, 1, ..., N-1}. Объявление будет выглядеть как публичные статические перестановки int [] [] (int N).
алгоритм, описанный в http://www.usna.edu/Users/math/wdj/book/node156.html Как я решил реализовать это.
Я довольно долго боролся с массивами и массивами ArrayLists и ArrayLists из ArrayLists, но до сих пор я был разочарован, особенно пытаясь преобразовать 2d ArrayList в 2d-массив.
поэтому я написал его на javascript. Это работает:
function allPermutations(N) {
// base case
if (N == 2) return [[0,1], [1,0]];
else {
// start with all permutations of previous degree
var permutations = allPermutations(N-1);
// copy each permutation N times
for (var i = permutations.length*N-1; i >= 0; i--) {
if (i % N == 0) continue;
permutations.splice(Math.floor(i/N), 0, permutations[Math.floor(i/N)].slice(0));
}
// "weave" next number in
for (var i = 0, j = N-1, d = -1; i < permutations.length; i++) {
// insert number N-1 at index j
permutations[i].splice(j, 0, N-1);
// index j is N-1, N-2, N-3, ... , 1, 0; then 0, 1, 2, ... N-1; then N-1, N-2, etc.
j += d;
// at beginning or end of the row, switch weave direction
if (j < 0 || j >= N) {
d *= -1;
j += d;
}
}
return permutations;
}
}
Итак, какая лучшая стратегия для порта этого на Java? Могу ли я сделать это с помощью примитивных массивов? Нужен ли мне массив ArrayLists? Или ArrayList с артефакты ArrayList? Или есть какой-то другой тип данных, который лучше? Что бы я ни использовал, мне нужно иметь возможность преобразовать его обратно в массив примитивов матрицы.
может быть, есть лучший алгоритм, который упростил бы это для меня...
спасибо заранее за Ваши советы!
5 ответов
Как вы знаете, количество перестановок заранее (это N!) а также вы хотите / должны вернуть int[][]
Я бы пошел на массив напрямую. Вы можете объявить его в начале с правильными размерами и вернуть его в конце. Таким образом, вам вообще не нужно беспокоиться о его преобразовании.
Так как вы в значительной степени было завершено в JavaScript, я буду идти вперед и дать вам Java-код для реализации алгоритма перестановки Штайнхаус'. Я в основном просто портировал ваш код на Java, оставив столько же, сколько мог, включая комментарии.
я протестировал его до N = 7. Я попытался рассчитать N = 8, но он работает уже почти 10 минут на процессоре Intel Core 2 Duo 2 GHz и все еще работает, lol.
Я уверен, если вы действительно, Вы могли бы значительно ускорить это, но даже тогда вы, вероятно, сможете выжать из него еще пару N-значений, если, конечно, у вас нет доступа к суперкомпьютеру ;-).
предупреждение - этот код правильный, а не надежный. Если вам это нужно, что вы обычно не делаете для домашних заданий, то это будет упражнение, оставленное вам. Я бы также рекомендовал реализовать его с помощью коллекций Java, просто потому, что это был бы отличный способ чтобы узнать in и out из API коллекций.
есть несколько "вспомогательных" методов, включая один для печати 2d-массива. Наслаждайтесь!
обновление: N = 8 заняло 25 минут 38 секунд.
Edit: исправлено N == 1 и N == 2.
public class Test
{
public static void main (String[] args)
{
printArray (allPermutations (8));
}
public static int[][] allPermutations (int N)
{
// base case
if (N == 2)
{
return new int[][] {{1, 2}, {2, 1}};
}
else if (N > 2)
{
// start with all permutations of previous degree
int[][] permutations = allPermutations (N - 1);
for (int i = 0; i < factorial (N); i += N)
{
// copy each permutation N - 1 times
for (int j = 0; j < N - 1; ++j)
{
// similar to javascript's array.splice
permutations = insertRow (permutations, i, permutations [i]);
}
}
// "weave" next number in
for (int i = 0, j = N - 1, d = -1; i < permutations.length; ++i)
{
// insert number N at index j
// similar to javascript's array.splice
permutations = insertColumn (permutations, i, j, N);
// index j is N-1, N-2, N-3, ... , 1, 0; then 0, 1, 2, ... N-1; then N-1, N-2, etc.
j += d;
// at beginning or end of the row, switch weave direction
if (j < 0 || j > N - 1)
{
d *= -1;
j += d;
}
}
return permutations;
}
else
{
throw new IllegalArgumentException ("N must be >= 2");
}
}
private static void arrayDeepCopy (int[][] src, int srcRow, int[][] dest,
int destRow, int numOfRows)
{
for (int row = 0; row < numOfRows; ++row)
{
System.arraycopy (src [srcRow + row], 0, dest [destRow + row], 0,
src[row].length);
}
}
public static int factorial (int n)
{
return n == 1 ? 1 : n * factorial (n - 1);
}
private static int[][] insertColumn (int[][] src, int rowIndex,
int columnIndex, int columnValue)
{
int[][] dest = new int[src.length][0];
for (int i = 0; i < dest.length; ++i)
{
dest [i] = new int [src[i].length];
}
arrayDeepCopy (src, 0, dest, 0, src.length);
int numOfColumns = src[rowIndex].length;
int[] rowWithExtraColumn = new int [numOfColumns + 1];
System.arraycopy (src [rowIndex], 0, rowWithExtraColumn, 0, columnIndex);
System.arraycopy (src [rowIndex], columnIndex, rowWithExtraColumn,
columnIndex + 1, numOfColumns - columnIndex);
rowWithExtraColumn [columnIndex] = columnValue;
dest [rowIndex] = rowWithExtraColumn;
return dest;
}
private static int[][] insertRow (int[][] src, int rowIndex,
int[] rowElements)
{
int srcRows = src.length;
int srcCols = rowElements.length;
int[][] dest = new int [srcRows + 1][srcCols];
arrayDeepCopy (src, 0, dest, 0, rowIndex);
arrayDeepCopy (src, rowIndex, dest, rowIndex + 1, src.length - rowIndex);
System.arraycopy (rowElements, 0, dest [rowIndex], 0, rowElements.length);
return dest;
}
public static void printArray (int[][] array)
{
for (int row = 0; row < array.length; ++row)
{
for (int col = 0; col < array[row].length; ++col)
{
System.out.print (array [row][col] + " ");
}
System.out.print ("\n");
}
System.out.print ("\n");
}
}
массивы java не изменяемы (в том смысле, что вы не можете изменить их длину). Для прямого перевода этого рекурсивного алгоритма вы, вероятно, захотите использовать интерфейс List (и, вероятно, реализацию LinkedList, поскольку вы хотите поместить числа посередине). Это List<List<Integer>>
.
остерегайтесь факториал быстро растет: для N = 13, есть 13! перестановок, что составляет 6 227 020 800. Но я думаю, вам нужно запустить его только для небольших значений.
алгоритм выше довольно сложным, моим решением было бы:
- создать
List<int[]>
для хранения всех перестановок - создайте один массив размера N и заполните его идентификатором ({1,2,3,..., N})
- функция программы, которая на месте создает следующую перестановку в лексикографическом порядке
- повторите это, пока не получите идентичность снова:
- поместите копию массива в конце списка
- вызвать метод, чтобы получить следующий перестановка.
Если вашей программе просто нужно вывести все перестановки, я бы избегал хранить их и просто печатать их сразу.
алгоритм вычисления следующей перестановки можно найти в интернете. вот например
используйте все, что хотите, массивы или списки,но не конвертируйте их - это только усложняет. Я не могу сказать, что лучше, вероятно, я бы пошел на ArrayList<int[]>
, Так как внешний список позволяет мне легко добавлять перестановку, а внутренний массив достаточно хорош. Это просто вопрос вкуса (но обычно предпочитают списки, так как они гораздо более гибкие).
согласно совету Говарда, я решил, что не хочу использовать ничего, кроме примитивного типа массива. Алгоритм, который я изначально выбрал, был болью для реализации на Java, поэтому благодаря совету сталкера я пошел с лексикографический упорядоченный алгоритм, описанный в Википедии. Вот что у меня получилось:
public static int[][] generatePermutations(int N) {
int[][] a = new int[factorial(N)][N];
for (int i = 0; i < N; i++) a[0][i] = i;
for (int i = 1; i < a.length; i++) {
a[i] = Arrays.copyOf(a[i-1], N);
int k, l;
for (k = N - 2; a[i][k] >= a[i][k+1]; k--);
for (l = N - 1; a[i][k] >= a[i][l]; l--);
swap(a[i], k, l);
for (int j = 1; k+j < N-j; j++) swap(a[i], k+j, N-j);
}
return a;
}
private static void swap(int[] is, int k, int l) {
int tmp_k = is[k];
int tmp_l = is[l];
is[k] = tmp_l;
is[l] = tmp_k;
}