排列等级

时间:2016-10-03 19:35:32

标签: python algorithm performance permutation combinatorics

所以有一个问题我无法解决,主要是因为计算能力或缺乏计算能力。想知道如何对此进行编码,以便我可以在我的计算机上实际运行它。问题的要点是:

我们假设您有一个字符串'xyz',并且您想要查找此字符串的所有唯一排列。然后,您对它们进行排序,并从唯一排列中找到'xyz'的索引。这似乎很简单,但一旦你得到一个非常长的字符串,我的电脑就会放弃。我所假设的数学方法是什么,会让我得到可以在笔记本电脑上实际运行的代码。

from itertools import permutations

def find_rank(n):
    perms = [''.join(p) for p in permutations(n)]
    perms = sorted(set(perms))
    loc = perms.index(n)
    return loc

但是,如果我想在长度为100个字母的字符串上运行此代码,那么对于我的计算机来说,它的处理方式太多了。

3 个答案:

答案 0 :(得分:2)

这个问题可以通过先简化和递归思考来轻松解决。

因此,让我们首先假设输入序列中的所有元素都是唯一的,然后是"唯一的"排列只是一组排列。

现在我可以在序列{​​{1}}中找到序列a_1, a_2, a_3, ..., a_n的排名:

  1. 对序列进行排序以获取b_1, b_2, ..., b_n。根据定义,这种排列的排名为0

  2. 现在我们比较a_1b_1。如果它们相同,那么我们只需将其从问题中移除:a_1, a_2, ..., a_n的等级将与仅a_2, ..., a_n的等级相同。

  3. 否则为b_1 < a_1,但以b_1开头的所有排列将小于a_1, a_2, ..., a_n。这种排列的数量很容易计算,只是(n-1)! = (n-1)*(n-2)*(n-3)*...*1

    但接着我们可以继续查看序列b_1, ..., b_n。如果b_2 < a_1,则以b_2开头的所有排列都会更小。 因此,我们应该再次将(n-1)!添加到我们的排名中。

    我们这样做,直到找到j索引b_j == a_j,然后我们最终到达第2点。

  4. 这很容易实现:

    import math
    
    def permutation_rank(seq):
        ref = sorted(seq)
        if ref == seq:
            return 0
        else:
            rank = 0
            f = math.factorial(len(seq)-1)
            for x in ref:
                if x < seq[0]:
                    rank += f
                else:
                    rank += permutation_rank(seq[1:]) if seq[1:] else 0
                    return rank
    

    解决方案非常快:

    In [24]: import string
        ...: import random
        ...: seq = list(string.ascii_lowercase)
        ...: random.shuffle(seq)
        ...: print(*seq)
        ...: print(permutation_rank(seq))
        ...: 
    r q n c d w s k a z b e m g u f i o l t j x p h y v
    273956214557578232851005079
    

    关于平等元素的问题:它们发挥作用的一点是(n-1)!是排列的数量,考虑到每个元素与其他元素不同。如果您的序列长度为n,则由符号s_1, ..., s_k和符号s_j组成c_j次,那么唯一排列的数量为`(n-1)! /(c_1!* c_2!* ... * c_k!)。

    这意味着我们不必仅添加(n-1)!,而是将其除以该数字,并且我们还希望将当前符号的计数c_t减少一个。

    这可以通过这种方式完成:

    import math
    from collections import Counter
    from functools import reduce
    from operator import mul
    
    def permutation_rank(seq):
        ref = sorted(seq)
        counts = Counter(ref)
    
        if ref == seq:
            return 0
        else:
            rank = 0
            f = math.factorial(len(seq)-1)
            for x in sorted(set(ref)):
                if x < seq[0]:
                    counts_copy = counts.copy()
                    counts_copy[x] -= 1
                    rank += f//(reduce(mul, (math.factorial(c) for c in counts_copy.values()), 1))
                else:
                    rank += permutation_rank(seq[1:]) if seq[1:] else 0
                    return rank
    

    我很确定有一种方法可以避免复制计数词典,但是现在我已经累了,所以我会把它作为读者的练习。

    供参考,最终结果:

    In [44]: for i,x in enumerate(sorted(set(it.permutations('aabc')))):
        ...:     print(i, x, permutation_rank(x))
        ...:     
    0 ('a', 'a', 'b', 'c') 0
    1 ('a', 'a', 'c', 'b') 1
    2 ('a', 'b', 'a', 'c') 2
    3 ('a', 'b', 'c', 'a') 3
    4 ('a', 'c', 'a', 'b') 4
    5 ('a', 'c', 'b', 'a') 5
    6 ('b', 'a', 'a', 'c') 6
    7 ('b', 'a', 'c', 'a') 7
    8 ('b', 'c', 'a', 'a') 8
    9 ('c', 'a', 'a', 'b') 9
    10 ('c', 'a', 'b', 'a') 10
    11 ('c', 'b', 'a', 'a') 11
    

    并表明它是有效的:

    In [45]: permutation_rank('zuibibzboofpaoibpaybfyab')
    Out[45]: 246218968687554178
    

答案 1 :(得分:1)

让我们看看如何在不查找字符串的所有排列的情况下计算字符串的索引。

考虑字符串s = "cdab".现在,在字符串s之前(按词汇顺序),字符串"a***", "b***"就在那里。 (*表示剩余字符。)

那是2*3!个字符串。因此,任何以c开头的字符串都会有超过此值的索引。

"a***"开头的"b***",'c'字符串开始后。 字符串s = 2*3! + index("dab")的索引。

现在以递归方式查找"dab"

的索引

为了说明,字符串的顺序如下:

    a*** --> 3! 
    b*** --> 3!
    ca** --> 2!
    cb** --> 2!
    cdab --> 1  

以下是python代码:

import math

def index(s):
    if(len(s)==1):
        return 1
    first_char = s[0]
    character_greater = 0
    for c in s:
        if(first_char>c):
            character_greater = character_greater+1
    return (character_greater*math.factorial((len(s)-1)) + index(s[1:len(s)])    

答案 2 :(得分:0)

这是我编写的一些Ruby代码。如果您有重复的元素(并决定如何处理它们),您需要修改它。

这有利于如果我们有n个元素,每个k元素的选择将精确显示(n-k)!倍。例如,[a,b,c,d] - 如果我们查看所有排列,(4-1)! = 3!他们将从'a','b','c'和'd'开始。特别是第3个!将从'a'开始,接下来的3!用b等等。然后你在其余的elts上进行递归。

  def get_perm_id(arr)
    arr_len = arr.length
    raise "get_perm_id requires an array of unique elts" if arr_len != arr.uniq.length
    arr_sorted = arr.sort
    perm_num = 0
    0.upto(arr_len - 2) do |i|
      arr_elt = self[i]
      sorted_index = arr_sorted.find_index(arr_elt)
      sorted_right_index = arr_sorted.length - sorted_index - 1
      right_index = arr_len - i - 1
      left_delta = [0, right_index - sorted_right_index].max
      perm_num += left_delta * (arr_len - i - 1).factorial
      arr_sorted.slice!(sorted_index)
    end
    perm_num
  end