【问题标题】:Recursively finding prime factors递归查找素因数
【发布时间】:2017-08-15 20:01:09
【问题描述】:

我开始自学 C 并尝试构建一组有用的函数和程序以供将来参考。如果相关,这是 C99。

这是我最近一次迭代的程序。在输出端,我想得到一个数 n 的素数分解。但是,按原样,我得到了所有因素的列表,并且没有重复任何因素。我包含了一些打印语句来尝试和调试,并发现错误与递归有关,但无法弄清楚如何进一步解释它。我之前尝试过创建 int 类型的递归函数,但很难让它与数组 p 一起使用。

  • n 是我要考虑的数字
  • p 是一个数组,用于存储找到的素数
  • j 是 p 的索引
  • c 是我作为 n 的除数测试的数字

我知道可能有更有效的方法来声明 p 以节省内存,但由于这主要是为了参考,内存不是一个大问题。

我发现了这些问题,但认为它们没有回答我的问题

我的主要问题是: 为什么输出显示 n 的所有因子? 为什么它不重复主要因素? 我该怎么做才能修复它?

  #include <stdio.h>

  #define NELEMS(x)  (sizeof(x) / sizeof((x)[0]))

  void factors(int n, int p[], int j) {
  /// if n is divisible by c, store c, and continue with n/c
      int c;
      for (c=2; c < n; c++) {
          if (c > n) break;
          if (n%c == 0) {
              p[j] = c;
              printf("%d has been added to p \t", c);
              printf("n has been reduced to %d \t", n/c);
              printf("j is %d \n", j);
              j++;
              if (n == c) break;
              factors(n/c, p, j);
          }
      }
  }

  int main() {
      /// set up number to factor, and array to hold factors
      int n = 24;
      int p[n/2];
      int i=0;
      for (i=0; i<NELEMS(p); i++) {
          p[i]=0;
      }

      int j = 0;
      factors(n, p, j);

      printf("the prime factors of %d are:\n",n);
      for (i=0; i<NELEMS(p); i++) {
          printf("%d \n", p[i]);
      }

  }

【问题讨论】:

  • 你好像发了半个程序。
  • “这表明递归不是素数分解的好选择” 事实上,递归对于你能想到的几乎所有任务都是不利的。递归的用途非常狭窄,我能想到的唯一用途是存储优化二叉树和一些数学模拟理论任务。在 99% 的情况下,递归会使算法变慢、消耗内存成为危险。它通常也很难阅读。尽管它无用,但学校和书籍往往将重点放在递归上,因为“它看起来很花哨”。但在现实世界中,您应该像避免瘟疫一样避免它。
  • @Lundin 在 C 中可能是正确的,但在其他语言中肯定不是这种情况,因为递归是您的主要(和惯用)循环结构
  • @naomik 这只是因为高级语言的程序员通常不知道无效、危险的递归会产生什么。此类语言的程序员通常不知道他们的源代码如何转换为机器代码。
  • @Lundin 你基本上说了两次同样的话

标签: c recursion prime-factoring


【解决方案1】:

你已经在 cmets 中被告知这个算法很差,这是一个证据。而且你真的应该学会使用调试器:通过调试器运行它会立即显示问题所在。

话虽如此,您的主要问题是当递归函数返回时该怎么办?。您没有问自己这个递归中必须的问题,而只是按顺序继续,这是完全错误的,因为您将重用已经在递归调用中处理过的数字。所以递归调用factors后必须立即添加返回行。

完成此操作后,还有另一个小问题(调试器会很明显),您只能搜索严格小于n 的因子。所以你错过了最后一个主要因素......

通过这 2 个即时修复,您的代码变为:

void factors(int n, int p[], int j) {
  /// if n is divisible by c, store c, and continue with n/c
      int c;
      for (c=2; c <= n; c++) {
          if (c > n) break;
          if (n%c == 0) {
              p[j] = c;
              printf("%d has been added to p \t", c);
              printf("n has been reduced to %d \t", n/c);
              printf("j is %d \n", j);
              j++;
              if (n == c) break;
              factors(n/c, p, j);
              return;
          }
      }
  }

