判断一个数是否为两个素数乘积_素数筛及其原理

博客以正整数为基础,使用Ruby语言介绍素数筛算法。先介绍最简单的Eratosthenes筛及其复杂度,接着对其进行优化,还提到通过改变数据结构降低算法时间复杂度,最后引入轮子概念进一步优化算法,给出了相关实现步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一些定义:

为第
个素数,

(Euler
函数),

(素数计数函数),

(Chebyshev
函数)。

提到的数都为正整数,区间都为整数区间。

为了代码简洁起见,使用Ruby语言。

首先介绍最简单的Eratosthenes筛。

令数组

存储某个数是否为素数。写出代码:
# 输入

或者,用循环加法代替乘法:

$n 

这是我们熟知的最简单的素数筛,每次筛去已知的素数的倍数。数组

存储了每个数是否是素数。

分析一下它的复杂度。它需要为

开辟
的存储空间。每次寻找下一个素数时,
自加
的操作最多需要
次,筛去已知素数的倍数需要的时间最多为

这里用到了性质

。证明可以在这里找到。

实际上这里数组

代表了某个数是否属于还未被筛去的数组成的集合

一开始我们假设

然后逐个筛去合数,最终使得

对于任意一个素数

,由它筛去的合数应为
,其中
。对于Eratosthenes筛,有

因此,一个素数筛可以写成

require 

这里还定义了一个

,表示
中比
大的最小的数。

对于不同的素数筛,归根结底只是上面代码中的两处注释位置的代码不同。即,只不过是不同的

的初值的与
的实现。

Eratosthenes筛可以被优化至只需要

次算术运算。

在Eratosthenes筛中,一个数

很可能被筛去多次,尤其当
具有很多不同的素因数时。实际上我们可以只保留原先的
的一部分。我们的目标是仅保留
中必须的部分,以致任一合数仅会被筛去一次。

我们的想法是任一合数仅会被它最小的素因数

所生成的
筛去。

设将被筛去的合数

,其中
的最小素因数,我们就能得到

因此,我们对

的定义为

那么如何计算

呢?实际上,由于
中包含了
中还没被筛去的合数,而它们正是不能被比
小的素数整除的数,因此

然而,计算交集并不快。这启发我们改变

的数据结构。我们让
的元素不是数,而是双向链表:
class 

这里

总是存储
中比
小的最大元素,
总是存储
中比
大的最小元素。当
中有元素
被筛去时,
都要相应改变。

使用下面的代码,重定义

方法,可以实现从
中删去元素
def 

这样的数据结构需要的存储空间为

接下来我们计算

。最简单的办法是一个个
def 

每次产出

时都会从
中筛去
。但是如果你运行代码会发现它将得出错误的结果。这说明这种办法有个问题。实际上它的问题就是在计算
的同时
会被改变,本应在
中的元素可能会意外地被从
中筛去。

有多种办法可以解决这一问题,其中一种办法就是一开始令

中的最大数,然后一个个
,最终算出
。虽然
仍然会改变,但由于从
中筛去的
总是大于
,因此不会影响到此后要加入
中的
def 

整合一下代码,我们得到

$n 

程序需要的时间为

实际上算法需要的时间可以进一步降低。为了得到更优的算法,我们需要介绍轮子(wheel)的概念:

(素数阶乘),

(第
个轮子),

(扩展至
的第
个轮子)。

实际上

中与前
个素数互素的数的集合。

我们还需要以下性质:

证:显然素数阶乘具有性质

的渐进估计(可以在这里找到),可以得到

因此

因此

由此可以证明

证:

(Euler乘积公式)

(Mertens第三定理,
是Euler-Mascheroni常数)

类似地,有

接下来我们介绍一种数据结构,它能让我们在

时间内判断某个数是否属于某个轮子(即是否与
互素),以及获取在这个轮子中比它大的最小元素。

我们用一个长度为

的数组
来表示轮子中的数,其中

中比
大的最小元素,

为了建立这一数据结构,我们这样做:

  1. 算出前
    个素数以及
  2. 对于每个
    ,令
  3. 从大到小地迭代每个
    ,过程中记录上个非零的
    ,每次遇到新的非零项时为它赋值为这两个非零项的
    之差。

接下来我们介绍一种时间复杂度低于

的算法。实际上这种技巧可以用于优化大部分的
算法。我们的算法包含下面几个步骤:
  1. 选择尽可能大的
    ,使得
    ,算出前
    个素数,并对于每个
    计算
  2. 初始化为扩展至
    的第
    个轮子(实际上
    以一个双向链表数组实现);
  3. 开始(即从
    而不是
    开始)使用之前介绍的筛法筛去
    中的合数;
  4. 输出

注意到

一开始就只包含
个元素,所以上面的第3步只需要
次算数计算。代码实现留作习题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值