用C++实现的增强Eratosthenes筛法程序
运行示例
PS H:\Read\num\x64\Release> .\eSievePro
Eratosthenes sieve: a method to find out all primes below the number that you specify here please: 100
Only the sum of all primes needed [y/n](y as default): n
Start to work out all primes below 100...
25 primes found in 0 milliseconds.
2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97
PS H:\Read\num\x64\Release> .\eSievePro
Eratosthenes sieve: a method to find out all primes below the number that you specify here please: 12345678
Only the sum of all primes needed [y/n](y as default):
Start to work out the sum of all primes below 12345678...
809227 primes found in 63 milliseconds.
PS H:\Read\num\x64\Release> .\eSievePro
Eratosthenes sieve: a method to find out all primes below the number that you specify here please: 123456789
Only the sum of all primes needed [y/n](y as default):
Start to work out the sum of all primes below 123456789...
7027260 primes found in 1031 milliseconds.
PS H:\Read\num\x64\Release> .\eSievePro
Eratosthenes sieve: a method to find out all primes below the number that you specify here please: 1234567890
Only the sum of all primes needed [y/n](y as default):
Start to work out the sum of all primes below 1234567890...
62106578 primes found in 12375 milliseconds.
PS H:\Read\num\x64\Release> .\eSievePro
Eratosthenes sieve: a method to find out all primes below the number that you specify here please: 1234567
Only the sum of all primes needed [y/n](y as default):
Start to work out the sum of all primes below 1234567...
95360 primes found in 0 milliseconds.
PS H:\Read\num\x64\Release>
和用C++实现的Eratosthenes筛法程序对比,性能有显著提升。
增强Eratosthenes筛法的C++程序实现
为说明方便,把用C++实现的Eratosthenes筛法程序里的实现称为eSieve,而把这里的新实现称为eSievePro。
宏定义和全局量定义
1 typedef unsigned char u8; 2 typedef unsigned long ulong; 3 static ulong s_last = 0; 4 static u8* s_pOdd = NULL; 5 static std::vector<ulong> s_vecPrime;
对照eSieve的相应部分,这里只是把之前的s_pAll换成了s_pOdd。
main函数实现
1 int main() 2 { 3 printf(" Eratosthenes sieve: a method to find out all primes below the number that you specify here please: "); 4 std::string strInput; 5 getline(std::cin, strInput); 6 ulong raw_last = strtoul(strInput.c_str(), 0, 10); 7 if (raw_last <= 2) { 8 printf("\n Wrong input.\n"); 9 return 0; 10 } 11 printf("\n Only the sum of all primes needed [y/n](y as default): "); 12 getline(std::cin, strInput); 13 bool bDetail = (strInput == "n"); 14 if (bDetail) 15 printf("\n Start to work out all primes below %u...\n", raw_last); 16 else 17 printf("\n Start to work out the sum of all primes below %u...\n", raw_last); 18 if (!eSievePro(raw_last, bDetail)) 19 return 0; 20 if (bDetail) 21 showDetails(); 22 return 0; 23 }
这里的main函数实现保留了交互输入的实现代码,而把增强的Eratosthenes筛法实现放到了单独的eSievePro函数里。
eSievePro函数实现
1 bool eSievePro(ulong raw_last, bool bDetail) 2 { 3 DWORD tickBegin = GetTickCount(); 4 s_last = raw_last / 2; 5 s_pOdd = new u8[s_last]; 6 if (!s_pOdd) { 7 printf("Lack of memory.\n"); 8 return false; 9 } 10 ulong sum = 1; 11 ulong curPrime = 2; 12 if (bDetail) 13 s_vecPrime.push_back(curPrime); 14 if (raw_last == 3) 15 goto Ending; 16 17 memset(s_pOdd, 1, s_last); 18 ulong curPrimeIdx = 0; 19 while (true) { 20 renewCurrentPrime(curPrimeIdx); 21 ++sum; 22 curPrime = (curPrimeIdx + curPrimeIdx) + 1; 23 if (bDetail) 24 s_vecPrime.push_back(curPrime); 25 s_pOdd[curPrimeIdx - 1] = 0; 26 ulong sqr = curPrime * curPrime; 27 if (sqr > raw_last) 28 break; 29 ulong step = curPrime + curPrime; 30 for (ulong idx = sqr; idx < raw_last; idx += step) { 31 s_pOdd[(idx - 1) / 2 - 1] = 0; 32 } 33 } 34 /// pick up all the left primes 35 for (ulong idx = curPrime + 2; idx < raw_last; idx += 2) { 36 ulong halfIdx = (idx - 1) / 2 - 1; 37 if (s_pOdd[halfIdx] == 1) { 38 ++sum; 39 if (bDetail) 40 s_vecPrime.push_back(idx); 41 } 42 } 43 44 Ending: 45 printf(" %u primes found in %u milliseconds.\n\n", sum, GetTickCount() - tickBegin); 46 delete []s_pOdd; 47 return true; 48 }
由
4 s_last = raw_last / 2;
5 s_pOdd = new u8[s_last];
这两行代码可以看出,s_pOdd的动态申请的空间是原来的一半,就是说原来的动态数组s_pAll记录的是1、2、3、……、upperLimit(upperLimit指代交互输入的上界值)所有自然数的素合状态(1表示素数,0表示合数);而这里的动态数组s_pOdd只用一半的空间来记录1到upperLimit之内的所有奇数的素合状态。这样处理不但节省了存储空间,而且直接省去了Eratosthenes筛法中用2筛去所有偶数的处理过程。
如下这几行筛法实现代码
26 ulong sqr = curPrime * curPrime;
29 ulong step = curPrime + curPrime; 30 for (ulong idx = sqr; idx < raw_last; idx += step) { 31 s_pOdd[(idx - 1) / 2 - 1] = 0; 32 }
相比之前的实现代码
36 for (int idx = curPrime - 1; idx <= s_last - 1; idx += curPrime) { 37 s_pAll[idx] = 0; 38 }
有两个改进之处。用一个实例说明一下:比如用Eratosthenes筛法筛去1到1000之内的合数,依次筛去2的倍数、3的倍数、5的倍数、7的倍数,当开始筛去11的倍数时,会依次筛去22、33、44、55、66、77、88、99、110、121、132、143、……,但是22、33、44、55、66、77、88、99、110这些数因为小于11*11,必然有小于11的素因素,说明这些数必然会在此前筛去2、3、5、7等素数的倍数的过程中被筛掉,因此当筛去一个素数的倍数时,从该素数的平方为起点开始筛会减少重复,在上面的代码对比上用标黄部分表示。
另一个改进之处,在上面的代码对比上用标蓝部分表示。仍沿用刚才的实例说明,当从121开始筛去11的倍数时,会依次筛去121、132、143、154、165、176、187、……,从这个列表可以看出奇数偶数交替出现,这也很好解释,因为筛法当前用到的素数(curPrime)是一个奇数,设为2n+1,它的平方也是奇数(即4nn+4n+1),再加上2n+1就会变成偶数(即4nn+6n+2),再加上2n+1又会变成奇数(4nn+8n+3),……。因为这些偶数在最早筛去2的倍数时就已经被筛去了,而且2之后的素数都是奇数,所以可以把2之后的筛法步长调整为当时使用素数的两倍,这样性能可以提升一倍。
当然,上述两个改进之处并不能彻底避免多次重复筛去一个合数的问题。比如,刚才的实例中使用步长22会依次筛去121、143、165、187,但是里面的165=11*5*3,在之前已经被3筛去过一次,又被5筛去过一次。
renewCurrentPrime和showDetails
1 bool renewCurrentPrime(ulong& primeIdx) 2 { 3 while (primeIdx < s_last) { 4 ++primeIdx; 5 if (s_pOdd[primeIdx - 1] == 1) 6 return true; 7 } 8 return false; 9 }
这里的renewCurrentPrime和eSieve实现的renewCurrentPrime几乎一样,只是因为s_last和输入参数的含义有变化以及s_pOdd与此前的的s_pAll的不同含义而有些微不同。
showDetails的实现则完全没有变动,这里不再重复放了。
其他
https://github.com/readalps/eSievePro上放了eSievePro实现的源码文件,以及一个运行结果文件。