i-th element of k-th permutation

后端 未结 2 935
执念已碎
执念已碎 2020-12-18 14:48

Is there a fast algorithm to compute the i-th element (0 <= i < n) of the k-th permutation (0 <= k < n!) of the sequence 0..n-1?

相关标签:
2条回答
  • 2020-12-18 15:23

    What jkff said. You could modify an algorithm like the one you posted to just return the i-th element of the k-th permutation, but you won't save much time (or space), and you certainly won't reduce the Big-O complexity of the basic algorithm.

    The unordered permutation code that you posted isn't really amenable to modification because it has to loop over all the elements performing its swaps, and it's painful to determine if it's possible to break out of the loop early.

    However, there's a similar algorithm which produces ordered permutations, and it is possible to break out of that one early, but you still need to perform i inner loops to get the i-th element of the k-th permutation.

    I've implemented this algorithm as a class, just to keep the various constants it uses tidy. The code below produces full permutations, but it should be easy to modify to just return the i-th element.

    #!/usr/bin/env python
    
    ''' Ordered permutations using factorial base counting 
    
        Written by PM 2Ring 2015.02.15
        Derived from C code written 2003.02.13
    '''
    
    from math import factorial
    
    class Permuter(object):
        ''' A class for making ordered permutations, one by one '''
        def __init__(self, seq):
            self.seq = list(seq)
            self.size = len(seq)
            self.base = factorial(self.size - 1)
            self.fac = self.size * self.base
    
        def perm(self, k):
            ''' Build kth ordered permutation of seq '''
            seq = self.seq[:]
            p = []
            base = self.base
            for j in xrange(self.size - 1, 0, -1):
                q, k = divmod(k, base)
                p.append(seq.pop(q))
                base //= j
            p.append(seq[0])
            return p
    
    
    def test(seq):
        permuter = Permuter(seq)
        for k in xrange(permuter.fac):
            print '%2d: %s' % (k, ''.join(permuter.perm(k)))
    
    
    if __name__ == '__main__':
        test('abcd')
    

    This algorithm has a little more overhead than the unordered permutation maker: it requires factorial to be calculated in advance, and of course factorial gets very large very quickly. Also, it requires one extra division per inner loop. So the time savings in bailing out of the inner loop once you've found the i-th element may be offset by these overheads.


    FWIW, the code in your question has room for improvement. In particular, k /= n should be written as k //= n to ensure that integer division is used; your code works ok on Python 2 but not on Python 3. However, since we need both the quotient and remainder, it makes sense to use the built-in divmod() function. Also, by reorganizing things a little we can avoid the multiple calculations of n - 1

    #!/usr/bin/env python
    
    def kth_permutation(n, k):
        p = range(n)
        while n:
            k, j = divmod(k, n)
            n -= 1
            p[n], p[j] = p[j], p[n]
        return p
    
    def test(n):
        last = range(n)
        k = 0
        while True:
            p = kth_permutation(n, k)
            print k, p
            if p == last:
                break
            k += 1
    
    test(3)
    

    output

    0 [1, 2, 0]
    1 [2, 0, 1]
    2 [1, 0, 2]
    3 [2, 1, 0]
    4 [0, 2, 1]
    5 [0, 1, 2]
    
    0 讨论(0)
  • 2020-12-18 15:43

    You probably cannot get the i'th digit of the k'th permutation of n elements in O(n) time or space, because representing the number k itself requires O(log(n!)) = O(n log n) bits, and any manipulations with it have corresponding time complexity.

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