Algorithm for finding numerical permutation given lexicographic index

前端 未结 4 904
我在风中等你
我在风中等你 2020-11-28 12:53

I am looking for an algorithm that given a set of numbers (for example 1 2 3) and an index (for example 2) will get me the second permutation of those numbers according to a

相关标签:
4条回答
  • 2020-11-28 13:31

    Here is a sample solution in Scala, which I will explain in detail:

    /**
        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)) }
    

    Since Scala isn't that well known, I think I have to explain the last line of the algorithm, which is, apart from that, pretty self explanatory.

        el :: elist
    

    constructs a new list from an element el and an list elist. Elist is a recursive call.

        list.filter (_ != el)
    

    is just the list without the element el.

    Test it exhaustively with a small List:

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

    Test the speed of a bigger List with 2 examples:

    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)
    

    The result appears immediately on a 5 years old laptop. There are 479 001 600 permutations for a List of 12 elements, but with 100 or 1000 elements, this solution should still work fast - you just would need to use BigInt as Index.

    How does it work? I made a graphic, to visualize the example, a List of (1, 2, 3, 4) and an index of 15:

    enter image description here

    A List of 4 Elements produces 4! permutations (=24). We choose an arbitrary index from 0 to 4!-1, let's say 15.

    We can visualize all permutations in a tree, with the first node from 1..4. We divide 4! by 4 and see, that every first-node leads to 6 subtrees. If we divide our index 15 by 6, the result is 2, and the value in our zero-based List with index 2 is 3. So the first Node is 3, and the rest of our List is (1, 2, 4). Here is a table, showing how 15 leads to the element with index 2 in the Array/List/whatever:

    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  |
    

    We now subtract 12, the first element of the cell (12...17) for the last 3 elements, which have 6 possible permutations, and see, how 15 maps to 3. Number 3 leads to the array index 1 now, which was element 2, so the result so far is List (3, 2, ...).

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

    Again, we subtract 2, and end in 2 remaining elements with 2 permutations, and index (0, 3), mapping to the values (1, 4). We see, that the second element, belonging to the 15 from top, maps to value 3, and the remaining element for the last step is the other one:

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

    Our result is List(3, 2, 4, 1) or the indexes (2, 1, 3, 0). Testing all indexes in order shows, that they yield all permutations in order.

    0 讨论(0)
  • 2020-11-28 13:35

    As you didn't specify what language you want it in, here's an implementation in python.

    You only have to get n-th element of the sequence.

    One idea of the algorithm might be to generate a sequence representing a cartesian product of the input sequence and iterate it, skipping items that have repeating elements.

    Note that this might not be the fastest way to do it, but definitely is an easy one. For a fast one see cyborg's answer.

    0 讨论(0)
  • 2020-11-28 13:40

    Link to article mentioned:  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
    }
    
    0 讨论(0)
  • 2020-11-28 13:55

    Here is a simple solution:

    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
    

    Output:

    3 2 1
    

    The time complexity is O(n^2) if p is a list, and O(n) amortized if p is a hash table and factorial is precomputed.

    0 讨论(0)
提交回复
热议问题