用于在c中查找唯一任意大小的数字组合的算法

时间:2015-09-28 11:04:37

标签: c algorithm

我需要一个干净的c代码来找出数字的组合。 任意数量的数字和任何大小的组合。 例如{1,2,3} 输出应为{1,2,3,12,13,23,123},注23和32相同。 那有没有干净的c程序?

致以最诚挚的问候,

2 个答案:

答案 0 :(得分:2)

有很多方法可以做到这一点。这是一种使用位操作的方法。

让给定的集合 a

这个代码非常小。理解以下内容,您将了解这一小段代码的工作原理。

你必须要意识到的第一件事是你找到给定集合的(2 n - 1)子集。

任何集都有 2 n 子集,在这里,您已经排除了 null 集。因此(2 n - 1)

现在,要生成这些子集,我们需要一个算法。

  

请注意以下事项:

           

001 --- 1

     

010 --- 2

     

011 --- 3

     

100 --- 4

     

101 --- 5

     

110 --- 6

     

111 --- 7

     

左数字构成右十进制数的二进制表示。

如果我们写出4位数的二进制数,那么就会有15种组合。请注意,我在上面的例子中排除了所有数字为零的组合。

通常,对于 n位二进制数,有(2 n - 1)不同的数字组合。我们可以用它来以非常简单的方式生成子集。

对于集合中的每个元素,您都可以:

  1. 选择当前子集的元素。
  2. 不要为当前子集选择该元素。
  3. 忽略你没有选择的情况。
  4. (因此,有(2 n - 1)子集)

    现在,我说要做以下事情:

    for i in [1,2^n - 1]:
         Let b = binary representation of i.
         for every jth bit in b:
             if the jth bit is set:
                 print a[j]
         print a newline character.
    

    这是C代码:

    // include your headers
    
    int isJthBitSet(int i,int j)  
    {
    // returns 1 if jth bit is set in the binary representation of i.
        return (i & (1 << j));
    }
    
    int main()
    {
         int n = 3;               // The size of the set for example. 
         int a[] = {1,2,3};      // the given set, for example.
         for(int i = 1; i < (1 << n); i++)   // i is from 1...2^n - 1
         {
              for(int j = 0; j < n; j++) // for every jth bit in the n-bit representation of i
              {
                    if(isJthBitSet(i,j)) // if the bit is set
                        printf("%d ", a[j]); // print the corresponding element
              }
              printf("\n");
         }
        return 0;
    }
    

    这就是它。

答案 1 :(得分:1)

虽然我通常不满足于展示完整的解决方案,但基于一些相对较新的类似问题及其答案,似乎有必要举例说明如何解决这些类型的组合问题。

使用k元素中的n元素构造所有唯一集合的简单方法是使用k嵌套循环,其中循环索引始终按递增顺序排列。例如,要从一组N个字符打印所有唯一的3个字符三元组,您可以使用

    const char all[N] = ...;
    char set[4];
    size_t i, j, k;

    set[3] = '\0'; /* End of string mark */

    for (i = 0; i < N-2; i++) {
        set[0] = all[i];

        for (j = i+1; j < N-1; j++) {
            set[1] = all[j];

            for (k = j+1; k < N; k++) {
                set[2] = all[k];

                puts(set);
            }
        }
    }

现在,OP希望所有具有最多 k元素的唯一子集来自一组n元素,这意味着我们需要不能使用上面的嵌套循环(因为我们不知道最大k)。无论如何,明确地说。相反,我们需要考虑如何重写它。

为了更好地掌握结构,让我们看一下五分之三的情况。十个结果集是

1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5

有明确的顺序和清晰的逻辑:增加最右边,除非它会变得太大。然后,找到左边的下一个索引,我们可以在不继续的情况下递增(为右侧的那些保留足够的元素)。如果我们不能在没有经过的情况下增加最左边,我们已经生成了所有集合。 (如果你考虑一下,这也是可变嵌套循环的直接实现。)递增后,按升序将元素设置为右边。

在大多数情况下,我们希望某种结构或对象能够跟踪状态和当前子集,并具有初始化,释放和切换到下一个子集的功能。这是一种可能性:

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>


typedef struct {
    char    *buffer;    /* Symbol buffer */
    size_t   length;    /* Number of symbols to choose */
    size_t  *index;     /* Index of each symbol */
    char    *symbol;    /* Array of symbols */
    size_t   symbols;   /* Number of symbols to choose from */
} generator;


void generator_free(generator *const g)
{
    if (g) {
        free(g->buffer);
        free(g->index);
        free(g->symbol);
        g->buffer  = NULL;
        g->length  = 0;
        g->index   = NULL;
        g->symbol  = NULL;
        g->symbols = 0;
    }
}


const char *generator_current(generator *const g, const char *const none)
{
    return (g && g->buffer) ? g->buffer : none;
}


int generator_init(generator *const g, const char *const symbol, const size_t choose)
{
    const size_t symbols = (symbol) ? strlen(symbol) : 0;
    size_t       i;

    if (!g || symbols < 1 || choose < 1 || choose > symbols)
        return EINVAL;

    g->buffer = malloc(choose + 1);
    g->index  = malloc((choose + 1) * sizeof g->index[0]);
    g->symbol = malloc(symbols + 1);
    if (!g->buffer || !g->index || !g->symbol) {
        free(g->buffer);
        free(g->index);
        free(g->symbol);
        g->buffer  = NULL;
        g->length  = 0;
        g->index   = NULL;
        g->symbol  = NULL;
        g->symbols = 0;
        return ENOMEM;
    }

    memcpy(g->buffer, symbol, choose);
    g->buffer[choose] = '\0';
    g->length = choose;

    for (i = 0; i < choose; i++)
        g->index[i] = i;
    g->index[choose] = symbols;

    memcpy(g->symbol, symbol, symbols);
    g->symbol[symbols] = '\0';
    g->symbols = symbols;

    return 0;
}


