Is there a function to generate a specific n Multichoose r combination, given the index number?

為{幸葍}努か 提交于 2019-12-02 01:38:14

问题


For example, 3 multichoose 2 has the following combinations:

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

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


回答1:


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

Yes. We have to do a little counting to get at the heart of this problem. To better illustrate how this can be broken down into very simple smaller problems, we will look at a larger example. Consider all combinations of 5 chosen 3 at a time with no repeats (we will say from here on out 5 choose 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

Notice the first 6 rows. If we remove the first column of these 6 rows and subtract 1 from every element, we obtain:

      [,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

The matrix on the right is precisely all of the combinations of 4 choose 2. Continuing on, we see that the "second" group (i.e. rows 7 through 9 of the original matrix) also looks to have order:

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

This is simply 3 choose 2. We are starting to see a pattern unfold. Namely, that all combinations of smaller n and r are contained in our parent combinations. This pattern continues as we move to the right. All that is left is to keep up with which combination we are after.

Below is the above algorithm written out in C++ (N.B. there isn't any data validation):

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;
}

Calling it on our example above with 5 choose 3 we obtain:

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

This approach is very efficient as well. Below, we get the billionth combination of 40 choose 20 (which generates more than 100 billion combinations) instantly:

      // 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

Edit

As the OP points out in the comments, they gave an example with repeats. The solution is very similar and it breaks down to counting. We first need a counting function similar to nChooseK but that considers repeats. The function below does just that:

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];
}

And here is the function that generates the ith combination with repeats.

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;
}

It is very similar to the first function above. You will notice that n1-- and j++ are removed from the end of the function and also that n1 is initialized to n instead of n - 1.

Here is the above example:

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


来源:https://stackoverflow.com/questions/50148010/is-there-a-function-to-generate-a-specific-n-multichoose-r-combination-given-th

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!