Bootstrap

素数的各种筛法(C实现)

原始暴力筛O(N^2)

#include <stdio.h>
#include <math.h>
int isprime(int n) {                   
	for (int i = 2; i <= n / 2; i++) { //i<=sqrt(n)
		if (n % i == 0) {
			return 0;
		}
	}
	return 1;
}

接近于O(N)

1朴素筛O(NlogN)

原理同2

#include <stdio.h>
#include <math.h>
#define MAX_N 100
int prime[MAX_N + 5] = { 0 };
void init() {
    for (int i = 2; i <= MAX_N; i++) {
        if (prime[i])continue;
        prime[++prime[0]] = i;//不断用prime[0]记录当前素数表里素数总个数
        for (int j = i * i; j <= MAX_N; j += i) {//因为是i*i,重复标记,并且若MAX_N存在溢出问题,可改进
            prime[j] = 1;
        }
    }
}
int main() {
    init();
    for (int i = 1; i <= prime[0]; i++) {
        printf("%d\n", prime[i]);
    }
    return 0;
}

2埃氏筛(埃拉托斯特尼筛法)O(NloglogN)

描述:
要得到自然数n以内的全部素数,必须把不大于的所有素数的倍数剔除,剩下的就是素数。
给出要筛数值的范围n,找出以内的素数。先用2去筛,即把2留下,把2的倍数剔除掉;再用下一个质数,也就是3筛,把3留下,把3的倍数剔除掉;接下去用下一个质数5筛,把5留下,把5的倍数剔除掉;不断重复下去…。

#include <stdio.h>
#include <math.h>
#define MAX_N 100
int prime[MAX_N + 5] = { 0 };
void init() {
    for (int i = 2; i <= MAX_N; i++) {
        if (!prime[i]) prime[++prime[0]] = i;
        for (int j = i; j <= MAX_N / i; j++) {
            prime[j * i] = 1;//重复标记合数,可改进
        }
    }
    return;
}
int main() {
    init();
    for (int i = 1; i <= prime[0]; i++) {
        printf("%d\n", prime[i]);
    }
    return 0;
}

线性筛O(N)

时间复杂度O(N),空间复杂度O(N)

在线性时间内(也就是O(N))用筛选的方法把素数找出来的一种算法,确切的说线性筛法并不是判定素数的,而是在线性时间内求出一个素数表,需要判定是否是素数的时候只要看该数是否在表内

如果用遍历取余,那么每判定一个数都要从头开始再遍历一遍,而线性筛法只在开始一次性运算完,以后只要查表即可,查表通常只需要1条语句。所以如果你的程序从始至终只需要判定那么几次素数那么用遍历取余即可,但是如果需要多次判定素数,而且这个数还不是很小的话,那么线性筛法就会体现出巨大的优越性来。

线性筛法的核心原理: 每个合数必有一个最大因子M(不包括它本身),用这个因子把合数N筛掉。还有另一种说法,每个合数必有一个最小素因子P,用这个因子P*M筛掉合数N。
证明:合数的因子一定多于2个,既然是几个数,就一定有最大的一个,并且最大因子是唯一的,所以合数只会被它自己唯一的最大因子筛掉一次(这里利用最大因子和最小素因子相乘进行只筛掉1次即可把时间复杂度降低为O(N)),把所有合数筛掉后剩下的就全是素数了。

筛掉N的条件:
1、N = P * M,且P为N中最小的素因子,M是N中最大的因子
2、P一定小于等于M的最小素因子
3、利用M * P(此处P是所有不大于M的最小素因子的集合,如若M=45,P可取2,3进行标记90和135)
如果想看线性筛的严格证明请看数论中的证明。
对于每一个数M,乘上小于等于M的最小素因数的素数集P{P1,P2…},就得到以M为最大因数的合数集N{N1,N2…}。设有一个数M,只要将所有以比M小的数为最大因数的合数筛去,那么比最大合数N小的数里剩下的就只有素数了。这就是线性筛法求素数的方法。

#include <stdio.h>
#include <math.h>
#define MAX_N 100
int prime[MAX_N + 5] = { 0 };
void init() {
    for (int i = 2; i <= MAX_N; i++) {
        if (!prime[i]) prime[++prime[0]] = i;//prime[0]记录当前素数表里素数,并在素数表下标1开始顺次存放素数
        for (int j = 1; j <= prime[0]; j++) {//prime[j]取自当前素数表里的素数
            if (prime[j] * i > MAX_N) break;//若超过所要标记最大合数MAX_N则停止
            prime[prime[j] * i] = 1;         //标记合数N`
            if (i % prime[j] == 0) break;    //不加这句也可正常标记,但是时间复杂度退回O(loglogN),该语句保证当前上一条语句所标记的N`为最大因子i(M)和最小素因子prime[j](P)
            //如i=4时,取到j=1,prime[j]=2,上一条语句已标记合数prime[8]=1,
            //若此时不退出当前循环,进行prime[prime[2]*4]=1 即prime[12]由3*4标记不满足12的最大因子6和最小素因子2进行标记的条件,此语句即是保证不进行重复标记的关键
        }
   }
    return;
}
int main() {
    init();
    for (int i = 1; i <= prime[0]; i++) {
        printf("%d\n", prime[i]);
    }
    return 0;
}

罗宾米勒测试(可以优化到O(1)) 数论:费马定理

后续更新

;