【问题标题】:Is this prime generator inefficient C++?这个素数生成器是低效的 C++ 吗?
【发布时间】:2010-09-18 21:14:11
【问题描述】:

这是否被视为高效的素数生成器。在我看来,这非常有效。是不是因为流的使用让程序运行变慢了?

我正在尝试将此提交给SPOJ,它告诉我超出了我的时间限制...

#include <iostream>
#include <sstream>

using namespace std;

int main() {
    int testCases, first, second, counter = 0;
    bool isPrime = true;
    stringstream out;

    cin >> testCases;

    for (int i = 0; i < testCases; i++) {
        // get the next two numbers
        cin >> first >> second;

        if (first%2 == 0)
            first++;

        // find the prime numbers between the two given numbers
        for (int j = first; j <= second; j+=2) {
            // go through and check if j is prime
            for (int k = 2; k < j; k++) {
                if (j%k == 0) {
                    isPrime = false;
                    break;
                }
            }
            if (isPrime) {
                out << j << "\n";
            }
            isPrime = true;
        }
        out << "\n";
    }

    cout << out.str();

    return 0;
}

编辑:该程序应该在输入中指定的数字之间生成素数。 (更多详情请看这里:Prime Generator Problem

-托梅克

【问题讨论】:

    标签: c++ performance primes


    【解决方案1】:

    这是朴素算法之上的一步(跳过偶数)。我建议Sieve Of Eratosthenes 作为一种更有效的算法。从上面的链接:

    算法的复杂度是 O((nlogn)(loglogn)) 有内存 O(n) 的要求。分段的 埃拉托色尼筛的版本, 带有基本的优化,例如轮子 分解,使用 O(n) 操作 和 O(n1 / 2loglogn / logn) 位 记忆。

    您给出的算法接近 O(n^2)。通过跳过偶数获得的加速并不是那么好,因为您会在第一次测试中发现偶数不是素数。筛子的内存需求要大得多,但对于大型 N,运行时复杂度要高得多。

    【讨论】:

    • 然而,这在可用内存中可能是不可能的;设置一个大小为 INTMAX 的数组,然后取消未使用的项目会占用大量 RAM;当然可以用链表来完成,但这也很慢
    • 您实际上只需要每个数字一位来实现该算法。
    • 出于 SPOJ 的目的,我怀疑他们是否会将 second-first 做得如此之大以至于内存会成为问题,如果他们这样做了,他们可能会对运行时非常宽容。一般来说,SPOJ 会强制您选择一种运行时高效(如果不是内存高效)的算法来及时完成。
    【解决方案2】:

    您搜索的数字比您需要的多很多 - 最多只需要转到&lt;= (sqrt(num))

    【讨论】:

    • 这是真的吗?如果我需要找到 1 和 9 之间的素数,如果我只测试 3,我将如何找到 5 和 7?你在考虑分解吗?
    • 他的意思是在循环中 2
    • 知道了。这很尴尬;-)
    【解决方案3】:

    它可以稍微提高效率。您不需要从 2 开始 k,您已经确保不要测试偶数。所以从 3 点开始 k。
    然后每次将 k 增加 2,因为您不需要测试其他偶数。 我能想到的最有效的方法是只测试一个数字是否可以被已知的素数整除(然后当你找到另一个时,将它添加到你测试的列表中)。

    【讨论】:

    • 同理,只有6k+1和6k+5可以是素数。
    【解决方案4】:
    for (int k = 2; k < j; k++) {
         if (j%k == 0) {
             isPrime = false;
             break;
         }
    }
    

    应该是:

    for(int k = 3; k <= j/2; k+=2 )
    {
      if( j % k == 0 )
          break;
    }
    

    j/2 确实应该是 sqrt(j) 但它通常是一个足够好的估计。

    【讨论】:

    • sqrt(j) 比 j/2 好得多,即使 j 为 100。
    【解决方案5】:

    这是一个简单的埃拉托色尼筛法。它不需要预先声明一个大的布尔数组,但它在时间和空间上仍然是 >>O(n)。不过,只要你有足够的内存,它应该比你目前的幼稚方法要快得多。

    #include <iostream>
    #include <map>
    
    using namespace std;
    
    template<typename T = int, typename M = map<T, T> >
    class prime_iterator {
        public:
            prime_iterator() : current(2), skips() { skips[4] = 2; }
            T operator*() { return current; }
            prime_iterator &operator++() {
                typename M::iterator i;
                while ((i = skips.find(++current)) != skips.end()) {
                    T skip = i->second, next = current + skip;
                    skips.erase(i);
                    for (typename M::iterator j = skips.find(next);
                            j != skips.end(); j = skips.find(next += skip)) {}
                    skips[next] = skip;
                }
                skips[current * current] = current;
                return *this;
            }
        private:
            T current;
            M skips;
    };
    
    int main() {
        prime_iterator<int> primes;
        for (; *primes < 1000; ++primes)
            cout << *primes << endl;
        return 0;
    }
    

    如果这对您来说仍然太慢,您可能需要使用 Sieve of Atkin,这是一种优化的埃拉托色尼筛。

    实际上,只有在生成的素数范围较低时,这些方法才相对有效。如果下限已经相当大,而上限又比下限大不了多少,那么筛分方法是浪费工作,你最好运行primality test

    【讨论】:

    • +1 非常好!但是......当看到素数时,您将一个素数平方的跳跃值添加到地图中。在看到与该平方相等的候选人之前,该信息将保持不需要。 IOW 你只需要在地图内有低于当前候选人 sqrt 的素数的跳过。这将使它成为〜O(sqrt(N))空间。另一件事是,current*current 会导致任何高于 46340 的素数出现错误;因此,您的代码 counts 664246 primes 低于 10 百万,而不是 the correct 664579 primes
    【解决方案6】:

    还有一件事,不要在循环中使用 sqrt(n):

    for(int k=1;k<sqrt(n);++k)
    

    如果没有很好的优化,每次迭代都会计算sqrt。

    使用

    for (int k=1;k*k < n;++k)
    

    或者干脆

    int sq = sqrt ( n );
    for (int k=1;k<sq;++k)
    

    【讨论】:

      猜你喜欢
      • 2013-05-27
      • 2012-03-06
      • 2011-11-22
      • 2012-01-15
      • 2019-09-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-04
      相关资源
      最近更新 更多