质数
一、定义
质数是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数,也叫素数。
二、判断素数
给定一个数 n ,判断它是不是素数?
答:
可以使用循环,如果 这个数不能被 [ 2 , n − 1 ] [2,n-1] [2,n−1] 内的所有数整除,那么它就是素数。
当然,这里 区间右侧只需要 到 n \sqrt{n} n 就可以,范围可以缩小到 [ 2 , n ] [2,\sqrt{n}] [2,n]。这叫做使用 试除法判断素数。
bool is_prime(int n){
if(n<=1) return 0; // 注意 1 不是素数。
for(int i=2;i*i<=n;i++){ // 这里使用 i*i<=n 和 i<=sqrt(n) 是一样的。同时要带等号。
if(n%i==0) return false;
}
return true;
}
这个函数中有一个循环,运行了 n \sqrt{n} n 次,时间复杂度为 n \sqrt{n} n。
三、获得素数表
给定一个n ,需要获得比它小的所有素数。
答:
可以从 i = 2 i = 2 i=2 开始 ,循环到 i = n i = n i=n,依次判断 i i i 是不是素数。这样的话一个一个单独判断太慢了。
3.1 埃式筛法
埃式筛法从最小的素数2 开始,对于每一个素数,都筛掉该素数的倍数。
比如选出 10 以内的所有素数:
【1】2 是素数,那么筛掉所有 2 的倍数:
2 3 [4] 5 [6] 7 [8] 9 [10]
【2】3 没有被筛掉,得到 3 是素数,筛掉所有 3 的倍数:
2 3 [4] 5 [6] 7 [8] [9] [10]
【3】4 被筛掉了,4不是素数。
【4】5 没有被筛掉,得到 5 是素数,筛掉所有 5 的倍数:
2 3 [4] 5 [6] 7 [8] [9] [10]
【5】6 被筛掉了,6不是素数。
【6】7没有被筛掉,得到 7 是素数,筛掉所有 7 的倍数:
2 3 [4] 5 [6] 7 [8] [9] [10]
【7】8被筛掉了,8不是素数。
【8】9被筛掉了,9不是素数。
【9】10被筛掉了,10不是素数。
最终得到结果 2 , 3 , 5 , 7 {2,3,5,7} 2,3,5,7。
可以发现 到 判断某个数 i i i ,是不是素数的时候,只要它没有被筛掉,那么它就是素数,这是为什么呢?因为如果这个数是不是素数,那么它一定有小于自己的 质数因子,而在这个数之前已经把小于该数的质数的倍数都筛掉了。
const int N = 1e7; // 定义空间大小
int prime[N+1]; // 用来存放所有的素数
bool v[N+1]={0}; // 判断有没有被筛掉。
int find_prime(int n){
int k=0; // 素数的个数
for(int i=2;i<=n;i++){
if(!v[i]){ // 如果 i 没有被筛掉
prime[k++] = i; // 将 i 装到prime数组
for(int j=i+i;j<=n;j+=i) // 将 i 的倍数筛掉
v[j] = true;
}
}
return k; // 返回素数个数。
}
时间复杂度是 n l o g l o g n nloglogn nloglogn
3.2 优化埃式筛选法
上面的程序有两个地方可以优化:
【1】上面用来筛选的数,只需要到 n \sqrt{n} n 就可以了,比如筛选 100 以内的素数,需要用到 的素数为 2,3,5,7就够了。
【2】可以看到上面筛选 10 以内的素数的时候,10 被筛选了两次,也就是被重复筛选了,因为2筛选了一次,5也筛选了一次,当 i = 5 i=5 i=5的时候,2x5,3x5,4x5,都已经在前面 i = 2 , i = 3 , i = 4 i=2,i=3,i=4 i=2,i=3,i=4 的时候筛选了,也就是说筛选的时候可以从 i ∗ i i*i i∗i 开始。
const int N = 1e7; // 定义空间大小
int prime[N+1]; // 用来存放所有的素数
bool v[N+1]={0}; // 判断有没有被筛掉。
int find_prime(int n){
int k =0;
for(int i=2;i*i<=n;i++){ // 开始筛选
if(!v[i]){ // 如果 i 没有被筛选
for(int j=i*i;j<=n;j+=i) v[j] = true; // 将 i 的倍数筛掉。
}
}
for(int i=2;i<=n;i++){ // 遍历 2-n 将没有被筛选的数装到 素数表中
if(!v[i]){
prime[k++] = i;
}
}
return k;
}