【问题标题】:How can I efficiently find all almost perfect numbers from 1 to n in C如何在 C 中有效地找到从 1 到 n 的所有几乎完美的数字
【发布时间】:2021-10-26 09:06:10
【问题描述】:

我一直试图在 C 中找到介于 1 和 n 之间的所有 almost perfect numbers

我知道几乎完美数的定义是一个数的除数之和应该等于那个数-1。

我的方法是从 1 迭代到该数字,计算其除数的总和,然后检查总和是否等于该数字 -1,如下面的代码所示。

int checkAlmoastPerfect(int n)
{
    int divisors = 0;

    for (int i = 1; i <=n; i++) {

        if (n % i == 0)
            divisors += i;
    }

    if (divisors == 2 * n - 1)
        return 1;

    return 0;
}


而且它似乎有效,但速度相当慢。检查 100 或 1000 个数字并不算太糟糕,但如果我们去到数百万范围内的东西,它需要很长时间,所以环顾四周后,我发现有一种方法我不需要从 1 中检查所有数字最多为该数字,但最多为该数字的平方根。

所以我把for循环改成这样:

for (int i = 1; i <=sqrt(n); i++) { ... } 

然后检查这个

if (divisors == n - 1)
        return 1;

但它现在根本不起作用:如果我检查从 1 到 100 的数字,我会发现只有 1、2 和 4 几乎是完美的数字,这是错误的,因为还有更多。

我在这里做错了什么,我怎样才能让我原来的方法更有效率?

【问题讨论】:

  • 什么是“近乎完美的数字”?
  • 考虑使用i * i &lt;= n作为循环条件
  • 您的原始循环是为i == n 执行的,因此是if (n % n == 0) divisors += n;。结果divisors &gt;= n永远不等于n-1
  • 如果只去sqrt(n),必须加2个除数,一个给i,一个给n/i
  • 1、2 和 4 确实是几乎完美的数字。更多信息mathworld.wolfram.com/AlmostPerfectNumber.html。为什么你认为你的代码是错误的?

标签: c numbers


【解决方案1】:

假设n 不是特别大,您可以尝试同时计算1n 范围内每个数字的除数之和。而不是检查每个数字是否可被d 整除以增加其除数之和,只需将d 添加到可被d 整除的数字的除数之和中。这导致了如下简单有效的算法。

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

int n = 10000;

int main() {
  int *sum = calloc(n + 1, sizeof *sum);
  for (int d = 1; d <= n; ++d)
    for (int p = 0; p <= n; p += d)
      sum[p] += d;

  // find number which sum of divisors (including itself) is 2n - 1
  for (int i = 0; i <= n; ++i)
    if (sum[i] == i + i - 1)
       printf("%d\n", i);

  free(sum);
}

程序产生:

1
2
4
8
16
32
64
128
256
512
1024
2048
4096
8192

返回 2 的幂,这与当前关于“几乎完美数”的知识相一致。

复杂度是:

n + n/2 + n/3 + .. = O(n * log(n))

虽然算法对缓存不是很友好,但不需要模或除。

【讨论】:

  • 这个想法很酷但是...... 1)它只能处理 n 高达 ~65000(假设 32 位 int)和 2)它使用大量内存。第一个问题可以通过进行 64 位计算来解决,但这只会使第二个问题变得更糟。但还是有点酷。
  • @4386427, 1) int 很好,unsigned 就足够了。 1..1e8 范围内除数的最大和为 487710720 对应 99459360。看起来限制像n * O(log n) 一样缓慢增长。 2) 大n 的内存可能有问题。有几种方法可以以使代码更复杂为代价来解决这个问题。 n 的最大值是多少?
  • 好吧,sum[0] 将是1+2+3+...+n,所以这将是大约 65000 的整数溢出(即 UB)但是... sum[0] 可以简单地跳过,因为它不会是无论如何解决。换句话说......用一个看起来很容易解决的小代码。
  • @4386427,是的,sum[0] 无论如何都是垃圾,因为它是无穷大的。它会溢出一切;)。顺便说一句,您可以随意使用固定宽度类型,例如 uint32_tint32_t
【解决方案2】:

它现在根本不起作用

求和也需要工作。

    //if (n % i == 0)
    //  divisors += i;

    if (n % i == 0) {
      divisors += i;
      if (i*i < n) divisors += n/i;
    } 

for (int i = 1; i < n/i; i++) {
    if (n % i == 0)
        divisors += i + n/i;
}
// look for perfect square
if (i == n/i) {
    if (n % i == 0)
        divisors += i;
}

i &lt;=sqrt(n); 在这里使用 FP 很狡猾,因为 sqrt() 可能只是弱且几乎正确。最好使用整数数学来解决整数问题。

// for (int i = 1; i <=sqrt(n); i++) { ... }
for (int i = 1; i <= n/i; i++) { ... }

【讨论】:

  • 为什么不把最后一个测试简化为if (i * i == n) divisors += i;?试图在n = INT_MAX 周围保持定义的行为?
  • @chqrlie 是的。代码// look for perfect square if (i == n/i) { 以这种方式形成以避免i*i 溢出。
【解决方案3】:

这是一个性能合理的程序。我省略了一些优化以保持简单和有教育意义。

#include <stdio.h>


/*  This is a program to find perfect numbers or "almost perfect" numbers.
    (The sum of the proper divisors of an almost perfect number n is n-1, so
    the sum of all the divisors is 2*n-1.  The "target" object can be set as
    desired to find numbers whose divisors sum to a specific total.)  It works
    by factoring candidates and computing the product of:

        1 + p + p^2 + … + p^e

    for each prime p, where e is the greatest integer such that p^e divides n.
    (Observe that, notionally, this product can be computed over every prime,
    all of the infinitely many primes, because, for any prime that does not
    divide n, e is 0, and then 1 + p + p^2 + … + p^e is just 1, and multiplying
    the product by 1 does not affect it.)

    Observe that each divisor of n is a product of some combination of the
    primes that divide n, each prime with some multiplicity.  Each of the 1 + p
    + p^2 + … + p^e factors in our product contains its prime p in each
    multiplicity from 0 to its e, inclusive.  When all of these factors are
    written as polynomials and multiplied, the terms of the expanded polynomial
    contain each combination of the primes that divide n with each combination
    of multiplicities.  Therefore, it is a list of all the divisors of n, and
    they are added together, so the polynomial equals the sum of the divisors
    of n (including 1 and n).  For example, with 72, 72 = 2^3 * 3^2, so our
    product is (1 + 2 + 4 + 8) * (1 + 3 + 9) = 1 + 3 + 9 + 2 + 6 + 18 + 4 + 12
    + 36 + 8 + 24 + 72 = 195, which is the sum of the divisors of 72.

    The program finds primes as it goes.

    Some things in it need to be verified, such as how far we can factor given
    the highest prime number we have remembered, especially as different
    control paths are taken.
*/


/*  Set a number of primes to remember.  This limits how far we can test as it
    limits our ability to factor numbers.
*/
#define AP  1000 // Number of elements Allocated for Primes.


int main(void)
{
    unsigned Primes[AP];
    unsigned NP = 0;    //  Number of filled elements in Primes.
    unsigned Limit = 4; //  Current limit on n we know we can support.

    /*  Test integers from 1 to Limit.  Limit will be increased as we find
        more primes.  It will stop increasing when our Primes array is full.
    */
    for (unsigned n = 1; n <= Limit; ++n)
    {
        //  Occasionally print a message to let user know of our progress.
        if (n % (1u<<22) == 0) printf("(Now at %u.)\n", n);
        
        /*  Set the target sum.  If it is 2*n, this program finds perfect
            numbers.  If it is 2*n-1, this program finds "almost perfect"
            numbers.
        */
        unsigned target = 2*n-1;

        /*  t is used as a temporary value as we factor n.  It starts at n and
            is divided by each prime factor found.

            Product records the product we are building.
        */
        unsigned t = n, Product = 1;

        /*  i indexes the Primes array and of course starts at 0.
            p is the current prime we are testing and starts at the first
            prime, 2.

            Once t is less than p*p, the loop stops, because then either t is
            1 or some prime number.  (Any composite number less than or equal
            to p*p has a factor less than or equal to p, so we would have found
            it and divided t by it.)
        */
        for (unsigned i = 0, p = 2; p*p <= t; p = Primes[++i])
        {
            //  Start the sum with p^0, which is 1.
            unsigned Sum = 1;

            /*  For each time p divides t, grow the Sum, forming 1+p+p^2+…p^e.
                For example, the Sum starts at 1.  If p divides t, then
                "Sum = 1 + Sum*p" sets it to 1+p.  If p divides t again, this
                sets it to 1+(1+p)*p = 1+p+p^2, and so on.
            */
            for (; t % p == 0; t /= p)
                Sum = 1 + Sum*p;

            //  Multiply the sum into the product.
            Product *= Sum;
        }

        //  If t has not been reduced to 1, it is the last prime factor of n.
        if (1 < t)
        {
            //  If there were no other factors, we have a new prime.
            if (Product == 1)
            {
                /*  If we still have space, remember the new prime and increase
                    Limit.  Knowing primes up to n lets us test numbers up to
                    n*n because any number up to n*n with a factor f larger
                    than n also has a factor smaller than n, namely that number
                    divided by f.
                */
                if (NP < AP)
                {
                    Primes[NP++] = n;
                    Limit = n*n;
                }
            }

            //  Multiply the series for the last factor into Product.
            Product *= 1+t;
        }

        //  If our criterion for n is met, report it.
        if (Product == target)
            printf("Found %u.\n", n);
    }
    printf("Tested numbers up to and including %u.\n", Limit);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-13
    • 1970-01-01
    • 2012-09-22
    • 2011-08-13
    • 2023-03-19
    • 1970-01-01
    相关资源
    最近更新 更多