前段时间我在python中使用了(超快速)primesieve,我在这里找到:Fastest way to list all primes below N
确切地说,这个实现:
def primes2(n):
""" Input n>=6, Returns a list of primes, 2 <= p < n """
n, correction = n-n%6+6, 2-(n%6>1)
sieve = [True] * (n/3)
for i in xrange(1,int(n**0.5)/3+1):
if sieve[i]:
k=3*i+1|1
sieve[ k*k/3 ::2*k] = [False] * ((n/6-k*k/6-1)/k+1)
sieve[k*(k-2*(i&1)+4)/3::2*k] = [False] * ((n/6-k*(k-2*(i&1)+4)/6-1)/k+1)
return [2,3] + [3*i+1|1 for i in xrange(1,n/3-correction) if sieve[i]]
现在我可以通过自动跳过2,3的倍数来略微掌握优化的想法,但是当把这个算法移植到C ++时,我会陷入困境(我对python有一个很好的理解和合理的/对C ++的理解不够,但对摇滚乐来说已经足够了。
我目前所做的就是这个(isqrt()
只是一个简单的整数平方根函数):
template <class T>
void primesbelow(T N, std::vector<T> &primes) {
T sievemax = (N-3 + (1-(N % 2))) / 2;
T i;
T sievemaxroot = isqrt(sievemax) + 1;
boost::dynamic_bitset<> sieve(sievemax);
sieve.set();
primes.push_back(2);
for (i = 0; i <= sievemaxroot; i++) {
if (sieve[i]) {
primes.push_back(2*i+3);
for (T j = 3*i+3; j <= sievemax; j += 2*i+3) sieve[j] = 0; // filter multiples
}
}
for (; i <= sievemax; i++) {
if (sieve[i]) primes.push_back(2*i+3);
}
}
这个实现很不错,并自动跳过2的倍数,但如果我可以移植Python实现,我认为它可以更快(50%-30%左右)。
为了比较结果(希望能成功回答这个问题),Q6600 Ubuntu 10.10上N=100000000
,g++ -O3
的当前执行时间为1230毫秒。
现在,我希望能够帮助理解上面的Python实现或者为我移植它(尽管不是很有用)。
修改
关于我发现困难的一些额外信息。
我在使用像校正变量这样的技术时遇到了麻烦,总的来说它是如何结合在一起的。链接到一个网站,解释不同的Eratosthenes优化(除了简单的网站,说“你只是跳过2,3和5的倍数”,然后用1000行C文件猛烈抨击)将是很棒的。
我认为我不会遇到100%直接和字面端口的问题,但毕竟这是为了学习而完全没用。
修改
在查看原始numpy版本中的代码之后,它实际上很容易实现,并且有些想法不太难理解。这是我提出的C ++版本。我在这里发布它的完整版本,以帮助更多的读者,以防他们需要一个非常有效的primesieve不是200万行代码。在与上述相同的机器上,该质量筛在约415毫秒内完成所有质量低于100000000的质数。这是一个3倍的加速,比我预期的更好!
#include <vector>
#include <boost/dynamic_bitset.hpp>
// http://vault.embedded.com/98/9802fe2.htm - integer square root
unsigned short isqrt(unsigned long a) {
unsigned long rem = 0;
unsigned long root = 0;
for (short i = 0; i < 16; i++) {
root <<= 1;
rem = ((rem << 2) + (a >> 30));
a <<= 2;
root++;
if (root <= rem) {
rem -= root;
root++;
} else root--;
}
return static_cast<unsigned short> (root >> 1);
}
// https://stackoverflow.com/questions/2068372/fastest-way-to-list-all-primes-below-n-in-python/3035188#3035188
// https://stackoverflow.com/questions/5293238/porting-optimized-sieve-of-eratosthenes-from-python-to-c/5293492
template <class T>
void primesbelow(T N, std::vector<T> &primes) {
T i, j, k, l, sievemax, sievemaxroot;
sievemax = N/3;
if ((N % 6) == 2) sievemax++;
sievemaxroot = isqrt(N)/3;
boost::dynamic_bitset<> sieve(sievemax);
sieve.set();
primes.push_back(2);
primes.push_back(3);
for (i = 1; i <= sievemaxroot; i++) {
if (sieve[i]) {
k = (3*i + 1) | 1;
l = (4*k-2*k*(i&1)) / 3;
for (j = k*k/3; j < sievemax; j += 2*k) {
sieve[j] = 0;
sieve[j+l] = 0;
}
primes.push_back(k);
}
}
for (i = sievemaxroot + 1; i < sievemax; i++) {
if (sieve[i]) primes.push_back((3*i+1)|1);
}
}
答案 0 :(得分:3)
我会尽力解释。 sieve
数组有一个不寻常的索引方案;它为每个与1或5 mod 6一致的数字存储一个位。因此,数字6*k + 1
将存储在位置2*k
中,k*6 + 5
将存储在位置{{1 }}。 2*k + 1
操作与此相反:它采用3*i+1|1
形式的数字并将其转换为2*n
,并将6*n + 1
转换为2*n + 1
(6*n + 5
将+1|1
转换为0
,将1
转换为3
)。主循环使用该属性迭代5
所有数字,从k
开始(当5
为1时); i
是数字i
的{{1}}的相应索引。对sieve
的第一个切片更新然后使用k
形式的索引清除筛子中的所有位(对于sieve
自然数);这些索引的相应数字从k*k/3 + 2*m*k
开始,每步增加m
。第二个切片更新从索引k^2
开始(6*k
的数字为k*(k-2*(i&1)+4)/3
,与k * (k+4)
mod k
一致,否则为1
,同样会增加每一步都按6
编号。
以下是另一种解释尝试:让k * (k+2)
成为至少为5且与6*k
或candidates
mod 1
一致的所有数字的集合。如果将该集合中的两个元素相乘,则会得到该集合中的另一个元素。让5
中的6
succ(k)
成为k
中candidates
的大于candidates
的下一个元素(按数字顺序排列)。在这种情况下,筛子的内环基本上(使用k
的正常索引):
sieve
由于for k in candidates:
for (l = k; ; l += 6) sieve[k * l] = False
for (l = succ(k); ; l += 6) sieve[k * l] = False
中存储元素的限制,因此与:
sieve
会在某个时刻从筛子中移除for k in candidates:
for l in candidates where l >= k:
sieve[k * l] = False
中除k
以外的candidates
的所有倍数(当前k
用作{ {1}}更早或当它现在用作k
时。
答案 1 :(得分:1)
Piggy-Backing对Howard Hinnant的反应,霍华德,你不必测试所有自然数的集合中的数字,这些数字本身不能被2,3或5整除。您只需要将数组中的每个数字(除了1,自我消除)乘以自身和数组中的每个后续数字。这些重叠的产品将为您提供阵列中的所有非素数,直到您扩展确定性乘法过程的任何点。因此,数组中的第一个非素数将是7平方,或49.第2,7次11或77等。这里有一个完整的解释:http://www.primesdemystified.com
答案 2 :(得分:0)
顺便说一句,你可以“近似”素数。调用近似素数P.以下是几个公式:
P = 2 * k + 1 //不能被2整除
P = 6 * k + {1,5} //不能整除2,3
P = 30 * k + {1,7,11,13,17,19,23,29} //不能分解为2,3,5
这些公式找到的数字集的属性是P可能不是素数,但是所有素数都在集合P中。如果你只测试集P中的数字为素数,你就不会错过任何一个。
您可以将这些公式重新制定为:
P = X * k + { - i,-j,-k,k,j,i}
如果这对你来说更方便。
Here是一些使用此技术的代码,其中P的公式不能被2,3,5,7整除。
此链接可能代表实际利用此技术的程度。