【问题标题】:C++ Prime Numbers from 3 to n从 3 到 n 的 C++ 素数
【发布时间】: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之后的目的是什么,不应该是换行还是其他空格?
  • 顺便说一句:你不需要检查偶数。

标签: c++ algorithm c++11


【解决方案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_prime{new 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】:
  1. 不建议使用 using namespace std; — 因为这会从 std.xml 导入所有标识符。请在 Stack Overflow 上查看此问题。

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

    for (int x = 2; x < candidate; x++)//not before N prime numbers divine up to them
    {
        if (candidate % x == 0)
            isPrime = false;
    }
    
  3. 你不应该发出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 秒内计算出来,写成一个简短的练习
    • 不是计算结果的时间,我只是程序启动时没有立即输入结束号。
    猜你喜欢
    • 2014-11-30
    • 2021-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-03
    相关资源
    最近更新 更多