求素数算法-网摘

article/2025/9/17 10:47:12

摘自:http://www.cnblogs.com/luluping/archive/2010/03/03/1677552.html

浅析求素数算法

注意: 如果没有特殊说明, 以下讨论的都是针对n为素数时的时间复杂度

1. 根据概念判断:

如果一个正整数只有两个因子, 1和p,则称p为素数.

代码:
复制代码
bool isPrime(int n)
{if(n < 2) return false;for(int i = 2; i < n; ++i)if(n%i == 0) return false;return true;
}
复制代码

时间复杂度O(n).


2. 改进, 去掉偶数的判断

代码:
复制代码
bool isPrime(int n)
{if(n < 2) return false;if(n == 2) return true;if(n%2==0) return false;for(int i = 3; i < n; i += 2)if(n%i == 0) return false;return true;
}
复制代码

时间复杂度O(n/2), 速度提高一倍.


3. 进一步减少判断的范围

定理: 如果n不是素数, 则n有满足1<d<=sqrt(n)的一个因子d.
证明: 如果n不是素数, 则由定义n有一个因子d满足1<d<n.
如果d大于sqrt(n), 则n/d是满足1<n/d<=sqrt(n)的一个因子.

代码:
复制代码
bool isPrime(int n)
{if(n < 2) return false;if(n == 2) return true;for(int i = 3; i*i <= n; i += 2)if(n%i == 0) return false;return true;
}
复制代码

时间复杂度O(sqrt(n)/2), 速度提高O((n-sqrt(n))/2).


4. 剔除因子中的重复判断.
例如: 11%3 != 0 可以确定 11%(3*i) != 0.

定理: 如果n不是素数, 则n有满足1<d<=sqrt(n)的一个"素数"因子d.
证明: I1. 如果n不是素数, 则n有满足1<d<=sqrt(n)的一个因子d.
I2. 如果d是素数, 则定理得证, 算法终止.
I3. 令n=d, 并转到步骤I1.

由于不可能无限分解n的因子, 因此上述证明的算法最终会停止.

代码:

复制代码
// primes[i]是递增的素数序列: 2, 3, 5, 7, ...
// 更准确地说primes[i]序列包含1->sqrt(n)范围内的所有素数bool isPrime(int primes[], int n)
{if(n < 2) return false;for(int i = 0; primes[i]*primes[i] <= n; ++i)if(n%primes[i] == 0) return false;return true;
}
复制代码

 

假设n范围内的素数个数为PI(n), 则时间复杂度O(PI(sqrt(n))).

函数PI(x)满足素数定理: ln(x)-3/2 < x/PI(x) < ln(x)-1/2, 当x >= 67时.

因此O(PI(sqrt(n)))可以表示为O(sqrt(x)/(ln(sqrt(x))-3/2)),

O(sqrt(x)/(ln(sqrt(x))-3/2))也是这个算法的空间复杂度.


5. 构造素数序列primes[i]: 2, 3, 5, 7, ...

由4的算法我们知道, 在素数序列已经被构造的情况下, 判断n是否为素数效率很高;

但是, 在构造素数序列本身的时候, 是否也可是达到最好的效率呢?

事实上这是可以的! -- 我们在构造的时候完全可以利用已经被构造的素数序列!

假设我们已经我素数序列: p1, p2, .. pn

现在要判断pn+1是否是素数, 则需要(1, sqrt(pn+1)]范围内的所有素数序列,

而这个素数序列显然已经作为p1, p2, .. pn的一个子集被包含了!

代码:

复制代码
// 构造素数序列primes[]void makePrimes(int primes[], int num)
{int i, j, cnt;primes[0] = 2;primes[1] = 3;for(i = 5, cnt = 2; cnt < num; i += 2){int flag = true;for(j = 1; primes[j]*primes[j] <= i; ++j){if(i%primes[j] == 0){flag = false; break;}}if(flag) primes[cnt++] = i;}
}
复制代码

 

makePrimes的时间复杂度比较复杂, 而且它只有在初始化的时候才被调用一次.

在一定的应用范围内, 我们可以把近似认为makePrimes需要常数时间.

在后面的讨论中, 我们将探讨一种对计算机而言更好的makePrimes方法.


6. 更好地利用计算机资源...

