Miller-Rabin质数测试

本文主要讨论使用Miller-Rabin算法编写素数的判定算法,题目来源于hihocoder

题目

题目要求

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

使用Miller-Rabin算法进行质数素数测试,要求输入一个数字,对其是否是素数进行判定,并打印出相对应的结果。

提示:Miller-Rabin质数测试

输入

第1行:1个正整数t,表示数字的个数,10≤t≤50

第2..t+1行:每行1个正整数,第i+1行表示正整数a[i]2≤a[i]≤10^18

输出

第1..t行:每行1个字符串,若a[i]为质数,第i行输出"Yes",否则输出"No"

样例输入

  1. 3
  2. 3
  3. 7
  4. 9

样例输出

  1. Yes
  2. Yes
  3. No

题目分析

Miller-Rabin算法是一种基于费马小定理的扩展算法,首先我们需要知道什么是费马小定理,然后还要知道整个Miller-Rabin算法是如何扩展出来的。

费马小定理

费马小定理:对于质数p和任意整数a,有a^p ≡ a(mod p)(同余)。反之,若满足a^p ≡ a(mod p)p也有很大概率为质数。

将两边同时约去一个a,则有a^(p-1) ≡ 1(mod p)

也即是说:假设我们要测试n是否为质数。我们可以随机选取一个数a,然后计算a^(n-1) mod n,如果结果不为1,我们可以100%断定n不是质数。

否则我们再随机选取一个新的数a进行测试。如此反复多次,如果每次结果都是1,我们就假定n是质数。

该测试被称为Fermat测试。需要注意的是:Fermat测试不一定是准确的,有可能出现把合数误判为质数的情况。

Miller和Rabin在Fermat测试上,建立了Miller-Rabin质数测试算法。

二次探测定理

如果p是奇素数,则 x^2 ≡ 1(mod p)的解为 x ≡ 1x ≡ p - 1(mod p)

如果a^(n-1) ≡ 1 (mod n)成立,Miller-Rabin算法不是立即找另一个a进行测试,而是看n-1是不是偶数。如果n-1是偶数,另u=(n-1)/2,并检查是否满足二次探测定理即a^u ≡ 1 a^u ≡ n - 1(mod n)

举个Matrix67 Blog上的例子,假设n=341,我们选取的a=2。则第一次测试时,2^340 mod 341=1。由于340是偶数,因此我们检查2^170,得到2^170 mod 341=1,满足二次探测定理。同时由于170还是偶数,因此我们进一步检查2^85 mod 341=32。此时不满足二次探测定理,因此可以判定341不为质数。

将这两条定理合起来,也就是最常见的Miller-Rabin测试。

加强版测试验证定理

尽可能提取因子2,把n-1表示成d*2^r,如果n是一个素数,那么或者a^d mod n==1,或者存在某个i使得a^(d*2^i) mod n=n-1 (0<=i<r)则我们认为n为素数。(注意i可以等于0,这就把a^d mod n=n-1的情况统一到后面去了)

这里需要注意的是,我们将该定理作为判定条件,仍然是一个不确定的概率判定条件。Miller-Rabin素性测试同样是不确定算法,我们把可以通过以a为底的Miller-Rabin测试的合数称作以a为底的强伪素数(strong pseudoprime)。第一个以2为底的强伪素数为2047。第一个以2和3为底的强伪素数则大到1 373 653。

所以我们在实际使用过程中,使用rand()函数生成随机数,或者进行多次检测判定,还是能够得到比较高的判定成功率,Miller-Rabin算法对于素数的研究判定有着巨大的辅助作用。

代码

