素数判定 高级程序员才知道的那些事儿

简介: 其原理是这样的,设置一个标记数组,开始先把2的所有倍数都标记了,然后往后走发现3没有被标记,那3肯定是个素数,然后在标记数组中把所有3的倍数标记掉,然后发现4已经被标记了 跳过,到5……,直到标记完所有数字,那么剩下未标记的数字就是素数了,见上图,代码如下:

在信息安全领域,经常需要用到一些大素数,比如著名的RSA算法就必须依赖到两个大素数。幸运的是自然数中素数还真不少(很简单就能证明素数有无穷多个),而且密度也不算低,所以找到一个素数不是那么难,但让你找一个能用在RSA算法里的素数就比较难了。


暴力试除

试想下,如果现在让你去寻找出一个素数,你会怎么办?记得刚上大学刚学会C语言基本语法后,有道课后题就是判定一个数是否是素数,具备基本编程能力的人一定能写出如下代码:


boolean checkPrime(int n) {
    for (int i = 2; i*i <= n; i++) {
        if (n%i == 0) {
            return false;
        }
    }
    return true;
}


素数判定最简单的方法就是试除,也就是上面代码。它的原理是从2到根号n,看n是否能被某个数除尽,如果能那n肯定不是素数,反之一定是素数。这确实是个简单粗暴且正确的方法,唯一的问题是它太慢了,判定一个数的时间复杂度是O(n)。如果让你用这种方法去判断一个几百位的数是否是素数,那可能用现在最先进的计算机,也需要n多年才能算出来。


筛选法

当然素数判定还有一个更快的批量判定算法——埃氏筛选,他找到n以内的所有素数只需要O(n log log n)的时间复杂度。


image.png

其原理是这样的,设置一个标记数组,开始先把2的所有倍数都标记了,然后往后走发现3没有被标记,那3肯定是个素数,然后在标记数组中把所有3的倍数标记掉,然后发现4已经被标记了 跳过,到5……,直到标记完所有数字,那么剩下未标记的数字就是素数了,见上图,代码如下:


int[] signs = new int[n+1];
void eratosthenes(int n) {
    for (int i = 2; i <= n; i++) {
        if (signs[i] == 0) {
            for (int j = i * i; j <= n; j += i) {
                signs[j] = 1;
            }
        }
    }
}


埃氏筛选法虽然看起来比较快,但他也有自己的问题。首先他只能批量,对单个的n判定时也是需要筛出所有小于n的素数的。其次,它还需要依赖存储空间来存储标记。所以它仍然无法被用在超大素数的判定上。


有没有更快找到一个素数的方法?自从中世纪以来,有好多的数学家都在致力于寻找传中的素数公式。比如欧拉在1772年发现,f ( n ) = n 2 + n + 41 f(n) = n^2 + n + 41f(n)=n

2

+n+41 当n小于41时 f(n)的值都是素数,虽然后来也有数学家相继发现了能生成更大素数的公式,但这些公式能生成的数依旧是很有限的。到了高斯时代,基本上确认了简单的质数公式是不存在的,因此,高斯认为对素性判定是一个相当困难的问题。


费马小定理

image.png

然而,事情总是有转机的。让我们一起回到1636年,著名数学家费马在一封信中写出这样一个公式。


如果p是一个素数,且a不是p的倍数,则有a^(p-1) ≡ 1(mod p)


后来证明a不是p的倍数这个条件不是必须的。 这个定理的含义就是只要p是素数,那么( a ( p − 1 ) ) m o d p (a^(p-1))mod p(a

(

p−1))modp恒等于1,这就是著名的费马小定理。可能你已经在想,能不能用这个定理来判定素数,确实费马小定理反过来也几乎是成立的,如果一个数p能使得a^(p-1) ≡ 1(mod p),p有很大概率是个素数,注意这里是几乎成立。


public class PrimeNumCheck {
    public static boolean check(long a, long p) {
        long res = fastMod(a, p-1, p);
        return res == 1;
    }
    public static long fastMod(long x, long n, long m) {
        if (n == 1) {
            return x % m;
        }
        long tmp = fastMod(x, n>>1, m);
        if (n % 2 == 0) {
            return (tmp * tmp) % m;
        } else {
            return (tmp * tmp * x) % m;
        }
    }
    public static void main(String[] args) {
        System.out.println(check(2, 7));
    }
}


用如上Java代码,可以快速的概率性判定一个数是否是素数(判定结果不是100%准确),这也取决于上述代码中a的选择。上面用到了快速幂算法,能将对一个数的n次幂取模的时间复杂度降到O(logn)。我们似乎可以将素数的判定时间复杂度从O(n)降低到O(logn),这是质的飞跃,从原来的几乎不可计算变为可计算,这才为大素数的应用铺平了道路。


但是别急,它还有些小缺陷。我刚说了费马小定理反过来是几乎成立的,我一直在强调几乎二字。因为有些和数n也能使得a ( n − 1 ) ≡ 1 ( m o d n ) a^(n-1) ≡ 1(mod n)a

