【问题标题】:Sigsegv Error in finding prime in a range在一个范围内寻找素数时的 Sigsegv 错误
【发布时间】:2016-08-14 18:54:03
【问题描述】:

在解决一个在给定范围内查找素数的问题时,我收到一个 Sigsegv 错误,我无法找到我的错误在哪里以及如何纠正它

#include<iostream>
#include<cmath>
using namespace std;
int primes[10000000];// stores prime upto a max value
int prime[10000000];//stores prime in a given range
int main()
{
  long long int t,m,n,s,k,q;
  for(long long int i=1;i<=1000000;i++){
      primes[i]=1;
      primes[1]=0;
  }

    //stores prime using sieve    
    for(long long int i=2;i<=sqrt(1000000);i++) 
    {
        if(primes[i]==1)
        {
            for(long long int j=2;i*j<=1000000;j++)
            {
                primes[i*j]=0;
            }
        }   
    }
    cin>>t;
    while(t--)
    {
        cin>>m>>n;
        //marking all indices as 1 
        for(long long int i=m;i<=n;i++)      
        {
          prime[i]=1;
        }
        //calculating which offset to mark
        for(long long int i=2;i<=n-m+1;i++)       
        {
            if(primes[i]==1)
            {
                long long int x=(m/i)*i;
                while(x<m)
                x=x+i;   
                for(long long int j=x;j<=n;j=j+i)
                {
                    if(primes[j]==0)
                    prime[j]=0;
               }  
             }
           }         
        for(long long int i=m;i<=n;i++)
        {
            if(prime[i]==1&&i!=1)
            cout<<i<<"\n";
        }
    }
    return 0;
}

【问题讨论】:

  • 你有核心转储吗?
  • 在 C++ 中,索引从 0 到 size-1。您至少应该更新您的 for 循环以遵守此规则。
  • @Beginner 您无法使用 : prime[10000000] 访问数组的最后一个元素。您必须使用素数[9999999]。并且第一个元素是 prime[0],而不是 prime[1]
  • @steiner:数组的大小是用于访问它们的最高索引的 10 倍。数零……

标签: c++ primes sieve-of-eratosthenes sieve


【解决方案1】:

考虑一个案例:

1
100000000 100000009

当我在 ideone 链接上运行您的代码时:here

它给出了运行时错误。


原因:您已经初始化了大小为 107 的素数数组,但 m, n 的范围可以达到 >109.

因此,一旦遇到prime[i]=1,您的系统就会崩溃。

for(long long int i=m;i<=n;i++)      
{
      prime[i]=1;
}

建议:学习埃拉托色尼筛法。而且由于m, n的范围可以是
1 &lt;= m &lt;= n &lt;= 1000000000, n-m&lt;=100000

如果我们取 109 的 sqrt-root,那么它将接近 31622。因此,这就是我们选择大小为 32000 (在我的代码中) 的数组 num 的原因。之后,我们计算了 2 - 32000 范围内的素数个数。

现在,考虑三种情况:

  1. m and n 两者都小于32000。然后只需使用计算出的prime 数组并打印所需的质数。

  2. m and n都在32000的范围之外时,检查一个数字i(在m和n的范围内)是否不是 可被任意素数整除(出现在代码中的prime 数组中)。如果i不能被任何数字整除,则打印它。

  3. 如果m and n的范围部分小于32000,部分在32000之外。然后将范围分成两部分,一个完全小于等于32000,另一个完全大于32000。然后对第一个范围重复步骤 1,对第二个范围重复步骤 2。

以下是我的代码,请觉得有用,但不要复制粘贴到 SPOJ 上。

#include<stdio.h>
#include<math.h>

int num[32000] = {0},prime[3500],prime_index = -1;

int main() {
    prime[++prime_index] = 2;
    int i,j,k;

    for(i=3;    i<179;     i += 2) {
        if(num[i] == 0) {
            prime[++prime_index] = i;

            for(j = i*i, k = 2*i;    j<=32000;   j += k) {
                num[j] = 1;
            }
        }
    }

    for(;   i<=32000;   i+= 2) {
        if(num[i] == 0) {
            prime[++prime_index] = i;
        }
    }

    int t,m,n;
    scanf("%i",&t);
    while(t--) {
        scanf("%i%i",&m,&n);

        if(m == 1)
            m++;

        if(m == 2 && m <= n) {
            printf("2\n");
        }

        int sqt = sqrt(n) + 1, arr[100005] = {0};

        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);
            }
        }

        for(i=0;    i<=sqt;     i++) {
            j = prime[i] * (m / prime[i]);

            if(j < m) {
                j += prime[i];
            }

            for(k=j;    k<=n;   k += prime[i]) {
                arr[k-m] = 1;
            }
        }

        for(i=0;    i<=n-m; i++) {
            if(!arr[i]) {
                printf("%i\n",m+i);
            }
        }

        printf("\n");
    }
    return 0;
}

欢迎有任何疑问。

【讨论】:

  • segmented 筛子在这里并没有真正的帮助(它与未分段筛子的工作相同,只是在不超过 L1 缓存大小的部分中)。关键是只筛选任务所需的 windows,使用可以分段或不分段的 windowed 筛子。这样,只需要筛选几十万个数字而不是十亿个。您的其余答案仅重复其他人一周前已经写过的内容。因此-1。
  • 完成筛子命名法:iterated 筛子的工作与普通筛子相同,但反复使用较小的筛子(最好是 L1 缓存大小),而不是分配一个代表所有要筛选的数字的大块。 rolling or sliding sieve 类似,但结构更简单,最适合惰性生成器或性能无关紧要的情况(比优化的筛子慢一到两个数量级)。迭代筛和分段筛经常混为一谈,因为它们都专注于 L1 缓存。
  • @DarthGizka。请检查我的编辑,如果仍然缺少任何一点,请告诉我。我不小心添加了 Segmented Sieve 点,因为我把这个 PRIME1 误认为是关于 SPOJ 的另一个问题。我已经解决了这个问题很久了,我猜是 4 年前。
  • 我撤回了-1,因为现在你的答案增加了一些新的东西;但是,您的代码还不完全值得 +1... 方法 - 窗口筛 - 确实是这项任务的绝佳选择,但我怀疑是否有人还不知道算法从你的代码中学习它是如何工作的;这也不是其他人可以效仿的好榜样。您可能希望将其发布在 Code Review 上,以获取有关如何使其更苗条、更高效以及 - 最重要的是 - 更健壮和更自我记录/自我解释的建议。您可以将其全部应用到 PRIME1 的大坏蛋 PRINT。
  • @DarthGizka。感谢您及时回复朋友,感谢您的建议,下次我会记住这一点。我已经解决了你刚才提到的所有这些问题。
【解决方案2】:

您使用的编译器可能不允许静态分配大块数据,例如

int primes[10000000];

超过 2^25 个字节。这么大的块可能会超出编译器的能力或其在 SPOJ 上的运行时间。 newmalloc() 这么大的块可能是可能的,但这种解决方法可能会让你走入死胡同。

另一个问题是您从输入中读取mn,而没有验证它们是否在安全范围内。 SPOJ 上的至少一个测试用例将比您的代码限制高出两个数量级,因为您的分配是 10^7,但 SPOJ 限制是 10^9。这意味着崩溃是不可避免的。

您不需要完整的 32 位整数来保存布尔值;您可以使用bool,从而将内存需求减少到四分之一。或者您可以将每个字节大小的数组单元视为具有 8 位的打包位图,与现在相比将内存使用减少到 1/32。而且由于您使用的是 C++,所以一切都已经以std::vector&lt;bool&gt; 的形式为您整齐地打包(在引擎盖下进行了一些打包)。

注意:数组的大小必须是 PRIME1 限制为 1,000,000,000 的所有数字的 100 倍。尽管可以筛选出该范围内的所有数字(时间限制非常大——大约是此任务所需的 10000 倍),但对于完全不熟悉编程的人来说,这可能并不容易。

但是,该任务并不要求筛选十亿个数字。它只要求筛选一小部分范围,每个范围不超过 100001 个数字。即使是简单的、未优化的代码也可以在一毫秒内完成,即使 std::vector&lt;bool&gt; 比任何合理的数据结构都慢一个数量级。

要注意的关键字是“埃拉托色尼的窗口筛”。代码审查中涉及 PRIME1 的主题有数百个。看看吧。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-13
    • 1970-01-01
    • 2014-02-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多