但是恕我直言,p[j] = c; 应该变成 *p = c;factors(n/c, p, j); 应该变成 factors(n/c, p+1, j);。换句话说,你直接传递一个指向下一个 slot 的指针。

【讨论】:

  • 如果将指针传递给下一个槽,则不需要传递j,它是下一个槽距p的偏移量。
  • 但这只是调试信息。您可以打印 p 的值。
【解决方案2】:

编辑我突然想到n的最小因数肯定是素数,所以我相应地编辑了答案。

为什么输出显示n的所有因子?

因为您测试 c 是否是 n 的因子,并将其添加到数组 p 中,无论 c 是否为素数。然后你继续测试c以上的数字,甚至是c的倍数。

为什么不重复质因数?

因为当你发现一个数 c 是一个因数时,你不一定要检查它来确定它是否是一个复合数本身。

将c添加到p后,需要在(n / c)上递归调用factor然后停止。

这大致是您需要的(但未经测试甚至编译)

int factorise(int n, int p[], int j)
{
    int factorsFound = 0;

    for (c = 2 ; c * c <= n && factorsFound == 0 ; ++ c)
    {
        if (n % c == 0)
        {
            p[j] = c;
            factorsFound = factorise(n / c, p, j + 1) + 1;
        }
    }
    if (factorsFound == 0) // n is prime
    {
        p[j] = n;
        factorsFound = 1;
    }
    return factorsFound;
}

同样在实际解决方案中,您可能希望传递 p 的大小,以便检测是否空间不足。

只是为了好玩,因为还没有其他人发布它,这里是一个非递归的解决方案。它实际上与上面相同,但递归已转换为循环。

int factorise(int number, int p[])
{
    int j = 0;

    for (int c = 2, int n = number ; n > 1 ; )
    {
        if (n % c = 0)
        {
            p[j++] = c;
            n = n / c;
        }
        else
        {
            c++;
        }
    }
    return j;
}

我不同意 Lundin 关于递归的一些 cmets。递归是将问题分解为更简单的子任务的一种自然方式,但不可否认的是,在 C 中它的效率较低,尤其是在堆栈空间方面,在这种特殊情况下,非递归版本更简单。

【讨论】:

  • 我很好奇你为什么在这里返回 j?是不是可以去掉那个语句,使整个函数类型为void?
  • @fyzx92 j 是找到的因子数。调用者知道有多少因素可能对调用者很有用,然后您不必遍历数组以寻找哨兵值。
【解决方案3】:

来自This answer

why does recursion cause stackoverflow so much more than loops do

因为每次递归调用都会占用堆栈上的一些空间。如果您的递归太深,则会导致 StackOverflow,具体取决于堆栈中允许的最大深度。

使用递归时,您应该非常小心,并确保提供了基本情况。递归的基本情况是递归结束和堆栈开始展开的条件。这是递归导致 StackOverflow 错误的主要原因。如果它没有找到任何基本情况,它将进入无限递归,这肯定会导致错误,因为堆栈只是有限的。

-

看来你的 for 妨碍了,c 会增加,并且不会再次检查相同的值。

例如,如果输入是 8,我们想要 (2,2,2) 而不是 (2,4)

我建议您暂时替换您的if (c%n ==0),不要忘记在此替换 n 的值,您不想在其中循环。

这似乎是一个很好的答案:

int primes(int nbr, int cur)
{
  if (cur > nbr)
    return (0);
  else
    {
      if (nbr % cur == 0 && isprime(cur) == 1)
        {
          printf("%d\n", cur);
          return (primes(nbr / cur, 2));
        }
      return (primes(nbr, cur + 1));
    }
}

在 main 中使用 cur = 2 调用该函数

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-01-19
    • 1970-01-01
    • 2022-06-28
    • 2016-11-26
    • 2012-03-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多