标签:否则 代码 数字 解法 替换 复杂度 控件 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