我有一个函数,它接受一个长度为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是等效的:
- A中共享一个类的所有元素,也在B中共享一个类。
- A中所有不共享类的元素,也不共享B中的类。
醇>
例如: 当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
答案 0 :(得分:1)
我还没有完全测试过这个,但我认为它可以让你得到你想要的东西。我有三个主要步骤:
expand.grid
来提供n和k的所有可能排列。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)
(即等效)。n=3
和k=6
时,唯一组合的数量从729减少到162: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);
}