找到长度为k的向量的所有非等效置换,取n个可能的值

时间:2015-01-14 06:36:09

标签: r algorithm function permutation combinatorics

我有一个函数,它接受一个长度为k的输入向量,其中向量中的每个元素最多可以占用n个值。

通常k在6:10范围内,n在2:(k-1)范围内。

对于任何给定的(n,k),将存在n ^ k-1个可能向量的排列。

目前,我将每个整数0:(n ^ k-1)映射到一个唯一的排列,并在该排列处评估函数,以找到所有可能向量的最优输入向量。

例如,当n = 3且k = 6时,映射将为:

0:1,1,1,1,1,1
1:1,1,1,1,1,2
2:1,1,1,1,1,3 
3:1,1,1,1,2,1
...
728:3,3,3,3,3,3

然而,就我的目的而言,一些排列是等价的。您可以将向量视为n个类中k个元素的分配。

如果以下两个都保持不变,则两个排列A和B是等效的:

  
      
  1. A中共享一个类的所有元素,也在B中共享一个类。
  2.   
  3. A中所有不共享类的元素,也不共享B中的类。
  4.   

例如: 当n = 2且k = 6时,矢量

1,2,1,1,2,1 
2,1,2,2,1,2 

是等价的。在两个向量中,元素{1,3,4,6}共享一个类,元素{2,5}共享一个类。

n = 3且k = 6,矢量

1,2,3,1,2,3
1,3,2,1,3,2
2,3,1,2,3,1
2,1,3,2,1,3
3,2,1,3,2,1
3,1,2,3,1,2 

都是等价的。

我的目标是找到一种更有效的方法来找到最佳向量,而不是尝试1:(n ^ k-1)范围内的每个输入。

我可以看到两种可能的前进方式:

选项1.枚举所有可能性,然后过滤掉所有等效矢量。

选项2:减少我需要提前检查的范围。例如,对于n = 3,k = 6,我相当自信(但尚未证明)我不需要检查161以上的任何内容:1,2,3,3,3,3并且在1:161范围内也应该有一些等效的排列。

我更喜欢选项2.

理想的解决方案是(n,k)的函数,它输出表示我需要检查的1:n ^ k-1中的间隔的向量列表。几乎同样好的是(n,k)的函数,它输出我需要检查的1:n ^ k-1中的最大整数/向量。

作为起点,这里有一些示例R代码:

vectorFromID <- function(id, n, k) {
  if(id >= n^k) {
    stop('ID too large!')
  }
  remainder <- id
  elements <- list()
  for(i in (k-1):0) {
    elements[[k-i]] <- (remainder  %/% (n ^ i))+1
    remainder <- remainder %% (n ^ i)
  }
  return(unlist(elements))
}

vectorToID <- function(inputVector, n, k) {
  total <- 0
  for(i in 0:(k-1)) {
    total <- total + (inputVector[i+1]-1) * (n ^ ((k-1)-i))
  }
  return(total)
}

# generate all possible vectors for n=3, k=6
all_vectors <- Map(function(x) vectorFromID(x, 3, 6), 0:728)

编辑添加递归解决方案的R实现,并对这两种解决方案进行基准测试。

enum <- function(v=NULL, n, k, maxv=0) {
  if (k == 0) {
    return(list(v))
  } else {
    acc <- list()
    for (i in 1:min(n, maxv+1)) {
      acc <- c(acc, enum(c(v,i), n, k-1, max(i,maxv)))
    }
    return(acc)
  }
}
res2 <- enum(NULL, 3, 6, 0)

两种解决方案都能产生相同的输出,但对于较大的k&amp;值而言, n递归解决方案要快得多。下面,time1指的是递归解决方案所用的时间(秒)。

n: 2 k: 6 rows1: 32 rows2: 32 match: TRUE time1: 0.02 time2: 0.05
n: 3 k: 6 rows1: 122 rows2: 122 match: TRUE time1: 0 time2: 0.51
n: 4 k: 6 rows1: 187 rows2: 187 match: TRUE time1: 0.01 time2: 3.32
n: 5 k: 6 rows1: 202 rows2: 202 match: TRUE time1: 0 time2: 16.8
n: 2 k: 7 rows1: 64 rows2: 64 match: TRUE time1: 0.02 time2: 0.11
n: 3 k: 7 rows1: 365 rows2: 365 match: TRUE time1: 0 time2: 1.83
n: 4 k: 7 rows1: 715 rows2: 715 match: TRUE time1: 0.05 time2: 19.62
n: 5 k: 7 rows1: 855 rows2: 855 match: TRUE time1: 0.04 time2: 277.81

2 个答案:

答案 0 :(得分:1)

我还没有完全测试过这个,但我认为它可以让你得到你想要的东西。我有三个主要步骤:

  1. 应用expand.grid来提供n和k的所有可能排列。
  2. 将值转换为因子,其级别基于外观顺序。然后,将它们转回数值(在循环中)。例如由于因子级别的顺序相似,c(1,2,3,1,2,3)c(3,2,1,3,2,1)将返回c(1,2,3,1,2,3)c(1,2,3,1,2,3)(即等效)。
  3. 仅返回唯一组合。使用n=3k=6时,唯一组合的数量从729减少到162:
  4. 功能combnmix

    combnmix <- function(n,k){
      tmp <- lapply(as.list(rep(n, k)), seq)
      res1 <- expand.grid(tmp)
      res2 <- NaN*res1
      for(i in seq(nrow(res1))){
        levs <- unique(c(res1[i,]))
        res2[i,] <- as.numeric(factor(res1[i,], levels=levs))
      }
      res3 <- unique(res2)
      res3
    }
    
    res <- combnmix(3,6)
    res
    

答案 1 :(得分:1)

让我们首先为每个等价类选择一个代表。假设向量p = {x_1 ... x_k}是一个代表,如果它是所有p_i的词典最小值,那么p_i~p。

注意x_i在范围(1..x_j + 1)forall j&lt;一世。如果这不成立,那么我们可以构造等效的p_i,其小于p字典。 (出于同样的原因,x_1 = 1)

此外,如果对于每个i,x_i在范围(1..x_j + 1)中,则p是代表。 否则存在一些q = {y_1 ... y_n},使得对于某些k,y_i = x_i对于所有i&lt; k和y_k&lt; X_K。但是对于那个k,来自(1..max(x_i))的所有值都在p的第一个k-1个元素中。所以它是y_k。但证明p不等于q。

因此p是代表性的iff x_i在范围(1..x_j + 1)中forall j&lt;一世。然后我们可以使用简单的递归过程派生所有这些代表。 很抱歉我的代码示例是在C ++中,我不知道R:

void printResult(std::vector<int>& v){
    for (auto val : v){
        std::cout << val << ' ';
    }
    std::cout << '\n';
}

void enumerate(std::vector<int>& v, int n, int k, int max){
    if (k == 0){
        printResult(v);
    } else {
        for (int i = 1; i <= std::min(n, max + 1); i++){
            v.push_back(i);
            enumerate(v, n, k - 1, std::max(i, max));
            v.pop_back();
        }
    }
}

void solve(int n, int k){
    std::vector<int> v;
    enumerate(v, n, k, 0);
}