整体代码

  1. #include <iostream>
  2. #include <cstdlib>
  3. using namespace std;
  4. typedef long long llong;
  5. //求取(x * y) % n
  6. llong mod(llong x, llong y,llong n)
  7. {
  8. llong res = 0;
  9. llong temp = x % n;
  10. while(y)
  11. {
  12. if(y & 0x1)
  13. if((res += temp) > n)
  14. res -= n;
  15. if((temp <<= 1) > n)
  16. temp -= n;
  17. y >>= 1;
  18. }
  19. return res;
  20. }
  21. //求取(x ^ y) % n
  22. llong get_mod(llong x, llong y, llong n)
  23. {
  24. llong res = 1;
  25. llong temp = x;
  26. while(y)
  27. {
  28. if(y & 0x1)
  29. res = mod(res, temp, n);
  30. temp = mod(temp, temp, n);
  31. y >>= 1;
  32. }
  33. return res;
  34. }
  35. //编写bool函数,判定是否为素数
  36. bool is_prime(llong n, int t)
  37. {
  38. if(n < 2)
  39. return false;
  40. if(n == 2)
  41. return true;
  42. if(!(n & 0x1))
  43. return false;
  44. llong k = 0, m, a, i;
  45. for(m = n -1; !(m & 0x1); m >>= 1, ++k);
  46. while(t--)
  47. {
  48. a = get_mod(rand() % (n - 2) + 2, m, n);
  49. if(a != 1)
  50. {
  51. for(i = 0; i < k && a != n-1; ++i)
  52. {
  53. cout << a << endl;
  54. a = mod(a, a, n);
  55. }
  56. //根据二次探测定理,只要不满足(a == 1) || (a == n - 1),就会一直遍历下去,直到最后返回false
  57. if(i >= k)
  58. return false;
  59. }
  60. }
  61. return true;
  62. }
  63. //主函数
  64. int main()
  65. {
  66. int times;
  67. llong num;
  68. cin >> times;
  69. while(times--)
  70. {
  71. cin >> num;
  72. if(is_prime(num, 1))
  73. cout << "Yes" << endl;
  74. else
  75. cout << "No" << endl;
  76. }
  77. return 0;
  78. }

代码分解

mod()函数

  1. //求取(x * y) % n
  2. llong mod(llong x, llong y,llong n)
  3. {
  4. llong res = 0;
  5. llong temp = x % n;
  6. while(y)
  7. {
  8. if(y & 0x1)
  9. if((res += temp) > n)
  10. res -= n;
  11. if((temp <<= 1) > n)
  12. temp -= n;
  13. y >>= 1;
  14. }
  15. return res;
  16. }

这个函数使用移位运算,通过将y转换成二进制形式,十分高效地求取了两个数字乘积的余数。

get_mod()函数

  1. //求取(x ^ y) % n
  2. llong get_mod(llong x, llong y, llong n)
  3. {
  4. llong res = 1;
  5. llong temp = x;
  6. while(y)
  7. {
  8. if(y & 0x1)
  9. res = mod(res, temp, n);
  10. temp = mod(temp, temp, n);
  11. y >>= 1;
  12. }
  13. return res;
  14. }

这个函数是经典的高次幂函数求余算法,即蒙哥马利算法,在上一篇博文中也有过介绍,博文链接

其核心思想就是将幂指数转换成二进制,通过移位运算快速地求取余数,避免了数据溢出,而且效率非常高。

is_prime()函数

  1. //编写bool函数,判定是否为素数
  2. bool is_prime(llong n, int t)
  3. {
  4. if(n < 2)
  5. return false;
  6. if(n == 2)
  7. return true;
  8. if(!(n & 0x1))
  9. return false;
  10. llong k = 0, m, a, i;
  11. for(m = n -1; !(m & 0x1); m >>= 1, ++k);
  12. while(t--)
  13. {
  14. a = get_mod(rand() % (n - 2) + 2, m, n);
  15. if(a != 1)
  16. {
  17. for(i = 0; i < k && a != n-1; ++i)
  18. {
  19. cout << a << endl;
  20. a = mod(a, a, n);
  21. }
  22. //根据二次探测定理,只要不满足(a == 1) || (a == n - 1),就会一直遍历下去,直到最后返回false
  23. if(i >= k)
  24. return false;
  25. }
  26. }
  27. return true;
  28. }

即数字是否是素数的判定函数,依照我们在上文提出的加强定理,包含如下要点:

  • 对所需判定的奇数n进行n-1提取因子2,把n-1表示成d*2^r的形式;
  • 取随机数a=rand(),如果a^d mod n == 1则判定为素数;
  • 如果a^d mod n != 1,则通过循环查找是否有i满足a^(d*2^i) mod n = n-1,若有,则判定为素数;
  • 如果上述条件都不成立,则遍历结果得到i == k,此时返回false

  • Tips:这里需要注意的是,Miller-Rabin算法是一个不确定算法,仍有一定的错误概率,正如上文所述的,第一个以2为底的强伪素数为2047。第一个以2和3为底的强伪素数则大到1 373 653。在一定的使用范围内仍然可以得到高效、准确的结果!

Githubhttps://github.com/haoyuanliu

个人博客http://haoyuanliu.github.io/

个人站点,欢迎访问,欢迎评论!

