【问题标题】:Algorithm for finding numerical permutation given lexicographic index给定字典索引的数值排列算法
【发布时间】:2012-02-14 23:06:45
【问题描述】:

我正在寻找一种算法,它给定一组数字(例如 1 2 3)和一个索引(例如 2),它将根据字典顺序得到这些数字的第二次排列。例如,在这种情况下,算法将返回 1 3 2。

【问题讨论】:

  • 到目前为止你尝试过什么?一种天真的方法是生成所有排列,按字典顺序排列它们,然后选择它的第 n 个。
  • 提示:首先找到一个算法来确定第n个排列的第一个数字。
  • 相关提示:尝试搜索“阶乘基数”和“Lehmer 码”

标签: algorithm permutation


【解决方案1】:

这是一个简单的解决方案:

from math import factorial # python math library

i = 5               # i is the lexicographic index (counting starts from 0)
n = 3               # n is the length of the permutation
p = range(1, n + 1) # p is a list from 1 to n

for k in range(1, n + 1): # k goes from 1 to n
    f = factorial(n - k)  # compute factorial once per iteration
    d = i // f            # use integer division (like division + floor)
    print(p[d]),          # print permuted number with trailing space
    p.remove(p[d])        # delete p[d] from p
    i = i % f             # reduce i to its remainder

输出:

3 2 1

如果p 是一个列表,时间复杂度是O(n^2),如果p 是一个哈希表,那么时间复杂度是O(n),并且factorial 是预计算的。

【讨论】:

  • 虽然这是最好的答案,但 list.remove() 在 python 中不是 O(1),因此这个解决方案是 Θ(n^2)。通过使用 BST 而不是列表,可以将其简化为 Θ(n log n)。这是假设 factorial() 是 O(1) (不是)。
【解决方案2】:

提到的文章链接: http://penguin.ewu.edu/~trolfe/#Shuffle

/* Converting permutation index into a permutation
 * From code accompanying "Algorithm Alley: Randomized Shuffling",
 * Dr. Dobb’s Journal, Vol. 25, No. 1  (January 2000)
 * http://penguin.ewu.edu/~trolfe/#Shuffle
 *
 * Author:  Tim Rolfe
 *          RolfeT@earthlink.net
 *          http://penguin.ewu.edu/~trolfe/
 */
#include <stdio.h>
#include <stdlib.h>

// http://stackoverflow.com/questions/8940470/algorithm-for-finding-numerical-permutation-given-lexicographic-index

// Invert the permutation index --- generate what would be
// the subscripts in the N-dimensional array with dimensions
// [N][N-1][N-2]...[2][1]
void IndexInvert(int J[], int N, int Idx)
{  int M, K;

   for (M=1, K=N-1; K > 1; K--)   // Generate (N-1)!
      M *= K;
   for ( K = 0; M > 1; K++ )
   {  J[K] = Idx / M;   // Offset in dimension K
      Idx = Idx % M;    // Remove K contribution
      M /= --N;         // next lower factorial
   }
   J[K] = Idx;          // Right-most index
}

// Generate a permutation based on its index / subscript set.
// To generate the lexicographic order, this involves _shifting_
// characters around rather than swapping.  Right-hand side must
// remain in lexicographic order
void Permute (char Line[], char first, int N, int Jdx[])
{  int Limit;

   Line[0] = first;
   for (Limit = 1; Limit < N; Limit++)
      Line[Limit] = (char)(1+Line[Limit-1]);

   for (Limit = 0; Limit < N; Limit++)
   {  char Hold;
      int Idx = Limit + Jdx[Limit];
      Hold = Line[Idx];
      while (Idx > Limit)
      {  Line[Idx] = Line[Idx-1];
         Idx--;
      }
      Line[Idx] = Hold;
   }
}

// Note:  hard-coded to generate permutation in the set [abc...
int main(int argc, char** argv)
{  int   N = argc > 1 ? atoi(argv[1]) : 4;
   char *Perm  = (char*) calloc(N+1, sizeof *Perm);
   int  *Jdx   = (int*)  calloc(N, sizeof *Jdx);
   int   Index = argc > 2 ? atoi(argv[2]) : 23;
   int   K, Validate;

   for (K = Validate = 1; K <= N; K++)
      Validate *= K;
   if (Index < 0 || Index >= Validate)
   {  printf("Invalid index %d:  %d! is %d\n", Index, N, Validate);
      return -1;   // Error return
   }
   IndexInvert(Jdx, N, Index);
   Permute (Perm, 'a', N, Jdx);
   printf("For N = %d, permutation %d in [0..%d] is %s\n",
          N, Index, Validate-1, Perm);
   return 0;       // Success return
}