当前的主流PC中, 一个整数的大小为2^32. 如果需要判断2^32大小的数是否为素数,

则可能需要测试[2, 2^16]范围内的所有素数(2^16 == sqrt(2^32)).

由4中提到的素数定理我们可以大概确定[2, 2^16]范围内的素数个数.

由于2^16/(ln(2^16)-1/2) = 6138, 2^16/(ln(2^16)-3/2) = 6834,

我们可以大概估计出[2, 2^16]范围内的素数个数6138 < PI(2^16) < 6834.

在对[2, 2^16]范围内的素数进行统计, 发现只有6542个素数:

p_6542: 65521, 65521^2 = 4293001441 < 2^32, (2^32 = 4294967296)
p_6543: 65537, 65537^2 = 4295098369 > 2^32, (2^32 = 4294967296)

在实际运算时unsigned long x = 4295098369;将发生溢出, 为131073.

在程序中, 我是采用double类型计算得到的结果.

分析到这里我们可以看到, 我们只需要缓冲6543个素数, 我们就可以采用4中的算法

高效率地判断[2, 2^32]如此庞大范围内的素数!

(原本的2^32大小的问题规模现在已经被减小到6543规模了!)

虽然用现在的计算机处理[2, 2^16]范围内的6542个素数已经没有一点问题,

虽然makePrimes只要被运行一次就可以, 但是我们还是考虑一下是否被改进的可能?!

我想学过java的人肯定想把makePrimes作为一个静态的初始化实现, 在C++中也可以

模拟java中静态的初始化的类似实现:

#define NELEMS(x) ((sizeof(x)) / (sizeof((x)[0])))

