码迷,mamicode.com
首页 > 编程语言 > 详细

算法:素数筛、线性筛

时间:2021-05-24 00:23:04      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:否则   代码   数字   解法   替换   复杂度   控件   clu   等于   

问题:

  求n以内所有素数,一般的做法是:

    1. 遍历2-n之间所有的数i

    2. 每个数i再遍历所有小于它的数看是否能被小于它的某个数整除,如果可以者该数i有可以被整除的数则是和数,没有则是素数。

 

两层for循环,时间复杂度高。

 

解法一:素数筛

  思想:用素数去标记合数,例如,已知最小的素数是2,那么2的所有倍数都是合数。

  算法步骤:

  1. 用prime[i]来标记i是否是合数

  2. 标记为1的数字为合数,否则为素数

  3. 第一次知道2是素数,则将2的倍数标记为1

  4. 向后找到第一个没有被标记的数字i

  5. 将i的倍数全部标记为合数

  6. 重复4-6步,知道标记完范围内所有数

控件复杂度:o(N);  时间复杂度:o(NlogNlongN) #存在重复标记

#include<stdio.h>
#define MAX_N 100

int prime[MAX_N + 5] = {0};

void init(){
    for (int i = 2; i <= MAX_N; i++){
        if (prime[i] != 0) continue;
        // 用素数标记合数,已经是合数的就不需要标记
        for (int j = 2 * i; j <= MAX_N; j += i) //i 素数i的倍数,所以从2*i,到每次j+i
        {
            prime[j] = 1;
        }
    }
}

int main(){
    init();
    for (int i = 2; i <= MAX_N; i++){
        if (prime[i] != 0) continue;
        printf("%d\n", i);
    }

    return 0;
}

 

小优化:用prime[0]这一位用作计数,后面每一位记录具体的素数。

(不会和合数的标记冲突,因为此时i已经遍历过了,相当于可以回收利用来记录具体的素数了,当然也可以再开一个数组记录所有素数,只是没有必要)。

#include<stdio.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;
        // 用素数标记合数,已经是合数的就不需要标记
        for (int j = 2 * i; j <= MAX_N; j += i) // 素数i的倍数,所以从2*i,到每次j+i
        {
            prime[j] = 1;
        }
    }
}

int main(){
    init();

    for (int i = 1; i <= prime[0]; i++){
        printf("%d\n", prime[i]);
    }

    return 0;
}

 

 

再次优化:

   仔细想想,上一步其实存在这重复标记,举个例子:

  对于素数2,会标记:4,6,8,10,12。。。

  对于素数3,会标记:6,9,12,15。。。

  可以看到6,12都被重复标记了。

可以将

for (int j = 2 * i; j <= MAX_N; j += i)

 

替换为

for (int j = i * i; j <= MAX_N; j += i)

 

这样就会减少一部分重复标记的过程。但是i*i又会遇到整数溢出的问题。故将乘法换成除法,最后标记一部分的代码可以改写成 

for (int j = i; j <= MAX_N / i; j++) {
    prime[j * i] = 1;
}

 

扩展问题1:求n以内所有数字的最小数因子

  思考:其实就是素数筛反过来思考,素数筛是通过素数去标记合数,标记的过程是从小到大标记的,所以只需要把每个合数的第一个素数输出即可。

  根据以上程序为框架完成这个问题

   此时prime数组是存每个数的最小素因子的

#include<stdio.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;
        for (int j = i; j <= MAX_N; j += i){
            if (prime[j]) continue;  //(1)
            prime[j] = i;
        }
    }
}

int main(){
    init();
    for (int i = 1; i <= MAX_N; i++){
        printf("%d min_factor=%d\n", i,  prime[i]);
    }
    return 0;
}

 

 扩展问题2:求n以内所有数的最大素因子

  只需将上述代码中(1)处的行注释掉即可,prime[]会一直更新它的素因子,最后得到的即为最大的。

 

 解法二:线性筛

  解法一,素数筛即使做了优化还是会有重复标记的过程,思考一种方法不重复的标记素数。

  任何一个正整数都可以写成素因子的幂次连乘的形式

  总体思想:用一个整数M去标记合数N,其中M和N具有如下性质

  1)N中最小的素数为P

  2)N可以表示称为P*M

  3)P一定小于等于M中最小的素因子

  4)利用M*P`(所有不大于M中最小素数的集合)标记为N1,N2,N3

举个栗子:

  对于30,用素数筛标记过程:2x15,3x10,5x6

  对于30,用线性筛标记过程:15x2,其中15即为M,2即为P

#include<stdio.h>
#define MAX_N 100

int prime[MAX_N + 5] = {0};
void init (){
    for (int i = 2; i <= MAX_N; i++){ //i相当于M
        if (!prime[i]) prime[++prime[0]] = i; //将素数保存
        for (int j = 1; j <= prime[0]; j++) {
            if (prime[j] * i > MAX_N) break;
            prime[prime[j] * i] = 1;    //prime[j] 想到于P
            if (i % prime[j] == 0) break;    
            //通过这行保证P是最小的素因子
        }                           
    }
}

int main(){
    init();
    for (int i = 1; i <= prime[0]; i++){
        printf("%d\n", prime[i]);
    }
    return 0;
}
            

 

 

 

    

算法:素数筛、线性筛

标签:否则   代码   数字   解法   替换   复杂度   控件   clu   等于   

原文地址:https://www.cnblogs.com/dylan-liang/p/14689431.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!