number到包含重复项的序列的唯一排列映射

时间:2013-01-08 09:38:51

标签: algorithm math mapping permutation combinatorics

我正在寻找一种能够将数字映射到序列的唯一排列的算法。由于类似的问题Fast permutation -> number -> permutation mapping algorithms,我已经了解了Lehmer代码和阶乘数系统,但该问题并未涉及序列中存在重复元素的情况。

例如,采用序列'AAABBC'。有6个! = 720种方式可以安排,但我相信只有6种! /(3!* 2!* 1!)= 60这个序列的独特排列。在这些情况下,如何将数字映射到排列?

编辑:将术语'set'更改为'sequence'。

5 个答案:

答案 0 :(得分:9)

从排列到数字:

设K为字符类的数量(例如:AAABBC有三个字符类)

设N [K]为每个字符类中的元素数。 (例如:对于AAABBC,我们有N [K] = [3,2,1],并且让N = sum(N [K])

序列的每个合法排列然后唯一地对应于不完整K路树中的路径。

然后,排列的唯一编号对应于K-ary树终端节点的后序遍历中的树节点的索引。

幸运的是,我们实际上不必执行树遍历 - 我们只需要知道树中有多少个终端节点按字典顺序比我们的节点少。这很容易计算,因为在树中的任何节点,当前节点下面的终端节点的数量等于使用序列中未使用的元素的排列数,其具有封闭形式解决方案是阶乘的简单乘法。

因此,鉴于我们的6个原始字母,并且我们的排列的第一个元素是'B',我们确定将有5!/ 3!1!1! = 20个以'A'开头的元素,所以我们的排列数必须大于20.如果我们的第一个字母是'C',我们可以将其计算为5!/ 2!2!1! (不是A)+ 5!/ 3!1!1! (不是B)= 30 + 20,或者作为 60(总) - 5!/ 3!2!0! (C)= 50

使用这个,我们可以进行排列(例如'BAABCA')并执行以下计算: Permuation#=(5!/ 2!2!1!)('B')+ 0('A')+ 0('A')+ 3!/ 1!1!1! ('B')+ 2!/ 1!

= 30 + 3 +2 = 35

检查这是否有效:CBBAAA对应

(5!/ 2!2!1!(不是A)+ 5!/ 3!1!1!(不是B))'C'+ 4!/ 2!2!0! (不是A)'B'+ 3!/ 2!1!0! (不是A)'B'=(30 + 20)+6 + 3 = 59

同样,AAABBC = 0('A')+ 0'A'+'0'A'+ 0'B'+ 0'B'+ 0'C = 0

示例实施:

import math
import copy
from operator import mul

def computePermutationNumber(inPerm, inCharClasses):
    permutation=copy.copy(inPerm)
    charClasses=copy.copy(inCharClasses)

    n=len(permutation)
    permNumber=0
    for i,x in enumerate(permutation):
        for j in xrange(x):
            if( charClasses[j]>0):
                charClasses[j]-=1
                permNumber+=multiFactorial(n-i-1, charClasses)
                charClasses[j]+=1
        if charClasses[x]>0:
            charClasses[x]-=1
    return permNumber

def multiFactorial(n, charClasses):
    val= math.factorial(n)/ reduce(mul, (map(lambda x: math.factorial(x), charClasses)))
    return val

从数字到排列: 这个过程可以反过来完成,但我不确定效率如何: 给定一个排列数,以及它生成的字母表,递归地减去小于或等于剩余排列数的最大节点数。

E.g。如果排列数为59,我们首先可以减去30 + 20 = 50('C'),然后我们可以减去'B'(6)和第二'B'(3),重新生成我们的原始排列

答案 1 :(得分:1)

这是Java中的一种算法,它通过将整数映射到序列来枚举可能的序列。

public class Main {

    private int[] counts = { 3, 2, 1 }; // 3 Symbols A, 2 Symbols B, 1 Symbol C
    private int n = sum(counts);

    public static void main(String[] args) {
        new Main().enumerate();
    }

    private void enumerate() {
        int s = size(counts);
        for (int i = 0; i < s; ++i) {
            String p = perm(i);
            System.out.printf("%4d -> %s\n", i, p);
        }

    }

    // calculates the total number of symbols still to be placed
    private int sum(int[] counts) {
        int n = 0;
        for (int i = 0; i < counts.length; i++) {
            n += counts[i];
        }
        return n;
    }

    // calculates the number of different sequences with the symbol configuration in counts
    private int size(int[] counts) {
        int res = 1;
        int num = 0;
        for (int pos = 0; pos < counts.length; pos++) {
            for (int den = 1; den <= counts[pos]; den++) {
                res *= ++num;
                res /= den;
            }
        }
        return res;
    }

    // maps the sequence number to a sequence
    private String perm(int num) {
        int[] counts = this.counts.clone();
        StringBuilder sb = new StringBuilder(n);
        for (int i = 0; i < n; ++i) {
            int p = 0;
            for (;;) {
                while (counts[p] == 0) {
                    p++;
                }
                counts[p]--;
                int c = size(counts);
                if (c > num) {
                    sb.append((char) ('A' + p));
                    break;
                }
                counts[p]++;
                num -= c;
                p++;
            }
        }
        return sb.toString();
    }

}

算法使用的映射如下。我使用问题中给出的例子(3 x A,2 x B,1 x C)来说明它。

总共有60(= 6!/ 3!/ 2!/ 1!)个可能的序列,其中30(= 5!/ 2!/ 2!/ 1!)有A at第一个地方,20(= 5!/ 3!/ 1!/ 1!)首先有B,而10(= 5!/ 3!/ 2!/ 0!)有一个{首先是{1}}。

数字0..29映射到以C开头的所有序列,30..49映射到以A开头的序列,50..59映射到序列以B开头。

对序列中的下一个位置重复相同的过程,例如,如果我们采用以C开头的序列,我们现在要映射数字0(= 30-30).. 19(= 49- 30)具有配置的序列(3×A,1×B,1×C)

答案 2 :(得分:0)

为排列映射数字的一种非常简单的算法由n个数字组成

number<-digit[0]*10^(n-1)+digit[1]*10^(n-2)+...+digit[n]*10^0

您可以找到大量用于生成排列的算法资源。我想你想在生物信息学中使用这个算法。例如,您可以使用Python中的itertools.permutations。

答案 3 :(得分:0)

假设结果数字相对容易地适合单词(例如32或64位整数),那么大部分链接文章仍然适用。来自变量库的编码和解码保持不变。基数的变化有何变化。

如果您正在创建序列的排列,则从您的符号桶中选择一个项目(从原始序列中)并将其放在开头。然后你从你的符号桶中挑出另一个项目并将其放在最后。你将继续挑选并在最后放置符号,直到你的桶中的符号用完为止。

重要的是每次从剩余符号的桶中选择哪个项目。剩余符号的数量是您不必记录的,因为您可以在构建排列时计算这些符号 - 这是您选择的结果,而不是选择本身。

这里的策略是记录你选择的内容,然后呈现一个剩下要选择的数组。然后选择,记录您选择的索引(通过变量基本方法打包),然后重复,直到没有什么可供选择。 (正如您在构建置换序列时的情况一样。)

如果是重复符号,则选择哪一个并不重要,因此您可以将它们视为相同的符号。不同的是,当你选择一个仍然有重复的符号时,你没有减少下一次要选择的桶中符号的数量。

让我们采用一个清晰​​的符号:

我们不会列出我们存储桶中留下的重复符号,而是选择c a b c a a,我们会列出它们以及仍有多少符号:c-2 a-3 b-1

请注意,如果您从列表中选择c,则该文件夹中会留下c-1 a-3 b-1。这意味着下次我们选择一些东西时,我们有三种选择。

但另一方面,如果我从列表中选择了b,则该文件夹中会留下c-2 a-3。这意味着下次我们选择一些东西,我们只有两个选择。

当重建置换序列时,我们只是像计算排列数一样维护桶。

实现细节并非易事,但它们使用标准算法很简单。唯一可以解决你的问题是当你的桶中的符号不​​再可用时该怎么办。

假设您的存储桶由对列表(如上所述)表示:c-1 a-3 b-1并且您选择c。您生成的存储桶为c-0 a-3 b-1。但是c-0不再是一个选择,所以你的列表应该只有两个条目,而不是三个。您可以将整个列表向下移动1,结果为a-3 b-1,但如果您的列表很长,则这很昂贵。一个快速简单的解决方案:将存储桶的最后一个元素移动到移除的位置并减小存储桶大小:c0 a-3 b-1变为b-1 a-3 <empty>或仅b-1 a-3

请注意,我们可以执行上述操作,因为无论列表中符号的顺序如何,只要在编码或解码数字时它的方式相同。

答案 4 :(得分:0)

由于我不确定g bronner's answer(或我的理解)中的代码,我在R中重新编码如下

ritpermz=function(n, parclass){
    return(factorial(n) / prod(factorial(parclass)))}

rankum <- function(confg, parclass){
    n=length(confg)
    permdex=1
    for (i in 1:(n-1)){
      x=confg[i]
      if (x > 1){
        for (j in 1:(x-1)){
            if(parclass[j] > 0){
                parclass[j]=parclass[j]-1
                permdex=permdex + ritpermz(n-i, parclass)
                parclass[j]=parclass[j]+1}}}
        parclass[x]=parclass[x]-1
    }#}
    return(permdex)
}

确实产生了具有正确整数范围的排名