(

n−1)≡1(modn)成立,这些使得a ( n − 1 ) ≡ 1 ( m o d n ) a^(n-1) ≡ 1(mod n)a

(

n−1)≡1(modn)的合数被称为基于a伪素数,比如前几个基于2的伪素数分别是341、561、645……。不过这种伪素数也非常少,实际上,**对于一个512位的数,其中基于2的伪素数不到1/1020**,如果是1024位的数的话,伪素数概率就只有不到1/1041了。这个概率究竟有多低,举个例子,你能随机找到一个512位基于2的伪素数的概率比你中五百万大奖的概率都小。 所以你是随机找一个素数,基于2的费马小定理判定已经足够用了。


当然如果你非要追求更高准确率的话,还是可以优化的,毕竟基于2的伪素数并不一定是基于其他a的伪素数,所以我们可以多换几个不同的a来进一步提升上述代码的准确性。 但历史告诉我们凡事总有意外。有些合数对于任意的a都能使得费马定理成立,这些数被称为卡迈克尔数(Carmichael Number),前几个卡迈克尔数分别是561 1105 1729…… 关于卡迈克尔数又是另一个故事了。


小结

费马小定理这种概率性的解法给了我们解决问题的一种新思路,就好比用布隆过滤器一样,它们都不是百分百准确,但可以在准确性可控的情况下得到更高效的解决方案。计算机的世界不仅可以用空间换时间,还可以用准确率换时间。


像费马定理这种神奇的数学定理,我感觉这似乎是上帝在造物时埋下的一个关于数字的小彩蛋,而我也坚信这种小彩蛋还有很多,没准那天我们可以发现上帝隐藏在圆周率里的笑话呢!!


参考资料

维基百科 素数测试

维基百科 埃氏筛选 Sieve of Eratosthenes

维基百科 卡迈克尔数

《算法导论》 第31章 素数测试

目录
相关文章
|
存储 文字识别 算法
文字识别OCR常见问题之图片超过40M不返回结果如何解决
文字识别OCR(Optical Character Recognition)技术能够将图片或者扫描件中的文字转换为电子文本。以下是阿里云OCR技术使用中的一些常见问题以及相应的解答。
610 2
|
1月前
|
存储 JSON 前端开发
绕过验证码与登录:Playwright 自动化测试的身份认证策略
在Playwright自动化测试中,登录和验证码常成“拦路虎”。本文介绍四种绕过策略:复用Cookie/LocalStorage状态、调用API获取Token、测试环境禁用验证码、使用第三方测试账号。核心思想是“绕过而非破解”,提升测试效率与稳定性。推荐优先使用状态复用,避免重复登录,让测试聚焦核心业务逻辑。
|
5月前
|
存储 人工智能 自然语言处理
AI-Compass GraphRAG技术生态:集成微软GraphRAG、蚂蚁KAG等主流框架,融合知识图谱与大语言模型实现智能检索生成
AI-Compass GraphRAG技术生态:集成微软GraphRAG、蚂蚁KAG等主流框架,融合知识图谱与大语言模型实现智能检索生成
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
733 4
|
云安全 存储 运维
首次全面解析云原生成熟度模型:解决企业「诊断难、规划难、选型难」问题
从“上云”到“云上”原生,云原生提供了最优用云路径,云原生的技术价值已被广泛认可。当前行业用户全面转型云原生已是大势所趋,用户侧云原生平台建设和应用云原生化改造进程正在加速。
2965 100
首次全面解析云原生成熟度模型:解决企业「诊断难、规划难、选型难」问题
|
机器学习/深度学习 人工智能 自然语言处理
|
机器学习/深度学习 人工智能 自动驾驶
AI的奇思妙想之旅:探索未来的无限可能
人工智能(AI)正迅速变革世界,从自动驾驶到智能助手,乃至艺术创作领域。AI不仅能生成多样风格的艺术品,还能创造新艺术形式。例如,利用Python和深度学习库可将普通照片转化为梵高风格的画作。此外,AI还助力建筑设计,通过生成对抗网络(GAN)快速生成建筑草图。在医疗领域,AI支持个性化医疗决策,如通过随机森林算法预测心脏病风险。AI不仅象征技术飞跃,更预示着未来生活的无限可能。
231 2
|
NoSQL 关系型数据库 MySQL
当查询的数据来自多个数据源,有哪些好的分页策略?
当查询的数据来自多个数据源,有哪些好的分页策略?
262 9
聊天框(番外篇)—如何实现@功能的整体删除
上一篇文章中,我们已经初步实现了聊天输入框,但其@功能是不完善的,例如无法整体删除、无法获取除用户名以外的数据(假设用户名不是唯一的)。有问题就要想办法解决,在网上百度了一圈后,倒是有一些收获。本文就着重解决@的整体删除以及获取额外数据。
1472 0
聊天框(番外篇)—如何实现@功能的整体删除
|
存储 数据处理 C++
内存 vs 硬盘:固态硬盘代替内存可以工作吗?
内存 vs 硬盘:固态硬盘代替内存可以工作吗?
505 2