来自n个列表的组合从给定索引的每个列表中挑选一个元素并且寻求最快的组合排队

时间:2018-01-21 20:05:48

标签: c algorithm parallel-processing combinatorics

我有十个列表,每个列表有七个数字,我想将每个列表的每个数字与其他列表中任何其他数字的数量组合在一起,这样就可以是7 x 7 x 7 x 7 x 7 x 7 x 7 x 7 x 7 x 7 = 282475249种组合。

现在,如果我使用下面的代码将它们保存在数组中,原因是我需要每天处理它们中的一些,我需要大量的内存,我没有:

pacman

有没有办法在给定索引的情况下生成这些组合,这样我可以在第一天做1到100000,在第二天做100001到200000等等?

由于

1 个答案:

答案 0 :(得分:1)

考虑以下示例:

#define  SIZE  10
#define  BASE  7

void set_combination(char          destination[SIZE],
                     unsigned int  index,
                     const char    source[SIZE][BASE])
{
    int  i = SIZE;

    /* Extract the 'SIZE' digits from 'index', starting
       with the least significant (rightmost) one. */
    while (i-->0) {
        destination[i] = source[i][ index % BASE ];
        index /= BASE;
    }

    /* Make sure destination[] is a string, by adding
       the string-terminating '\0' char. */
    destination[SIZE] = '\0';
}        

该函数将索引解释为基本BASE中的SIZE位数。

索引的有效范围是0到BASE SIZE -1,包括在内。范围包围,因此索引0和BASE SIZE 在目标中生成完全相同的字符串。 (假设unsigned int有足够的范围来表示该数字。在这种特殊情况下,BASE = 7且SIZE = 10,7 10 -1 = 282,475,248,这将适合{{ 1}}很好。)

最左边的数字为unsigned int,其中source[0][i]为0到BASE-1(含)。每个数字都有自己的字符数组。最右边的数字为i,其中source[SIZE-1][k]为0到BASE-1,包括在内。

在评论中,OP询问这种方法是否适用于源集合为59个数字(1到59,包括1和59)的情况,其中三个被选中。 (在这种情况下,唯一组合的数量是59 * 58 * 57 = 195054.)

我已将k in this answer显示为一种方法。与上面示例的不同之处在于,此时construct_word()集现在是可变的,每当我们选择一个字母/数字时,我们就会从源集中删除它。 (每个组合都以相同的source集合开始;更改仅在生成单个组合期间发生。)根据OP的特定情况,它类似于以下内容:

source

void choose_3of59(char destination[3], unsigned int index) { char source[59] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59 }; unsigned int base = 59; int i = 3; while (i-->0) { /* Remove next 'base'-base digit out of index */ const unsigned int k = index % base; index /= base; /* Add that digit to destination */ destination[i] = source[k]; /* Remove this digit from the base set. */ base--; source[k] = source[base]; } } 从0到195053(= 59 * 58 * 57 - 1),产生所有三位数组合,其中每个数字都是唯一的,从1到59,包括在内。

请注意,虽然上述内容确实产生了所有组合,但组合不是按升序排列的(即index不按升序排列)。

某些组合比其他组合更难产生。

例如,要生成三元组1≤n1<1。 n2&lt; n3≤59,n1,n2和n3整数,我们可以从为每个三元组(n1,n2,n3)生成适当索引(在destination[0]*59*59 + destination[1]*58 + destination[2]0之间)的公式开始:

(57*58*59)/(1*2*3) - 1

接下来,我们可以使用二分搜索来首先找到最重要的数字n1(在1到57之间最多6步)。然后,当找到它时,我们可以使用另一个二进制搜索来首先是中间数n2。最后,我们可以使用上面的等式来求解最低有效数n3:

index(n1, n2, n3) = ((n1 - 174)*n1 + 10091)*n1 / 6 + (117 - n2)*n2 / 2 + n3 - 1771

验证以上内容与

