如何使用埃拉托斯特内斯筛算法改进素数程序

How can I improve my prime number program with Sieve of Eratosthenes algorithm?

本文关键字:程序 算法 埃拉 何使用 托斯特      更新时间:2023-10-16

我的程序打印来自以下表达式的所有素数:

((1 + sin(0.1*i))*k) + 1, i = 1, 2, ..., N.

输入格式:

不超过100个示例。每个例子在同一行上都有两个正整数。

输出格式:

将每个数字单独打印一行。

样本输入:

4 10

500 100

样本输出:

5

17

但是我的算法不够有效。我如何添加埃拉托斯尼筛,使其足够有效而不打印";由于超时而终止";。

#include <iostream>
#include <cmath>
using namespace std;
int main() {
long long k, n;
int j;
while (cin >> k >> n) {
if (n>1000 && k>1000000000000000000) continue;
int count = 0;
for (int i = 1; i <= n; i++) {
int res = ((1 + sin(0.1*i)) * k) + 1;
for (j = 2; j < res; j++) {
if (res % j == 0) break;
}
if (j == res) count++;
}
cout << count << endl;
}
system("pause");

您只需在试用部门做得更好,就可以将速度提高10倍。您正在测试从2到res的所有整数,而不是将2视为特例,只测试从3到res的平方根的奇数:

// k <= 10^3, n <= 10^9
int main() {
unsigned k;
unsigned long long n;
while (cin >> k >> n) {
unsigned count = 0;
for (unsigned long long i = 1; i <= n; i++) {
unsigned long long j, res = (1 + sin(0.1 * i)) * k + 1;
bool is_prime = true;
if (res <= 2 || res % 2 == 0) {
is_prime = (res == 2);
} else {
for (j = 3; j * j <= res; j += 2) {
if (res % j == 0) {
is_prime = false;
break;
}
}
}
if (is_prime) {
count++;
}
}
cout << count << endl;
}
}

尽管k=500和n=500000000仍然需要40秒左右的时间

EDIT:我添加了第三个平均值来提高效率
EDIT2:添加了为什么Sieve不应该是解决方案的解释以及一些三角关系。此外,我添加了一个关于问题历史的注释

你的问题不是计算给定范围内的所有素数,而是只计算函数生成的素数。

因此,我不认为Eratosthenes筛是这个特定练习的解决方案,原因如下:n总是很小,而k可能很大。如果k非常大,那么Sieve算法将不得不生成大量素数,以便最终将其用于少量候选。

你可以通过三种方式提高程序的效率:

  • 避免每次计算sin(.)。例如,可以使用三角关系。此外,第一次计算这些值时,将它们存储在数组中并重用这些值。sin()的计算非常耗时
  • 在检查数字是否素数的测试中,将搜索限制为sqrt(res)。此外,考虑仅用奇数j加上2进行测试
  • 如果候选res与前一个相同,则避免重新进行测试

一些三角法
如果c=cos(0.1)和s=sin(0.1),则可以使用以下关系式:

  • sin (0.1(i+1)) = s*cos (0.1*i) + c*sin(0.1*i))
  • cos (0.1(i+1)) = c*cos (0.1*i) - s*sin(0.1*i))

如果n较大,则应定期通过函数重新计算sin(),以避免过多的舍入误差计算。但这里不应该是这种情况,因为n总是很小。

然而,正如我所提到的,最好在第一步只使用"记忆"技巧,并检查它是否足够。

关于这个问题的历史以及为什么这个答案的说明:

最近,这个网站收到了几个问题"如何改进我的程序,计算这个k*sin()函数生成的素数的数量……"据我所知,这些问题都是重复的,因为筛子是解决方案,并且在之前的一个类似(但略有不同)的问题中进行了解释。现在,同样的问题以稍微不同的形式再次出现:"我如何在这个程序中插入Sieve算法…(再次使用k*sin())"。然后我意识到筛子不是解决方案。这不是对之前结束语的批评,因为我在理解这个问题时犯了同样的错误。然而,我认为是时候提出一个新的解决方案了,即使它与的新问题并不完全匹配

当您使用简单的Wheel因子分解时,您可以获得非常好的代码加速。2阶的轮因子分解利用了这样一个事实,即对于自然的n,所有大于3的素数都可以写成6n+16n+5。这意味着每6个数字只需要进行2次除法运算。或者更进一步,所有大于5的素数都可以写成30n+m,其中m{1,7,11,13,17,19,23,29}。(每30个数字8个分区)。

使用这个简单的原理,你可以编写以下函数来测试你的素数(轮子{2,3}):

bool isPrime(long long num) {
if (num == 1)     return false;   // 1 is not prime
if (num  < 4)     return true;    // 2 and 3 are prime
if (num % 2 == 0) return false;   // divisible by 2
if (num % 3 == 0) return false;   // divisible by 3
int w = 5;
while (w*w <= num) {
if(num % w     == 0) return false; // not prime
if(num % (w+2) == 0) return false; // not prime
w += 6;
}
return true;                     // must be prime
}

您可以对车轮{2,3,5}进行上述调整。此功能可在主程序中用作:

int main() {
long long k, n;
while (cin >> k >> n) {
if (n>1000 && k>1000000000000000000) continue;
int count = 0;
for (int i = 1; i <= n; i++) {
long long res = ((1 + sin(0.1*i)) * k) + 1;
if (isPrime(res)) { count++; }
}
cout << count << endl;
}
return 0;
}

一个简单的定时给了我原始代码(g++ prime.cpp)

% time echo "6000 100000000" | ./a.out 
12999811
echo "6000 100000000"  0.00s user 0.00s system 48% cpu 0.002 total
./a.out  209.66s user 0.00s system 99% cpu 3:29.70 total

而优化版本给了我

% time echo "6000 100000000" | ./a.out                                                                                                                                                                                                        
12999811
echo "6000 100000000"  0.00s user 0.00s system 51% cpu 0.002 total
./a.out  10.12s user 0.00s system 99% cpu 10.124 total

可以进行其他改进,但可能影响较小:

  1. i从0到1000预计算正弦表sin(0.1*i)。这将避免重复计算这些正弦。然而,这只会产生较小的影响,因为大部分时间都浪费在了原始测试上
  2. 检查res(i) == res(i+1):这几乎没有任何影响,因为根据nk,大多数连续的res不相等
  3. 使用查找表,可能更方便,这确实会产生影响

原始答案:

我的建议如下:

  1. i从0到1000预先计算可供使用的sin(0.1*i)。这将避免重复计算这些正弦。此外,要明智行事(见第3点)
  2. 找到res的最大可能值,即res_max=(2*k)+1
  3. 用Eratosthenes筛求出res_max的所有素数。此外,要意识到,对于自然n,所有大于3的素数都可以写成6n+16n+5。或者更进一步,所有大于5的素数都可以写成30n+m,其中m{1,7,11,13,17,19,23,29}。这就是所谓的车轮分解。所以,不要麻烦检查任何其他号码。(此处提供更多信息)

  4. 有一个查找表,说明一个数字是否是素数。

  5. 在查找表上执行所有循环操作