繁体   English   中英

我的Eratosthenes筛子花了太长时间

[英]My Sieve of Eratosthenes takes too long

我已经实现了Eratosthenes 筛网以解决SPOJ问题PRIME1 尽管输出很好,但是我的提交超出了时间限制。 如何减少运行时间?

int main()
{
  vector<int> prime_list;
  prime_list.push_back(2);
  vector<int>::iterator c;
  bool flag=true;
  unsigned int m,n;
  for(int i=3; i<=32000;i+=2)
  {
    flag=true;
    float s = sqrt(static_cast<float>(i));
    for(c=prime_list.begin();c<=prime_list.end();c++)
    {
        if(*c>s)
            break;
        if(i%(*c)==0)
        {
            flag=false;
            break;
        }
    }
    if(flag==true)
    {
        prime_list.push_back(i);
    }
  }
  int t;
  cin>>t;
  for (int times = 0; times < t; times++)
  {
    cin>> m >> n;
    if (t) cout << endl;
    if (m < 2)
        m=2;
    unsigned int j;
    vector<unsigned int> req_list;
    for(j=m;j<=n;j++)
    {
        req_list.push_back(j);
    }
    vector<unsigned int>::iterator k;
    flag=true;
    int p=0;
    for(j=m;j<=n;j++)
    {
        flag=true;
        float s = sqrt(static_cast<float>(j));
        for(c=prime_list.begin();c<=prime_list.end();c++)
        {
            if((*c)!=j)
            {
                if((*c)>s)
                    break;
                if(j%(*c)==0)
                {
                    flag=false;
                    break;
                }
            }
        }
        if(flag==false)
        {
            req_list.erase (req_list.begin()+p);
            p--;
        }
        p++;
    }
    for(k=req_list.begin();k<req_list.end();k++)
    {
        cout<<*k;
        cout<<endl;
    }
  }
}

您的代码很慢,因为您没有实现Eratosthenes的Sieve算法。 该算法以这种方式工作:

1) Create an array with size n-1, representing the numbers 2 to n, filling it with boolean values true (true means that the number is prime; do not forget we start counting from number 2 i.e. array[0] is the number 2)
2) Initialize array[0] = false.
3) Current_number = 2;
3) Iterate through the array by increasing the index by Current_number.
4) Search for the first number (except index 0) with true value.
5) Current_number = index + 2;
6) Continue steps 3-5 until search is finished.

该算法花费O(nloglogn)时间。 实际上,您执行的操作会花费更多时间(O(n ^ 2))。 顺便说一句,在第二步中(您搜索n和m之间的质数),您不必检查这些数是否再次为质数,理想情况下,您将在算法的第一阶段对其进行计算。

正如我在该站点中看到的那样,您链接的主要问题是您实际上无法创建大小为n-1的数组,因为最大数量n为10 ^ 9,如果您以这种幼稚的方式进行操作,则会导致内存问题。 这个问题是你的:)

我会扔掉您所拥有的东西,然后从一个非常简单的筛子实施开始,如果确实需要,只会增加更多的复杂性。 这是一个可能的起点:

#include <vector>
#include <iostream>

int main() {
    int number = 32000;
    std::vector<bool> sieve(number,false);
    sieve[0] = true;  // Not used for now, 
    sieve[1] = true;  //   but you'll probably need these later.

    for(int i = 2; i<number; i++) {
        if(!sieve[i]) {
            std::cout << "\t" << i;
            for (int temp = 2*i; temp<number; temp += i)
                sieve[temp] = true;
        }
    }
    return 0;
}

对于给定的范围(最多32000),此操作可以在一秒钟之内很好地运行(输出定向到文件-在屏幕上通常会比较慢)。 不过这取决于您...

