【问题标题】:Ranking and unranking of permutations with duplicates重复排列的排序和取消排序
【发布时间】:2011-07-30 17:13:03
【问题描述】:

我正在阅读有关排列的文章,并且对排名/取消排名方法感兴趣。

来自论文摘要:

n 个符号排列的排序函数分配一个唯一的 [0, n! 范围内的整数- 1]到每个n!排列。相应的 unranking 函数是相反的:给定一个介于 0 和 n 之间的整数! - 1、 该函数的值是具有该等级的排列。

我使用 next_permutation 在 C++ 中进行了排名和取消排名函数。但这对于 n>8 是不切实际的。我正在寻找一种更快的方法,factoradics 似乎很受欢迎。 但我不确定这是否也适用于重复项。那么,什么是对具有重复项的排列进行排名/取消排名的好方法呢?

【问题讨论】:

  • 取决于,你使用什么样的排列?我的意思是,原始集合和排名函数是什么?
  • 按字典顺序排列。因此,如果一个排列的秩为 r,则下一个排列的秩为 r+1。相同的排列应该具有相同的排名(在重复的情况下)。

标签: c++ algorithm permutation


【解决方案1】:

我将在这个答案中涵盖您问题的一半 - “unranking”。目标是有效地找到有序字符串 [abcd...] 的字典第“K”个排列。

为此,我们需要了解阶乘数字系统(factoradics)。阶乘数字系统使用阶乘值而不是数字的幂(二进制系统使用 2 的幂,十进制使用 10 的幂)来表示位值(或基数)。

位置值(基础)是 -

5!= 120    4!= 24    3!=6    2!= 2    1!=1    0!=1 etc..

第 0 位的数字始终为 0。第一位的数字(底数 = 1!)可以是 0 或 1。第二位的数字(底数为 2!)可以是 0,1 或2 以此类推。一般来说,第n位的数字可以取0-n之间的任意值。

前几个数字表示为因子-

0 -> 0 = 0*0!
1 -> 10 = 1*1! + 0*0!
2 -> 100 = 1*2! + 0*1! + 0*0!
3 -> 110 = 1*2! + 1*1! + 0*0!
4 -> 200 = 2*2! + 0*1! + 0*0!
5 -> 210 = 2*2! + 1*1! + 0*0!
6 -> 1000 = 1*3! + 0*2! + 0*1! + 0*0!
7 -> 1010 = 1*3! + 0*2! + 1*1! + 0*0!
8 -> 1100 = 1*3! + 1*2! + 0*1! + 0*0!
9 -> 1110
10-> 1200

字符串的第 n 次字典排列与其因子表示之间存在直接关系。

例如,这里是字符串“abcd”的排列。

0  abcd       6  bacd        12  cabd       18  dabc
1  abdc       7  badc        13  cadb       19  dacb
2  acbd       8  bcad        14  cbad       20  dbac
3  acdb       9  bcda        15  cbda       21  dbca
4  adbc       10  bdac       16  cdab       22  dcab
5  adcb       11  bdca       17  cdba       23  dcba

如果仔细观察,我们可以在这里看到一个模式。第一个字母在每 6 次 (3!) 排列后发生变化。第二个字母在 2(2!) 排列后发生变化。第三个字母在每个 (1!) 排列后更改,第四个字母在每个 (0!) 排列后更改。我们可以利用这个关系直接找到第n个排列。

一旦我们用因子表示表示 n,我们就会考虑其中的每个数字,并将给定字符串中的一个字符添加到输出中。如果我们需要找到“abcd”的 14 次排列。 14 in factoradics -> 2100.

从第一个数字开始 -> 2,字符串是'abcd'。假设索引从 0 开始,从字符串中取出位置 2 的元素并将其添加到输出中。

Output                    String
  c                         abd
  2                         012

下一个数字 -> 1.String 现在是“abd”。再次,在位置 1 处提取字符并将其添加到输出中。

Output                    String
 cb                         ad
 21                         01

下一个数字 -> 0。字符串是“广告”。将位置 1 的字符添加到输出。

Output                   String
 cba                        d
 210                        0

下一个数字 -> 0。字符串是“d”。将位置 0 处的字符添加到输出。

输出字符串 cbad'' 2100

要将给定的数字转换为阶乘数字系统,请将该数字依次除以 1、2、3、4、5 等等,直到商变为零。每一步的提醒形成阶乘表示。

例如,将 349 转换为阶乘,

              Quotient        Reminder       Factorial Representation
349/1            349               0                             0
349/2            174               1                            10
174/3            58                0                           010
58/4             14                2                          2010
14/5             2                 4                         42010
2/6              0                 2                        242010

349 的因数表示是 242010。

【讨论】:

    【解决方案2】:

    一种方法是通过一组特定的相等数字对索引的选择进行排名和取消排名,例如,

    def choose(n, k):
        c = 1
        for f in xrange(1, k + 1):
            c = (c * (n - f + 1)) // f
        return c
    
    def rank_choice(S):
        k = len(S)
        r = 0
        j = k - 1
        for n in S:
            for i in xrange(j, n):
                r += choose(i, j)
            j -= 1
        return r
    
    def unrank_choice(k, r):
        S = []
        for j in xrange(k - 1, -1, -1):
            n = j
            while r >= choose(n, j):
                r -= choose(n, j)
                n += 1
            S.append(n)
        return S
    
    def rank_perm(P):
        P = list(P)
        r = 0
        for n in xrange(max(P), -1, -1):
            S = []
            for i, p in enumerate(P):
                if p == n:
                    S.append(i)
            S.reverse()
            for i in S:
                del P[i]
            r *= choose(len(P) + len(S), len(S))
            r += rank_choice(S)
        return r
    
    def unrank_perm(M, r):
        P = []
        for n, m in enumerate(M):
            S = unrank_choice(m, r % choose(len(P) + m, m))
            r //= choose(len(P) + m, m)
            S.reverse()
            for i in S:
                P.insert(i, n)
        return tuple(P)
    
    if __name__ == '__main__':
        for i in xrange(60):
            print rank_perm(unrank_perm([2, 3, 1], i))
    

    【讨论】:

      【解决方案3】:

      对于较大的 n-s,您需要任意精度库,例如 GMP

      这是我之前用python写的一个unranking函数的帖子,我觉得它可读性强,跟伪代码差不多,cmets里也有一些解释:Given a list of elements in lexicographical order (i.e. ['a', 'b', 'c', 'd']), find the nth permutation - Average time to solve?

      基于此你应该可以算出排名函数,基本上是一样的逻辑;)

      【讨论】:

      • 链接代码适用于没有重复的排列(一旦传递的li arg 发生突变的问题得到解决),但当碱基序列包含重复时,它不会产生正确的排列项目。所以I wrote my own。 ;)
      【解决方案4】:

      Java,来自https://github.com/timtiemens/permute/blob/master/src/main/java/permute/PermuteUtil.java(我的公共域代码,减去错误检查):

      public class PermuteUtil {
       public <T> List<T> nthPermutation(List<T> original, final BigInteger permutationNumber)  {
              final int size = original.size();
      
              // the return list:
              List<T> ret = new ArrayList<>();
              // local mutable copy of the original list:
              List<T> numbers = new ArrayList<>(original);
      
              // Our input permutationNumber is [1,N!], but array indexes are [0,N!-1], so subtract one:
              BigInteger permNum = permutationNumber.subtract(BigInteger.ONE);
      
              for (int i = 1; i <= size; i++) {
                  BigInteger factorialNminusI = factorial(size - i);
      
                  // casting to integer is ok here, because even though permNum _could_ be big,
                  //   the factorialNminusI is _always_ big
                  int j = permNum.divide(factorialNminusI).intValue();
      
                  permNum = permNum.mod(factorialNminusI);
      
                  // remove item at index j, and put it in the return list at the end
                  T item = numbers.remove(j);
                  ret.add(item);
              }
      
              return ret;
        }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-01-12
        • 1970-01-01
        • 2019-09-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-12-10
        • 1970-01-01
        相关资源
        最近更新 更多