algorithm - 给定索引号,是否有生成特定 n Multichoose r 组合的函数?

标签 algorithm combinations combinatorics

例如3 multichoose 2有以下组合:

i   combo
0 = [0,0]
1 = [0,1]
2 = [0,2]
3 = [1,1]
4 = [1,2]
5 = [2,2]

是否可以编写一个函数,其参数为 n、r、i 并返回有问题的组合,而无需遍历它之前的每个组合?

最佳答案

Could a function be written whose arguments are n,r,i and returns the combination in question, without iterating through every combination before it?

是的。我们必须做一些计算才能找到这个问题的核心。为了更好地说明如何将其分解为非常简单的小问题,我们将看一个更大的例子。一次考虑 5 选 3 的所有组合,不重复(我们将从这里开始说 5 选 3)。

      [,1] [,2] [,3]
 [1,]    1    2    3
 [2,]    1    2    4
 [3,]    1    2    5
 [4,]    1    3    4
 [5,]    1    3    5
 [6,]    1    4    5
 [7,]    2    3    4
 [8,]    2    3    5
 [9,]    2    4    5
[10,]    3    4    5

注意前 6 行。如果我们移除这 6 行的第一列并从每个元素中减去 1,我们得到:

      [,1] [,2]                   [,1] [,2]
[1,]    2    3              [1,]    1    2
[2,]    2    4  subtract 1  [2,]    1    3
[3,]    2    5    --->>>>   [3,]    1    4
[4,]    3    4              [4,]    2    3
[5,]    3    5              [5,]    2    4
[6,]    4    5              [6,]    3    4

右边的矩阵恰好是 4 选 2 的所有组合。继续,我们看到“第二”组(即原始矩阵的第 7 行到第 9 行)看起来也有顺序:

     [,1] [,2]                    [,1] [,2]
[1,]    3    4              [1,]    1    2
[2,]    3    5  subtract 2  [2,]    1    3
[3,]    4    5    --->>>>   [3,]    2    3

这只是 3 选 2。我们开始看到一种模式展开。也就是说,所有较小的 nr 的组合都包含在我们的父组合中。当我们向右移动时,这种模式会继续。剩下的就是跟上我们所追求的组合。

以下是用 C++ 编写的上述算法(注意,没有任何数据验证):

template <typename T>
double nChooseK(T n, T k) {
    // Returns number of k-combinations from n elements.
    // Mathematically speaking, we have: n!/(k!*(n-k)!)
    if (k == n || k == 0)
        return 1;
    else if (k > n || n < 0)
        return 0;

    double nCk;
    double temp = 1;
    for (int i = 1; i <= k; i++)
        temp *= (double) (n - k + i) / i;

    nCk = std::round(temp);
    return nCk;
}

std::vector<int> nthCombination(int n, int r, double i) {

    int j = 0, n1 = n - 1, r1 = r - 1;
    double temp, index1 = i, index2 = i;
    std::vector<int> res(r);

    for (int k = 0; k < r; k++) {
        temp = nChooseK(n1, r1);
        while (temp <= index1) {
            index2 -= nChooseK(n1, r1);
            n1--;
            j++;
            temp += nChooseK(n1, r1);
        }
        res[k] = j;
        n1--;
        r1--;
        j++;
        index1 = index2;
    }

    return res;
}

在我们上面的例子中用 5 选 3 调用它,我们得到:

nthCombination(5, 3, 0) -->> 0 1 2
nthCombination(5, 3, 1) -->> 0 1 3
nthCombination(5, 3, 2) -->> 0 1 4
nthCombination(5, 3, 3) -->> 0 2 3
nthCombination(5, 3, 4) -->> 0 2 4
nthCombination(5, 3, 5) -->> 0 3 4
nthCombination(5, 3, 6) -->> 1 2 3
nthCombination(5, 3, 7) -->> 1 2 4
nthCombination(5, 3, 8) -->> 1 3 4
nthCombination(5, 3, 9) -->> 2 3 4

这种方法也非常有效。下面,我们立即得到第 10 亿个组合 40 选 20(这产生了超过 1000 亿个组合):

      // N.B. base zero so we need to subtract 1
nthCombination(40, 20, 1000000000 - 1)  -->>
   0  1  2  3  4  5  8  9 14 16 18 20 22 23 31 33 34 35 38 39

编辑

正如 OP 在评论中指出的那样,他们给出了一个重复的例子。解决方案非常相似,分解为计数。我们首先需要一个类似于 nChooseK 但考虑重复的计数函数。下面的函数就是这样做的:

double combsWithReps(int n, int r) {
    // For combinations where repetition is allowed, this
    // function returns the number of combinations for
    // a given n and r. The resulting vector, "triangleVec"
    // resembles triangle numbers. In fact, this vector
    // is obtained in a very similar method as generating
    // triangle numbers, albeit in a repeating fashion.

    if (r == 0)
        return 1;

    int i, k;
    std::vector<double> triangleVec(n);
    std::vector<double> temp(n);

    for (i = 0; i < n; i++)
        triangleVec[i] = i+1;

    for (i = 1; i < r; i++) {
        for (k = 1; k <= n; k++)
            temp[k-1] = std::accumulate(triangleVec.begin(), triangleVec.begin() + k, 0.0);

        triangleVec = temp;
    }

    return triangleVec[n-1];
}

下面是生成第 ith<​​ 次组合的函数。

std::vector<int> nthCombWithRep(int n, int r, double i) {

    int j = 0, n1 = n, r1 = r - 1;
    double temp, index1 = i, index2 = i;
    std::vector<int> res(r);

    for (int k = 0; k < r; k++) {
        temp = combsWithReps(n1, r1);
        while (temp <= index1) {
            index2 -= combsWithReps(n1, r1);
            n1--;
            j++;
            temp += combsWithReps(n1, r1);
        }
        res[k] = j;
        r1--;
        index1 = index2;
    }

    return res;
}

它与上面的第一个功能非常相似。您会注意到 n1--j++ 从函数的末尾删除,并且 n1 被初始化为 n 而不是 n - 1

这是上面的例子:

nthCombWithRep(40, 20, 1000000000 - 1)  -->>
    0  0  0  0  0  0  0  0  0  0  0  4  5  6  8  9 12 18 18 31

关于algorithm - 给定索引号,是否有生成特定 n Multichoose r 组合的函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50148010/

相关文章:

algorithm - 为图中的每个节点计算距离 n 处的未访问节点

sql - 选择带有关键字的列但同时排除另一个词的高效 SQL

自动排列实体关系图的算法

python - 在 Pandas 数据框中创建新列作为其他列的总排列

arrays - 在Perl中,如何生成列表的所有可能组合?

java - 算法复杂度和效率,指数运算java

java - 列表的可能组合

haskell - 计算 n 元笛卡尔积

c++ - 计算数字中特定位的排列

javascript - 确定从序列中删除一组值的所有可能方法的算法