临睡前翻看了下《编程珠玑(续)》这本书,看到第一章就被吸引了,性能监视工具这节从计算素数入手。题目是:
打印所有小于1000的素数
简单直白的方法就是,针对每个小于1000的数字n,从2开始到n-1,如果能被任意一个数整除,那它就不是素数。代码如下:
int prime(int n)
{
int i;
for(i = 2; i < n; i++)
{
if(n % i == 0)
return 0;
}
return 1;
}
int main()
{
int i, n ;
n = 1000;
for(i = 2; n <= n; i++)
{
if(prime(i))
printf("%d\n", i);
}
}
这里存在可优化的地方,优化的地方就是根本不需要计算到n-1这个大小。因为根据数论的理论,只要求得至多不大于
N‾‾√
的整型数即可得出这个数是否为素数:
如下为理论证明:
第一,对于一个自然数N,只要能被一个非1非自身的数整除,它就肯定不是素数,所以不
必再用其他的数去除。
第二,对于N来说,只需用小于N的素数去除就可以了。例如,如果N能被15整除,实际
上就能被3和5整除,如果N不能被3和5整除,那么N也决不会被15整除。
第三,对于N来说,不必用从2到N一1的所有素数去除,只需用小于等于√N(根号N)的所有素数去除就可以了。这一点可以用反证法来证明:
如果N是合数,则一定存在大于1小于N的整数d1和d2,使得N=d1×d2。
如果d1和d2均大于√N,则有:N=d1×d2>√N×√N=N。
而这是不可能的,所以,d1和d2中必有一个小于或等于√N。
因此可以只求得
N‾‾√
之内的数即可判断该数是不是素数:
int root(int n)
{ return (int) sqrt((float) n);}
int prime(int n)
{
int i, bound;
bound = root(n);
for(i = 2; i <= bound; i++)
{
if(n % i == 0)
return 0;
}
return 1;
}
int main()
{
int i, n ;
n = 1000;
for(i = 2; n <= n; i++)
{
if(prime(i))
printf("%d\n", i);
}
}
可是这个方法还不是特别好,因为求平方根的操作比较耗费时间,因此如果能把这个时间省略就更好了,因此想到了如下办法:
int root(int n)
{ return (int) sqrt((float) n);}
int prime(int n)
{
int i, bound;
bound = root(n);
for(i = 2; i * i <= n; i++)
{
if(n % i == 0)
return 0;
}
return 1;
}
int main()
{
int i, n ;
n = 1000;
for(i = 2; n <= n; i++)
{
if(prime(i))
printf("%d\n", i);
}
}
是不是很惊喜啊,这样运算速度又快了一步,不过还不是最优的,试想将一个数先判断是否能被2,3,5整除,那么这个数肯定不是素数,这样一下子剔除了好多数,然后只考虑奇数的因子,这样时间上又优化了很多
int prime(int n)
{
int i;
if(n % 2 == 0)
return (n==2); //一定要排除2本身这个素数,下同
if(n % 3 == 0)
return (n == 3);
if (n % 5 == 0 )
return (n == 5);
for(i = 7; i * i<= n; i += 2)
{
if(n % i == 0)
return 0;
}
return 1;
}
int main()
{
int i, n ;
n = 1000;
for(i = 2; n <= n; i++)
{
if(prime(i))
printf("%d\n", i);
}
}
这样又优化了很多啊,这本书讲的真实厉害!后面还有的优化就是厄拉多塞筛法
简单介绍一下厄拉多塞筛法。厄拉多塞是一位古希腊数学家,他在寻找素数时,采用了一种与众不同的方法:先将2-N的各数放入表中,然后在2的上面画一个圆圈,然后划去2的其他倍数;第一个既未画圈又没有被划去的数是3,将它画圈,再划去3的其他倍数;现在既未画圈又没有被划去的第一个数 是5,将它画圈,并划去5的其他倍数……依次类推,一直到所有小于或等于N的各数都画了圈或划去为止。这时,表中画了圈的以及未划去的那些数正好就是小于 N的素数。
可以用一个数组保存每个元素,数组下标对应元素,此时对这个数组进行标记,如果为某个数的倍数,就将其标记为0,之后,没有被标记的元素就是素数。
int num[N];
for (int i = 0; i < N; i++)
{
num[i] = i;
}
for (int j = 2; j < N; j++)
{
if(0 != num[j])
{
for (int k = 2; k*j < N; k++)
{
num[k*j] = 0;
}
}
}
for (int n = 0; n < N; n++)
{
if(0 != num[n])
{
cout<<" "<<num[n];
}
}
其实这还可以继续优化,因为用数组表示每个数,当数组范围较大时,占用内存较多,可以采用位图法降低空间消耗。