【问题标题】:Counting number of primes within a given range of long int using C使用C计算给定long int范围内的素数数量
【发布时间】:2015-06-07 09:34:32
【问题描述】:

嗯,在 SO 和其他论坛中有很多这样的问题。然而,这些都没有帮助。

我用“C”写了一个程序来查找一个范围内的素数。范围 i in long int。我正在使用Sieve of Eratosthenes" 算法。我正在使用一个长整数数组来存储从 1 到极限的所有数字。如果不使用数组,我想不出更好的方法来实现。该代码工作正常,直到 10000000。但在那之后,它耗尽了内存并退出。下面是我的代码。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
typedef unsigned long uint_32;

int main() {

    uint_32 i, N, *list, cross=0, j=4, k, primes_cnt = 0;
    clock_t start, end;
    double exec_time;

    system("cls");
    printf("Enter N\n");
    scanf("%lu", &N);
    list = (uint_32 *) malloc( (N+1) * sizeof(uint_32));
    start = clock();
    for(i=0; i<=N+1; i++) {
        list[i] = i;
    }

    for(i=0; cross<=N/2; i++) { 
        if(i == 0)
            cross = 2;
        else if(i == 1)
            cross = 3;
        else {

            for(j=cross+1; j<=N; j++) {
                if(list[j] != 0){

                    cross = list[j];
                    break;
                }
            }
        }

        for(k=cross*2; k<=N; k+=cross) {
            if(k <= N)
                list[k] = 0;
        }
    }

    for(i=2; i<=N; i++) {

        if(list[i] == 0)
            continue;
        else
            primes_cnt++;
    }

    printf("%lu", primes_cnt);
    end = clock();
    exec_time = (double) (end-start);
    printf("\n%f", exec_time);
    return 0;
}

我被困住了,想不出更好的方法来实现这一点。任何帮助将不胜感激。谢谢。

编辑:

我的目标是生成并打印范围以下的所有素数。由于打印耗费了大量时间,我想到了正确迈出第一步。

【问题讨论】:

  • 对于初学者,您可以将数组用作位集(回想一下,您实际上需要它来判断真/假,而不是 2^32 值)。通过为数字分配一个位,它将为您提供 X32 更多可以存储的元素。 (对于 2^64 值仍然不够)。
  • 感谢您的回复。我实际上需要打印范围以下的所有素数。但由于打印需要很多时间,我想先让我数一下总素数。如果我做对了,下一步会更容易。还有其他线索吗?
  • 即使有一个位字段,你仍然可以得到数字。
  • 您在问题中说过要生成范围内的素数。而到目前为止您的代码显示您正在生成从零到 n 的素数。对吗?
  • 请注意,对于 Eratosthenes 算法的每次迭代,您从 n*n 而不是 n*2 开始消除。例如:要消除 2 的倍数,您从 2*2=4、6、8 ...开始,对于 3,您从 3*3=9、12、15....开始,对于 5,您从 5*5 开始=25,30,35,40...

标签: c algorithm


【解决方案1】:

还有其他算法不需要您生成最多 N 的素数来计算低于 N 的素数。最容易实现的算法是勒让德素数计数。该算法要求您仅生成 sqrt(N) 素数以确定低于 N 的素数。

算法背后的想法是

pi(n) = phi(n, sqrt(n)) + pi(sqrt(n)) - 1

where
   pi(n) = number of prime below N
   phi(n, m) = number of number below N that is not divisible by any prime below m.

这意味着 phi(n, sqrt(n)) = sqrt(n) 到 n 之间的素数。 phi的计算方法可以到以下链接(Feasible implementation of a Prime Counting Function)

之所以效率更高,是因为计算 phi(n, m) 比计算 pi(n) 更容易。假设我要计算 phi(100, 3) 意味着有多少小于或等于 100 的数不能被 2 和 3 整除。您可以执行以下操作。 phi(100, 3) = 100 - 100/2 - 100/3 + 100/6。

【讨论】:

  • 感谢 invisal 提供这种方法。事实上,我需要生成 N 以下的所有素数。应该在问题中提到这一点。将对其进行编辑。
【解决方案2】:

您的代码使用的内存大约是其所需内存的 32 倍。请注意,由于您初始化了list[i] = i,因此赋值cross = list[j] 可以替换为cross = j,从而可以将list 替换为位向量。

但是,这不足以使范围达到 264,因为您的实现需要 261 字节 (2 exbibytes) 的内存,所以您需要进一步优化。

接下来要注意的是,当“交叉”数字时,你不需要上升到N/2:√N 就足够了(你应该能够通过考虑除以一个合数的结果来证明这一点由其以上 √N 的除数)。这使内存需求触手可及,因为您的“交叉”素数将适合大约 4 GB 的内存。

一旦你有了一个交叉素数数组,你就可以为任何范围构建一个部分筛子,而不用在内存中保留它之前的所有范围。这称为分段筛。您可以在primesieve generator 的页面上找到有关它的详细信息以及简单的实现。这种方法的另一个优点是您可以将其并行化,从而进一步缩短时间。

【讨论】:

  • 感谢您的意见。学到了一些新东西。将尝试这些。
【解决方案3】:

您可以稍微调整算法以计算块中的素数。

加载数组的一部分(尽可能多地适合内存),并保存所有已知素数的列表。
每当您加载一个块时,首先要检查已知的素数,并且类似于常规筛子,将所有非素数设置为这样。
然后,再次检查数组,标记任何你能做的,并将找到的新素数添加到列表中。

完成后,您将获得一个包含所有素数的列表。

【讨论】:

    【解决方案4】:

    我可以看到您使用的方法是 Eratosthenes 的基本实现,首先突出所有 2 的倍数,然后是 3 的倍数,依此类推。

    但我对这个问题有更好的解决方案。实际上,spoj PRINT 有问题。请仔细阅读并检查它遵循的约束。下面是我针对这个问题的代码 sn-p:

    #include<stdio.h>
    #include<math.h>
    #include<cstdlib>
    
    int num[46500] = {0},prime[5000],prime_index = -1;
    
    int main() {
        /* First, calculate the prime up-to the sqrt(N) (preferably greater than, but near to 
           sqrt(N) */   
        prime[++prime_index] = 2;    int i,j,k;
    
        for(i=3;    i<216;     i += 2) {
            if(num[i] == 0) {
                prime[++prime_index] = i;
                for(j = i*i, k = 2*i;    j<=46500;   j += k) {
                    num[j] = 1;
                }
            }
        }
    
        for(;   i<=46500;   i+= 2) {
            if(num[i] == 0) {
                prime[++prime_index] = i;
            }
        }
    
        int t;            // Stands for number of test cases  
    
        scanf("%i",&t);
        while(t--) {
    
            bool arr[1000005] = {0};   int m,n,j,k;
    
            scanf("%i%i",&m,&n);
    
            if(m == 1)
                m++;
    
            if(m == 2 && m <= n) {
                printf("2\n");
            }
    
            int sqt = sqrt(n) + 1;
    
            for(i=0;    i<=prime_index; i++) {
                if(prime[i] > sqt) {
                    sqt = i;
                    break;
                }
            }
    
            for(;   m<=n && m <= prime[prime_index];   m++) {
                if(m&1 && num[m] == 0) {
                    printf("%i\n",m);
                }
            }
    
            if(m%2 == 0) {
                m++;
            }
    
            for(i=1;    i<=sqt;     i++) {
                j = (m%prime[i]) ? (m + prime[i] - m%prime[i]) : (m);
                for(k=j;    k<=n;   k += prime[i]) {
                    arr[k-m] = 1;
                }
            }
    
            for(i=0;    i<=n-m; i += 2) {
                if(!arr[i]) {
                    printf("%i\n",m+i);
                }
            }
            printf("\n");
        }
        return 0;
    }
    

    希望你明白了:

    而且,正如您提到的,您的程序在 10^7 之前都可以正常工作,但在超过 10^7 时它会失败,这一定是因为您的内存必须用完。

    注意:我仅出于知识目的共享我的代码。请不要复制和粘贴它,直到你明白这一点。

    【讨论】:

      猜你喜欢
      • 2019-05-22
      • 2018-07-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多