您的位置:首页 > 其它

ACM 进阶学习第一课----素数相关(2)

2013-11-05 00:59 281 查看
今天学习数论的第二个方面

素数相关

主要内容

算术基本定理

欧拉定理

素数测试

Pollard rho方法

算术基本定理

筛法

目标:求出n以内的所有质数

【原始算法步骤】

初始时容器内为2到n的所有正整数

取出容器中最小的数p,p一定是质数,删去p的所有倍数(注:只需从p2开始删除即可)

重复上述步骤直到容器为空

【原始算法分析】

优点:算法简单,空间上只需要一个O(n)的bool数组
缺陷:一个数可能被重复删去多次,影响效率

例:在p=2,3,5,7时均会尝试删除210
一般的,若x有k个质因子,则x会被处理k次

【算法改进】

初始时容器内为2到n的所有正整数

取出容器中最小的数p,p一定是质数

删去所有的pi,令q为第一个未被删除的数,保留q,删去所有的piq,再令q为下一个未被删除的数...直到q遍历所有未被删除的数为止(这一步骤可以把最小质因数为p的所有数删去)
重复上面两个步骤直到容器为空

也即是:
 1.开一个大的bool型数组prime[],大小就是n+1就可以了.先把所有的下标为奇数的标为true,下标为偶数的标为false.
 2.然后:

for( i=3; i<=sqrt(n); i+=2 )
{   if(prime)
for( j=i+i; j<=n; j+=i ) prime[j]=false;
}

 3.最后输出bool数组中的值为true的单元的下标,就是所求的n以内的素数了。
   原理很简单,就是当i是质(素)数的时候,i的所有的倍数必然是合数。如果i已经被判断不是质数了,那么再找到i后面的质数来把这个质
数的倍数筛掉。

示例代码

#include<iostream>
#include<math.h>
#include<stdlib.h>
using namespace std;
const int MAXV = 100; //素数表范围
bool flag[MAXV+1]; //标志一个数是否为素数
int prime[MAXV+1]; //素数表,下标从0开始
int size=0; //素数个数
void genPrime(int max)
{
memset(flag, true, sizeof(flag));//首先对标签数组进行初始化,全部设为true。
for(int i = 2; i <= max / 2; i++)
{
/*
从2开始,删除2的倍数
*/
if(flag[i])
{
//j=i<<1等价于 j=i*2,即j是i的两倍,而最后的j+=i,则表示下一个循环j是i的3倍,接着4倍。。。
//i的所有2~N倍数肯定都不是素数,因此将flag置为0,直到最后一位。
for(int j = i << 1 ; j <= max; j += i)
{
flag[j] = false;
}
}
}
for(i = 2 ; i <= max; i++)
{
if(flag[i])
{
prime[size++] = i;//存储素数。将所有标志位依然为1的标志写入素数数组中去。
}
}
}
void genPrime2(int max)
{
memset(flag, true, sizeof(flag));//首先对标签数组进行初始化,全部设为true。
int sq=sqrt((double)max)+1;  //一个数 n 如果是合数,那么它的所有的因子不超过sqrt(n)
int i,j, k;
for(i = 2;i<=sq; i++)
{
if(flag[i])
for(j=2,k=max/i+1;j<k;j++)
flag[i*j] = false; //所有i的j倍都不是素数
}
for( i = 2 ; i <= max; i++)
{
if(flag[i])
{
prime[size++] = i;//存储素数。将所有标志位依然为1的标志写入素数数组中去。
}
}
}
int main()
{
//  genPrime(MAXV);
genPrime2(MAXV);
//输出所有素数。
for(int i=0;i<size;i++)
cout<<prime[i]<<" ";
cout<<endl;
system("pause");
return 0;
}
/*
3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
请按任意键继续. . .
*/


运行结果:

3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
请按任意键继续. . .

【算法改进进一步设想】

就是bool型数组里面只存奇数不存偶数。
如定义prime
,则0表示3,1表示5,2表示7,3表示9...。
如果prime[0]为true,则表示3时素数。prime[3]为false意味着9是合数。
这样的优化不是简单的减少了一半的循环时间,比如按照原始的筛法,数组的下标就对应数。则在计算30以内素
数的时候3个步骤加起来走了15个单位时间。但是用这样的优化则是这样:
则由于只存3 5 7 9 11 13 15 17 19 21 23 25 27 29,只需要14个单元
第 1 步 把14个单元赋为true (每个单元代表的数是2*i+3,如第0单元代表3,第1单元代表5...)
第 2 步开始:
     i=0;  由于prime[0]=true,
把 [3], [6], [9], [12]标为false.
     i=1;  由于prime[1]=true,
把 [6], [11]标为false
     i=2  2*i+3>sqrt(30)算法结束。
这样优化以后总共只走6个单位时间。当n相当大以后这样的优化效果就更加明显,效率绝对不仅仅是翻倍。
出了这样的优化以外,另外在每一次用当前已得出的素数筛选后面的数的时候可以一步跳到已经被判定不是素数的
数后面,这样就减少了大量的重复计算。(比如我们看到的,i=0与i=1时都标了[6],这个就是重复的计算。)
我们可以发现一个规律,那就是3(即i=0)是从下标为[3]的开始筛的,5(即i=1)是从下标为[11]开始筛的(因为[6]
已经被3筛过了)。然后如果n很大的话,继续筛。7(i=2)本来应该从下标为[9]开始筛,但是由于[9]被筛过了,而
[16]也已经被5(i=1)筛过了。于是7(i=2)从[23](就是2*23+3=49)开始筛。
于是外围循环为i时,内存循环的筛法是从 i+(2*i+3)*(i+1)即i*(2*i+6)+3开始筛的。
这个优化也对算法复杂度的降低起到了很大的作用。
相比于一般的筛法,加入这两个优化后的筛法要高效很多。

时间有点晚了,欧拉函数早上再搞。

【未完待续】。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: