雪花飘落

埃氏筛法求质数

题目

输入两个数m和n(m<=n),先计算a = m(m+1)(m+2)……(n-2)(n-1)n,然后再统计2到a之间(包含a)存在多少个合数。

暴力枚举

暴力枚举是最简单也是最基础的做法,直接从定义出发,遍历2到a之间的每个数,依次通过取余的方法去判断:

m = int(input())
n = int(input())

a = 1
for i in range(m, n + 1):
    a *= i

c = 0
for i in range(2, a + 1):
    for j in range(2, i):
        if i % j == 0:
            c += 1
            break

print(c)

埃氏筛法

使用暴力枚举很容易就会发现问题,当m取1,n取10的时候,看起来很小,但是这个时候a的值已经变成了3628800。对于这样的数,暴力枚举显然已经不可接受了,这里就需要介绍一下,埃氏筛法了。

原理:因为一个合数必定可以分解出至少一个质因数,所以要得到自然数n以内的全部素数,必须把不大于根号n的所有素数的倍数剔除,剩下的就是素数。

 

思想:我们可以从第一个已知的质数2开始遍历,标记它的倍数。当我们遍历到某个数i时,如果这个数i还没有被标记过,那证明在i之前没有它的质因数,那i就肯定是质数,于是我们又去标记i的倍数。如果i已经被标记了,i为合数,直接跳过,继续往后遍历。

 

解法:有了上面的思想之后,如何去标记数就似乎最大的问题,我们可以建立一个列表ls,列表的索引对应数i,该索引对应的元素取布尔值(或者01),表示是否被标记。ls[i] == True,就表示这个数i已经被标记了。

 

比传统的暴力枚举效率会高非常多。

import math

m = int(input())
n = int(input())

a = 1
for i in range(m, n + 1):
    a *= i

# 因为需要ls[a],所以列表的长度为a+1,用0表示没被标记,1表示标记了,最后用count数1的个数就可以了。
ls = [0 for i in range(a + 1)]

# 只遍历到a的平方根就可以了,如果平方根左侧没有因数,那右侧必定也没有。
for i in range(2, int(math.sqrt(a + 1))):
    if not ls[i]:
        j = 2
        while i * j <= a:
            ls[i * j] = 1
            j += 1

print(ls.count(1))

欧拉筛

其实仔细思索,埃氏筛也会有很多的重复计算,比如12,在质因子2的时候会被筛一次,在质因子3的时候也会筛一次,如果数据足够大,同一个合数会被重复的筛很多次。欧拉筛和埃氏筛的核心原理是一样的,但是欧拉筛做了一个很重要的改进,即:每个合数只被他最小的质因子筛一次。

 

具体的实现:

  1. 和埃氏筛一样,定义一个列表ls来表示每个数是否被标记,初始全部表示为质数,即未被标记,然后去筛掉合数
  2. 再建一个列表primes,来存已经找到的质数,当我们遍历i的时候,如果发现i未被标记,则记录下这个质数
  3. 对于每一个i,遍历所有已有的质数,将他们的i倍数全部筛掉,然后,如果发现当前遍历的质数p_n是当前数字i的质因子,则直接break,因为我们要保证每个合数只被它最小的质因子筛掉一次。例如当i=4时,此时存在已有质数2、3,那么就会标记8和12,当i=6时,此时存在已有质数2、3、5,那么就会标记12、18、30,此时会发现12被重复标记了,所以在i=4时,当遇到它的质因子2时,就直接标记8,然后break,不在对3*4的数做标记了
  4. 标记时注意判断是否越界
a = int(input())
# 每个数的状态,0是质数,1是合数
ls = [0 for _ in range(a + 1)]
# 记录已经找到的质数
primes = []
for i in range(2, a+1):
    # 如果i是质数,记录下这个质数
    if not ls[i]:
        primes.append(i)
    # 开始筛,筛掉所有已有质数的i倍
    for p_n in primes:
        # 避免越界,只找范围内的数字
        if i * p_n > a:
            break
        ls[i * p_n] = 1
        # 每个数只被他的第一个质因子筛一次,避免重复筛
        if i % p_n == 0:
            break

print(primes)

 

posted @ 2022-10-11 14:37  haruyuki  阅读(101)  评论(0编辑  收藏  举报