int generator_next(generator *const g)
{
    size_t i;

    if (!g || !g->buffer || g->length < 1 || !g->index)
        return EINVAL;

    if (g->index[0] >= g->symbols - g->length)
        return ENOENT;

    if (++g->index[g->length - 1] >= g->symbols) {

        i = g->length - 1;
        while (i > 0 && g->index[i] + 1 >= g->symbols - i)
            i--;

        g->index[i]++;

        if (!i && g->index[0] > g->symbols - g->length) {
            memset(g->buffer, '\0', g->length + 1);
            return ENOENT;
        }

        while (i++ < g->length)
            g->index[i] = g->index[i-1] + 1;
    }

    for (i = 0; i < g->length; i++)
        g->buffer[i] = g->symbol[g->index[i]];
    g->buffer[g->length] = '\0';

    return 0;
}

generator_current()提供当前集(作为字符串)。当没有有效集时,它将返回您指定的字符串作为第二个参数,而不是返回NULL。 (这只是为了方便,这背后没有真正的原因。)

generator_free()丢弃生成器,generator_init()初始化一个新生成器,generator_next()将生成器推进到下一个子集。

注意generator_init()也初始化第一个子集;连续选择元素的那个。 (虽然->symbol只是一个包含整个集合中所有字符的字符数组,但该函数会附加一个字符串结尾标记,因此您也可以将其视为字符串。)

generator_next()中的第一个if子句确保生成器初始化;这只是一个健全检查。第二个检查发电机是否完整。

generator_next()中的第三个if子句递增最右边的索引,更改子集中的最后一个元素。如果它用完了有效元素,则while循环将搜索索引的i索引,该索引可以在不耗尽元素的情况下递增。请注意,由于索引按升序排列(确保了唯一的子集),因此必须记住要考虑其余位置所需的元素。

如果i变为零并且溢出,则不再有子集,并且->buffer成员被清除为空字符串(以防万一)。

否则,第二个while循环使用连续值填充i右侧的索引。 (参见上面五个三分之一的示例,第一个元素从1变为2的情况,以说明为什么需要这样做。)

最后,for循环用于根据索引将->symbol数组中的元素复制到->buffer

对于askers情况,子集的大小会有所不同,因此要生成所有子集,需要循环。例如:

generator g;
size_t    i;

for (i = 1; i <= 2; i++) {
    if (generator_init(&g, "123", i)) {
        fprintf(stderr, "generator_init() failed!\n");
        exit(EXIT_FAILURE);
    }
    do {

        /* Print the set and a newline */
        puts(generator_current(&g, ""));

    } while (!generator_next(&g));
    generator_free(&g);
}

为了进行测试,我使用了以下辅助函数和main()

int parse_size(const char *s, size_t *const dst)
{
    const char   *endptr = NULL;
    unsigned long value;
    size_t        result;
    int           skip = -1;

    if (!s || !*s)
        return EINVAL;

    errno = 0;
    value = strtoul(s, (char **)&endptr, 0);
    if (errno)
        return errno;
    if (!endptr || endptr == s)
        return EEXIST;

    (void)sscanf(endptr, " %n", &skip);
    if (skip > 0)
        endptr += skip;
    if (*endptr)
        return EEXIST;

    result = (size_t)value;
    if ((unsigned long)result != value)
        return EDOM;

    if (dst)
        *dst = result;

    return 0;
}

int main(int argc, char *argv[])
{
    generator g;
    size_t    symbols, length, len;

    if (argc != 3 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s DIGITS LENGTH\n", argv[0]);
        fprintf(stderr, "\n");
        fprintf(stderr, "This will print each unique set of LENGTH characters from DIGITS,\n");
        fprintf(stderr, "one set per line.\n");
        fprintf(stderr, "\n");
        return EXIT_FAILURE;
    }

    symbols = (argv[1]) ? strlen(argv[1]) : 0;
    if (symbols < 1) {
        fprintf(stderr, "No DIGITS specified.\n");
        return EXIT_FAILURE;
    }

    if (parse_size(argv[2], &length) || length < 1 || length > symbols) {
        fprintf(stderr, "%s: Invalid LENGTH.\n", argv[2]);
        return EXIT_FAILURE;
    }

    for (len = 1; len <= length; len++) {

        if (generator_init(&g, argv[1], len)) {
            fprintf(stderr, "Generator initialization failed.\n");
            return EXIT_FAILURE;
        }

        do {
            puts(generator_current(&g, ""));
        } while (!generator_next(&g));

        generator_free(&g);
    }

    return EXIT_SUCCESS;
}

在Linux中,我更喜欢使用gcc -Wall -Wextra -ansi -pedantic -O2 main.c -o example编译上述内容。最初的问题要求

./example 123 2

输出

1
2
3
12
13
23

更大的例子更有趣。例如,

./example 12345 3

列出前五位数字中的所有一位,两位和三位数字。输出是

1
2
3
4
5
12
13
14
15
23
24
25
34
35
45
123
124
125
134
135
145
234
235
245
345

有问题吗?