【问题标题】:Tips implementing permutation algorithm in Java在 Java 中实现置换算法的技巧
【发布时间】:2011-04-17 07:05:28
【问题描述】:

作为学校项目的一部分,我需要编写一个函数,该函数将采用整数 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 的最佳策略是什么?我可以只使用原始数组吗?我需要一个 ArrayList 数组吗?还是 ArrayList 的 ArrayList?还是有其他更好的数据类型?无论我使用什么,我都需要能够将其转换回原始数组的数组。

也许有更好的算法可以为我简化这个...

提前感谢您的建议!

【问题讨论】:

  • allPermutations(1) 将递归直到出现StackOverflow
  • 我也过着那样的生活。祝兄弟好运。

标签: java algorithm permutation


【解决方案1】:

正如您事先知道排列的数量(它是 N!)并且您希望/必须返回一个 int[][] 我会直接选择一个数组。您可以在开始时使用正确的尺寸声明它并在最后返回它。因此,您完全不必担心事后转换它。

【讨论】:

    【解决方案2】:

    由于您几乎已经用 javascript 自己完成了它,我将继续为您提供用于实现 Steinhaus 排列算法的 Java 代码。我基本上只是将你的代码移植到 Java 中,尽可能多地保留它,包括 cmets。

    我测试了它直到 N = 7。我试图让它计算 N = 8,但它已经在 2 GHz Intel Core 2 Duo 处理器上运行了将近 10 分钟,并且仍在运行,哈哈。

    我敢肯定,如果你真的努力工作,你可以显着加快速度,但即便如此,你也可能只能从中挤出更多的 N 值,除非你当然有访问超级计算机;-)。

    警告 - 此代码正确,但不可靠。如果你需要它健壮,而你通常不会在家庭作业中使用它,那么这将是一个留给你的练习。我还建议使用 Java Collections 来实现它,因为这将是学习 Collections API 的进出的好方法。

    其中包含多种“帮助”方法,包括一种用于打印二维数组的方法。享受吧!

    更新: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");
      }
    }
    

    【讨论】:

      【解决方案3】:

      java 数组是不可变的(从某种意义上说,你不能改变它们的长度)。对于此递归算法的直接翻译,您可能希望使用 List 接口(并且可能是 LinkedList 实现,因为您希望将数字放在中间)。那是List&lt;List&lt;Integer&gt;&gt;

      注意阶乘增长很快:对于 N = 13,有 13 个!排列是 6 227 020 800。但我想你只需要为小的值运行它。

      上面的算法很复杂,我的解决办法是:

      • 创建List&lt;int[]&gt; 以保存所有排列
      • 创建一个大小为 N 的数组并用标识 ({1,2,3,...,N}) 填充它
      • 在适当的位置创建字典顺序中的下一个排列的程序函数
      • 重复此操作,直到您再次获得身份:
        • 将数组的副本放在列表末尾
        • 调用方法获取下一个排列。

      如果您的程序只需要输出所有排列,我会避免存储它们并立即打印它们。

      计算下一个排列的算法可以在互联网上找到。 Here for example

      【讨论】:

      • 好建议;这几乎就是我最终做的事情。我发现字典式算法在 Java 中更容易实现。非常感谢!
      【解决方案4】:

      使用任何你想要的东西,数组或列表,但不要转换它们——它只会让它变得更难。我不知道什么更好,可能我会选择ArrayList&lt;int[]&gt;,因为外部列表允许我轻松添加排列并且内部数组足够好。这只是个人喜好问题(但通常更喜欢列表,因为它们更灵活)。

      【讨论】:

        【解决方案5】:

        按照霍华德的建议,我决定除了原始数组类型之外我不想使用任何东西。我最初选择的算法很难在 Java 中实现,所以感谢 stalker 的建议,我选择了lexicographic-ordered algorithm described at Wikipedia。这是我最终得到的结果:

        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;
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-11-07
          • 1970-01-01
          • 2010-12-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-01-04
          • 1970-01-01
          相关资源
          最近更新 更多