素数的定义
三分筛法主要针对于素数的判断,所以应先了解素数的定义
质数又称素数。一个 大于1的自然数,除了 1 和 它自身 外,不能被其他自然数整除的数叫做质数;否则称为合数(规定1既不是质数也不是合数)。
比如:2 、3 、5 、7 、11 、13 ······都属于素数
暴力筛法
直接一个个判断,没什么算法可言
- 优点:代码简单,通俗易懂,容易上手
- 缺点:耗费内存,运行缓慢,容易超时
下面是计算出 2 ~40000 之间素数个数的代码:
public class Main {
public static void main(String[] args) {
long start = System.currentTimeMillis(); //暴力筛法开始前记录当时的时间戳
int count=0;
for (int i = 2; i <= 40000; i++) { //求出 2 ~ 40000 的素数,数据大一点可以看出效率
if (isPrime(i)) { //如果是素数的话
count++;
}
}
long end = System.currentTimeMillis(); //暴力筛法结束后记录当时的时间戳
System.out.println(count);
System.out.println(end - start); //相减得到该算法所需要的时间 约为 2.5ms
}
public static boolean isPrime(int i) { //其中 i 是要判断是否为素数的数字
//下面 for 循环中没有用 Math 的 sqrt 是因为调用 Math.sqrt() 的时间 大于 两个数相除的时间
//for 循环中不是 j * j <= i 是因为这样子容易溢出整数 int 范围,而且 j 是从 2 开始的,不用担心被除数不能为 0
for (int j = 2; j <= i / j; j++) { //从 2 开始找,找到根号i 为止
if (i % j == 0) { //如果其中 i 可以被整除,说明 i 不是素数,就直接返回 false
return false;
}
}
return true; //到这里表明 i 是素数,返回 true
}
}
耗时约为 2.5 ms
埃氏筛法
要得到自然数n以内的全部素数,必须把不大于的所有素数的倍数剔除,剩下的就是素数。
详细步骤:
-
列出2以后的所有序列:
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 -
标出序列中的第一个素数,也就是2,序列变成:
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 -
将剩下序列中,划掉2的倍数,序列变成:
2 3 5 7 9 11 13 15 17 19 21 23 25 -
如果这个序列中最大数小于最后一个标出的素数的平方,那么剩下的序列中所有的数都是素数,否则回到第二步。
-
本例中,因为25大于2的平方,我们返回第二步:
-
剩下的序列中第一个素数是3,将主序列中3的倍数划掉,主序列变成:
2 3 5 7 11 13 17 19 23 25 -
我们得到的素数有:2,3
-
25仍然大于3的平方,所以我们还要返回第二步:
-
序列中第一个素数是5,同样将序列中5的倍数划掉,主序列成了:
2 3 5 7 11 13 17 19 23 -
我们得到的素数有:2,3,5 。
-
因为23小于5的平方,跳出循环.
结论:2到25之间的素数是:2 3 5 7 11 13 17 19 23。
下面是计算出 2 ~40000 之间素数个数的代码:
public class Main {
public static void main(String[] args) {
long start = System.currentTimeMillis(); //埃氏筛法开始前记录当时的时间戳
int count = 0;
boolean[] isPrime = new boolean[40001]; //boolean默认值为 false,其中 false代表是素数,true 代表的是合数
for (int i = 2; i < 40001; i++) {
if (!isPrime[i]) { //如果是素数的话
count++;
for (int j = i * i; j < 40001; j += i) { //将 j 赋值为 i * i,每次都让 j+=i 这样子 j 永远是 i 的倍数
isPrime[j] = true; //标记合数 j
}
}
}
long end = System.currentTimeMillis(); //埃氏筛法结束后记录当时的时间戳
System.out.println(count);
System.out.println(end - start); //相减得到该算法所需要的时间 约为 1ms
}
}
耗时约为 1 ms
欧拉筛法
由于所有合数都有一个最小质因子,所以在埃氏筛法的基础上,让每个合数只被它的最小质因子筛选一次,以达到不重复的目的。
- 优点:效率高
- 缺点:过程难理解,数据太小效率不高
算法步骤:
- 2 到某数N之问的自然数列出来,标准格式为:2,3,4,......,N
- 2 设为第一个素数
- 2 的倍数全部剔除
- 到下一个未被剔除的数,将其设为第二个素数
- 该数的倍数全部剔除
- 重复第1、5步,直到所有小于某数的素数都被找出来
下面是计算出 2 ~40000 之间素数个数的代码:
public class Main {
public static void main(String[] args) {
long start = System.currentTimeMillis(); //埃氏筛法开始前记录当时的时间戳
int count = 0;
// 建立一个bool类型的数组,以下标为要判断的数字 以该下标的值为素数的标志,
//若i是素数 则 isPrime[i]=false
boolean[] isPrime = new boolean[40000];
isPrime[0] = isPrime[1] = true; //数字0和1都不是素数,所以赋true
int[] Prime = new int[40000]; //存放素数的数组
int t = 0;
Prime[t++] = 2;//把2放进素数表
for (int i = 2; i < 40000; i++) {
if (!isPrime[i]) { //若当前数是素数
Prime[t++] = i; //则存入素数数组
count++;
}
for (int j = 0; j < t && Prime[j] * i < 40000; j++) {
isPrime[i * Prime[j]] = true;
if (i % Prime[j] == 0)
break; //避免重筛,使得程序更有效率
}
}
long end = System.currentTimeMillis(); //欧拉筛法结束后记录当时的时间戳
System.out.println(count);
System.out.println(end - start); //相减得到该算法所需要的时间 约为 2ms
}
}
耗时约为 2 ms
总结
因为该40000太小了,不能体现欧拉筛法的效率,当数据足够大时,可以更好的展现欧拉筛法的高效性。比如当计算 2 ~ 10000000 时,暴力算法耗时 2834 ms,而使用欧拉筛法耗时 65 ms。但对于埃氏筛法来说不行,因为其中有个for 循环 int j = i * i,此时如果 i 太大 (i 大于46340)的话,会超过 j (int)的上限。所以如果数据量较小时(小于50000),建议直接暴力筛法,如果数据量过大就采用欧拉筛法。
帮助
如果关于欧拉筛法还有困惑的,不知道是怎么进行的,可以去哔哩哔哩观看视频,效果会更好
推荐b站:秋水共长天一色566 其中的《欧拉筛,几行就行,一次就好》