【算法】素数筛
admin
2024-05-15 07:27:47
0

目录

  • 1.概述
  • 2.代码实现
    • 2.1.试除法
    • 2.2.埃拉托斯特尼筛法
    • 2.3.欧拉筛法

1.概述

(1)在了解素数筛之前,我们先复习一下素数的定义:素数 (Prime number) 又称质数,一个大于 1 的自然数,除了 1 和它自身外,不能被其他自然数整除的数叫做质数;否则称为合数 (Composite number),并且规定 1 既不是质数也不是合数

(2)素数筛指将一堆数中的合数筛出掉,留下素数的一个过程。求某个大小范围内(例如 2 ~ n)的素数个数,是用到素数筛的最基础的问题,下面将以该问题为切入点,来具体介绍几种常见素数筛算法:试除法、埃拉托斯特尼筛法、欧拉法。

2.代码实现

2.1.试除法

(1)试除法是根据素数的定义而设计的算法,正如字面意思一样,尝试相除。要判断 n 是否为素数,则判断 [2, sqrt(n)] 内的整数是否为 n 的因数,即判断 n % i 的值是否为 0,如果为 0,则说明 i 是 n 的因数,故 n 不是素数,返回 false。当判断结束后仍未找到 n 的因数,则说明 n 为素数,返回 true 即可。

(2)上面判断的范围之所以为 [2, sqrt(n)] 而非 [2, n - 1],其目的在于减少循环次数。原理如下:

  • 根据素数的定义反推,如果一个数不是素数,那么它一定等于另外两个数(不包括 1 和其本身)的乘积。
  • 例如已知 num = sqrt(num) * sqrt(num),假设 num = i * j,那么 i 和 j 中一定有一个 <= sqrt(num),另一个 >= sqrt(num),因此只需判断较小的那个除数是否存在即可判断 n 是否为素数。

(3)具体的代码实现如下:

class Solution {/*** @param1: 要判断的数* @return: 如果 num 为 素数,返回 true,否则返回 false* @description: 判断 num 是否为素数*/public boolean isPrime(int num) {if (num < 2) {System.out.println("请输入大于 1 的自然数!");return false;}if (num == 2) {return true;}//判断 [2, sqrt(n)] 内的整数是否是 num 的因数for (int i = 2; i <= Math.sqrt(num); i++) {// i 是 num 的因数,故 num 不是素数if (num % i == 0)return false;}return true;}/*** @param1: 大于 1 的自然数 n* @return: 2 ~ n 之间的素数个数* @description: 获取 2 ~ n 之间的素数个数*/public int trialDivision(int n) {int cnt = 0;for (int i = 2; i <= n; i++) {if (isPrime(i)) {cnt++;}}return cnt;}
}

2.2.埃拉托斯特尼筛法

(1)埃拉托斯特尼筛法 (Sieve of Eratosthenes) ,简称埃氏筛,其思想是把 [2, n] 内小于等于 sqrt(n) 的所有素数的倍数(即合数)剔除,那么剩下的数就是 [2, n] 中的所有素数

(2)例如要找出 [2, n] 内的所有素数。首先用质数 2 去筛选,即把 2 留下,把 2 的倍数剔除掉;再用下一个质数,也就是 3 来筛选,把 3 留下,把 3 的倍数剔除掉;接下去用下一个质数 5 筛选,把 5 留下,把 5 的倍数剔除掉;不断重复下去…,直到筛选到最后一个小于等于 sqrt(n) 的素数为止,此时剩下的数即为 [2, n] 中的所有素数。

(3)具体的代码实现如下:

class Solution {/*** @param1: 大于 1 的自然数 n* @return: 2 ~ n 之间的素数个数* @description: 获取 2 ~ n 之间的素数个数*/public static int eratosthenes(int n) {// checked[i] = true: 表示数字 i 为合数(2 <= i <= n)boolean[] checked = new boolean[n + 1];//存储筛得到的素数List primes = new ArrayList<>();for (int i = 2; i <= n; i++) {if (!checked[i]) {//将素数 i 加入到 primes 中primes.add(i);// j 表示倍数,从 2 倍开始剔除for (int j = 2; i * j <= n; j++) {//将素数 i 的倍数标记为合数checked[i * j] = true;}}}return primes.size();}
}

(4)埃氏筛法的效率不够高,其原因在于一个合数可能会被重复剔除。例如合数 12,在用质数 2 去筛选时,会被 2 * 6 = 12 剔除,也可能在用质数 3 去筛选时,被 3 * 4 = 12 剔除,这样一来,合数 12 被剔除了多次。一个合数越大,其质素因子越多,则重复被剔除的次数也就越多

2.3.欧拉筛法

(1)欧拉筛法与埃氏筛类似,不过欧拉筛做了一点改进:让每个合数只被它的最小质因子筛选一次,这样可以达到不重复筛选的目的

(2)首先来看具体的代码实现:

class Solution1 {/*** @param1: 大于 1 的自然数 n* @return: 2 ~ n 之间的素数个数* @description: 获取 2 ~ n 之间的素数个数*/public static int euler(int n) {// checked[i] = true: 表示数字 i 为合数(2 <= i <= n)boolean[] checked = new boolean[n + 1];//存储筛得到的素数List primes = new ArrayList<>();for (int i = 2; i <= n; i++) {if (!checked[i]) {//将素数 i 加入到 primes 中primes.add(i);}for (int j = 0; j < primes.size(); j++) {int curPrime = primes.get(j);if (i * curPrime > n) {break;}checked[i * curPrime] = true;if (i % curPrime == 0) {//保证每个合数只被其最小的质因子剔除,从而避免了重复剔除break;}}}return primes.size();}
}

(3)欧拉筛法的代码需要仔细分析,下面先来分析关键代码:

// curPrime = primes.get(j)
if (i % curPrime == 0) {break;
}

上面的代码保证了每个合数只被其最小的质因子剔除,从而避免了重复剔除,具体分析如下:

  • i% primes.get(j) == 0 说明 primes.get(j) 是 i 的最小的素因子(primes 升序存放素数),所以 i 可以表示为如下形式:
// primes.get(j) 是 i 的最小质因子,故 x >= i
i = x * primes.get(j);
  • 从而可以推导出如下形式:
i * primes.get(j + 1) = x * primes.get(j) * primes.get(j + 1) = primes.get(j) * x * primes.get(j + 1);
  • 所以 i * primes.get(j + 1) 是 primes.get(j) 的倍数,当 i 往后递增到 y = x * primes.get(j + 1) 时,合数 i * primes.get(j + 1) = y * primes.get(j) 就会被剔除,这就相当于延迟剔除 i * primes.get(j + 1)
  • i * primes.get(j + 1) 是这样被剔除的,往后的 i * primes.get(j + 2) 也同理。

(4)举例说明如下:
① 例如 i = 12,此时 primes 中的素数有 2、3、5、7、11;
② 当进入到内层循环时,从 primes 中取出的第一个素数 curPrime = 2,此时 12 * 3 = 24 被剔除了(2 是 24 的最小质因子),但由于 12 % 2 == 0,因此退出了内层循环。
③ 那么为什么 12 % 2 == 0 时就得退出循环呢?而不是继续剔除 3 * 12 = 36 呢?

  • 首先,按照欧拉筛法的思想可知每个合数只被其最小的质因子剔除,显然合数 36 的最小质因子是 2,并非 3;
  • 其次,正如上面所推导的那样,36 可以通过 12 的最小素因子 2 与其它数相乘得到,即 36 = 2 * 18,因此 36 只是被延迟剔除了;
  • 最后,这样的延迟操作保证了每一个合数,只被其最小的质因子剔除一次,不会被重复剔除。

(4)欧拉筛法的正确性证明(即如何保证每个范围内的每个合数都会被剔除)

  • 假设我们要剔除合数 a,且 a 的最小质因数为 p1,令 a = p1b,那么显然 b < a,b 会先被外层 for 循环遍历到
  • 设 b 的最小质因数为 p2,当外层循环遍历到 b 时,此时要剔除它的倍数,由于 p1 是 a 的最小质因数,所以 p2 ≥ p1,即 p1 先于 p2 存放到 primes 中,这样就保证 b 在剔除 a 前不会 break 语句处跳出循环,即合数 a 必然会被剔除
  • 即使 p2 = p1,那么也会先剔除 a 后再退出循环(即先后执行下面的代码),这样就保证了每个合数都会被剔除
//剔除合数 a = b * p1 = b * p2
checked[i * curPrime] = true;
// p2 是 b 的最小质因数,即 b % p2 = i % curPrime = 0,b 已经被 p2 剔除过一次
if (i % curPrime == 0) {//保证每个合数只被其最小的素因子剔除,从而避免了重复剔除break;
}

(5)时空复杂度分析:

  • 时间复杂度:由于欧拉筛法保证了每个合数只被剔除一次,故即使代码看似有两层循环,但是其时间复杂度仍为 O(n);
  • 空间复杂度:比较明显,可直接看出来,为O(n);

相关内容

热门资讯

去新疆旅游攻略七日游,新疆7天... 新疆,这片位于中国西北边陲的神秘土地,以其广袤无垠的沙漠、巍峨壮丽的天山、色彩斑斓的湖泊以及独特的民...
黄山四天三晚景点推荐,黄山四天... 黄山,是一处大自然的奇迹,是地球上最令人惊叹的自然景观之一。它的奇松,或挺拔于山峰之巅,或扎根于悬崖...
杀生为护生,斩业非斩人,这句话... 杀生为护生,斩业非斩人,这句话谁说的那你到是说说,这句话错在哪儿,那么正确的又当如何啊出自《霹雳布袋...
原创 打... 作者/摄影:莫国良 住宿延边珲春市区,隔日与上海的老乡拼车去打卡我国最让人遗憾的国界线珲春防川。实话...
杭盖草原上的“鞍匠”阿拉坦巴根... 中新网 兴安盟7月8日电 题:杭盖草原上的“鞍匠”阿拉坦巴根:守艺“马背摇篮” 中新网 记者 张玮 ...
西安5天跟团纯玩报名方式,西安... 家人们,我敢打赌,在每一个热爱旅行、怀揣历史情怀的人心中,西安绝对有着不可撼动的地位。这座十三朝古都...
求一首粤语歌 求一首粤语歌杜丽莎《若你要离开我》若你要离开我,若你觉得好过,请你挥手,请你挥手。若你要离开我,若你...
四川本地团暑期特惠,3天2晚跟... 每年暑期,都是亲子家庭出游的黄金时段。我和爱人一直盼着能带孩子出去长长见识,开阔开阔眼界。可一想到规...
【走进泰国】2026泰国QS排... 全球高等教育分析机构QS Quacquarelli Symonds(QS)于今日正式发布了2026Q...
黄山4天3晚美食推荐,黄山4天... 黄山,不仅是一座自然奇观的宝库,更是一座承载着深厚历史文化底蕴的名山。自古以来,黄山便是文人墨客的灵...
工作我受委屈老公不理解我反而会... 工作我受委屈老公不理解我反而会说我的不是,我怎么办?可以感受到作为一个在外面受尽委屈回家后的老婆,她...
上海人去了太原和运城,直言不讳... 作为土生土长的上海小囡,总以为北方城市都是“差不多”的豪爽画风。但这次山西之行,我特意选了太原和运城...
笨狼找爸爸妈妈 笨狼找爸爸妈妈笨狼的故事读后感这几天我读了一本非常有趣的书,名字叫做《笨狼的故事》。《笨狼的故事》是...
北京旅游3天2晚景点+行程推荐... 家人们,北京,这座古老而又充满活力的城市,它就像是一本厚重的历史书,每一页都写满了故事。它是中国的首...
带井和巿的成语 带井和巿的成语市井小人 [shì jǐng xiǎo rén] 基本释义指城市中庸俗鄙陋之人。出 ...
黄山4日游旅游攻略,大学生去黄... 黄山,是一处大自然的奇迹,是地球上最令人惊叹的自然景观之一。它的奇松,或挺拔于山峰之巅,或扎根于悬崖...
英语几次怎么说? 英语几次怎么说?一次:once两次:twice三次:three times四次:four times...
全域旅游数据一屏掌控,决策更精... 在文旅产业数字化转型浪潮中,海鳗云旅游大数据平台以“全域数据融合+智能决策”为核心,为文旅局、景区管...
北京纯玩6天花多少钱?北京旅游... 在繁忙的工作与生活间隙,每个人心中都渴望一场说走就走的旅行,去拥抱远方的风景,放松紧绷的神经。北京,...