Miller-Rabin质数测试的更多相关文章

  1. POJ1811_Prime Test【Miller Rabin素数测试】【Pollar Rho整数分解】

    Prime Test Time Limit: 6000MS Memory Limit: 65536K Total Submissions: 29193 Accepted: 7392 Case Time ...

  2. HDU 3864 D_num Miller Rabin 质数推断+Pollard Rho大整数分解

    链接:http://acm.hdu.edu.cn/showproblem.php? pid=3864 题意:给出一个数N(1<=N<10^18).假设N仅仅有四个约数.就输出除1外的三个约 ...

  3. HDU1164_Eddy&#39;s research I【Miller Rabin素数测试】【Pollar Rho整数分解】

    Eddy's research I Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others ...

  4. 关于素数:求不超过n的素数,素数的判定(Miller Rabin 测试)

    关于素数的基本介绍请参考百度百科here和维基百科here的介绍 首先介绍几条关于素数的基本定理: 定理1:如果n不是素数,则n至少有一个( 1, sqrt(n) ]范围内的的因子 定理2:如果n不是 ...

  5. 与数论的厮守01:素数的测试——Miller Rabin

    看一个数是否为质数,我们通常会用那个O(√N)的算法来做,那个算法叫试除法.然而当这个数非常大的时候,这个高增长率的时间复杂度就不够这个数跑了. 为了解决这个问题,我们先来看看费马小定理:若n为素数, ...

  6. Miller Rabin 算法简介

    0.1 一些闲话 最近一次更新是在2019年11月12日.之前的文章有很多问题:当我把我的代码交到LOJ上,发现只有60多分.我调了一个晚上,尝试用{2, 3, 5, 7, 11, 13, 17, 1 ...

  7. Miller Rabin算法学习笔记

    定义: Miller Rabin算法是一个随机化素数测试算法,作用是判断一个数是否是素数,且只要你脸不黑以及常数不要巨大一般来讲都比\(O(\sqrt n)\)的朴素做法更快. 定理: Miller ...

  8. 【数论基础】素数判定和Miller Rabin算法

    判断正整数p是否是素数 方法一 朴素的判定   

  9. Miller Rabin 详解 && 小清新数学题题解

    在做这道题之前,我们首先来尝试签到题. 签到题 我们定义一个函数:\(qiandao(x)\) 为小于等于 x 的数中与 x 不互质的数的个数.要求 \(\sum\limits _{i=l}^r qi ...

随机推荐

  1. C# 异步操作

    在程序中,普通的方法是单线程的.但中途如果有大型的操作,比如读取大文件,大批量操作数据库,网络传输等,都会导致程序阻塞,表现在界面上就是程序卡或者死掉,界面元素不动了,不响应了.C#异步调用很好的解决 ...

  2. Spring配置多数据源错误总结

    由于系统需要调用多个数据源包含mysql,sqlServe和Oracle,所以要在Spring的xml文件中配置多数据源,一下是配置过程中常见的错误: 1.配置的是mysql的数据源,却报oracle ...

  3. java_设计模式_适配器模式_Adapter Pattern(2016-08-09)

    概念 将一个接口转换成客户希望的另外一个接口.(该模式使得原本不兼容的类可以一起工作). UML图 适配器模式有类的适配器模式和对象的适配器模式两种不同的形式. (1)对象的适配器模式结构图 (2)类 ...

  4. 【POJ1151】【扫描线+线段树】Atlantis

    Description There are several ancient Greek texts that contain descriptions of the fabled island Atl ...

  5. gvim 常用命令

    插入: insert 强退: :q! 退出: :q 保存: :w 保存退出::wq 复制: yy(单行)   多行:8yy 删除: dd(单行)   多行:8dd 或者 :4,8d 执行脚本: :! ...

  6. 每天一条linux命令——login

    login命令用于给出登录界面,可用于重新登录或者切换用户身份,也可通过它的功能随时更换登入身份.当/etc/nologin文件存在时,系统只root帐号登入系统,其他用户一律不准登入. 语法: lo ...

  7. 自定义Excel导出简易组件

    1.组件原理 excel的数据存储是以xml格式存储的,所以导出Excel文件可以通过生成XML来实现.当然XML必须符合一定的格式要求. 2.组件实现 (1)新建类库文件“MyExcel” (2)添 ...

  8. jQuery备忘录--私家版

    最近在看jQuery,总是看过了忘,不知道该怎么办?准备开启洗脑模式,日常念一念,紧箍咒加身. 1.jQuery方法第一步:ready=>加载html的骨架.而onload=>整个页面加载 ...

  9. Python爬虫第一步

    这只是记录一下自己学习爬虫的过程,可能少了些章法.我使用过的是Python3.x版本,IDE为Pycharm. 这里贴出代码集合,这一份代码也是以防自己以后忘记了什么,方便查阅. import req ...

  10. bt种子文件文件结构

      估计80%以上接触互联网的人都知道bt是什么东西,任何一个用bt下载的人都知道这样一个概念,种子.bt种子就是记录了p2p对等网络中tracker, nodes, files等信息,也就是说,这个 ...