i-th element of k-th permutation

天大地大妈咪最大 提交于 2019-11-29 08:46:51

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.

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]
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!