找到给定排列的索引

时间:2012-12-23 18:09:49

标签: algorithm permutation space combinatorics

我正在按顺序逐个阅读数字0, 1, ..., (N - 1)。我的目标是仅使用O(1)空间找到此给定排列的词典编纂索引。

之前曾问过这个问题,但我找到的所有算法都使用O(N)空间。我开始认为这是不可能的。但是,通过减少分配数量,这对我帮助很大。

7 个答案:

答案 0 :(得分:3)

考虑以下数据:

chars = [a, b, c, d]
perm = [c, d, a, b]
ids = get_indexes(perm, chars) = [2, 3, 0, 1]

排列重复的可能解决方案如下:

len = length(perm)         (len = 4)
num_chars = length(chars)  (len = 4)

base = num_chars ^ len     (base = 4 ^ 4 = 256)
base = base / len          (base = 256 / 4 = 64)

id = base * ids[0]         (id = 64 * 2 = 128)
base = base / len          (base = 64 / 4 = 16)

id = id + (base * ids[1])  (id = 128 + (16 * 3) = 176)
base = base / len          (base = 16 / 4 = 4)

id = id + (base * ids[2])  (id = 176 + (4 * 0) = 176)
base = base / len          (base = 4 / 4 = 1)

id = id + (base * ids[3])  (id = 176 + (1 * 1) = 177)

反向过程:

id = 177
(id / (4 ^ 3)) % 4 = (177 / 64) % 4 =   2 % 4 = 2 -> chars[2] -> c
(id / (4 ^ 2)) % 4 = (177 / 16) % 4 =  11 % 4 = 3 -> chars[3] -> d
(id / (4 ^ 1)) % 4 = (177 / 4)  % 4 =  44 % 4 = 0 -> chars[0] -> a
(id / (4 ^ 0)) % 4 = (177 / 1)  % 4 = 177 % 4 = 1 -> chars[1] -> b

可能的排列数由num_chars ^ num_perm_digits给出,num_chars为可能的字符数,num_perm_digits为排列中的位数。

这需要O(1)空间,将初始列表视为常数成本;并且它需要及时O(N),将N视为您的排列所具有的位数。

根据上述步骤,您可以:

function identify_permutation(perm, chars) {

    for (i = 0; i < length(perm); i++) {
        ids[i] = get_index(perm[i], chars);
    }

    len = length(perm);
    num_chars = length(chars);

    index = 0;
    base = num_chars ^ len - 1;
    for (i = 0; i < length(perm); i++) {
        index += base * ids[i];
        base = base / len;
    }

}

这是一种伪代码,但转换为任何语言也很容易(:

答案 1 :(得分:3)

如果您正在寻找获取词典索引或独特组合排名而不是排列的方法,那么您的问题属于二项式系数。二项式系数处理在K组中选择唯一组合以及总共N个项目的问题。

我在C#中编写了一个类来处理使用二项式系数的常用函数。它执行以下任务:

  1. 以任意N选择K到文件的格式输出所有K索引。 K-index可以用更具描述性的字符串或字母代替。

  2. 将K索引转换为正确的词典索引或排序二项式系数表中条目的等级。这种技术比依赖迭代的旧发布技术快得多。它通过使用Pascal三角形中固有的数学属性来实现这一点,并且与迭代集合相比非常有效。

  3. 将已排序的二项系数表中的索引转换为相应的K索引。我相信它也比旧的迭代解决方案更快。

  4. 使用Mark Dominus方法计算二项式系数,这样就不太可能溢出并使用更大的数字。

  5. 该类是用.NET C#编写的,它提供了一种通过使用通用列表来管理与问题相关的对象(如果有)的方法。此类的构造函数采用名为InitTable的bool值,当为true时,将创建一个通用列表来保存要管理的对象。如果此值为false,则不会创建表。为了使用上述4种方法,不需要创建该表。提供访问者方法来访问该表。

  6. 有一个关联的测试类,它显示了如何使用该类及其方法。它已经过2个案例的广泛测试,并且没有已知的错误。

  7. 要阅读此课程并下载代码,请参阅Tablizing The Binomial Coeffieicent

    以下测试代码将遍历每个独特的组合:

    public void Test10Choose5()
    {
       String S;
       int Loop;
       int N = 10;  // Total number of elements in the set.
       int K = 5;  // Total number of elements in each group.
       // Create the bin coeff object required to get all
       // the combos for this N choose K combination.
       BinCoeff<int> BC = new BinCoeff<int>(N, K, false);
       int NumCombos = BinCoeff<int>.GetBinCoeff(N, K);
       // The Kindexes array specifies the indexes for a lexigraphic element.
       int[] KIndexes = new int[K];
       StringBuilder SB = new StringBuilder();
       // Loop thru all the combinations for this N choose K case.
       for (int Combo = 0; Combo < NumCombos; Combo++)
       {
          // Get the k-indexes for this combination.  
          BC.GetKIndexes(Combo, KIndexes);
          // Verify that the Kindexes returned can be used to retrive the
          // rank or lexigraphic order of the KIndexes in the table.
          int Val = BC.GetIndex(true, KIndexes);
          if (Val != Combo)
          {
             S = "Val of " + Val.ToString() + " != Combo Value of " + Combo.ToString();
             Console.WriteLine(S);
          }
          SB.Remove(0, SB.Length);
          for (Loop = 0; Loop < K; Loop++)
          {
             SB.Append(KIndexes[Loop].ToString());
             if (Loop < K - 1)
                SB.Append(" ");
          }
          S = "KIndexes = " + SB.ToString();
          Console.WriteLine(S);
       }
    }
    

    您应该能够轻松地将此课程移植到您选择的语言上。您可能不必移植类的通用部分来实现您的目标。根据您使用的组合数量,您可能需要使用大于4字节整数的字大小。

答案 2 :(得分:0)

有N!排列。要表示索引,您至少需要N位。

答案 3 :(得分:0)

如果你想假设算术运算是恒定的时间,这是一种方法:

def permutationIndex(numbers):
  n=len(numbers)
  result=0
  j=0
  while j<n:
    # Determine factor, which is the number of possible permutations of
    # the remaining digits.
    i=1
    factor=1
    while i<n-j:
      factor*=i
      i+=1
    i=0
    # Determine index, which is how many previous digits there were at
    # the current position.
    index=numbers[j]
    while i<j:
      # Only the digits that weren't used so far are valid choices, so
      # the index gets reduced if the number at the current position
      # is greater than one of the previous digits.
      if numbers[i]<numbers[j]:
        index-=1
      i+=1
    # Update the result.
    result+=index*factor
    j+=1
  return result

我故意写出某些计算,这些计算可以使用一些Python内置操作更简单地完成,但我想更明显的是没有使用额外的非恒定空间量。

正如maxim1000所指出的那样,表示结果所需的位数会随着n的增加而迅速增长,因此最终需要大整数,不再有恒定时间算术,但我认为这段代码能够满足你的精神。问题

答案 4 :(得分:0)

geekviewpoint上有一个解决这个问题的java解决方案。它有一个很好的解释,为什么它是真的,代码很容易遵循。 http://www.geekviewpoint.com/java/numbers/permutation_index。它还有一个单元测试,用不同的输入运行代码。

答案 5 :(得分:0)

这个想法没有什么新东西,但是没有显式循环或递归的完全matricial方法(使用Numpy但很容易适应):

import numpy as np
import math
vfact = np.vectorize(math.factorial, otypes='O')

def perm_index(p):
    return np.dot( vfact(range(len(p)-1, -1, -1)),
                   p-np.sum(np.triu(p>np.vstack(p)), axis=0) )

答案 6 :(得分:-1)

我只是使用Visual Basic编写了一个代码,我的程序可以直接计算每个索引或每个相应的排列到一个给定的索引,最多17个元素(这个限制是由于数字的科学符号近似于17!编译器)。

如果您有兴趣,我可以发送程序或将其发布到某个地方进行下载。 它工作正常,它可以用于测试和调用代码的输出。

我使用James D. McCaffrey的方法称为factoradic,你可以阅读here以及here(在页面末尾的讨论中)。