前 10000 个素数的最有效代码?
Posted
技术标签:
【中文标题】前 10000 个素数的最有效代码?【英文标题】:Most efficient code for the first 10000 prime numbers? 【发布时间】:2010-09-05 06:19:40 【问题描述】:我想打印前 10000 个素数。 谁能给我最有效的代码? 澄清:
-
对于 n >10000,您的代码是否效率低并不重要。
代码大小无关紧要。
您不能以任何方式对值进行硬编码。
【问题讨论】:
请记住,找到前 10000 个素数是一项相对较小的任务。您可能会看到快速算法和慢速算法之间存在几秒钟的差异。 奇怪的是,这让我想起了 Project Euler 的问题 7:projecteuler.net/index.php?section=problems&id=7 @stalepretzel 例如,可以通过连续执行算法 1000 次来克服这种测量限制。 【参考方案1】:The Sieve of Atkin 可能就是你要找的,它的运行时间上限是 O(N/log log N)。
如果你只运行比 6 的倍数多 1 和少 1 的数字,它可能会更快,因为所有高于 3 的素数都与某个 6 的倍数相差 1。 Resource for my statement
【讨论】:
Eratosthenes 筛对于小 N 可能更快。请参阅我的答案。 虽然这是一个很好的答案,但两个 Sieves 只生成 [2, N] 范围内的素数,而不是 前 N 个素数。 丹尼尔:第 10,000 个素数小于 105,000,所以他只需对他的筛子进行硬编码即可达到 105,000。 @Daniel - 另见质数定理 - 具体来说,en.wikipedia.org/wiki/… 给出了第 N 个质数的理论下限和上限。 因为这被标记为“答案”:当 OP 想要一个小的固定 n 为 10000 的效率时,-1 用于谈论渐近线。-1 用于连接阿特金筛子而不用略读它:文章本身提到阿特金只是渐近地比埃拉托色尼好,但在实践中,如果在实施上付出同样的努力,它总是会变慢;对于 OP 的问题,Eratosthenes 使用更简单的代码速度要快几个数量级。关于 mod 6 车轮的评论没有多大意义:Atkin 已经基于 mod 30 车轮,所以没有办法用较小的车轮加速它。【参考方案2】:根本没有效率,但您可以使用正则表达式来测试素数。
/^1?$|^(11+?)\1+$/
这测试,对于由 k “1
”s 组成的字符串,k 是否为 非素数(即字符串是否由一个“1
”或任意数量的“1
”组成,可以表示为n元积)。
【讨论】:
【参考方案3】:我已经修改了 CodeProject 上的代码来创建以下内容:
ArrayList primeNumbers = new ArrayList();
for(int i = 2; primeNumbers.Count < 10000; i++)
bool divisible = false;
foreach(int number in primeNumbers)
if(i % number == 0)
divisible = true;
if(divisible == false)
primeNumbers.Add(i);
Console.Write(i + " ");
在我的 ASP.NET 服务器上进行测试需要大约 1 分钟才能运行。
【讨论】:
当你到达 number>squareroot(i) 时退出 foreach 循环,你可以加快速度。 1min for 10000 相当慢。在 C# 中(不使用并行 fors/foreachs),平均我得到 13 秒,最多 10,000,000。使用一个平行线,我在相同的范围内平均得到 10 秒。 这可以加快几个数量级:设置divisible = true
时中断,只处理超过2的奇数,以及其他几种技术中的任何一种包括@Clayton 提到的那个。当然不是“最有效的”。【参考方案4】:
我推荐一个筛子,Sieve of Eratosthenes 或 Sieve of Atkin.
筛子或埃拉托色尼可能是查找素数列表的最直观的方法。基本上你:
-
写下一个从 2 到您想要的任何限制的数字列表,比如 1000。
取第一个未被划掉的数字(对于第一次迭代,这是 2)并从列表中划掉该数字的所有倍数。
重复步骤 2,直到到达列表末尾。所有没有被划掉的数字都是素数。
显然有很多优化可以让这个算法运行得更快,但这是基本思想。
阿特金的筛子使用了类似的方法,但不幸的是,我对此了解的不够多,无法向您解释。但我确实知道,我链接的算法需要 8 秒才能计算出古代 Pentium II-350 上 1000000000 以内的所有素数
埃拉托色尼筛源代码:http://web.archive.org/web/20140705111241/http://primes.utm.edu/links/programs/sieves/Eratosthenes/C_source_code/
阿特金筛源代码:http://cr.yp.to/primegen.html
【讨论】:
"并划掉所有倍数" 但是如何找到这些倍数是一个关键问题,经常出错,所以你最终会得到一个试除法筛,这比筛子差得多Eratosthenes 渐近,在实践中也是如此,除了非常小的n
。【参考方案5】:
Sieve of Eratosthenes 是要走的路,因为它简单且速度快。我在 C 中的实现
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>
int main(void)
unsigned int lim, i, j;
printf("Find primes upto: ");
scanf("%d", &lim);
lim += 1;
bool *primes = calloc(lim, sizeof(bool));
unsigned int sqrtlim = sqrt(lim);
for (i = 2; i <= sqrtlim; i++)
if (!primes[i])
for (j = i * i; j < lim; j += i)
primes[j] = true;
printf("\nListing prime numbers between 2 and %d:\n\n", lim - 1);
for (i = 2; i < lim; i++)
if (!primes[i])
printf("%d\n", i);
return 0;
查找素数的 CPU 时间(在 Pentium Dual Core E2140 1.6 GHz 上,使用单核)
~ 4s for lim = 100,000,000
【讨论】:
lim=1_000_000 的时间是几点?不能同时是“ 名称primes
具有误导性,在您的代码中其含义为is_composite_number
。如果将malloc
替换为calloc
,则可以消除第一个循环。表达式j+=i
可以溢出大的lim
(在这种情况下你会错过一些素数)。
已修复。 calloc 和替代数组名称。我还认为 primes[] 很混乱,但想不出更好的名字。
用 calloc
替换循环现在可以在 ~4 秒内完成 lim = 100,000,000
这没有回答这个问题:他要求的是前 N 个素数,而不是直到 N 的所有素数...【参考方案6】:
GateKiller,在foreach
循环中将break
添加到if
怎么样?这会加快速度很多,因为如果像 6 可以被 2 整除,你不需要检查 3 和 5。(如果我有足够的声誉,我会投票支持你的解决方案:- ) ...)
ArrayList primeNumbers = new ArrayList();
for(int i = 2; primeNumbers.Count < 10000; i++)
bool divisible = false;
foreach(int number in primeNumbers)
if(i % number == 0)
divisible = true;
break;
if(divisible == false)
primeNumbers.Add(i);
Console.Write(i + " ");
【讨论】:
你仍然可以通过打破 if number > sqrt(i) 来大大加快速度。【参考方案7】:使用 GMP,可以编写以下内容:
#include <stdio.h>
#include <gmp.h>
int main()
mpz_t prime;
mpz_init(prime);
mpz_set_ui(prime, 1);
int i;
char* num = malloc(4000);
for(i=0; i<10000; i++)
mpz_nextprime(prime, prime);
printf("%s, ", mpz_get_str(NULL,10,prime));
在我的 2.33GHz Macbook Pro 上,它执行如下:
time ./a.out > /dev/null
real 0m0.033s
user 0m0.029s
sys 0m0.003s
在同一台笔记本电脑上计算 1,000,000 个素数:
time ./a.out > /dev/null
real 0m14.824s
user 0m14.606s
sys 0m0.086s
GMP 针对这类事情进行了高度优化。除非您真的想通过自己编写算法来理解算法,否则建议您使用 C 语言下的 libGMP。
【讨论】:
mpz_nextprime
一次计算一个素数,而不是一次用筛子计算所有素数【参考方案8】:
这并不严格违反硬编码限制,但非常接近。为什么不以编程方式下载此列表并将其打印出来呢?
http://primes.utm.edu/lists/small/10000.txt
【讨论】:
对于大多数计算机,计算这些值会比通过 Internet 下载它们所涉及的延迟更快。 但不是因为列表已在内存中准备好。他大概就是这个意思。 哈哈@krog。为什么您每次都要费心设置网络连接和所有爵士乐到 DL 静态文件?当然你会预先下载它,哎呀,甚至将它硬编码到一个数组中。【参考方案9】:@Matt: log(log(10000)) 是 ~2
来自***文章(您引用的)Sieve of Atkin:
这个筛子可以计算最多 N 个素数 使用
O(N/log log N)
操作 只有 N1/2+o(1) 位内存。那是 比筛子好一点 使用O(N)
的埃拉托色尼 操作和 O(N1/2(log log N)/log N) 内存位(A.O.L. Atkin, D.J. Bernstein, 2004)。这些渐近 计算复杂性包括 简单的优化,比如*** 因式分解和拆分 计算到更小的块。
鉴于O(N)
(对于 Eratosthenes)和 O(N/log(log(N)))
(对于 Atkin)的渐近计算复杂性,我们不能说(对于小的 N=10_000
)如果实施哪种算法会更快。
Achim Flammenkamp 在The Sieve of Eratosthenes 中写道:
引用:
@num1
对于大于 10^9 的间隔, 当然对于那些> 10 ^ 10,筛子 埃拉托色尼的表现优于 阿特金斯和伯恩斯坦筛 使用不可约二元二次 形式。参见他们的论文了解背景 信息以及第 5 段 W. Galway 博士论文。
因此,10_000
埃拉托色尼筛法可能比阿特金筛法更快。
回答OP的代码是prime_sieve.c(引用num1
)
【讨论】:
【参考方案10】:改编自GateKiller,这是我使用的最终版本。
public IEnumerable<long> PrimeNumbers(long number)
List<long> primes = new List<long>();
for (int i = 2; primes.Count < number; i++)
bool divisible = false;
foreach (int num in primes)
if (i % num == 0)
divisible = true;
if (num > Math.Sqrt(i))
break;
if (divisible == false)
primes.Add(i);
return primes;
基本相同,但我添加了“break on Sqrt”建议并更改了一些变量以使其更适合我。 (我正在研究欧拉,需要第 10001 个素数)
【讨论】:
【参考方案11】:筛子似乎是错误的答案。筛子为您提供 最多 个 N 的素数,而不是 前 N 个素数。运行@Imran 或@Andrew Szeto,你得到的素数最多为 N。
如果您继续尝试筛选越来越大的数字,直到您达到一定大小的结果集,并且使用一些已经获得的数字缓存,该筛选器可能仍然可用,但我相信它仍然不会比解决方案快就像@Pat's。
【讨论】:
所需的上限很容易估计,还有一些备用,如m = n(log n + log (log n))
,用于n>= 6
(参见wikipedia)。此外,Eratosthenes 的筛子可以重新设计为分段的,使其真正递增。【参考方案12】:
这是我几天前在 PowerShell 中编写的埃拉托色尼筛法。它有一个参数,用于标识应该返回的素数个数。
#
# generate a list of primes up to a specific target using a sieve of eratosthenes
#
function getPrimes #sieve of eratosthenes, http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
param ($target,$count = 0)
$sieveBound = [math]::ceiling(( $target - 1 ) / 2) #not storing evens so count is lower than $target
$sieve = @($false) * $sieveBound
$crossLimit = [math]::ceiling(( [math]::sqrt($target) - 1 ) / 2)
for ($i = 1; $i -le $crossLimit; $i ++)
if ($sieve[$i] -eq $false)
$prime = 2 * $i + 1
write-debug "Found: $prime"
for ($x = 2 * $i * ( $i + 1 ); $x -lt $sieveBound; $x += 2 * $i + 1)
$sieve[$x] = $true
$primes = @(2)
for ($i = 1; $i -le $sieveBound; $i ++)
if($count -gt 0 -and $primes.length -ge $count)
break;
if($sieve[$i] -eq $false)
$prime = 2 * $i + 1
write-debug "Output: $prime"
$primes += $prime
return $primes
【讨论】:
【参考方案13】:在 Python 中
import gmpy
p=1
for i in range(10000):
p=gmpy.next_prime(p)
print p
【讨论】:
【参考方案14】:我是用 python 写的,因为我刚开始学习它,它工作得很好。此代码生成的第 10,000 个素数与 http://primes.utm.edu/lists/small/10000.txt 中提到的相同。要检查n
是否为素数,请将n
除以2
到sqrt(n)
之间的数字。如果这个数字范围中的任何一个完美地整除n
,那么它就不是素数。
import math
print ("You want prime till which number??")
a = input()
a = int(a)
x = 0
x = int(x)
count = 1
print("2 is prime number")
for c in range(3,a+1):
b = math.sqrt(c)
b = int(b)
x = 0
for b in range(2,b+1):
e = c % b
e = int(e)
if (e == 0):
x = x+1
if (x == 0):
print("%d is prime number" % c)
count = count + 1
print("Total number of prime till %d is %d" % (a,count))
【讨论】:
【参考方案15】:这是我的 VB 2008 代码,它在我的工作笔记本电脑上在 1 分 27 秒内找到所有质数
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles
Button1.Click
Dim TestNum As Integer
Dim X As Integer
Dim Z As Integer
Dim TM As Single
Dim TS As Single
Dim TMS As Single
Dim UnPrime As Boolean
Dim Sentinal As Integer
Button1.Text = "Thinking"
Button1.Refresh()
Sentinal = Val(SentinalTxt.Text)
UnPrime = True
Primes(0) = 2
Primes(1) = 3
Z = 1
TM = TimeOfDay.Minute
TS = TimeOfDay.Second
TMS = TimeOfDay.Millisecond
For TestNum = 5 To Sentinal Step 2
Do While Primes(X) <> 0 And UnPrime And Primes(X) ^ 2 <= TestNum
If Int(TestNum / Primes(X)) - (TestNum / Primes(X)) = 0 Then
UnPrime = False
End If
X = X + 1
Loop
If UnPrime = True Then
X = X + 1
Z = Z + 1
Primes(Z) = TestNum
End If
UnPrime = True
X = 0
Next
Button1.Text = "Finished with " & Z
TM = TimeOfDay.Minute - TM
TS = TimeOfDay.Second - TS
TMS = TimeOfDay.Millisecond - TMS
ShowTime.Text = TM & ":" & TS & ":" & TMS
End Sub
【讨论】:
【参考方案16】:我花了一些时间编写一个计算大量素数的程序,这是我用来计算包含前 1.000.000.000 个素数的文本文件的代码。它是德语的,但有趣的部分是方法calcPrimes()
。素数存储在一个名为 Primzahlen 的数组中。我推荐使用 64 位 CPU,因为计算是使用 64 位整数。
import java.io.*;
class Primzahlengenerator
long[] Primzahlen;
int LastUnknown = 2;
public static void main(String[] args)
Primzahlengenerator Generator = new Primzahlengenerator();
switch(args.length)
case 0: //Wenn keine Argumente übergeben worden:
Generator.printHelp(); //Hilfe ausgeben
return; //Durchfallen verhindern
case 1:
try
Generator.Primzahlen = new long[Integer.decode(args[0]).intValue()];
catch (NumberFormatException e)
System.out.println("Das erste Argument muss eine Zahl sein, und nicht als Wort z.B. \"Tausend\", sondern in Ziffern z.B. \"1000\" ausgedrückt werden.");//Hinweis, wie man die Argumente angeben muss ausgeben
Generator.printHelp(); //Generelle Hilfe ausgeben
return;
break;//dutchfallen verhindern
case 2:
switch (args[1])
case "-l":
System.out.println("Sie müsen auch eine Datei angeben!"); //Hilfemitteilung ausgeben
Generator.printHelp(); //Generelle Hilfe ausgeben
return;
break;//durchfallen verhindern
case 3:
try
Generator.Primzahlen = new long[Integer.decode(args[0]).intValue()];
catch (NumberFormatException e)
System.out.println("Das erste Argument muss eine Zahl sein, und nicht als Wort z.B. \"Tausend\", sondern in Ziffern z.B. \"1000\" ausgedrückt werden.");//Hinweis, wie man die Argumente angeben muss ausgeben
Generator.printHelp(); //Generelle Hilfe ausgeben
return;
switch(args[1])
case "-l":
Generator.loadFromFile(args[2]);//Datei Namens des Inhalts von Argument 3 lesen, falls Argument 2 = "-l" ist
break;
default:
Generator.printHelp();
break;
break;
default:
Generator.printHelp();
return;
Generator.calcPrims();
void printHelp()
System.out.println("Sie müssen als erstes Argument angeben, die wieviel ersten Primzahlen sie berechnen wollen."); //Anleitung wie man das Programm mit Argumenten füttern muss
System.out.println("Als zweites Argument können sie \"-l\" wählen, worauf die Datei, aus der die Primzahlen geladen werden sollen,");
System.out.println("folgen muss. Sie muss genauso aufgebaut sein, wie eine Datei Primzahlen.txt, die durch den Aufruf \"java Primzahlengenerator 1000 > Primzahlen.txt\" entsteht.");
void loadFromFile(String File)
// System.out.println("Lese Datei namens: \"" + File + "\"");
try
int x = 0;
BufferedReader in = new BufferedReader(new FileReader(File));
String line;
while((line = in.readLine()) != null)
Primzahlen[x] = new Long(line).longValue();
x++;
LastUnknown = x;
catch(FileNotFoundException ex)
System.out.println("Die angegebene Datei existiert nicht. Bitte geben sie eine existierende Datei an.");
catch(IOException ex)
System.err.println(ex);
catch(ArrayIndexOutOfBoundsException ex)
System.out.println("Die Datei enthält mehr Primzahlen als der reservierte Speicherbereich aufnehmen kann. Bitte geben sie als erstes Argument eine größere Zahl an,");
System.out.println("damit alle in der Datei enthaltenen Primzahlen aufgenommen werden können.");
/* for(long prim : Primzahlen)
System.out.println("" + prim);
*/
//Hier soll code stehen, der von der Datei mit angegebenem Namen ( Wie diese aussieht einfach durch angeben von folgendem in cmd rausfinden:
//java Primzahlengenerator 1000 > 1000Primzahlen.txt
//da kommt ne textdatei, die die primzahlen enthält. mit Long.decode(String ziffern).longValue();
//erhält man das was an der entsprechenden stelle in das array soll. die erste zeile soll in [0] , die zweite zeile in [1] und so weiter.
//falls im arry der platz aus geht(die exception kenn ich grad nich, aber mach mal:
//int[] foo = 1, 2, 3;
//int bar = foo[4];
//dann kriegst ne exception, das ist die gleiche die man kriegt, wenn im arry der platzt aus geht.
void calcPrims()
int PrimzahlNummer = LastUnknown;
// System.out.println("LAstUnknown ist: " + LastUnknown);
Primzahlen[0] = 2;
Primzahlen[1] = 3;
long AktuelleZahl = Primzahlen[PrimzahlNummer - 1];
boolean IstPrimzahl;
// System.out.println("2");
// System.out.println("3");
int Limit = Primzahlen.length;
while(PrimzahlNummer < Limit)
IstPrimzahl = true;
double WurzelDerAktuellenZahl = java.lang.Math.sqrt(AktuelleZahl);
for(int i = 1;i < PrimzahlNummer;i++)
if(AktuelleZahl % Primzahlen[i] == 0)
IstPrimzahl = false;
break;
if(Primzahlen[i] > WurzelDerAktuellenZahl) break;
if(IstPrimzahl)
Primzahlen[PrimzahlNummer] = AktuelleZahl;
PrimzahlNummer++;
// System.out.println("" + AktuelleZahl);
AktuelleZahl = AktuelleZahl + 2;
for(long prim : Primzahlen)
System.out.println("" + prim);
【讨论】:
【参考方案17】:在 Haskell 中,我们几乎可以逐字记下 Eratosthenes 筛的数学定义,“素数是大于 1 的自然数,没有任何合数,通过枚举每个素数的倍数来找到合数 em>":
import Data.List.Ordered (minus, union)
primes = 2 : minus [3..] (foldr (\p r -> p*p : union [p*p+p, p*p+2*p..] r)
[] primes)
primes !! 10000
几乎是瞬时的。
参考资料:
Sieve of Eratosthenes Richard Bird's sieve (see pp. 10,11) minus, union上面的代码很容易调整为只处理赔率,primes = 2 : 3 : minus [5,7..] (foldr (\p r -> p*p : union [p*p+2*p, p*p+4*p..] r) [] (tail primes))
。通过折叠成树状结构,时间复杂度得到了很大改善(比最佳值高出一个 log 因子),空间复杂度为 drastically improved by multistage primes production,in
primes = 2 : _Y ( (3:) . sieve 5 . _U . map (\p -> [p*p, p*p+2*p..]) )
where
_Y g = g (_Y g) -- non-sharing fixpoint combinator
_U ((x:xs):t) = x : (union xs . _U . pairs) t -- ~= nub.sort.concat
pairs (xs:ys:t) = union xs ys : pairs t
sieve k s@(x:xs) | k < x = k : sieve (k+2) s -- ~= [k,k+2..]\\s,
| otherwise = sieve (k+2) xs -- when s⊂[k,k+2..]
(在 Haskell 中,括号用于分组,函数调用仅通过并列表示,(:)
是列表的 cons 运算符,并且(.)
是一个函数组合运算符:(f . g) x = (\y -> f (g y)) x = f (g x)
)。
【讨论】:
最近我想出了一个modified version of Sieve of Sundaram,它的性能似乎是埃拉托色尼筛的两倍。我已经在 JS 中实现了它,但我很想看看它在 Haskell 中的效果(我目前正在学习)。如果值得,您可以检查一下并将其包含在您的答案中。 @Redu s。 Sundaram 的算法应该不如 s。的 E.,仅限于奇数,而 s。可以很容易地修改 E. 以使用更高的***,例如 2,3,5,7-coprimes 甚至更高。还有,可以分段吗?这种改进也很关键。 这种“改良”的 Sundaram 筛分技术真是太酷了。你详细阅读过my answer吗?请在可能的情况下抽出一些时间阅读。它真的很快,也可以分段。结果证明它是我能找到的所有 JS 算法中最快的。老实说,你可能是我唯一可以依靠这个社会进行公平评价的人。 :) @Redu 你说得对,我还没读过。现在我必须这样做! :)【参考方案18】:using System;
namespace ConsoleApplication2
class Program
static void Main(string[] args)
int n, i = 3, j, c;
Console.WriteLine("Please enter your integer: ");
n = Convert.ToInt32(Console.ReadLine());
if (n >= 1)
Console.WriteLine("First " + n + " Prime Numbers are");
Console.WriteLine("2");
for(j=2;j<=n;)
for(c=2;c<=i-1;c++)
if(i%c==0)
break;
if(c==i)
Console.WriteLine(i);
j++;
i++;
Console.Read();
【讨论】:
【参考方案19】:以下 Mathcad 代码在 3 分钟内计算出前一百万个素数。
请记住,这将对所有数字使用浮点双精度,并且基本上是解释的。我希望语法清晰。
【讨论】:
【参考方案20】:这是一个 C++ 解决方案,使用 SoE 的一种形式:
#include <iostream>
#include <deque>
typedef std::deque<int> mydeque;
void my_insert( mydeque & factors, int factor )
int where = factor, count = factors.size();
while( where < count && factors[where] ) where += factor;
if( where >= count ) factors.resize( where + 1 );
factors[ where ] = factor;
int main()
mydeque primes;
mydeque factors;
int a_prime = 3, a_square_prime = 9, maybe_prime = 3;
int cnt = 2;
factors.resize(3);
std::cout << "2 3 ";
while( cnt < 10000 )
int factor = factors.front();
maybe_prime += 2;
if( factor )
my_insert( factors, factor );
else if( maybe_prime < a_square_prime )
std::cout << maybe_prime << " ";
primes.push_back( maybe_prime );
++cnt;
else
my_insert( factors, a_prime );
a_prime = primes.front();
primes.pop_front();
a_square_prime = a_prime * a_prime;
factors.pop_front();
std::cout << std::endl;
return 0;
请注意,此版本的 Sieve 可以无限计算素数。
另请注意,STL deque
需要 O(1)
时间来执行 push_back
、pop_front
以及通过下标进行随机访问。
resize
操作需要O(n)
时间,其中n
是要添加的元素数。由于我们使用此函数的方式,我们可以将其视为一个小常数。
my_insert
中的while
循环体被执行O(log log n)
次,其中n
等于变量maybe_prime
。这是因为while
的条件表达式对于maybe_prime
的每个素数因子都会计算一次为真。请参阅***上的“Divisor function”。
乘以my_insert
被调用的次数,表明它应该花费O(n log log n)
时间来列出n
素数...毫不奇怪,这就是埃拉托色尼筛应该具有的时间复杂度.
然而,虽然这段代码是高效的,但它并不是最高效的...我强烈建议使用专门的库来生成素数,例如primesieve .任何真正高效、优化良好的解决方案都需要比任何人都想在 *** 中输入更多的代码。
【讨论】:
你自己想出了算法吗? -- 你能补充几个关于它是如何工作的 cmets 吗? @Will:这是双端筛。它非常优雅但效率相当低,因此它主要是像阿特金筛子一样的智力好奇心。与后者不同,它在某些情况下实际上很有用,因为它在 L1 缓存中的停留时间比简单的、未分段的筛子要长得多,并且比迭代的分段筛子稍微简单一些(假设有足够的双端队列实现可用)。我在网上找不到任何好的论述,所以我将其作为单独的答案写出来。 related comment. @DarthGizka 我已经编写了它的 Haskell 类等效代码(计数,减去双端队列)。这是at the bottom here。它的可读性不是很好。 @DarthGizka 不,这很简单;重复了haskellwiki prime numbers page 中的大部分内容。新旧事物按个数计算:tail(cycle (1:replicate(p-1)0))
。而不是一个双端队列(可破坏地)插入新的p
s,而是为每个p
拥有自己的(不可变的)1,0,0,1,0,0,1,...
流(即3),并将它们与zipWith
成对@一起粉碎987654349@ 等),在将前缀从一个素数的平方跳到下一个素数的平方之后。而[x|u==0]
保留仍然存在的 0 数字。【参考方案21】:
deque sieve algorithm mentioned by BenGoldberg 值得仔细研究一下,不仅因为它非常优雅,还因为它偶尔会在实践中有用(不像阿特金筛子,这是一个纯粹的学术练习)。
双端队列筛算法背后的基本思想是使用一个小的滑动筛,它的大小仅足以包含每个当前“活动”素数因子的至少一个单独的倍数 - 即平方不超过的素数当前由移动筛子表示的最小数字。与 SoE 的另一个区别是,deque sieve 将实际因子存储到组合的槽中,而不是布尔值。
该算法会根据需要扩展筛子窗口的大小,从而在很宽的范围内实现相当均匀的性能,直到筛子开始明显超过 CPU 的 L1 高速缓存的容量。最后一个完全拟合的素数是 25,237,523(第 1,579,791 个素数),这为算法的合理操作范围提供了一个粗略的数字。
该算法相当简单且稳健,其性能甚至比未分段的埃拉托色尼筛法更广泛。后者要快得多,只要它的筛子完全适合缓存,即对于具有字节大小布尔值的仅赔率筛子,最多 2^16。然后它的性能越来越下降,尽管它总是比双端队列快得多,尽管有障碍(至少在 C/C++、Pascal 或 Java/C# 等编译语言中)。
这是 C# 中 deque sieve 算法的渲染,因为我发现这种语言 - 尽管有许多缺陷 - 对于原型算法和实验来说比极其繁琐和迂腐的 C++ 更实用。 (旁注:我使用的是免费的LINQPad,它可以让我直接投入其中,而不会因为设置项目、makefile、目录或其他东西而陷入混乱,它给了我与python 提示)。
C# 没有显式的双端队列类型,但普通的 List<int>
可以很好地演示算法。
注意:这个版本没有对素数使用双端队列,因为从 n 个素数中弹出 sqrt(n) 根本没有意义。去掉 100 个素数并留下 9900 有什么好处?至少通过这种方式,所有素数都被收集在一个整洁的向量中,为进一步处理做好准备。
static List<int> deque_sieve (int n = 10000)
Trace.Assert(n >= 3);
var primes = new List<int>() 2, 3 ;
var sieve = new List<int>() 0, 0, 0 ;
for (int sieve_base = 5, current_prime_index = 1, current_prime_squared = 9; ; )
int base_factor = sieve[0];
if (base_factor != 0)
// the sieve base has a non-trivial factor - put that factor back into circulation
mark_next_unmarked_multiple(sieve, base_factor);
else if (sieve_base < current_prime_squared) // no non-trivial factor -> found a non-composite
primes.Add(sieve_base);
if (primes.Count == n)
return primes;
else // sieve_base == current_prime_squared
// bring the current prime into circulation by injecting it into the sieve ...
mark_next_unmarked_multiple(sieve, primes[current_prime_index]);
// ... and elect a new current prime
current_prime_squared = square(primes[++current_prime_index]);
// slide the sieve one step forward
sieve.RemoveAt(0); sieve_base += 2;
这是两个辅助函数:
static void mark_next_unmarked_multiple (List<int> sieve, int prime)
int i = prime, e = sieve.Count;
while (i < e && sieve[i] != 0)
i += prime;
for ( ; e <= i; ++e) // no List<>.Resize()...
sieve.Add(0);
sieve[i] = prime;
static int square (int n)
return n * n;
理解该算法的最简单方法可能是将其想象为一个特殊的分段埃拉托色尼筛,分段大小为 1,伴随着一个溢出区域,当素数射过分段末端时,它们会在该溢出区域停止。除了段的单个单元格(a.k.a.sieve[0]
)在我们到达它时已经被筛分,因为它在溢出区域的一部分时被碾压了。
sieve[0]
表示的数字保存在sieve_base
中,尽管sieve_front
或window_base
也是一个很好的名称,可以与 Ben 的代码或分段/窗口筛的实现相提并论。
如果sieve[0]
包含一个非零值,则该值是sieve_base
的因子,因此可以识别为复合值。由于单元 0 是该因子的倍数,因此很容易计算其下一跳,即 0 加上该因子。如果该单元格已经被另一个因子占据,那么我们只需再次添加该因子,依此类推,直到我们找到当前没有其他因子停放的因子的倍数(如果需要,扩展筛子)。这也意味着不需要像在正常的分段筛中那样存储从一个段到下一个段的各种素数的当前工作偏移量。每当我们在sieve[0]
中找到一个因子时,它的当前工作偏移量就是0。
当前素数通过以下方式发挥作用。一个素数只有在它自己出现在流中之后才能成为当前的(即当它被检测为素数时,因为没有用因子标记),并且它将保持最新状态直到sieve[0]
到达它的正方形的确切时刻。由于较小的素数的活动,这个素数的所有较低倍数都必须被剔除,就像在正常的 SoE 中一样。但是没有一个较小的素数可以从正方形中删除,因为正方形的唯一因素是素数本身,并且此时它还没有流通。这解释了算法在sieve_base == current_prime_squared
的情况下采取的行动(顺便说一下,这意味着sieve[0] == 0
)。
现在sieve[0] == 0 && sieve_base < current_prime_squared
的情况很容易解释:这意味着sieve_base
不能是任何小于当前素数的素数的倍数,否则它会被标记为合数。 I 也不能是当前素数的更高倍数,因为它的值小于当前素数的平方。因此它一定是一个新的素数。
该算法显然受到埃拉托色尼筛法的启发,但同样明显的是,它非常不同。埃拉托色尼筛法的卓越速度源于其基本操作的简单性:一个单一的索引添加和一个操作的每个步骤的存储是它在很长一段时间内所做的一切。
这是一个简单的、未分段的 Eratosthenes 筛,我通常使用它来筛选 ushort 范围内的因子素数,即高达 2^16。对于这篇文章,我通过将 int
替换为 ushort
将其修改为超过 2^16
static List<int> small_odd_primes_up_to (int n)
var result = new List<int>();
if (n < 3)
return result;
int sqrt_n_halved = (int)(Math.Sqrt(n) - 1) >> 1, max_bit = (n - 1) >> 1;
var odd_composite = new bool[max_bit + 1];
for (int i = 3 >> 1; i <= sqrt_n_halved; ++i)
if (!odd_composite[i])
for (int p = (i << 1) + 1, j = p * p >> 1; j <= max_bit; j += p)
odd_composite[j] = true;
result.Add(3); // needs to be handled separately because of the mod 3 wheel
// read out the sieved primes
for (int i = 5 >> 1, d = 1; i <= max_bit; i += d, d ^= 3)
if (!odd_composite[i])
result.Add((i << 1) + 1);
return result;
在筛选前 10000 个素数时,将超出 32 KiByte 的典型 L1 缓存,但该函数仍然非常快(即使在 C# 中也只有几分之一毫秒)。
如果您将此代码与 deque sieve 进行比较,那么很容易看出 deque sieve 的操作要复杂得多,并且它无法有效地摊销其开销,因为它总是尽可能缩短交叉点连续(在跳过所有已经被划掉的倍数之后,恰好是一个划线)。
注意:C# 代码使用int
而不是uint
,因为较新的编译器习惯于为uint
生成不合标准的代码,可能是为了将人们推向有符号整数......在C++ 版本的上面的代码我自然而然地使用了unsigned
;基准必须在 C++ 中,因为我希望它基于所谓的足够的双端队列类型(std::deque<unsigned>
;使用unsigned short
没有性能提升)。以下是我的 Haswell 笔记本电脑 (VC++ 2015/x64) 的编号:
deque vs simple: 1.802 ms vs 0.182 ms
deque vs simple: 1.836 ms vs 0.170 ms
deque vs simple: 1.729 ms vs 0.173 ms
注意:C# 时间几乎是 C++ 时间的两倍,这对 C# 来说非常好,并且表明 List<int>
即使被用作双端队列也不会懈怠。
即使双端队列已经超出正常工作范围(L1 缓存大小超过 50%,伴随着缓存抖动),简单的 sieve 代码仍然会将 deque 排除在外。这里的主要部分是筛选出的素数的读取,这不受缓存问题的影响很大。在任何情况下,该函数都是为筛选因素的因素而设计的,即 3 级筛选层次结构中的 0 级,通常它只需要返回几百个因素或少量的数千个因素。因此它很简单。
通过使用分段筛并优化用于提取已筛出的素数的代码(步进 mod 3 并展开两次,或 mod 15 并展开一次),性能可以提高一个数量级以上,而且还可以提高性能通过使用带有所有装饰的 mod 16 或 mod 30 ***从代码中挤出(即所有残留物的完全展开)。我在 Code Review 上对 Find prime positioned prime number 的回答中解释了类似的问题,其中讨论了类似的问题。但是很难看出为一次性任务改进亚毫秒时间的意义...
为了让事情更直观,这里是筛选多达 100,000,000 个的 C++ 时间:
deque vs simple: 1895.521 ms vs 432.763 ms
deque vs simple: 1847.594 ms vs 429.766 ms
deque vs simple: 1859.462 ms vs 430.625 ms
相比之下,C# 中带有一些花里胡哨的分段筛子在 95 毫秒内完成相同的工作(没有可用的 C++ 计时,因为我目前只在 C# 中进行代码挑战)。
在 Python 这样的解释型语言中,情况可能看起来完全不同,其中每个操作都有很高的成本,并且解释器开销使由于预测与错误预测的分支或子循环操作(移位、加法)与多循环造成的所有差异相形见绌ops(乘法,甚至除法)。这势必会削弱埃拉托色尼筛法的简单性优势,这可能会使双端队列解决方案更具吸引力。
此外,其他受访者在该主题中报告的许多时间可能都以输出时间为主。那是一场完全不同的战争,我的主要武器是这样一个简单的类:
class CCWriter
const int SPACE_RESERVE = 11; // UInt31 + '\n'
public static System.IO.Stream BaseStream;
static byte[] m_buffer = new byte[1 << 16]; // need 55k..60k for a maximum-size range
static int m_write_pos = 0;
public static long BytesWritten = 0; // for statistics
internal static ushort[] m_double_digit_lookup = create_double_digit_lookup();
internal static ushort[] create_double_digit_lookup ()
var lookup = new ushort[100];
for (int lo = 0; lo < 10; ++lo)
for (int hi = 0; hi < 10; ++hi)
lookup[hi * 10 + lo] = (ushort)(0x3030 + (hi << 8) + lo);
return lookup;
public static void Flush ()
if (BaseStream != null && m_write_pos > 0)
BaseStream.Write(m_buffer, 0, m_write_pos);
BytesWritten += m_write_pos;
m_write_pos = 0;
public static void WriteLine ()
if (m_buffer.Length - m_write_pos < 1)
Flush();
m_buffer[m_write_pos++] = (byte)'\n';
public static void WriteLinesSorted (int[] values, int count)
int digits = 1, max_value = 9;
for (int i = 0; i < count; ++i)
int x = values[i];
if (m_buffer.Length - m_write_pos < SPACE_RESERVE)
Flush();
while (x > max_value)
if (++digits < 10)
max_value = max_value * 10 + 9;
else
max_value = int.MaxValue;
int n = x, p = m_write_pos + digits, e = p + 1;
m_buffer[p] = (byte)'\n';
while (n >= 10)
int q = n / 100, w = m_double_digit_lookup[n - q * 100];
n = q;
m_buffer[--p] = (byte)w;
m_buffer[--p] = (byte)(w >> 8);
if (n != 0 || x == 0)
m_buffer[--p] = (byte)((byte)'0' + n);
m_write_pos = e;
写入 10000 个(排序后的)数字需要不到 1 毫秒的时间。它是一个静态类,因为它旨在将文本包含在编码挑战提交中,以最少的麻烦和零开销。
总的来说,我发现如果集中工作在整个批次上完成,它会快得多,这意味着筛选某个范围,然后将所有素数提取到向量/数组中,然后爆破整个数组,然后筛选下一个范围等等,而不是将所有内容混合在一起。具有专注于特定任务的单独功能还可以更容易地混合和匹配,它可以重用,并且可以简化开发/测试。
【讨论】:
我希望有一个描述性的伪代码,无法理解他的代码的特性(factors.resize(3)
后跟 int factor = factors.front();
... 没有看到任何东西放入双端队列,所以他得到了什么出来了吗?...)。将不得不研究您的代码,希望它会更清晰,更直接。
@Will: resize(3)
在空的双端队列或向量上具有将其初始化为三个零的效果,就像我的代码对初始化器 0, 0, 0
所做的那样。进入算法的最简单方法是进行几次迭代的心理符号执行,或者将其加载到 LINQPad 并调试(即观察它的工作)。希望我的代码应该比 Ben 的代码更容易一些......另外:在给定插槽中存储一个因子不仅将插槽标记为复合(即该因子的倍数);它也是存储该因子的唯一位置,并且隐含地是该因子的“工作偏移量”。
...也许是为了提高效率而这样做(相比之下,PQ 可能表现不佳?... OTOH 这个筛子需要更多空间)。 “通常的滑动筛子”是指例如this one, in Python。 --- 那么,你知道这个筛子的来源吗?我也在本的回答下问过这个问题,但他还没有回应。它是众所周知的,以某种方式出名吗?..
当然,在 Haskell 中,它是真正具有启发性的单行 2 : fix ( (3:) . minus [5,7..] . unionAll . map (\p-> [p*p, p*p+2*p..]) )
(使用 Data.List.Ordered
module 的 minus
和 unionAll
即)与 Haskell 的 laziness 保持“本地”。同样,不是表现过度,而是很有启发性。 :) 再次感谢您的指点。
@Will:deque sieve 与 Bennion's Hopping Sieve 有很多共同点(更多信息包括 Will Galway 的 SieveStuff page 中的 C 源代码),后者由 Robert Bennion 在 1970 年代开发。无论如何,这对每个鉴赏家来说都是一本有趣的书!【参考方案22】:
使用埃拉托色尼筛法,与“已知范围”的素数算法相比,计算速度要快得多。
通过使用它的 wiki (https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes) 中的伪代码,我可以在 C# 上找到解决方案。
/// Get non-negative prime numbers until n using Sieve of Eratosthenes.
public int[] GetPrimes(int n)
if (n <= 1)
return new int[] ;
var mark = new bool[n];
for(var i = 2; i < n; i++)
mark[i] = true;
for (var i = 2; i < Math.Sqrt(n); i++)
if (mark[i])
for (var j = (i * i); j < n; j += i)
mark[j] = false;
var primes = new List<int>();
for(var i = 3; i < n; i++)
if (mark[i])
primes.Add(i);
return primes.ToArray();
GetPrimes(100000000) 需要 2 秒和 330 毫秒。
注意:数值可能因硬件规格而异。
【讨论】:
您可以引入一个新的 varcount=0
并在每次将 true mark[j]
设置为 false
时递增它。这样您最终将获得正确的计数,并且可以直接分配数组,而无需先创建列表。
是的,优化素数变量的分配。谢谢!【参考方案23】:
我在寻找素数方面已经工作了大约一年。这是我发现最快的:
import static java.lang.Math.sqrt;
import java.io.PrintWriter;
import java.io.File;
public class finder
public static void main(String[] args)
primelist primes = new primelist();
primes.insert(3);
primes.insert(5);
File file = new File("C:/Users/Richard/Desktop/directory/file0024.txt");
file.getParentFile().mkdirs();
long time = System.nanoTime();
try
PrintWriter printWriter = new PrintWriter ("file0024.txt");
int linenum = 0;
printWriter.print("2");
printWriter.print (" , ");
printWriter.print("3");
printWriter.print (" , ");
int up;
int down;
for(int i =1; i<357913941;i++)//
if(linenum%10000==0)
printWriter.println ("");
linenum++;
down = i*6-1;
if(primes.check(down))
primes.insert(down);
//System.out.println(i*6-1);
printWriter.print ( down );
printWriter.print (" , ");
linenum++;
up = i*6+1;
if(primes.check(up))
primes.insert(up);
//System.out.println(i*6+1);
printWriter.print ( up );
printWriter.print (" , ");
linenum++;
printWriter.println ("Time to execute");
printWriter.println (System.nanoTime()-time);
//System.out.println(primes.length);
printWriter.close ();
catch(Exception e)
class node
node next;
int x;
public node ()
node next;
x = 3;
public node(int z)
node next;
x = z;
class primelist
node first;
int length =0;
node current;
public void insert(int x)
node y = new node(x);
if(current == null)
current = y;
first = y;
else
current.next = y;
current = y;
length++;
public boolean check(int x)
int p = (int)sqrt(x);
node y = first;
for(int i = 0;i<length;i++)
if(y.x>p)
return true;
else if(x%y.x ==0)
return false;
y = y.next;
return true;
1902465190909 纳秒从 2 点开始到达 2147483629。
【讨论】:
【参考方案24】:这是我找到的代码 在我的笔记本电脑上,前 10,000 个素数在 0.049655 秒内,前 1,000,000 个素数在 6 秒内,前 2,000,000 个在 15 秒内 一点解释。此方法使用 2 种技术来查找素数
-
首先,任何非素数都是素数的倍数,因此此代码通过将测试数除以较小的素数而不是任何数字进行测试,这对于 4 位数字减少了至少 10 倍的计算,并且更大的数字甚至更多
其次,除了除以素数外,它只除以小于或等于被测数根的素数,这进一步减少了计算,这是因为任何大于根数的数都会有一个对应的数字必须小于数字的根,但由于我们已经测试了所有小于根的数字,因此我们不需要为大于被测试数字的根的数字而烦恼。
前 10,000 个素数的样本输出https://drive.google.com/open?id=0B2QYXBiLI-lZMUpCNFhZeUphck0 https://drive.google.com/open?id=0B2QYXBiLI-lZbmRtTkZETnp6Ykk
这是C语言的代码, 输入 1,然后输入 10,000 以打印出前 10,000 个素数。 编辑:我忘记了这个包含数学库,如果你在 Windows 或 Visual Studio 上应该没问题,但在 linux 上你必须使用 -lm 参数编译代码,否则代码可能不起作用 示例:gcc -Wall -o "%e" "%f" -lm
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <limits.h>
/* Finding prime numbers */
int main()
//pre-phase
char d,w;
int l,o;
printf(" 1. Find first n number of prime numbers or Find all prime numbers smaller than n ?\n"); // this question helps in setting the limits on m or n value i.e l or o
printf(" Enter 1 or 2 to get anwser of first or second question\n");
// decision making
do
printf(" -->");
scanf("%c",&d);
while ((w=getchar()) != '\n' && w != EOF);
if ( d == '1')
printf("\n 2. Enter the target no. of primes you will like to find from 3 to 2,000,000 range\n -->");
scanf("%10d",&l);
o=INT_MAX;
printf(" Here we go!\n\n");
break;
else if ( d == '2' )
printf("\n 2.Enter the limit under which to find prime numbers from 5 to 2,000,000 range\n -->");
scanf("%10d",&o);
l=o/log(o)*1.25;
printf(" Here we go!\n\n");
break;
else printf("\n Try again\n");
while ( d != '1' || d != '2' );
clock_t start, end;
double cpu_time_used;
start = clock(); /* starting the clock for time keeping */
// main program starts here
int i,j,c,m,n; /* i ,j , c and m are all prime array 'p' variables and n is the number that is being tested */
int s,x;
int p[ l ]; /* p is the array for storing prime numbers and l sets the array size, l was initialized in pre-phase */
p[1]=2;
p[2]=3;
p[3]=5;
printf("%10dst:%10d\n%10dnd:%10d\n%10drd:%10d\n",1,p[1],2,p[2],3,p[3]); // first three prime are set
for ( i=4;i<=l;++i ) /* this loop sets all the prime numbers greater than 5 in the p array to 0 */
p[i]=0;
n=6; /* prime number testing begins with number 6 but this can lowered if you wish but you must remember to update other variables too */
s=sqrt(n); /* 's' does two things it stores the root value so that program does not have to calaculate it again and again and also it stores it in integer form instead of float*/
x=2; /* 'x' is the biggest prime number that is smaller or equal to root of the number 'n' being tested */
/* j ,x and c are related in this way, p[j] <= prime number x <= p[c] */
// the main loop begins here
for ( m=4,j=1,c=2; m<=l && n <= o;)
/* this condition checks if all the first 'l' numbers of primes are found or n does not exceed the set limit o */
// this will divide n by prime number in p[j] and tries to rule out non-primes
if ( n%p[j]==0 )
/* these steps execute if the number n is found to be non-prime */
++n; /* this increases n by 1 and therefore sets the next number 'n' to be tested */
s=sqrt(n); /* this calaulates and stores in 's' the new root of number 'n' */
if ( p[c] <= s && p[c] != x ) /* 'The Magic Setting' tests the next prime number candidate p[c] and if passed it updates the prime number x */
x=p[c];
++c;
j=1;
/* these steps sets the next number n to be tested and finds the next prime number x if possible for the new number 'n' and also resets j to 1 for the new cycle */
continue; /* and this restarts the loop for the new cycle */
// confirmation test for the prime number candidate n
else if ( n%p[j]!=0 && p[j]==x )
/* these steps execute if the number is found to be prime */
p[m]=n;
printf("%10dth:%10d\n",m,p[m]);
++n;
s = sqrt(n);
++m;
j=1;
/* these steps stores and prints the new prime number and moves the 'm' counter up and also sets the next number n to be tested and also resets j to 1 for the new cycle */
continue; /* and this restarts the loop */
/* the next number which will be a even and non-prime will trigger the magic setting in the next cycle and therfore we do not have to add another magic setting here*/
++j; /* increases p[j] to next prime number in the array for the next cycle testing of the number 'n' */
// if the cycle reaches this point that means the number 'n' was neither divisible by p[j] nor was it a prime number
// and therfore it will test the same number 'n' again in the next cycle with a bigger prime number
// the loops ends
printf(" All done !!\n");
end = clock();
cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;
printf(" Time taken : %lf sec\n",cpu_time_used);
【讨论】:
【参考方案25】:这是我制作的代码:
enter code here
#include <cmath>
#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int main()
/* Enter your code here. Read input from STDIN. Print output to STDOUT*/
unsigned long int n;
int prime(unsigned long int);
scanf("%ld",&n);
unsigned long int val;
for(unsigned long int i=0;i<n;i++)
int flag=0;
scanf("%ld",&val);
flag=prime(val);
if(flag==1)
printf("yes\n");
else
printf("no\n");
return 0;
int prime(unsigned long int n)
if(n==2) return 1;
else if (n == 1||n%2==0) return 0;
for (unsigned long int i=3; i<=sqrt(n); i+=2)
if (n%i == 0)
return 0;
return 1;
【讨论】:
【参考方案26】:在 javascript 中使用 Array.prototype.find() 方法。 2214.486 毫秒
function isPrime (number)
function prime(element)
let start = 2;
while (start <= Math.sqrt(element))
if (element % start++ < 1)
return false;
return element > 1;
return [number].find(prime)
function logPrimes (n)
let count = 0
let nth = n
let i = 0
while (count < nth)
if (isPrime(i))
count++
console.log('i', i) //NOTE: If this line is ommited time to find 10,000th prime is 121.157ms
if (count === nth)
console.log('while i', i)
console.log('count', count)
i++
console.time(logPrimes)
logPrimes(10000)
console.timeEnd(logPrimes) // 2214.486ms
【讨论】:
【参考方案27】:我可以给你一些提示,你必须执行它。
-
对于每个数字,获取该数字的一半。例如。对于检查 21,仅通过将其从范围 2-10 中除来获得余数。
如果是奇数,只除以奇数,反之亦然。如 21,只除以 3、5、7、9。
迄今为止我发现的最有效的方法。
【讨论】:
【参考方案28】:因为你只想要前 10000 个素数,而不是编码复杂的算法,我建议 以下
boolean isPrime(int n)
//even but is prime
if(n==2)
return true;
//even numbers filtered already
if(n==0 || n==1 || n%2==0)
return false;
// loop for checking only odd factors
// i*i <= n (same as i<=sqrt(n), avoiding floating point calculations)
for(int i=3 ; i*i <=n ; i+=2)
// if any odd factor divides n then its not a prime!
if(n%i==0)
return false;
// its prime now
return true;
现在通话是您需要的首选
for(int i=1 ; i<=1000 ; i++)
if(isPrime(i))
//do something
【讨论】:
【参考方案29】:这是一个老问题,但这里有一些每个人都缺少的东西......
对于这么小的素数,试除法并没有那么慢...只有 25 个低于 100 的素数。由于要测试的素数如此之少,而且素数如此之小,我们可以得出一个巧妙的把戏!
如果 a 与 b 互质,则 gcd a b = 1。互质。有趣的词。意味着它不共享任何主要因素。因此,我们可以通过一次 GCD 调用来测试多个素数的可分性。多少?嗯,前 15 个素数的乘积小于 2^64。并且接下来10的乘积也小于2^64。这就是我们需要的全部 25 个。但值得吗?
让我们看看:
check x = null $ filter ((==0) . (x `mod`)) $ [<primes up to 101>]
Prelude> length $ filter check [101,103..85600]
>>> 9975
(0.30 secs, 125,865,152 bytes
a = 16294579238595022365 :: Word64
b = 14290787196698157718
pre = [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]
primes = (pre ++) $ filter ((==1) . gcd a) $ filter ((==1) . gcd b) [99,101..85600]
main = print $ length primes
Prelude> main
>>> 10000
(0.05 secs, 36,387,520 bytes)
那里提高了 6 倍。
(length
是强制计算列表。默认情况下,Haskell 一次打印 1 个 Unicode 字符,因此实际上 打印列表将支配时间或支配数量实际使用的代码。)
当然,这是在 GHCi 中运行的——一个运行解释代码的 repl——在一台旧笔记本电脑上,它不会将这些数字中的任何一个解释为 int64
s 甚至 BigInt
s,即使你问它也不会它(嗯,你可以强制它,但它很难看,并没有真正的帮助)。它将那里的每一个数字解释为可以通过字典查找专门用于某些特定类型的通用 Integer-like 事物,并且它正在遍历一个链表(这里没有融合,因为它没有被编译) 3次。有趣的是,手动融合两个过滤器实际上会减慢 REPL 中的速度。
让我们编译它:
...\Haskell\8.6\Testbed>Primes.exe +RTS -s
10000
606,280 bytes allocated in the heap
Total time 0.000s ( 0.004s elapsed)
使用 RTS 报告,因为 Windows。一些行被修剪是因为它们不相关——它们是其他 GC 数据,或者只是部分执行的测量值,加起来等于 0.004 秒(或更少)。它也不是不断折叠,因为 Haskell 实际上并没有做太多。如果我们不断地折叠自己 (main = print 10000
),我们会得到显着降低的分配:
...Haskell\8.6\Testbed>Primes.exe +RTS -s
10000
47,688 bytes allocated in the heap
Total time 0.000s ( 0.001s elapsed)
从字面上看,只是足以加载运行时,然后发现除了打印一个数字并退出之外别无他法。让我们添加***分解:
wheel = scanl (+) 7 $ cycle [4, 2, 4, 2, 4, 6, 2, 6]
primes = (pre ++) $ filter ((==1) . gcd a) $ filter ((==1) . gcd b) $ takeWhile (<85600) wheel
Total time 0.000s ( 0.003s elapsed)
相对于我们的参考main = print 10000
减少了大约 1/3,但肯定还有更多优化的空间。例如,它实际上停止在那里执行 GC,而通过调整不应该有任何堆使用。出于某种原因,在此处进行分析编译实际上将运行时间缩短到 2 毫秒:
Tue Nov 12 21:13 2019 Time and Allocation Profiling Report (Final)
Primes.exe +RTS -p -RTS
total time = 0.00 secs (2 ticks @ 1000 us, 1 processor)
total alloc = 967,120 bytes (excludes profiling overheads)
我将暂时保留它,我很确定随机抖动开始占主导地位。
【讨论】:
【参考方案30】:def compute_primes(bound):
"""
Return a list of the prime numbers in range(2, bound)
Implement the Sieve of Eratosthenes
https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
"""
primeNumber = [True for i in range(bound + 1)]
start_prime_number = 2
primes = []
while start_prime_number * start_prime_number <=bound:
# If primeNumber[start_prime_number] is not changed, then it is a prime
if primeNumber[start_prime_number]:
# Update all multiples of start_prime_number
for i in range(start_prime_number * start_prime_number, bound + 1, start_prime_number):
primeNumber[i] = False
start_prime_number += 1
# Print all prime numbers
for start_prime_number in range(2, bound + 1):
if primeNumber[start_prime_number]:
primes.append(start_prime_number)
return primes
打印(len(compute_primes(200)))
打印(len(compute_primes(2000)))
【讨论】:
以上是关于前 10000 个素数的最有效代码?的主要内容,如果未能解决你的问题,请参考以下文章