试除法质数打表方法的逐步改进--经典面试题
参考博客:点击打开链接
求质数算法的N种境界 - 试除法和初级筛法
http://blog.csdn.net/wdqfzy/article/details/7034784
◇需求1
请实现一个函数,对于给定的整型参数 N,该函数能够把自然数中,小于 N 的质数,从小到大打印出来。
比如,当 N = 10,则打印出
2 3 5 7
★试除法
"试除",顾名思义,就是不断地尝试能否整除。比如要判断自然数 x 是否质数,就不断尝试小于 x 且大于1的自然数,只要有一个能整除,则 x 是合数;否则,x 是质数。
显然,试除法是最容易想到的思路。不客气地说,也是最平庸的思路。不过捏,这个最平庸的思路,居然也有好多种境界。
◇境界1
在试除法中,最最土的做法,就是:
假设要判断 x 是否为质数,就从 2 一直尝试到 x-1。这种做法,其效率应该是最差的。
◇境界2
稍微聪明一点点的程序猿,会想:x 如果有(除了自身以外的)质因数,那肯定会小于等于 x/2,所以捏,他们就从 2 一直尝试到 x/2 即可。
这一下子就少了一半的工作量哦,但依然是很笨的办法。
◇境界3
再稍微聪明一点的程序猿,会想了:除了2以外,所有可能的质因数都是奇数。所以,他们就先尝试 2,然后再尝试从 3 开始一直到 x/2 的所有奇数。
这一下子,工作量又少了一半哦。但是,俺不得不说,依然很土。就算代码完全正确也只能得3分。
◇境界4
比前3种程序猿更聪明的,就会发现:其实只要从 2 一直尝试到√x,就可以了。为什么只要到√x 即可?
简单解释一下:因数都是成对出现的。比如,100的因数有:1和100,2和50,4和25,5和20,10和10。看出来没有?成对的因数,其中一个必然小于等于100的开平方,另一个大于等于100的开平方。
◇境界5
一些更加聪明的程序猿,会发现一个问题:尝试从 3 到√x 的所有奇数,还是有些浪费。比如要判断101是否质数,101的根号取整后是10,那么,按照境界4,需要尝试的奇数分别是:3,5,7,9。但 是你发现没有,对9的尝试是多余的。不能被3整除,必然不能被9整除......顺着这个思路走下去,这些程序猿就会发现:其实,只要尝试小于√x 的质数即可。而这些质数,恰好前面已经算出来了(是不是觉得很妙?)。所以,处于这种境界的程序猿,会把已经算出的质数,先保存起来,然后用于后续的试除,效率就大大提高了。顺便说一下,这就是算法理论中经常提到的:以空间换时间。
#include<iostream>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const long int N=201357;//2013年5月7日
/**=======================================**/
void prime_v1(long int n)
{
long int i,j;
for(i=2;i<n;i++)
{
for(j=2;j<i;j++)
{
if(i%j==0)
{
break;
}
}
if(j>=i)
{
//cout<<setw(9)<<i;
}
}
cout<<endl;
}
void test_prime_v1()
{
long now=clock();
prime_v1(N);
cout<<"prime_v1 running time is: "<<(double)(clock()-now)/CLOCKS_PER_SEC<<endl;
}
/**=======================================**/
void prime_v2(long int n)
{
long int i,j,k;
bool flag=true;
for(i=2;i<n;i++)
{
flag=true;
k=i/2+1;
for(j=2;j<k;j++)
{
if(i%j==0)
{
flag=false;
break;
}
}
if(true==flag)
{
//cout<<setw(9)<<i;
}
}
cout<<endl;
}
void test_prime_v2()
{
long now=clock();
prime_v2(N);
cout<<"prime_v2 running time is: "<<(double)(clock()-now)/CLOCKS_PER_SEC<<endl;
}
/**=======================================**/
void prime_v3(long int n)
{
long int i,j,k;
bool flag=true;
for(i=2;i<n;i++)
{
flag=true;
k=i/2+1;
for(j=2;j<k;j%2?j+=2:j++)
{
if(i%j==0)
{
flag=false;
break;
}
}
if(flag==true)
{
//cout<<setw(9)<<i;
}
}
cout<<endl;
}
void test_prime_v3()
{
long now=clock();
prime_v3(N);
cout<<"prime_v3 running time is: "<<(double)(clock()-now)/CLOCKS_PER_SEC<<endl;
}
/**=======================================**/
void prime_v4(long int n)
{
long int i,j;
bool flag=true;
for(i=2;i<n;i++)
{
flag=true;
for(j=2;j<sqrt(i+1);j%2?j+=2:++j)
{
if(i%j==0)
{
flag=false;
break;
}
}
if(flag==true)
{
//cout<<setw(9)<<i;
}
}
cout<<endl;
}
void test_prime_v4()
{
long now=clock();
prime_v4(N);
cout<<"prime_v4 running time is: "<<(double)(clock()-now)/CLOCKS_PER_SEC<<endl;
}
/**=======================================**/
void prime_print(long int n)
{
long int i,j;
bool flag=true;
for(i=2;i<n;i++)
{
flag=true;
for(j=2;j<sqrt(i+1);j%2?j+=2:++j)
{
if(i%j==0)
{
flag=false;
break;
}
}
if(flag==true)
{
cout<<std::right<<setw(9)<<i;
}
}
cout<<endl;
}
int main()
{
test_prime_v1();
test_prime_v2();
test_prime_v3();
test_prime_v4();
cout<<"*=======================================**"<<endl;
prime_print(512);
return 0;
}
/*********************
prime_v1 running time is: 7.893
prime_v2 running time is: 3.885
prime_v3 running time is: 1.934
prime_v4 running time is: 0.094
*=======================================**
2 3 5 7 11 13 17 19 2
3 29 31 37 41 43 47 53 59
61 67 71 73 79 83 89 97 101
103 107 109 113 127 131 137 139 149
151 157 163 167 173 179 181 191 193
197 199 211 223 227 229 233 239 241
251 257 263 269 271 277 281 283 293
307 311 313 317 331 337 347 349 353
359 367 373 379 383 389 397 401 409
419 421 431 433 439 443 449 457 46
1 463 467 479 487 491 499 503 509
Process returned 0 (0x0) execution time : 14.664 s
Press any key to continue.
*********************/
上述方法仍有改进的空间,因为for(j=2;j<k;j%2?j+=2:j++) 这里对奇偶的判断只有第一次是有效的,之后全是无效的判断,因为全是奇数了,在循环体里边增加判断语句无疑消耗了大量程序运行时间。稍后补充境界5的实验程序以及筛法的实验程序。