从 3 到 n 的 C++ 素数

Posted

技术标签:

【中文标题】从 3 到 n 的 C++ 素数【英文标题】:C++ Prime Numbers from 3 to n 【发布时间】:2017-10-18 07:09:58 【问题描述】:

我编写了这段代码来打印用户输入的所有介于 3 和 'n' 之间的素数,但在运行时,它什么也没产生。

你能帮忙吗?

#include <iostream>
using namespace std;

int main()

    int n;
    cin >> n;

    for (int candidate = 3; candidate < n; ++candidate)
    
        bool isPrime = true;
        for (int x = 2; x < n; x++)
        
            if (candidate % x == 0)
            
                isPrime = false;
            
        

        if (isPrime)
        
            cout << n << "";
        
    

【问题讨论】:

你试过调试了吗? 你不应该打印出candidate而不是n吗? for 循环应该是 for x 如前所述为什么不打印出candidate,还有""在打印出n之后的目的是什么,不应该是换行还是其他空格? 顺便说一句:你不需要检查偶数。 【参考方案1】:

你应该知道的一件事。如果我们开始通过除法运算检查 n 是否为素数,那么检查的次数不应超过 sqrt(n)

    for (int candidate = 3; candidate < n; ++candidate)
    
        bool isPrime = true;
        for (int x = 2; x*x < candidate; x++)
        
            if (candidate % x == 0)
            
                isPrime = false;
                break;
            
        

        if (isPrime)
        
            cout << candidate << "";
        
    

更好的方法是为此使用Sieve of Eratosthenes:

isPrime: initialize with all true
for(int i=2;i*i<=n;i++)
  for(int j =i*i;j<=n;j+=i)
    isPrime[j]=false;   // the if was not needed.

现在您知道了 3 和 n 之间的质数。

【讨论】:

你不需要在筛算法中使用if:无论如何设置isPrime[j]=false。这大概比分支快。【参考方案2】:

在您的代码中可以节省多种效率。最有效的方法(除了对小型 (n 使用预先计算的查找表)是 sieve of Erastosthenes。

这个算法的一个非常幼稚的版本(另见answer by coderredoc)

std::vector<int> primes(int n)

    std::vector<bool> is_prime(n+1,true);
    for(auto divisor=2; divisor*divisor <= n; ++divisor)
        for(auto candidate=divisor*divisor; candidate <= n; candidate+=divisor)
            is_prime[candidate]=false;
    std::vector<int> result;
    for(auto candidate=2; candidate <= n; ++candidate)
        if(is_prime[candidate]) result.push_back(candidate);
    return result;

与您的原始算法相比,基本上反转了candidates 和divisors 上的循环,但仅测试满足divisor*divisor&lt;=candidatedivisors。

通过意识到我们只需要测试素数除数(这是筛子的主要技巧),可以大幅改进该算法

std::vector<int> primes(int n)

    std::vector<bool> is_prime(n+1,true);
    for(auto divisor=2; divisor*divisor <= n; ++divisor)
        if(is_prime[divisor])
            for(auto candidate=divisor*divisor; candidate <= n; candidate+=divisor)
                is_prime[candidate]=false;
    std::vector<int> result;
    for(auto candidate=2; candidate <= n; ++candidate)
        if(is_prime[candidate]) result.push_back(candidate);
    return result;

这减少了对大型 n 的测试。甚至避免candidates 和divisors 也可以进一步节省效率(空间和时间减少约2 倍):

std::vector<int> primes(int n)

    if(n<2) return ;
    if(n==2) return 2;
    std::vector<bool> is_prime((n+1)>>1,true);
    for(auto divisor=3; divisor*divisor <= n; divisor+=2)
        if(is_prime[divisor>>1])
            for(auto candidate=divisor*divisor; candidate <= n; candidate+=2*divisor)
                is_prime[candidate>>1]=false;
    std::vector<int> result(1,2);
    for(auto candidate=3; candidate <= n; candidate+=2)
        if(is_prime[candidate>>1]) result.push_back(candidate);
    return result;

该算法的内存访问模式很差(进入is_prime[]),这对于非常大的n 来说是个问题。一种更复杂的方法,分段筛,可以避免这种情况,请参见上面的链接。

【讨论】:

仍然 +1 以获得简洁的答案 =) @Sergey.quixoticaxis.Ivanov 实际上,我对此进行了实验并将其替换为std::vector&lt;char&gt;。令我惊讶的是,代码变慢了(Apple clang 编译器,-O3)。 这很奇怪,我不知道 char,但在 Intel i5 6600 和 Intel i7 4790k 上,将 ::std::vector&lt;bool&gt; 更改为 bool*(我不会“push_back”任何东西并且预先只为is_prime 分配缓冲区一次)对 msvc 和 Intel C++ 17 都有很大的影响。 *效果(对不起^-^) 我也尝试过(虽然使用std::unique_ptr&lt;bool[]&gt; is_primenew bool[n+1];),然后使用true进行初始化。我想,后者效率低下(最好一次初始化 8 个字节,这大概是 std::vector&lt;bool&gt; 所做的)。但是初始化不应该主导成本。无论如何,这也比 std::vector&lt;bool&gt; 慢,也许 std::vector&lt;bool&gt; 的 msvc 或 Intel (icpc) 实现较差?【参考方案3】:

