筛素数
整理一下筛素数的方法
我在网上了解到两种筛素数的方法
一种是 1/3n*判断 的时间复杂度
一种是的时间复杂度应该是比这个低
先说一下第一种的思路
首先:一个数如果他除以一个素数除不尽,那么他除以该素数的倍数也除不尽
所以我们可以这么考虑
如果一个数是二或三的倍数 那么它一定不是素数
于是 对于 1 2 3 4 5 6 7 8 9 10 11 12……
那么排除2和3的倍数
剩下的是 1 5 7 11 ……
对于六个数
6*n 6*n+1 6*n+2 6*n + 3 6*n + 4 6*n + 5
只需要检测 6*n+1 和6*n+5是不是素数就可以了
然后就是小的优化了
判断一个数x是不是素数
一种是枚举2~x-1 一种是2~sqrt(x) 都不是最优化的
由于之前已经存储了一些素数 可以枚举这些素数来判断 因为如果一个数除以一个素数除不尽 那么除以该素数的倍数一定除不尽
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <cmath> 5 using namespace std; 6 7 const int maxn = 105; 8 int is[maxn], prm[maxn]; 9 10 int get(int n) { 11 int k = 0; 12 prm[k++] = 2; 13 prm[k++] = 3; 14 int x = 4; 15 for(int i = 5; i <= n; i += x) { 16 x = 6 - x; 17 bool flag = true; 18 for(int j = 0; prm[j] * prm[j] <= i && j < k; j++) { 19 if(i % prm[j] == 0) { 20 flag = false; 21 break; 22 } 23 } 24 if(flag) { 25 prm[k++] = i; 26 } 27 } 28 return k; 29 } 30 31 int main() { 32 int k = get(100); 33 for(int i = 0; i < k; i++) { 34 printf("%d ", prm[i]); 35 } puts(""); 36 }
第二类有很多种优化
先写一下最原始的代码
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <cmath> 5 using namespace std; 6 7 const int maxn = 105; 8 int flag[maxn], prm[maxn]; 9 10 int get(int n) { 11 int k = 0; 12 memset(flag, 1, sizeof(flag)); 13 for(int i = 2; i <= n; i++) { 14 if(flag[i]) { 15 prm[k++] = i; 16 for(int j = i; j <= n; j += i) { 17 flag[j] = 0; 18 } 19 } 20 } 21 return k; 22 } 23 24 int main() { 25 int k = get(100); 26 for(int i = 0; i < k; i++) { 27 printf("%d ", prm[i]); 28 } puts(""); 29 }
思路 是大一刚开学在杭电的一个题解上看到的
首先 2是素数 那么 2的倍数4 6 8……就一定不是素数
然后3是素数 那么 3的倍数也就一定不是素数
但是这种方法有很大的缺点
就是比如删除2的倍数的时候 会删到6 12 18等而删3的倍数的时候也会删到 于是造成了删除的冗余 所以该算法的执行效率一般
而接下来的优化全部都是对于这个方向的优化
第一中优化是来自一篇博客
他的思路是这样的
一个数如果不是素数那么他的因子一定有他小的素数
那么对于每个数i只要把i之前的所有素数都乘以i那么 就不会错过下一个数i+1是不是合数
这个方法应该能理解
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <cmath> 5 using namespace std; 6 7 const int maxn = 105; 8 int flag[maxn], prm[maxn]; 9 10 int get(int n) { 11 int k = 0; 12 memset(flag, 1, sizeof(flag)); 13 for(int i = 2; i <= n; i++) { 14 if(flag[i]) { 15 prm[k++] = i; 16 } 17 for(int j = 0; j < k && prm[j] * i <= n; j++) { 18 flag[prm[j] * i] = 0; 19 } 20 } 21 return k; 22 } 23 24 int main() { 25 int k = get(100); 26 for(int i = 0; i < k; i++) { 27 printf("%d ", prm[i]); 28 } puts(""); 29 }
还有一个优化 明早起来写
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <cmath> 5 using namespace std; 6 7 const int maxn = 105; 8 int flag[maxn], prm[maxn]; 9 10 int get(int n) { 11 int k = 0; 12 memset(flag, 1, sizeof(flag)); 13 for(int i = 2; i <= n; i++) { 14 if(flag[i]) { 15 prm[k++] = i; 16 } 17 for(int j = 0; j < k && prm[j] * i <= n; j++) { 18 flag[prm[j] * i] = 0; 19 if(i % prm[j] == 0) 20 break; 21 } 22 } 23 return k; 24 } 25 26 int main() { 27 int k = get(100); 28 for(int i = 0; i < k; i++) { 29 printf("%d ", prm[i]); 30 } puts(""); 31 }
昨晚想了两件事 一件就是整理了一下筛素数的思路 一个是 我相信的一些东西
继续昨晚写的这个优化
之前的优化是这样的
对于一个数i的因子一定比i小
所以只要在其之前枚举所有素数可以了
然后这就引出一个问题
会不会有重复去掉的数字
答案是肯定的
比如12 你会在 6的时候去除6*2 又会在4的时候去除4 * 3
所以会有很多去除的重复
我们最理想的状态时每个合数都被它最小的素因子去除一次
刚刚那个例子
去除重复的原因是
由于4的时候去去除4*2 4*3 但是2能被4整除 这就牵扯到一个问题
就是 以后的所有的数 都可以写成2*2*x的形式
也就是说4已经不是后面那些数的最小因子了 所以直接跳出循环就可以了
吉大有个更优化的筛素数模板:
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <cmath> 5 using namespace std; 6 7 const int maxn = 10000005; 8 9 bool is[maxn]; int prm[maxn]; 10 11 int get(int n) { 12 int e = (int) (sqrt(0.0 + n) + 1); 13 memset(is, 1, sizeof(is)); 14 int k = 0; 15 prm[k++] = 2; 16 is[0] = is[1] = 0; 17 int i; 18 for(i = 4; i < n; i += 2) is[i] = 0; 19 for(i = 3; i < e; i += 2) if(is[i]) { 20 prm[k++] = i; 21 for(int s = i * 2, j = i * i; j < n; j += s) { 22 is[j] = 0; 23 } 24 } 25 for(; i < n; i += 2) if(is[i]) prm[k++] = i; 26 return k; 27 } 28 29 int main() { 30 int n; 31 int k = get(10000000); 32 for(int i = 0; i < k; i++) { 33 printf("%d ", prm[i]); 34 } puts(""); 35 return 0; 36 }
等我研究透了再来写
1209
今天我又学到了一个非常nb的方法 简直叹为观止
在这里非常感谢这篇博客的作者 对我的启发很大
http://blog.csdn.net/morewindows/article/details/7354571
在这里 作者讲到了一个很厉害的 叫数组状态压缩 叹为观止
作者是这么考虑的
我们知道一个整数的每一位进行位运算来对每个位进行赋一或者判断
一个数组可以考虑成一个‘非常大的一个整数’ 因为一个数组占据的也是一段连续的空间
一个整形数据占据32个字节 但是我们用到的只是01 所以可以把一个整形压缩到1/32
用的是|和&这两种运算
代码:
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <cmath> 5 using namespace std; 6 7 const int maxn = 105; 8 9 int prm[maxn / 3 + 1]; 10 int is[maxn / 32 + 1]; 11 12 13 int get(int n) { 14 memset(is, 0, sizeof(is)); 15 int k = 0; 16 for(int i = 2; i <= n; i++) { 17 if((is[i / 32] & ( 1 << (i % 32) ) ) == 0) { 18 prm[k++] = i; 19 } 20 for(int j = 0; j < k && prm[j] * i <= n; j++) { 21 int num = prm[j] * i; 22 is[num / 32] |= (1 << ( num % 32) ); 23 if(i % prm[j] == 0) break; 24 } 25 } 26 return k; 27 } 28 int main() { 29 int k = get(maxn - 5); 30 for(int i = 0; i < k; i++) { 31 printf("%d ", prm[i]); 32 } puts(""); 33 return 0; 34 }
这两天一直整理素数这个博客了
现在总结一下学到的这几种方法
1、筛素数最原始的方法就是对于每一个素数把其倍数全部删除 这个没什么好说的 缺点也很明显 就是有很多书都被删除了多次
2、第一种优化是对于删除数字不是直接把其后面的数字全部删除 而是 对于数组i删除的是i与之前素数的乘积
原因就是对于一个数i若其是个合数那么气必有一个比他小的素数的因数
3这种方法是对于第二种的时间上的优化 只加了一小步 但是确是画龙点睛 如果一个数是之前一个素数的倍数 那么 不用再继续往下乘下去 直接break就可以了
原因是 该数可以转化成其最小素因子成绩的形式
4最后一种是对于第三种在空间上的优化 运用了状态压缩的思想
思路也很简单 一个整形数据有32位 我们可以利用其中一位来完成真或者假的判断
1 if i was a prime j = i j <= n j+= i is[j] = false
2 i * pre prime[j] = false
3 if(i % prm[i] == 0) break
4 is[x / 32] |= ( 1 << ( x % 32 ) ) if( ( is[x/32] & ( 1 << (x % 32) ) ) == 0)