我不确定您是否实施了Erasthotenes筛子。 无论如何,可以在某种程度上加快算法速度的几件事是:通过预分配空间(查找std::vector<>::reserve )避免向量内容的多次重分配。 sqrt操作非常昂贵,您可以通过修改测试来完全避免它(当x*x > y而不是检查x < sqrt(y)时停止。

同样,通过修改实际算法,您将获得更好的改进。 从粗略的外观来看,似乎您正在遍历所有候选对象,并且对于每个候选对象,都试图除以可能是因素的所有已知素数。 Erasthotenes的筛网采用一个素数,并在一次通过中丢弃该素数的所有倍数。

请注意,筛子不会执行任何操作来测试数字是否为质数,如果在此之前未将其丢弃则为质数。 对于每个唯一因子,每个非素数仅被访问一次。 另一方面,您的算法正在处理每个数字很多次(相对于现有素数)

我认为稍微加快筛选速度的一种方法是防止在此行中使用mod运算符。

if(i%(*c)==0)

代替(相对)昂贵的mod操作,也许如果您在筛子中向前进行添加迭代。

老实说,我不知道这是否正确。 没有注释并且使用单字母变量名很难阅读您的代码。

我理解问题的方式是必须生成[m,n]范围内的所有素数。

执行此操作而不必从[0,n]计算所有素数的一种方法(因为这很可能使您放慢速度)是首先生成[0,sqrt(n)]范围内的所有素数。

然后使用结果在[m,n]范围内进行筛分。 要生成素数的初始列表,请实现Eratosthenes筛子的基本版本(几乎只有Wikipedia文章中的伪代码的幼稚实现都可以实现)。

这应该使您能够在很短的时间内解决问题。

这是Eratosthenes筛子的简单示例实现:

std::vector<unsigned> sieve( unsigned n ) {
    std::vector<bool> v( limit, true ); //Will be used for testing numbers
    std::vector<unsigned> p;            //Will hold the prime numbers

    for( unsigned i = 2; i < n; ++i ) {
        if( v[i] ) {                    //Found a prime number
            p.push_back(i);             //Stuff it into our list

            for( unsigned j = i + i; j < n; j += i ) {
                v[i] = false;           //Isn't a prime/Is composite
            }
        }
    }

    return p;
}

它返回一个仅包含从0到n的质数的向量。 然后,您可以使用它来实现我提到的方法。 现在,我将不为您提供实现,但是,您基本上必须执行与Eratosthenes筛子相同的操作,但是您不必使用所有整数[2,n],而只需使用找到的结果即可。 不知道这是否给了太多?

由于原始问题中的SPOJ问题并未指定必须使用Eratosthenes筛来解决,因此这是基于本文的替代解决方案。 在我六岁的笔记本电脑上,最坏的单个测试案例(nm = 100,000)可以运行约15 ms。

#include <set>
#include <iostream>

using namespace std;

int gcd(int a, int b) {
   while (true) {
      a = a % b;

      if(a == 0)
         return b;

      b = b % a;

      if(b == 0)
         return a;
   }
}

/**
 * Here is Rowland's formula. We define a(1) = 7, and for n >= 2 we set 
 *
 * a(n) = a(n-1) + gcd(n,a(n-1)). 
 *
 * Here "gcd" means the greatest common divisor. So, for example, we find
 * a(2) = a(1) + gcd(2,7) = 8. The prime generator is then a(n) - a(n-1),
 * the so-called first differences of the original sequence.
 */
void find_primes(int start, int end, set<int>* primes) {
   int an;        // a(n)
   int anm1 = 7;  // a(n-1)
   int diff;

   for (int n = start; n < end; n++) {
      an = anm1 + gcd(n, anm1);

      diff = an - anm1;

      if (diff > 1)
         primes->insert(diff);

      anm1 = an;
   }
}

int main() {
   const int end = 100000;
   const int start = 2;

   set<int> primes;

   find_primes(start, end, &primes);
   ticks = GetTickCount() - ticks;

   cout << "Found " << primes.size() << " primes:" << endl;

   set<int>::iterator iter = primes.begin();
   for (; iter != primes.end(); ++iter)
      cout << *iter << endl;
}

分析您的代码,找到热点,消除它们。 WindowsLinux分析器链接。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM