Советы по реализации алгоритма перестановки в Java - PullRequest
11 голосов
/ 17 апреля 2011

В рамках школьного проекта мне нужно написать функцию, которая будет принимать целое число N и возвращать двумерный массив каждой перестановки массива {0, 1, ..., N-1}.Объявление будет выглядеть как public static int [] [] permutations (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 из ArrayLists?Или есть какой-то другой тип данных, который лучше?Что бы я ни использовал, мне нужно иметь возможность преобразовать его обратно в массив примитивных массивов.

Может быть, есть лучший алгоритм, который упростил бы это для меня ...

Спасибо зазаранее за ваш совет!

Ответы [ 5 ]

6 голосов
/ 17 апреля 2011

Поскольку вы заранее знаете количество перестановок (это N!), А также вы хотите / должны вернуть int[][], я бы пошел напрямую к массиву.Вы можете объявить это в самом начале с правильными размерами и вернуть его в конце.Таким образом, вам вообще не нужно беспокоиться о его преобразовании.

2 голосов
/ 18 апреля 2011

Поскольку вы в значительной степени завершили его самостоятельно в javascript, я продолжу и дам вам Java-код для реализации алгоритма перестановки Steinhaus. Я просто перенес ваш код на Java, оставив как можно больше, включая комментарии.

Я проверил его до N = 7. Я пытался заставить его рассчитать N = 8, но он уже почти 10 минут работает на процессоре Intel Core 2 Duo с частотой 2 ГГц и все еще работает, смеется.

Я уверен, что если бы вы действительно работали над этим, вы могли бы значительно ускорить это, но даже тогда вы, вероятно, сможете выжать из него, возможно, еще пару N-значений, если, конечно, у вас нет доступ к суперкомпьютеру; -).

Предупреждение - этот код правильный, а не надежный. Если вам это нужно, что вам обычно не нужно делать при выполнении домашних заданий, тогда это было бы вашим заданием. Я бы также порекомендовал реализовать его с помощью Java Collections, просто потому что это был бы отличный способ изучить вход и выход API Collections.

Включено несколько вспомогательных методов, в том числе один для печати 2d-массива. Наслаждайтесь!

Обновление: N = 8 заняло 25 минут 38 секунд.

Редактировать: исправлено 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");
  }
}
1 голос
/ 17 апреля 2011

Java-массивы не являются изменяемыми (в том смысле, что вы не можете изменить их длину). Для прямой трансляции этого рекурсивного алгоритма вы, вероятно, захотите использовать интерфейс List (и, возможно, реализацию LinkedList, поскольку вы хотите поместить числа в середину). Это List<List<Integer>>.

Осторожно, факториал быстро растет: для N = 13 их 13! перестановок это 6 227 020 800. Но я думаю, вам нужно запустить его только для небольших значений.

Алгоритм выше довольно сложный, мое решение будет:

  • создать List<int[]> для хранения всех перестановок
  • создать один массив размером N и заполнить его идентификатором ({1,2,3, ..., N})
  • программная функция, которая создает следующую перестановку в лексикографическом порядке
  • повторяйте это, пока вы снова не получите личность:
    • поставить копию массива в конец списка
    • вызов метода для получения следующей перестановки.

Если вашей программе просто нужно вывести все перестановки, я бы избегал их сохранения и просто распечатывал их сразу.

Алгоритм вычисления следующей перестановки можно найти в интернете. Вот например

0 голосов
/ 14 августа 2013

По совету Говарда я решил, что не хочу использовать ничего, кроме примитивного типа массива.Алгоритм, который я изначально выбрал, было непросто реализовать в 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;
}
0 голосов
/ 17 апреля 2011

Используйте все, что вы хотите, массивы или списки, но не конвертируйте их - это только усложняет.Я не могу сказать, что лучше, вероятно, я бы пошел на ArrayList<int[]>, так как внешний список позволяет мне легко добавлять перестановку, и внутренний массив достаточно хорош.Это просто вопрос вкуса (но обычно предпочитают списки, так как они гораздо более гибкие).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...