子序列计算器。我可以提高效率吗?

时间:2015-11-11 18:11:51

标签: c subsequence

这篇文章很好地解释了子序列的概念:Generate subsequences

但我不明白这个问题的答案,因为我是初学者。

我想知道的是,如果我能使我的C程序更有效率,同时仍然保持简单易懂且不使用函数?

#include <stdio.h>
#define NUM 123456

int main(void) {

    int i,num=NUM,x,y,mask,digits=0,max=1;

    while ( num != 0 ) {  //calculate digits
        num /= 10;
        digits++;
    }

    for ( i = 1; i <= digits; i++ ) {  //calculate max number of subsequences
        max *= 2;     
    }
    max=max-2;

    printf("Subsequences are:\n");
    for ( i = 1; i <= max ; i++ ) {
        mask = i;     //digit selector
        x = 1;        //multiplier
        num = NUM;
        y=0;          //subsequence value

        while ( num != 0 ) {
            if ( mask % 2 == 1 ) {
                y += num % 10 * x;
                x *= 10; 
            }
            num /= 10;
            mask /= 2;
        }
        printf("%d \n" , y);
    } 

    return 0;
}

请注意,当我们将NUM定义为诸如5111或100之类的数字时,某些子序列会出现两次。有没有简单的方法来解决这个问题? 谢谢!

2 个答案:

答案 0 :(得分:0)

某些子序列与某些数字出现不止一次的原因之一是因为这些数字具有相同数字的重复。

通过将每个子序列保存在数组中并检查该数组以查看特定子序列是否已存在于数组中,可以消除重复。如果已在阵列中,请勿打印。否则,将子序列添加到数组内容并打印

答案 1 :(得分:0)

问题可以分为两个任务:(1)查找数字数组的所有子序列;(2)将整数打包并解压缩为数字。

让我们考虑数组{a, b, c}的子序列。您可以通过从左到右遍历数组来生成它们,并遵循两条路径:一条路径包含子序列中的当前元素,另一条路径则不包含。

这导致我们可以表示为树的递归方法:

                  {}
               /      \
         {}               {a}
       /    \           /     \
    {}        {b}     {a}      {ab}
   /  \      /   \   /   \    /    \
  {}  {c}  {b} {bc} {a} {ac} {ab} {abc}

当我们向左分支时,我们跳过当前元素,当我们向右移动时,我们包含元素。元素本身就是树的深度:在第一级,我们处理元素a,在下一个b和最后c上。

底行包含所有子序列。这包括您不想要的空序列和完整序列。但是现在让我们把它们包括在内。 (底行中的数组通常称为power set,这是一个很好的网络可搜索术语。)

我提到了递归,它需要递归函数,并且函数已经完成。

所以我们需要以另一种方式解决问题。让我们把树转向它。破折号表示该元素已被跳过。右边的符号使用另一种表示法:0表示跳过了元素,1表示包含的元素:

- - -        000
- - c        001
- b -        010
- b c        011
a - -        100
a - c        101
a b -        110
a b c        111

我希望右边的代码看起来很熟悉,因为这是你在二进制文件中从000计算到111的方式。这是枚举元素的好方法。现在我们需要一种方法来判断每个数字中设置了哪些位。

当数字为奇数时,设置最右边的位。我们可以通过重复地将数字除以2来找出其他位,二进制是向右移位,丢弃最右边的位。

现在如何从原始数字中提取数字?该数字是十进制数;它位于基数10.我们可以使用与查找二进制数中的位相同的方法,因为位0和1是二进制数字。

从数字开始。最后一位是将除法后的余数除以10的结果。然后将数字除以10,直到它为零。此代码从右到左生成数字。用于查找位的代码也是如此,这意味着我们可以找到是否设置了一个位以及在单个循环中打印哪个数字,始终取最右边的位,如果已设置,则打印原始数字的最右边的数字。 / p>

空子序列和完整子序列是枚举中的第一个和最后一个项目。如果您不想要它们,请跳过它们。

如果数字有重复的数字,则会出现重复子序列的问题。我没有看到一个简单的方法来解决这个问题,除了user3629249建议创建子序列然后检查是否已经打印。

一种简单的方法是保留子序列的数组。该数组有max个条目。填充完该数组后,对其进行排序然后打印,但跳过与上一个条目相同的条目。

这是一个使用数字数组的示例实现,因此每次都不必分解原始数字。它使用来自qsort的排序函数<stdlib.h>,它需要一个排序函数:

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

#define NUM 412131

typedef unsigned int uint;

int uintcmp(const void *pa, const void *pb)
{
    const uint *a = pa;
    const uint *b = pb;

    return (*a > *b) - (*a < *b);
}

int main(void)
{
    uint digit[20];             // array of digits
    size_t ndigit = 0;          // length of array
    uint num = NUM;
    uint max = 1;
    size_t i;

    while (num) {
        digit[ndigit++] = num % 10;
        num /= 10;
        max *= 2;
    }

    uint res[max];              // array of subsequences

    for (i = 0; i < max; i++) {
        uint mask = i;          // mask for bit detection
        uint j = ndigit;        // index into digit array
        uint s = 0;

        while (j--) {
            if (mask % 2) s = s*10 + digit[j];
            mask /= 2;
        }

        res[i] = s;
    }

    qsort(res, max, sizeof(*res), uintcmp);

    for (i = 1; i < max - 1; i++) {
        if (res[i] != res[i - 1]) printf("%u\n", res[i]);
    }

    return 0;
}