static int primes[6542+1];
static struct _Init { _Init(){makePrimes(primes, NELEMS(primes);} } _init;

如此, 就可以在程序启动的时候自动掉用makePrimes初始化素数序列.

但, 我现在的想法是: 为什么我们不能在编译的时候调用makePrimes函数呢?

完全可以!!! 代码如下:

代码:

复制代码
// 这段代码可以由程序直接生成const static int primes[] = 
{
2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,
107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,
223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,
337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,
457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,
593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,
719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,
857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,
...
65521, 65537
};
复制代码

 

有点不可思议吧, 原本makePrimes需要花费的时间复杂度现在真的变成O(1)了!

(我觉得叫O(0)可能更合适!)

7. 二分法查找

现在我们缓存了前大约sqrt(2^32)/(ln(sqrt(2^32)-3/2))个素数列表, 在判断2^32级别的

素数时最多也只需要PI(sqrt(2^32))次判断(准确值是6543次), 但是否还有其他的方式判断呢?

当素数比较小的时候(不大于2^16), 是否可以直接从缓存的素数列表中直接查询得到呢?

答案是肯定的! 由于primes是一个有序的数列, 因此我们当素数小于2^16时, 我们可以直接

采用二分法从primes中查询得到(如果查询失败则不是素数).

代码:
复制代码
// 缺少的代码请参考前边#include <stdlib.h>static bool cmp(const int *p, const int *q)
{return (*p) - (*q);
}bool isPrime(int n)
{if(n < 2) return false;if(n == 2) return true;if(n%2 == 0) return false;if(n >= 67 && n <= primes[NELEMS(primes)-1]){return NULL !=bsearch(&n, primes, NELEMS(primes), sizeof(n), cmp);}else{for(int i = 1; primes[i]*primes[i] <= n; ++i)if(n%primes[i] == 0) return false;return true;}
}
复制代码

 

时间复杂度:


if(n <= primes[NELEMS(primes)-1] && n >= 67): O(log2(NELEMS(primes))) < 13;
if(n > primes[NELEMS(primes)-1]): O(PI(sqrt(n))) <= NELEMS(primes).

8. 素数定理+2分法查找

在7中, 我们对小等于primes[NELEMS(primes)-1]的数采用2分法查找进行判断.

我们之前针对2^32缓冲的6453个素数需要判断的次数为13次(log2(1024*8) == 13).

对于小的素数而言(其实就是2^16范围只内的数), 13次的比较已经完全可以接受了.

不过根据素数定理: ln(x)-3/2 < x/PI(x) < ln(x)-1/2, 当x >= 67时, 我们依然

可以进一步缩小小于2^32情况的查找范围(现在是0到NELEMS(primes)-1范围查找).

我们需要解决问题是(n <= primes[NELEMS(primes)-1):

如果n为素数, 那么它在素数序列可能出现的范围在哪?

---- (n/(ln(n)-1/2), n/(ln(n)-3/2)), 即素数定理!

上面的代码修改如下:

代码:

复制代码
bool isPrime(int n)
{if(n < 2) return false;if(n == 2) return true;if(n%2 == 0) return false;int hi = (int)ceil(n/(ln(n)-3/2));if(n >= 67 && hi < NELEMS(primes)){int lo = (int)floor(n/(ln(n)-1/2));return NULL !=bsearch(&n, primes+lo, hi-lo, sizeof(n), cmp);}else{for(int i = 1; primes[i]*primes[i] <= n; ++i)if(n%primes[i] == 0) return false;return true;}
}
复制代码

 

时间复杂度:

if(n <= primes[NELEMS(primes)-1] && n >= 67): O(log2(hi-lo))) < ???;
if(n > primes[NELEMS(primes)-1]): O(PI(sqrt(n))) <= NELEMS(primes).


9. 打包成素数库(给出全部的代码)

到目前为止, 我已经给出了我所知道所有改进的方法(如果有人有更好的算法感谢告诉我).

这里需要强调的一点是, 这里讨论的素数求法是针对0-2^32范围的数而言, 至于像寻找

成百上千位大小的数不在此讨论范围, 那应该算是纯数学的内容了.

代码保存在2个文件: prime.h, prime.cpp.

代码:
10. 回顾, 以及推广

到这里, 关于素数的讨论基本告一段落. 回顾我们之前的求解过程, 我们会发现

如果缺少数学的基本知识会很难设计好的算法; 但是如果一味地只考虑数学原理,

而忽律了计算机的本质特征, 也会有同样的问题.

一个很常见的例子就是求Fibonacci数列. 当然方法很多, 但是在目前的计算机中

都没有实现的必要!

因为Fibonacci数列本身是指数增长的, 32位的有符号整数所能表示的位置只有前46个:

代码:

复制代码
 
复制代码
  1 // file: prime.h2 3 #ifndef PRIME_H_2006_10_27_4 #define PRIME_H_2006_10_27_5 6 extern int  Prime_max(void);        // 素数序列的大小7 extern int  Prime_get (int i);        // 返回第i个素数, 0 <= i < Prime_max8 9 extern bool Prime_test(int n);        // 测试是否是素数, 1 <= n < INT_MAX10 11 #endif12 13 ///14 15 // file: prime.cpp16 17 #include <assert.h>18 #include <limits.h>19 #include <math.h>20 #include <stdlib.h>21 22 #include "prime.h"23 24 // 计算数组的元素个数25 26 #define NELEMS(x) ((sizeof(x)) / (sizeof((x)[0])))27 28 // 素数序列, 至少保存前6543个素数!29 30 static const int primes[] = 31 {32 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,33 107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,34 223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,35 337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,36 457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,37 593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,38 719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,39 857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,40 ...41 65521, 6553742 };43 44 // bsearch的比较函数45 46 static int cmp(const void *p, const void *q)47 {48     return (*(int*)p) - (*(int*)q);49 }50 51 // 缓冲的素数个数52 53 int Prime_max()54 {55     return NELEMS(primes);56 }57 58 // 返回第i个素数59 60 int Prime_get(int i)61 {62     assert(i >= 0 && i < NELEMS(primes));63     return primes[i];64 }65 66 // 测试n是否是素数67 68 bool Prime_test(int n)69 {70     assert(n > 0);71 72     if(n < 2) return false;73     if(n == 2) return true;74     if(!(n&1)) return false;75 76     // 如果n为素数, 则在序列hi位置之前77 78     int lo, hi = (int)ceil(n/(log(n)-3/2.0));79 80     if(hi < NELEMS(primes))81     {82         // 确定2分法查找的范围83         // 只有n >= 67是才满足素数定理84 85         if(n >= 67) lo = (int)floor(n/(log(n)-1/2.0));86         else { lo = 0; hi = 19; }87 88         // 查找成功则为素数89 90         return NULL !=91             bsearch(&n, primes+lo, hi-lo, sizeof(n), cmp);92     }93     else94     {95         // 不在保存的素数序列范围之内的情况96 97         for(int i = 1; primes[i]*primes[i] <= n; ++i)98             if(n%primes[i] == 0) return false;99 
100         return true;
101     }
102 }