生成完全相同的组合
/* Given 0 <= index <= 32508, generate a triplet of integers,
         1 <= num[0] < num[1] < num[2] <= 59
*/
static void set_triplet59(char num[3], unsigned int index)
{
    int  num0, num1, num2;

    /* Make sure 0 <= i <= 32508. */
    index %= 32509;

    /* Note that index 'index' corresponds to triplet 'num0', 'num1', 'num2':
        index(num0, num1, num2) = ( (num0 - 174)*num0 + 10091)*num0/6 + (117 - num1)*num1/2 + num2 - 1771

       Also note that
        index(num0, num0+1, num0+2) = index0(num0)
        index(num0, num1, num1+1) = index01(num0, num1) = index0(num0) + index1(num0, num1)
        index(num0, num1, num2) = index0(num0) + index1(num0, num1) + (num2 - num1 - 1)
       where
        index0(num0) = (num0*(num0 - 176) + 10266)*(num0 - 1)/6
        index1(num0, num1) = (num1 + num0 - 118)*(num1 - num0 - 1)/2
        index01(num0, num1) = index0(num0) + index1(num0, num1)
                            = 1/6*num0^3 - 29*num0^2 - 1/2*num1^2 + 10091/6*num0 + 119/2*num1 - 1770
                            = ((num0 - 174)*num0 + 10091)*num0 / 6 - (num1 - 119)*num1/2 - 1770
    */

    /* Use a binary search to find the largest possible num0, where
        index(num0, num0+1, num0+2) <= index.
       The binary search does at most six iterations.
    */
    {
        const int  limit6 = index*6; /* To avoid division by 6 for index0(num0). */
        int        min0   = 1;       /* Inclusive */
        int        max0   = 58;      /* Exclusive */
        while (max0 - min0 > 1) {
            num0 = (min0 + max0) / 2;
            if ((num0*(num0 - 176) + 10266)*(num0 - 1) > limit6)
                max0 = num0;
            else
                min0 = num0;
        }
        num0 = min0;
    }

    /* Use a binary search to find the largest possible num1, where
        index(num0, num1, num1+1) <= index.
       The search does at most six iterations. Note that
        ((num0 - 174)*num0 + 10091)*num0/6 + (117 - num1)*num1/2 + num1 + 1 - 1771 > index
       is can be simplified to
        (117 - num1)*num1/2 + num1 > index + 1770 - ((num0 - 174)*num0 + 10091)*num0/6
       and further to
        (117 - num1)*num1 + 2*num1 > 2*index + 3540 - ((num0 - 174)*num0 + 10091)*num0/3
       and finally to
        (119 - num1)*num1 > 2*index + 3540 - ((num0 - 174)*num0 + 10091)*num0/3
       where the right side is 'limit'.
    */
    {
        const int limit = 2*index + 3540 - ((num0 - 174)*num0 + 10091)*num0 / 3;
        int       min1 = num0 + 1;  /* Inclusive */
        int       max1 = 59;        /* Exclusive */
        while (max1 - min1 > 1) {
            num1 = (min1 + max1) / 2;
            if ((119 - num1)*num1 > limit)
                max1 = num1;
            else
                min1 = num1;
        }
        num1 = min1;
    }

    /* Since we know index, num0, and num1, we can calculate num2 directly. */
    num2 = index + 1771 + num1*(num1 - 117)/2 - num0*(10091 - num0*(174 - num0))/6;

    num[0] = num0;
    num[1] = num1;
    num[2] = num2;
}

在英特尔酷睿i5-7200U(x86-64)上,index = 0; for (num[0] = 1; num[0] <= 57; num[0]++) for (num[1] = num[0]+1; num[1] <= 58; num[1]++) for (num[2] = num[1]+1; num[2] <= 59; num[2]++, index++) { /* index, num[0], num[1], num[2] */ } 在所有有效三元组中间中位数为145个周期。作为比较,set_triplet59(triplet, index)在所有组合中的中位数为115个周期。 (换句话说,在x86-64上,您可以预期choose_3of59(triplet, index)set_triplet()慢大约四分之一到三分之一。)