【讨论】:

    【解决方案3】:

    这是一个Scala中的示例解决方案,我将详细解释:

    /**
        example: index:=15, list:=(1, 2, 3, 4)
    */ 
    def permutationIndex (index: Int, list: List [Int]) : List [Int] = 
      if (list.isEmpty) list else {
        val len = list.size     // len = 4
        val max = fac (len)     // max = 24
        val divisor = max / len // divisor = 6
        val i = index / divisor // i = 2
        val el = list (i)
        el :: permutationIndex (index - divisor * i, list.filter (_ != el)) }
    

    由于 Scala 并不为人所知,我想我必须解释一下算法的最后一行,除此之外,它是非常不言自明的。

        el :: elist
    

    从元素 el 和列表 elist 构造一个新列表。 Elist 是一个递归调用。

        list.filter (_ != el)
    

    只是没有元素 el 的列表。

    用一个小列表彻底测试它:

    (0 to fac (4) - 1).map (permutationIndex (_, List (1, 2, 3, 4))).mkString ("\n")
    

    用 2 个例子测试更大 List 的速度:

    scala> permutationIndex (123456789, (1 to 12).toList) 
    res45: List[Int] = List(4, 2, 1, 5, 12, 7, 10, 8, 11, 6, 9, 3)
    
    scala> permutationIndex (123456790, (1 to 12).toList) 
    res46: List[Int] = List(4, 2, 1, 5, 12, 7, 10, 8, 11, 9, 3, 6)
    

    结果会立即出现在一台 5 年前的笔记本电脑上。 12 个元素的列表有 479 001 600 个排列,但是对于 100 或 1000 个元素,这个解决方案应该仍然可以快速工作 - 你只需要使用 BigInt 作为索引。

    它是如何工作的?我制作了一个图形,以可视化示例,一个 (1, 2, 3, 4) 的列表和一个 15 的索引:

    4 个元素的列表产生 4 个!排列(=24)。我们选择从 0 到 4 的任意索引!-1,比如说 15。

    我们可以可视化一棵树中的所有排列,第一个节点来自 1..4。我们分4!乘以 4 可以看出,每个第一个节点都会导致 6 个子树。如果我们将索引 15 除以 6,则结果为 2,并且索引为 2 的从零开始的 List 中的值是 3。所以第一个 Node 是 3,而 List 的其余部分是 (1, 2, 4) .这是一个表格,显示了 15 如何导致 Array/List/whatever 中索引为 2 的元素:

    0 1 2 3 4 5 | 6 ... 11 | 12 13 14 15 16 17 | 18 ... 23
         0      |     1    |         2         |     3
                |          | 0  1  2  3  4  5  |
    

    我们现在减去 12,即最后 3 个元素的第一个元素 (12...17),它们有 6 种可能的排列,看看 15 如何映射到 3。数字 3 导致数组索引 1现在,它是元素 2,所以到目前为止的结果是 List (3, 2, ...)。

                            | 0 1 | 2 3 | 4 5 |
                            |  0  |  1  |  3  |
                                  | 0 1 |
    

    同样,我们减去 2,并以 2 个剩余元素结束,具有 2 个排列和索引 (0, 3),映射到值 (1, 4)。我们看到,第二个元素,属于从顶部开始的 15,映射到值 3,最后一步的剩余元素是另一个:

                                 | 0 | 1 |
                                 | 0 | 3 |
                                 | 3 | 0 |
    

    我们的结果是 List(3, 2, 4, 1) 或索引 (2, 1, 3, 0)。按顺序测试所有索引表明,它们按顺序产生所有排列。

    【讨论】:

      【解决方案4】:

      因为你没有指定你想要的语言,here's an implementation in python

      你只需要得到序列的第 n 个元素。

      该算法的一个想法可能是生成一个表示输入序列的笛卡尔积的序列并对其进行迭代,跳过具有重复元素的项目。

      请注意,这可能不是最快的方法,但绝对是一种简单的方法。对于快速的,请参阅 cyborg 的答案。

      【讨论】:

      • 这个解决方案需要 O(k!) 时间(k 是排列的长度),而 O(k) 很容易获得。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-08-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-25
      • 2017-11-12
      相关资源
      最近更新 更多