改变你的内部循环

    for (int x = 2; x < n; x++)
    
        if (candidate % x == 0)
        
            isPrime = false;
        
    

    for (int x = 2; x < candidate; x++)
    
        if (candidate % x == 0)
        
            isPrime = false;
            break;
        
    

否则x 最终将成为candidate 本身,而candidate%candidate0,这将导致isPrime 成为false

使用break 语句是因为在确定数字不是素数之后,就不需要进一步的迭代了。

由于您只考虑来自 3 的数字,您可以更改外部循环以节省一些迭代,例如

for (int candidate = 3; candidate < n; candidate+=2)

这将使candidate 每次增加2。这没关系,因为没有大于 2 的偶数不是素数。

另外,如果您考虑的数字范围包括n,您可以将外部for循环修改为

for (int candidate = 3; candidate < n; candidate+=2)

也可以考虑n

【讨论】:

这是错误的。它只会打印一个素数。他想把它们都打印出来。 @MartinBonner 哎呀!我没有意识到。已编辑。感谢您指出。 我只会删除旧的错误答案,而不是保留它。 删除旧部分【参考方案4】:

    不建议使用 using namespace std; — 因为这会从 std.xml 导入所有标识符。请在 Stack Overflow 上查看此问题。

    第二个循环的结束条件应该是candidate而不是n,即

    for (int x = 2; x < candidate; x++)//not before N prime numbers divine up to them
    
        if (candidate % x == 0)
            isPrime = false;
    
    

    你不应该发出candidate而不是n

【讨论】:

【参考方案5】:

我认为这应该可行

#include <iostream>
#include <vector>


int main()

    //setup start parameters
    //test sequence
    std::vector<int> test_sequence 2;




    int end_number, start_number;
    start_number = 3;
    //take wished end number
    std::cin >> end_number;

    // test is wished number smaler than start number
    if (start_number < end_number)
    
        // loop which goes trough every number between start and end number
        for (start_number; start_number <= end_number; start_number++)
        
            bool isPrime = true;
            //test is this number prime
            for (int n = 0; n < test_sequence.size(); n++)
            
                if (start_number % test_sequence[n] == 0 && start_number != test_sequence[n] )
                
                isPrime = false;
                
            

            if (isPrime)
            
                std::cout << start_number << std::endl;
                //hold result for next test
                test_sequence.push_back(start_number);
            
        

    
    else
    
    std::cout << "Wrong input";
    


前 200 名的结果

【讨论】:

执行时间 10.115 s 这太可怕了。对于高达 200 的素数,一个好的算法需要大约 4μs(任何现代 CPU 的时间),比它短 1000 万倍。 1000 万个第一个素数可以用简单的算法在 3-4 秒内计算出来,写成一个简短的练习 不是计算结果的时间,我只是程序启动时没有立即输入结束号。

以上是关于从 3 到 n 的 C++ 素数的主要内容,如果未能解决你的问题,请参考以下文章

使用集合计算素数,C++

C++判断素数的代码

用C++求50~100内的素数,具体问题如下

用C++编写程序求100以内的所有素数(质素)的和。

筛选法求n以内所有的素数

求前n个素数(C++)