【问题标题】:Is there a function to generate a specific n Multichoose r combination, given the index number?给定索引号,是否有生成特定 n Multichoose r 组合的函数?
【发布时间】:2019-08-11 01:34:51
【问题描述】:

例如3多选2有以下组合:

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

是否可以编写一个函数,其参数为 n,r,i 并返回相关组合,而不遍历它之前的每个组合?

【问题讨论】:

  • 是的,它可以通过一些数学、组合和余数运算来实现。虽然代码很棘手
  • 基本上你会一一找到每个元素。例如假设 i = 4。您发现(以封闭形式)有多少组合以 0 开头(有 3 个),因此您更新 i = 4-3 = 1 并仅考虑以 >= 1 开头的组合。有 2组合从 1 开始,并且 i
  • @pkpnd,不需要任何花哨的东西(即余数运算、二进制搜索等)。只是在一天结束时数数。

标签: algorithm combinations combinatorics


【解决方案1】:

是否可以编写一个函数,其参数为 n,r,i 并返回相关组合,而不遍历它之前的每个组合?

是的。我们必须做一些计算才能找到这个问题的核心。为了更好地说明如何将其分解为非常简单的小问题,我们将看一个更大的示例。一次考虑 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

这种方法也非常有效。下面,我们立即得到 40 选择 20 的第 10 亿个组合(产生超过 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 在 cmets 中指出的那样,他们举了一个重复的例子。解决方案非常相似,它分解为计数。我们首先需要一个类似于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

【讨论】:

  • 对不起,我想我对这个问题不是很清楚。我见过很多“n 选择 r”的解决方案,但我需要的是“n 多选 r”的解决方案。换句话说,“n Choose r with repeats”。
  • @是的,没问题。当我编写这个解决方案时,我继续处理这两种情况。还有,这也不是你的错,我读得不够仔细……我被数数得意忘形了。任何人,我会尽快发布另一半。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-14
  • 2013-07-30
  • 2011-07-26
  • 1970-01-01
  • 2017-07-07
  • 1970-01-01
相关资源
最近更新 更多