题目
朴素筛法
朴素筛法就是一一验证1∼ n \sqrt{n } n之内的数判断,时间复杂度O(n n \sqrt{n } n)。
int countPrimes(int n){
int count=0;
for(int i=2;i<n;i++){
for(int j=2;j*j<=i;j++){//优化:对于每个数字i,只需要计算2~根号i范围内是否存在非质因子
if(i% == 0)){//数字i存在因子j(非素数)
break;
}
count++;
}
}
return count;
}
缺点:每次都需要与比根号i小的数进行计算(2~根号i),因此存在大量重复计算,时间复杂度较高。
Eratosthenes筛法
把素数i对应的倍数全部筛掉,时间复杂度O(n loglog n)。
int countPrimes(int n){
//记录素数个数
int count=0;
//标记下标i对应的元素i是否为素数
int isPrime[100010];
//初始化2~n的元素均为素数
for(int i=2;i<=n;i++)
isPrime[i]=1;
//0和1为非素数
isPrime[0]=isPrime[1]=0;
for(int i=2;i<=n;i++){
if(isPrime[i]==1){//如果当前元素是素数,则将当前元素i的所有倍数均标记为非素数。当遇到非素数时,则不会进入循环,从而降低了时间复杂度。
for(int j=i*2;j<=n;j+=i)
isPrime[j]=0;
}
}
//统计素数个数
for(int i=1;i<=n;i++){
if(isPrime[t]==1) {
count++;
}
}
return count;
}
缺点:此算法仍存在部分重复计算,如数字6。43=12,26=12。那么在标记4倍数和标记6的倍数时都会对6进行标记,存在了重复标记的情况,增加了时间复杂度。
欧拉筛
欧拉筛的出现就是找到了唯一筛掉一个数的筛法,不会存在重复计算,所以时间复杂度是O(n)。
int countPrimes(int n){
//统计素数个数
int count=0;
//标记当前元素i是否为非素数,1代表是非素数,0代表是素数
int* record = malloc(n * sizeof(int));
//存储所有找到的素数
int* prime = malloc(n * sizeof(int));
//初始化所有元素均为素数
memset(record, 0, n * sizeof(int));
for(int i=2;i<n;i++){
if(!record[i]){//如果当前元素i是素数,存储当前元素
prime[count++] = i;
}
//对于当前元素,将所有已经找到的素数与它的乘积标记为非素数
for(int j=0;(j<count) && (i*prime[j]<n);j++){
record[i*prime[j]] = 1;
//判断当前元素是否是最小质因子
//此步骤即保证了不会出现重复计算类似4*3=12和6*2=12的情况
//执行4 *2 =8之后,4%prime[0] = 4%2 == 0就会跳出循环,不会执行4*3=12。因为4存在质因子2,则4不是12的最小质因子,所以跳出循环。
//执行6*2=12时,会对12进行标记,然后跳出循环。(下一次循环所对应的6*3=18与9*2=18类似这种情况,所以在此跳出循环了,不会对其进行标记,而是在执行9*2=18的时候进行标记。)
//综述来看,整体而言,保证了12会被标记为非素数,且只会标记一次。
if(!(i%prime[j])){//i是素数的倍数
break;
}
}
}
free(prime);
free(record);
return count;
}