我的Eratosthenes筛子需要太长时间

时间:2010-10-05 21:44:17

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

我已实施Sieve of 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;
    }
  }
}

7 个答案:

答案 0 :(得分:4)

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

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,如果你用这个天真的话做就会导致内存问题办法。这个问题是你的:)

答案 1 :(得分:3)

我会抛弃你所拥有的东西并重新开始使用一个非常简单的简单的筛子实现,只有在真正需要的时候才会增加更多的复杂性。这是一个可能的起点:

#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),这在一秒钟内运行良好(输出指向文件 - 到屏幕它通常会慢一些)。虽然这取决于你......

答案 2 :(得分:2)

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

然后,通过修改实际算法,您将获得更好的改进。从粗略的看来,似乎你在迭代所有候选人和每一个候选人,试图与所有已知的素数分开,这可能是因素。 Erasthotenes的筛子需要一个素数,并且在一次通过中丢弃所有素数的倍数。

请注意,筛子不会执行任何操作来测试数字是否为素数,如果它没有被丢弃,那么它就是素数。对于每个唯一因子,每个非素数仅被访问一次。另一方面,您的算法多次处理每个数字(与现有的素数相对)

答案 3 :(得分:1)

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

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

而不是(相对)昂贵的mod操作,也许你可以在你的筛子中向前进行迭代。

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

答案 4 :(得分:1)

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

这种方法可以在不必计算[0,n]中的所有素数的情况下完成此操作,因为这很可能会减慢你的速度,首先生成[0,sqrt(n)]范围内的所有素数。

然后使用结果筛选[m,n]范围。要生成素数的初始列表,请实现Eratosthenes筛子的基本版本(在维基百科的文章中,非常简单的伪代码实现就可以了。)

这可以让您在很短的时间内解决问题。

以下是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],你只需使用你找到的结果。不确定这是否过多了?

答案 5 :(得分:-1)

由于原始问题中的 SPOJ problem 未指定必须使用Eratosthenes筛选解决,因此这是基于 {{3}的替代解决方案} 即可。在我六岁的笔记本电脑上,最差的单个测试用例(n-m = 100,000)大约需要15毫秒。

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

答案 6 :(得分:-3)

描述您的代码,找到热点,消除它们。 WindowsLinux个人档案链接。