【问题标题】:Reduce execution time of prime number generator减少素数生成器的执行时间
【发布时间】:2017-07-10 01:47:48
【问题描述】:

我必须打印两个限制 nmt 次之间的数字。

我创建了t 变量和两个指向t 整数值的保留内存块的指针n, m

我使用指针而不是数组来执行更快的操作。

外部 for 循环迭代每个测试用例并增加 mn 指针。

内部 for 循环打印从 m[i]n[i] 的素数。

代码

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

int is_prime(int);

int main(void) {
    
    int t;
    int *n = malloc(sizeof(int) * t);
    int *m = malloc(sizeof(int) * t);

    scanf("%d", &t);
    for (int i = 0; i < t; i++, m++, n++) {
        scanf("%d %d", &m[i], &n[i]);
        for (int j = m[i]; j <= n[i]; j++) {
            if (is_prime(j)) {
                printf("%d\n", j);
            }
        }
        if (i < t - 1) printf("\n");
    }

    return 0;
}

int is_prime(int num)
{

    if (num <= 1) return 0;
    if (num % 2 == 0 && num > 2) return 0;
     
    for(int i = 3; i < num / 2; i+= 2){
         if (num % i == 0)
             return 0;
    }
    
    return 1;
}

问题:http://www.spoj.com/problems/PRIME1/

代码在 http://ideone.com 上正确编译,但是当我尝试在 SPOJ 上提交此代码时出现“超出时间限制”错误。我怎样才能减少这个素数生成器的执行时间?

【问题讨论】:

  • 看起来你只是在做蛮力审判除法,这非常慢。查找筛子。关于素数的 Wiki 页面应该提及您的选择。此外,您可以检查所有小于sqrt(n) 的数字,而不是检查所有小于num / 2 的数字。如果缓存sqrt(n)的值,应该会更快。
  • 另外@Carcigenicate的提示:使用条件i*i &lt;= num可以完全避免平方根的计算
  • 回复:“我没有创建数组而不是指针,因为指针操作更快”:这句话有太多错误,我几乎不知道从哪里开始。 :-/
  • 您正在使用未初始化的变量 t 来计算数组的大小你给t赋值。
  • 你的代码对我来说真的很奇怪!!!您在使用未初始化变量 (t) 计算的区域大小上使用 malloc。我真的很怀疑:(1)这里真的值得使用malloc(); (2) 它给出了一个安全的最后的结果(请注意,您没有释放分配的内存区域)。

标签: c algorithm primes


【解决方案1】:

正如@Carcigenicate 所建议的那样,您超出了时间限制,因为您的主要发电机太慢了;而且它太慢了,因为你使用的是一种低效的算法。

事实上,您不应该简单地测试每个连续数字的素数(顺便说一句,您这样做也是无效的),而是使用已知素数(可能还有您计算的其他素数)一次排除多个值。例如,您不需要检查 5 和 10 的倍数(实际值 5 除外)的素数,因为您知道 5 可以整除它们。因此只需将各种素数的倍数“标记”为无关紧要即可。

...当然,这只是为了让您入门,您可以使用各种技巧进行优化 - 算法和实现相关。

【讨论】:

    【解决方案2】:

    我知道您正在寻找算法改进,但以下技术优化可能会有所帮助:

    如果您使用的是 Visual Studio,则可以使用 alloca 而不是 malloc,以便 n 和 m 进入堆栈而不是堆。

    您还可以尝试使用数组而不是指针来重写您的算法,以将 n 和 m 放入堆栈。

    如果您想继续使用指针,请在星号后使用 __restrict 关键字,这会提醒编译器您没有引用这两个指针。

    【讨论】:

    • 请注意,如果 alloca() 分配失败,则无法恢复。此外,您的堆栈是有限的(例如,我相信在 WIndows 上为 1 MiB;在 Unix 上只有 8 MiB,Windows 值更小),而 malloc() 可以在许多系统上分配千兆字节。 (而且,不管它值多少钱,alloca() 都可以通过 GCC 获得,并且 AFAIK 在添加到 MSVS 之前在 GCC 中可用。它不会影响我;我不使用它。)
    • 实际上,在 Windows 上,您可以从 alloca() 失败中恢复(使用结构化异常处理;msdn.microsoft.com/en-us/library/wb1s57t5.aspx 有一个示例)。仍然不是一个好主意(特别是如果你关心可移植性)......在这里使用 alloca 不会明显加快速度。如果您真的想坚持使用幼稚的方法,最好的办法就是将其并行化。
    【解决方案3】:

    您甚至可以在不使用指针或数组的情况下做到这一点

    #include <stdio.h>
    #include<math.h>
    
    int is_prime(long n){
        if (n == 1 || n % 2 == 0)
            return 0;
        if (n == 2)
            return 1;
        for (long i = 3; i <= sqrt(n); i += 2) {
            if(n % i == 0)
                return 0;
        }
        return 1;
    }
    int main() {
        int t;
        scanf("%d",&t);
        while(t--) {
            long n, m;
            scanf("%ld %ld",&n,&m);
    
            for (long i = n; i <= m; i++) {
                if (is_prime(i) == 1)
                printf("%ld
    ",i);
            }
        }
        return 0;
    }
    

    【讨论】:

      【解决方案4】:

      有几种方法可以改进整数 n 的素数检查。以下是您可能会发现有用的一些内容。

      1. 减少检查次数:一个众所周知的定理给出了一个事实,如果你想寻找 n 的因数,比如 n = a * b,那么你可以寻找 1sqrt(n) 之间的除数。 (证明很简单,主要论点是我们有三种情况,a = b = sqrt(n),或者我们有a &lt; sqrt(n) &lt; bb &lt; sqrt(n) &lt; a。而且,无论我们属于哪种情况,两者之间都会有一个n的因数1sqrt(n))。

      2. 使用埃拉托色尼筛法:这种方式允许丢弃以前被取消资格的不必要的候选人(参见Sieve of Eratosthenes(维基百科))

      3. 使用概率算法:现在检查素性的最有效方法是使用概率测试。实现起来有点复杂,但效率更高。您可以在here(维基百科)中找到其中的一些技巧。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-12-31
        • 1970-01-01
        • 2020-09-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-02-04
        • 1970-01-01
        相关资源
        最近更新 更多