转载于:https://www.cnblogs.com/ghostTao/p/3975883.html


http://chatgpt.dhexx.cn/article/OfrAq3Ha.shtml

相关文章

用JAVA编写50以内的素数_java求50以内的素数

java求50以内的素数 [2021-02-01 12:46:22] 简介: python求100内的所有素数的方法&#xff1a;使用判断该数除了1和它本身以外不再有其他因数即可&#xff0c;代码为【i2 for i in range(2,100): if(i%j0):break else:num.append(i)】。相关免费学 php去除nbsp的方法&#xff…

浅析求素数算法

原文地址为&#xff1a; 浅析求素数算法 2009-12-05 16:15:31 阅读18 评论0 字号&#xff1a;大中小 浅析求素数算法时间: 2006-10-27注意: 如果没有特殊说明, 以下讨论的都是针对n为素数时的时间复杂度1. 根据概念判断:如果一个正整数只有两个因子, 1和p&#xff0c;则称p为…

Python中的所有运算符以及运算符的优先级

文章目录 前言一、算术运算符二、赋值运算符三、比较运算符四、逻辑运算符五、位运算符六、运算符的优先级案例实战总结 前言 运算符是一些人为定义的特殊符号&#xff0c;比如我们生活当中最常见的 、-、、&#xff0c;它们主要用于数学计算、比较大小和逻辑运算等等。那这些…

在c语言中逻辑运算符的优先级是,c语言中逻辑运算符优先级是什么?

c语言中逻辑运算符优先级由高到低依次是&#xff1a;&#xff01;(逻辑非)、&&(逻辑与)、||(逻辑或)。逻辑表达式的值为逻辑值&#xff1b;逻辑值分为逻辑真值和逻辑假值&#xff0c;在判断时&#xff0c;仅有零值被判断为逻辑假值(false)&#xff0c;一切非零值均可被…

算数运算符,比较运算符,逻辑运算符,赋值运算符,运算优先级

文章目录 1.算数运算符1.1注意事项2.关系运算符&#xff0c;比较运算符2.1注意事项 3.逻辑运算符3.1注意事项 4.赋值运算法4.1注意特点 5.运算优先级 1.算数运算符 package mainimport "fmt"func main() {//说明&#xff0c;如果运算的数都是整数&#xff0c;那么除后…

【C语言】运算符的优先级

目录 算术运算符&#xff1a; 关系运算符&#xff1a; 逻辑运算符 三种运算符优先级高低比较&#xff1a; 算术运算符&#xff1a; 5种算术运算符&#xff1a;, -, *, /, % 优先级&#xff1a;*&#xff0c;/&#xff0c;%大于&#xff0c;- 关系运算符&#xff1a; 6种…

逻辑运算符与++的使用与优先级差别

我们的教材通常会按级次区别优先级&#xff0c;如下图所示&#xff1a; 那对于一个简单的编程题&#xff1a; int a,b,c,y;abc1;y a||b&&c 此时输出结果为&#xff1a;y1,a2,b1,c1 按照逻辑运算符&&优先于||&#xff0c;结果不应该为2&#xff0c;2&#xff…

逻辑运算符、位运算符、移位运算符、三目运算符、运算符的优先级

一、逻辑运算符 &#xff1a;逻辑运算符的作用是用于连接布尔表达式的。 1、& &#xff08;与&#xff0c;并且&#xff09; 规律&#xff1a; 只有左右变量同时为true&#xff0c;那么结果才是true&#xff0c;否则就false。 2、| &#xff08;或&#xff0c;或者&…

C/C++逻辑运算符 | | 和 的优先级误区——优先级决定运算顺序吗?

C运算符优先级表&#xff1a; 根据这个表格&#xff0c;我们很容易得出结论&#xff1a;&&的优先级大于|| 下面看一段代码&#xff0c;来验证这个结论 #include<iostream> using namespace std; int main() {int x 3;int y 1 || (x 1) && (x 1);pr…

运算符的优先级顺序

运算符优先级口诀&#xff1a;单目算术位关系&#xff0c;逻辑三目后赋值。 单目&#xff1a; 、- - 算术&#xff1a;、-、*、/、% 位&#xff1a;位移运算符&#xff1a;<<&#xff08;左移位&#xff09;、>>&#xff08;带符号右移位&#xff09;、>>>…

c++运算符优先级归纳

C一共有 18个优先级&#xff0c;运算中按优先级进行性计算&#xff0c;当优先级相同时&#xff0c;根据结合性规则来决定。 结合性&#xff1a; 1.从左到右&#xff08;L-R&#xff09;&#xff1a;操作数和操作符结合的顺序大部分是从左到右结合性的&#xff0c;例如&#xff…

C++运算符的优先级

有括号的优先级最高&#xff0c;涉及对象的运算符&#xff1b; 自增自减&#xff0c;取值&#xff0c;取地址运算符&#xff0c;逻辑非&#xff0c;按位取反&#xff0c;强制类型转换&#xff0c;长度运算符 先乘除后加减 左移右移运算符 比较运算符、三目运算符 各类赋值运算…

运算符优先级

C语言中&#xff0c;运算符除了常见的三大类&#xff0c;算术运算符、关系运算符与逻辑运算符之外&#xff0c;还有一些用于完成特殊任务的运算符。 运算符的运算优先级共分为15 级&#xff0c;1 级最高&#xff0c;15 级最低。 在表达式中&#xff0c;优先级较高的先于优先级…

【博客624】MAC地址表、ARP表、路由表(RIB表)、转发表(FIB表)

MAC地址表、ARP表、路由表(RIB表/FIB表) MAC地址表 MAC地址表是交换机等网络设备记录MAC地址和端口的映射关系&#xff0c;代表了交换机从哪个端口学习到了某个MAC地址&#xff0c;交换机把这个信息记录下来&#xff0c;后续交换机需要转发数据的时候就可以根据报文的目的MAC地…

【新星计划-2023】什么是ARP?详解它的“解析过程”与“ARP表”。

一、什么是ARP ARP&#xff08;地址解析协议&#xff09;英文全称“Address Resolution Protocol”&#xff0c;是根据IP地址获取物理地址的一个TCP/IP协议。主机发送信息时将包含目标IP地址的ARP请求广播到局域网络上的所有主机&#xff0c;并接收返回消息&#xff0c;以此确…

ARP表 MAC表 路由表

ARP表是一个动态表&#xff0c;存储在计算机当中&#xff0c;目的是做一个ip地址与mac地址的对应。 假设在同一子网段&#xff0c;计算机A与计算机B通信 计算机A的ip地址192.168.0.1 MAC地址AA-AA-AA-AA-AA-AA 计算机B的ip地址已知192.168.0.2 MAC地址未知 那么计算机A就要…

Linux上ARP表,路由表的解释

为厘清数据在网络中的传输流向和具体的实现过程&#xff0c;现对ARP表&#xff0c;路由表进行学习如下。 ARP&#xff08;Address Resolution Protocol&#xff09;地址解析协议。IP数据包经常通过以太网进行发送&#xff0c;以32位的IP地址作为源/目的地址。但是以太网设备不能…

Windows7 arp表删除方法

Windows7 arp表删除方法 1 使用arp命令删除2 使用其他命令删除 Winsows7系统删除arp表有很多方法&#xff0c;这里介绍两种简单的方法&#xff0c;都是直接使用命令提示符应用&#xff08;cmd&#xff09;进行删除。 1 使用arp命令删除 1.打开cmd&#xff1a;直接在搜索中搜索…

FDB表-ARP表-路由表

简介 二层转发通过查询FDB表实现&#xff0c;不对报文做任何修改。三层转发通过查询路由表和arp表实现&#xff0c;路由表确定出接口&#xff0c;然后以出接口的MAC作为报文的源MAC&#xff1b;以报文的目的IP查询arp表获取下一跳MAC&#xff0c;以该MAC作为报文的目的MAC; 因…

转发表(MAC表)、ARP表、路由表总结

全文概要 计算机网络中一个关键步骤在于通信路径上不同节点对于流经本节点的数据包转发&#xff0c;常见的交换设备主要是交换机(第二层、三层)和路由器(第三层)&#xff0c;在实际运行时&#xff0c;它们各自维护一些表结构帮助完成数据包的正确寻址与转发&#xff0c;本文详…