简体   繁体   English

计算并打印第 n 个质数

[英]Calculating and printing the nth prime number

I am trying to calculate prime numbers, which I've already done.我正在尝试计算素数,我已经完成了。 But I want to calculate and print ONLY the nth prime number (User input), while calculating the rest (They won't be printed) only the nth prime number will be printed.但是我只想计算和打印第 n 个素数(用户输入),而计算其余的(它们不会被打印)只打印第 n 个素数。

Here's what I've written so far:这是我到目前为止所写的内容:

import java.util.Scanner;
/**
 * Calculates the nth prime number
 * @author {Zyst}
 */
public class Prime {
    public static void main(String[] args) {

        Scanner input = new Scanner(System.in);
        int n, 
            i = 2, 
            x = 2;

        System.out.printf("This program calculates the nth Prime number\n");
        System.out.printf("Please enter the nth prime number you want to find: ");
        n = input.nextInt();

        for(i = 2, x = 2; i <= n; i++) {
            for(x = 2; x < i; x++) {
                if(i % x == 0) {
                    break;
                }
            }
            if(x == i) {
                System.out.printf("\n%d is prime", x);
            }
        }
    }
}

This is the program I wrote to calculate the prime numbers from 1 to n.这是我编写的用于计算从 1 到 n 的素数的程序。 However, I want it to only print the nth prime number,但是,我希望它只打印第 n 个素数,

What I've thought of doing is making some sort of count int and ++ing it every time it finds a prime, and when the count == n then it prints out that number, but I can't quite figure out how to land it.我想做的是在每次找到素数时进行某种 count int 和 ++ing,当 count == n 时它会打印出该数字,但我无法弄清楚如何着陆。

To calculate the n-th prime, I know two main variants.为了计算第 n 个素数,我知道两个主要变体。

The straightforward way直截了当的方式

That is to count all the primes starting from 2 as you find them until you have reached the desired n th .也就是说,当您找到它们时,从 2 开始计算所有素数,直到达到所需的 n th

This can be done with different levels of sophistication and efficiency, and there are two conceptually different ways to do it.这可以通过不同程度的复杂性和效率来完成,并且有两种概念上不同的方法来做到这一点。 The first is第一个是

Testing the primality of all numbers in sequence按顺序测试所有数字的素性

This would be accomplished by a driver function like这将通过像这样的驱动程序功能来完成

public static int nthPrime(int n) {
    int candidate, count;
    for(candidate = 2, count = 0; count < n; ++candidate) {
        if (isPrime(candidate)) {
            ++count;
        }
    }
    // The candidate has been incremented once after the count reached n
    return candidate-1;
}

and the interesting part that determines the efficiency is the isPrime function.而决定效率的有趣部分是isPrime函数。

The obvious way for a primality check, given the definition of a prime as a number greater than 1 that is divisible only by 1 and by itself that we learned in school¹, is给定素数的定义为一个大于 1 且只能被 1 及其自身整除的数,质数检查的显而易见的方法是我们在学校中学到的知识¹,是

Trial division审判分庭

The direct translation of the definition into code is将定义直接翻译成代码是

private static boolean isPrime(int n) {
    for(int i = 2; i < n; ++i) {
        if (n % i == 0) {
            // We are naive, but not stupid, if
            // the number has a divisor other
            // than 1 or itself, we return immediately.
            return false;
        }
    }
    return true;
}

but, as you will soon discover if you try it, its simplicity is accompanied by slowness.但是,如果您尝试一下,您很快就会发现,它的简单性伴随着缓慢。 With that primality test, you can find the 1000 th prime, 7919, in a few milliseconds (about 20 on my computer), but finding the 10000 th prime, 104729, takes seconds (~2.4s), the 100000 th prime,1299709, several minutes (about 5), the millionth prime, 15485863, would take about eight and a half hours, the ten-millionth prime, 179424673, weeks, and so on.通过该素性测试,您可以在几毫秒内找到第 1000素数 7919(在我的计算机上大约为 20 个),但是找到第 10000素数 104729 需要几秒钟(~2.4 秒),第 100000素数,1299709 ,几分钟(大约 5),百万分之一素数 15485863 需要大约八个半小时,百万分之一素数 179424673,周,依此类推。 The runtime complexity is worse than quadratic - Θ(n² * log n).运行时复杂度比二次方差 - Θ(n² * log n)。

So we'd like to speed the primality test up somewhat.所以我们想稍微加快素性测试。 A step that many people take is the realisation that a divisor of n (other than n itself) can be at most n/2 .许多人采取的一个步骤是意识到n的除数( n本身除外)最多可以是n/2 If we use that fact and let the trial division loop only run to n/2 instead of n-1 , how does the running time of the algorithm change?如果我们使用这个事实,让试除法循环只运行到n/2而不是n-1 ,算法的运行时间如何变化? For composite numbers, the lower loop limit doesn't change anything.对于合数,循环下限不会改变任何东西。 For primes, the number of trial divisions is halved, so overall, the running time should be reduced by a factor somewhat smaller than 2. If you try it out, you will find that the running time is almost exactly halved, so almost all the time is spent verifying the primality of primes despite there being many more composites than primes.对于素数,试除的次数减半,所以总体来说,运行时间应该减少一个比2小一些的因子。如果你尝试一下,你会发现运行时间几乎正好减半,所以几乎所有的尽管复合数比素数多得多,但验证素数的素性仍需花费时间

Now, that didn't help much if we want to find the one-hundred-millionth prime, so we have to do better.现在,如果我们想找到百万分之一的素数,那没有多大帮助,所以我们必须做得更好。 Trying to reduce the loop limit further, let us see for what numbers the upper bound of n/2 is actually needed.尝试进一步减少循环限制,让我们看看实际需要n/2的上限的数字是多少。 If n/2 is a divisor of n , then n/2 is an integer, in other words, n is divisible by 2. But then the loop doesn't go past 2, so it never (except for n = 4 ) reaches n/2 .如果n/2是的除数n ,然后n/2是整数,换句话说, n是由2整除但随后的循环不晃过2,所以它永远不会(除了n = 4 )达到n/2 Jolly good, so what's the next largest possible divisor of n ?太好了,那么n的下一个最大可能除数是多少? Why, n/3 of course.为什么,当然是n/3 But n/3 can only be a divisor of n if it is an integer, in other words, if n is divisible by 3. Then the loop will exit at 3 (or before, at 2) and never reach n/3 (except for n = 9 ).但是,如果n/3是整数,则只能是n的除数,换句话说,如果n可以被 3 整除。那么循环将在 3(或之前,在 2)处退出并且永远不会到达n/3 (除了对于n = 9 )。 The next largest possible divisor ...下一个最大可能的除数......

Hang on a minute!等一下! We have 2 <-> n/2 and 3 <-> n/3 .我们有2 <-> n/23 <-> n/3 The divisors of n come in pairs. n 的除数成对出现。

If we consider the pair (d, n/d) of corresponding divisors of n , either d = n/d , ie d = √n , or one of them, say d , is smaller than the other.如果我们考虑到一对(d, n/d)的相应的除数n ,或者d = n/d ,即d = √n ,或它们中的一个,比方说d ,比其他小。 But then d*d < d*(n/d) = n and d < √n .但是然后d*d < d*(n/d) = n并且d < √n Each pair of corresponding divisors of n contains (at least) one which does not exceed √n . n每对相应的除数包含(至少)一个不超过√n

If n is composite, its smallest nontrivial divisor does not exceed √n .如果n是合数,则其最小非平凡除数不超过√n

So we can reduce the loop limit to √n , and that reduces the runtime complexity of the algorithm.所以我们可以将循环限制减少到√n ,从而降低算法的运行时复杂度。 It should now be Θ(n 1.5 * √(log n)), but empirically it seems to scale a little bit better - however, there's not enough data to draw reliable conclusions from empirical results.它现在应该是 Θ(n 1.5 * √(log n)),但从经验上看,它的比例似乎要好一点——但是,没有足够的数据从经验结果中得出可靠的结论。

That finds the millionth prime in about 16 seconds, the ten-millionth in just under nine minutes, and it would find the one-hundred-millionth in about four and a half hours.这将在大约 16 秒内找到百万分之一,在不到 9 分钟内找到百万分之一,并且将在大约四个半小时内找到一百万分之一。 That's still slow, but a far cry from the ten years or so it would take the naive trial division.那还是很慢的,但与天真的试炼师十年左右的时间相差甚远。

Since there are squares of primes and products of two close primes, like 323 = 17*19, we cannot reduce the limit for the trial division loop below √n .由于存在素数的平方和两个相近素数的乘积,例如 323 = 17*19,我们无法将试除循环的限制降低到√n以下。 Therefore, while staying with trial division, we must look for other ways to improve the algorithm now.因此,在保持试验划分的同时,我们现在必须寻找其他方法来改进算法。

One easily seen thing is that no prime other than 2 is even, so we need only check odd numbers after we have taken care of 2. That doesn't make much of a difference, though, since the even numbers are the cheapest to find composite - and the bulk of time is still spent verifying the primality of primes.一个很容易看到的事情是,除了 2 之外没有质数是偶数,所以我们只需要在处理 2 之后检查奇数。不过,这没有太大区别,因为偶数是最便宜的复合 - 而且大部分时间仍然花在验证素数的素数上。 However, if we look at the even numbers as candidate divisors, we see that if n is divisible by an even number, n itself must be even, so (excepting 2) it will have been recognised as composite before division by any even number greater than 2 is attempted.但是,如果我们将偶数视为候选除数,我们会看到如果n可被偶数整除,则n本身必须是偶数,因此(除了 2)它在被任何更大的偶数除之前将被识别为合数比 2 尝试。 So all divisions by even numbers greater than 2 that occur in the algorithm must necessarily leave a nonzero remainder.因此,算法中发生的所有被大于 2 的偶数除法必须留下非零余数。 We can thus omit these divisions and check for divisibility only by 2 and the odd numbers from 3 to √n .因此,我们可以省略这些除法,只检查 2 和从 3 到√n的奇数的可√n This halves (not quite exactly) the number of divisions required to determine a number as prime or composite and therefore the running time.这使确定一个数为素数或合数所需的除法次数减半(不完全是),因此运行时间减半。 That's a good start, but can we do better?这是一个好的开始,但我们能做得更好吗?

Another large family of numbers is the multiples of 3. Every third division we perform is by a multiple of 3, but if n is divisible by one of them, it is also divisible by 3, and hence no division by 9, 15, 21, ... that we perform in our algorithm will ever leave a remainder of 0. So, how can we skip these divisions?另一个大家族是 3 的倍数。 我们执行的每三次除法都是 3 的倍数,但如果n可以被其中一个整除,它也可以被 3 整除,因此不能被 9、15、21 整除, ... 我们在算法中执行的操作将永远留下 0 的余数。那么,我们如何跳过这些除法呢? Well, the numbers divisible by neither 2 nor 3 are precisely the numbers of the form 6*k ± 1 .好吧,既不能被 2 也不能被 3 整除的数正是6*k ± 1形式的数。 Starting from 5 (since we're only interested in numbers greater than 1), they are 5, 7, 11, 13, 17, 19, ..., the step from one to the next alternates between 2 and 4, which is easy enough, so we can use从 5 开始(因为我们只对大于 1 的数字感兴趣),它们是 5, 7, 11, 13, 17, 19, ...,从一个到下一个的步骤在 2 和 4 之间交替,也就是很简单,所以我们可以使用

private static boolean isPrime(int n) {
    if (n % 2 == 0) return n == 2;
    if (n % 3 == 0) return n == 3;
    int step = 4, m = (int)Math.sqrt(n) + 1;
    for(int i = 5; i < m; step = 6-step, i += step) {
        if (n % i == 0) {
            return false;
        }
    }
    return true;
}

This gives us another speedup by a factor of (nearly) 1.5, so we'd need about one and a half hours to the hundred-millionth prime.这使我们再次加速(接近)1.5 倍,因此我们需要大约一个半小时才能达到第一亿个素数。

If we continue this route, the next step is the elimination of multiples of 5. The numbers coprime to 2, 3 and 5 are the numbers of the form如果我们继续这条路线,下一步就是消去 5 的倍数。 与 2、3 和 5 互质的数是以下形式的数

30*k + 1, 30*k + 7, 30*k + 11, 30*k + 13, 30*k + 17, 30*k + 19, 30*k + 23, 30*k + 29

so we'd need only divide by eight out of every thirty numbers (plus the three smallest primes).所以我们只需要每 30 个数字除以 8(加上三个最小的素数)。 The steps from one to the next, starting from 7, cycle through 4, 2, 4, 2, 4, 6, 2, 6. That's still easy enough to implement and yields another speedup by a factor of 1.25 (minus a bit for more complicated code).从一个到下一个的步骤,从 7 开始,循环到 4, 2, 4, 2, 4, 6, 2, 6. 这仍然很容易实现并产生另一个 1.25 倍的加速(减去一点更复杂的代码)。 Going further, the multiples of 7 would be eliminated, leaving 48 out of every 210 numbers to divide by, then 11 (480/2310), 13 (5760/30030) and so on.更进一步,将消除 7 的倍数,每 210 个数字中留下 48 个要除以,然后是 11 (480/2310)、13 (5760/30030) 等等。 Each prime p whose multiples are eliminated yields a speedup of (almost) p/(p-1) , so the return decreases while the cost (code complexity, space for the lookup table for the steps) increases with each prime.每个素数被消除的素数p产生(几乎) p/(p-1)的加速,因此回报减少而成本(代码复杂性,步骤查找表的空间)随着每个素数增加。

In general, one would stop soonish, after eliminating the multiples of maybe six or seven primes (or even fewer).一般来说,在消除可能六七个素数(甚至更少)的倍数之后,很快就会停止。 Here, however, we can follow through to the very end, when the multiples of all primes have been eliminated and only the primes are left as candidate divisors.然而,在这里,我们可以坚持到最后,当所有素数的倍数都被消除时,只剩下素数作为候选除数。 Since we are finding all primes in order, each prime is found before it is needed as a candidate divisor and can then be stored for future use.由于我们正在按顺序查找所有素数,因此在需要将其作为候选除数之前找到每个素数,然后可以将其存储以备将来使用。 This reduces the algorithmic complexity to - if I haven't miscalculated - O(n 1.5 / √(log n)).这将算法复杂性降低到 - 如果我没有错误计算 - O(n 1.5 / √(log n))。 At the cost of space usage for storing the primes.以存储素数的空间使用为代价。

With trial division, that is as good as it gets, you have to try and divide by all primes to √n or the first dividing n to determine the primality of n .随着审判庭,这是尽善尽美,你必须尝试所有素数鸿沟√n或第一分n确定的素性n That finds the hundred-millionth prime in about half an hour here.这在大约半小时内找到了第一亿个素数。

So how about那么怎么样

Fast primality tests快速素性测试

Primes have other number-theoretic properties than the absence of nontrivial divisors which composite numbers usually don't have.除了不存在合数通常没有的非平凡除数之外,素数还有其他数论性质。 Such properties, if they are fast to check, can form the basis of probabilistic or deterministic primality tests.如果这些属性可以快速检查,则可以构成概率或确定性素性测试的基础。 The archetypical such property is associated with the name of Pierre de Fermat, who, in the early 17 th century, found that该原型财产与皮埃尔·德·费马,谁,在17世纪初,发现的名称相关联

If p is a prime, then p is a divisor of (a p -a) for all a .如果p是一个素数,则p是(为p-A)为所有的除数a

This - Fermat's so-called 'little theorem' - is, in the equivalent formulation这 - 费马所谓的“小定理” - 在等价公式中

Let p be a prime and a not divisible by p .p是一个素数及a不整除p Then p divides a p-1 - 1.然后p除以p-1 - 1。

the basis of most of the widespread fast primality tests (for example Miller-Rabin) and variants or analogues of that appear in even more (eg Lucas-Selfridge).大多数广泛使用的快速素性检验的基础(例如 Miller-Rabin)及其变体或类似物出现在更多(例如 Lucas-Selfridge)中。

So if we want to know if a not too small odd number n is a prime (even and small numbers are efficiently treated by trial division), we can choose any number a (> 1) which is not a multiple of n , for example 2, and check whether n divides a n-1 - 1. Since a n-1 becomes huge, that is most efficiently done by checking whether a^(n-1) ≡ 1 (mod n) , ie by modular exponentiation.因此,如果我们想知道一个不太小的奇数n是否是素数(偶数和小数可以通过试除有效地处理),我们可以选择任何不是n倍数的数a (> 1),例如2,并检查n是否除以n-1 - 1。由于n-1变得很大,最有效的方法是检查是否a^(n-1) ≡ 1 (mod n) ,即通过模幂运算。 If that congruence doesn't hold, we know that n is composite.如果该同余不成立,我们就知道n是合数。 If it holds, however, we cannot conclude that n is prime, for example 2^340 ≡ 1 (mod 341) , but 341 = 11 * 31 is composite.但是,如果它成立,我们不能得出结论n是素数,例如2^340 ≡ 1 (mod 341) ,但341 = 11 * 31是合数。 Composite numbers n such that a^(n-1) ≡ 1 (mod n) are called Fermat pseudoprimes for the base a .复合数n使得a^(n-1) ≡ 1 (mod n)被称为基a费马伪素数。

But such occurrences are rare.但这种情况很少见。 Given any base a > 1 , although there are an infinite number of Fermat pseudoprimes to base a , they are much rarer than actual primes.给定任何基数a > 1 ,虽然有无数费马伪素数作为基数a ,但它们比实际素数少得多。 For example, there are only 78 base-2 Fermat pseudoprimes and 76 base-3 Fermat pseudoprimes below 100000, but 9592 primes.例如,100000以下的2碱基费马伪素数只有78个,3碱基费马伪素数有76个,但有9592个素数。 So if one chooses an arbitrary odd n > 1 and an arbitrary base a > 1 and finds a^(n-1) ≡ 1 (mod n) , there's a good chance that n is actually prime.因此,如果选择任意奇数n > 1和任意底数a > 1并找到a^(n-1) ≡ 1 (mod n) ,则n很有可能实际上是素数。

However, we are in a slightly different situation, we are given n and can only choose a .但是,我们的情况略有不同,给定了n并且只能选择a So, for an odd composite n , for how many a , 1 < a < n-1 can a^(n-1) ≡ 1 (mod n) hold?那么,对于奇数复合n ,对于多少a1 < a < n-1可以a^(n-1) ≡ 1 (mod n)成立? Unfortunately, there are composite numbers - Carmichael numbers - such that the congruence holds for every a coprime to n .不幸的是,合数-卡迈克尔数-使得一致性适用于a互质n That means that to identify a Carmichael number as composite with the Fermat test, we have to pick a base that is a multiple of one of n 's prime divisors - there may not be many such multiples.这意味着要使用费马测试将卡迈克尔数识别为复合数,我们必须选择一个底数,该底数是n的素数除数之一的倍数 - 可能没有很多这样的倍数。

But we can strengthen the Fermat test so that composites are more reliably detected.但是我们可以加强费马测试,以便更可靠地检测复合材料。 If p is an odd prime, write p-1 = 2*m .如果p是奇素数,则写p-1 = 2*m Then, if 0 < a < p ,然后,如果0 < a < p

a^(p-1) - 1 = (a^m + 1) * (a^m - 1)

and p divides exactly one of the two factors (the two factors differ by 2, so their greatest common divisor is either 1 or 2). p正好整除这两个因数之一(这两个因数相差 2,因此它们的最大公约数是 1 或 2)。 If m is even, we can split a^m - 1 in the same way.如果m是偶数,我们可以用同样的方式拆分a^m - 1 Continuing, if p-1 = 2^s * k with k odd, write继续,如果p-1 = 2^s * kk奇数,则写

a^(p-1) - 1 = (a^(2^(s-1)*k) + 1) * (a^(2^(s-2)*k) + 1) * ... * (a^k + 1) * (a^k - 1)

then p divides exactly one of the factors.然后p正好除以因子之一。 This gives rise to the strong Fermat test,这产生了强费马测试,

Let n > 2 be an odd number.n > 2为奇数。 Write n-1 = 2^s * k with k odd.k奇数写n-1 = 2^s * k Given any a with 1 < a < n-1 , if给定任何a 1 < a < n-1 ,如果

  1. a^k ≡ 1 (mod n) or a^k ≡ 1 (mod n)
  2. a^((2^j)*k) ≡ -1 (mod n) for any j with 0 <= j < s a^((2^j)*k) ≡ -1 (mod n)为任何j0 <= j < s

then n is a strong (Fermat) probable prime for base a .然后n为碱基的强(费马)可能素a A composite strong base a (Fermat) probable prime is called a strong (Fermat) pseudoprime for the base a .一种复合强碱a (费马)可能素被称为对于基部的强(费马)伪素a Strong Fermat pseudoprimes are even rarer than ordinary Fermat pseudoprimes, below 1000000, there are 78498 primes, 245 base-2 Fermat pseudoprimes and only 46 base-2 strong Fermat pseudoprimes.强费马伪素数比普通费马伪素数还要稀少,1000000以下,有78498个素数,245个碱基2费马伪素数,2碱基强费马伪素数只有46个。 More importantly, for any odd composite n , there are at most (n-9)/4 bases 1 < a < n-1 for which n is a strong Fermat pseudoprime.更重要的是,对于任何奇数复合n ,最多有(n-9)/4碱基1 < a < n-1其中n是强费马伪素数。

So if n is an odd composite, the probability that n passes k strong Fermat tests with randomly chosen bases between 1 and n-1 (exclusive bounds) is less than 1/4^k .因此,如果n是奇数组合,则n通过k强费马测试的概率小于1/4^k其中随机选择的基数介于 1 和n-1 (互斥边界)。

A strong Fermat test takes O(log n) steps, each step involves one or two multiplications of numbers with O(log n) bits, so the complexity is O((log n)^3) with naive multiplication [for huge n , more sophisticated multiplication algorithms can be worthwhile].强费马测试需要 O(log n) 步,每一步都涉及 O(log n) 位数字的一两次乘法,因此复杂度为 O((log n)^3) 与朴素乘法 [对于巨大的n ,更复杂的乘法算法可能是值得的]。

The Miller-Rabin test is the k-fold strong Fermat test with randomly chosen bases. Miller-Rabin 检验是随机选择碱基的 k 折强费马检验。 It is a probabilistic test, but for small enough bounds, short combinations of bases are known which give a deterministic result.这是一个概率测试,但对于足够小的边界,已知的短碱基组合会给出确定性结果。

Strong Fermat tests are part of the deterministic APRCL test.强费马测试是确定性 APRCL 测试的一部分。

It is advisable to precede such tests with trial division by the first few small primes, since divisions are comparatively cheap and that weeds out most composites.建议在这些测试之前先用前几个小素数进行试除,因为除法相对便宜,而且可以剔除大多数复合材料。

For the problem of finding the n th prime, in the range where testing all numbers for primality is feasible, there are known combinations of bases that make the multiple strong Fermat test correct, so that would give a faster - O(n*(log n) 4 ) - algorithm.对于寻找第n素数的问题,在测试所有数的素数可行的范围内,存在使多重强费马测试正确的已知碱基组合,这样会给出更快的 - O(n*(log n) 4 ) - 算法。

For n < 2^32 , the bases 2, 7, and 61 are sufficient to verify primality.对于n < 2^32 ,基数 2、7 和 61 足以验证素性。 Using that, the hundred-millionth prime is found in about six minutes.使用它,可以在大约六分钟内找到第 100 万个素数。

Eliminating composites by prime divisors, the Sieve of Eratosthenes用素数除数消除复合,埃拉托色尼筛法

Instead of investigating the numbers in sequence and checking whether each is prime from scratch, one can also consider the whole set of relevant numbers as one piece and eliminate the multiples of a given prime in one go.也可以将一整套相关数字视为一个整体,一次性消除给定素数的倍数,而不是按顺序调查数字并从头开始检查每个数字是否为素数。 This is known as the Sieve of Eratosthenes:这被称为埃拉托色尼筛法:

To find the prime numbers not exceeding N求不超过N的素数

  1. make a list of all numbers from 2 to N列出从 2 到N的所有数字
  2. for each k from 2 to N : if k is not yet crossed off, it is prime;对于从 2 到N每个k :如果k尚未划掉,则它是素数; cross off all multiples of k as composites划掉所有k倍数作为复合

The primes are the numbers in the list which aren't crossed off.素数是列表中没有划掉的数字。

This algorithm is fundamentally different from trial division, although both directly use the divisibility characterisation of primes, in contrast to the Fermat test and similar tests which use other properties of primes.该算法与试除法有着根本的不同,尽管两者都直接使用质数的可分性表征,而费马检验和使用质数其他性质的类似检验则相反。

In trial division, each number n is paired with all primes not exceeding the smaller of √n and the smallest prime divisor of n .在试除法中,每个数n与不超过√nn的最小素数除数中的较小者的所有素数配对。 Since most composites have a very small prime divisor, detecting composites is cheap here on average.由于大多数复合物的质数除数非常小,因此平均而言,检测复合物的成本很低。 But testing primes is expensive, since there are relatively many primes below √n .但是测试素数是很昂贵的,因为√n以下的素数相对较多。 Although there are many more composites than primes, the cost of testing primes is so high that it completely dominates the overall running time and renders trial division a relatively slow algorithm.尽管复合数比素数多得多,但测试素数的成本如此之高,以至于它完全支配了整个运行时间,并使试除法成为一种相对较慢的算法。 Trial division for all numbers less than N takes O(N 1.5 / (log N)²) steps.对所有小于N数字进行试除需要 O(N 1.5 / (log N)²) 步。

In the sieve, each composite n is paired with all of its prime divisors, but only with those.在筛子中,每个复合n与其所有的质因数配对,但与那些质因数配对。 Thus there the primes are the cheap numbers, they are only ever looked at once, while the composites are more expensive, they are crossed off multiple times.因此,质数是便宜的数字,它们只看一次,而复合数更昂贵,它们被多次划掉。 One might believe that since a sieve contains many more 'expensive' numbers than 'cheap' ones, it would overall be a bad algorithm.人们可能会认为,由于筛子包含的“昂贵”数字比“便宜”数字要多得多,因此总体而言,这将是一种糟糕的算法。 However, a composite number does not have many distinct prime divisors - the number of distinct prime divisors of n is bounded by log n , but usually it is much smaller, the average of the number of distinct prime divisors of the numbers <= n is log log n - so even the 'expensive' numbers in the sieve are on average no more (or hardly more) expensive than the 'cheap' numbers for trial division.然而,合数没有许多相异素因子-的相异素因子的数量n为界log n ,但通常小得多,平均的数字的不同的素因子的数量的<= nlog log n - 因此,即使筛子中的“昂贵”数字平均也不会比试除的“便宜”数字贵(或几乎不贵)。

Sieving up to N , for each prime p , there are Θ(N/p) multiples to cross off, so the total number of crossings-off is Θ(∑ (N/p)) = Θ(N * log (log N)) .最多筛选N ,对于每个素数p ,有Θ(N/p)倍数要交叉,因此交叉的总数为Θ(∑ (N/p)) = Θ(N * log (log N)) This yields much faster algorithms for finding the primes up to N than trial division or sequential testing with the faster primality tests.与使用更快的素性测试的试除或顺序测试相比,这产生更快的算法来查找最多N的素数。

There is, however, a disadvantage to the sieve, it uses O(N) memory.然而,sieve 有一个缺点,它使用O(N)内存。 (But with a segmented sieve, that can be reduced to O(√N) without increasing the time complexity.) (但是使用分段筛,可以在不增加时间复杂度的情况下将其减少到O(√N) 。)

For finding the n th prime, instead of the primes up to N , there is also the problem that it is not known beforehand how far the sieve should reach.为了找到第n素数,而不是直到N的素数,还有一个问题是事先不知道筛子应该到达多远。

The latter can be solved using the prime number theorem.后者可以使用素数定理解决。 The PNT says PNT 说

π(x) ~ x/log x (equivalently: lim π(x)*log x/x = 1),

where π(x) is the number of primes not exceeding x (here and below, log must be the natural logarithm, for the algorithmic complexities it is not important which base is chosen for the logarithms).其中π(x)是不超过x的素数的数量(此处和以下, log必须是自然对数,对于算法复杂性,为对数选择哪个底并不重要)。 From that, it follows that p(n) ~ n*log n , where p(n) is the n th prime, and there are good upper bounds for p(n) known from deeper analysis, in particular由此可知, p(n) ~ n*log n ,其中p(n)是第n素数,并且从更深入的分析中得知p(n)有很好的上限,特别是

n*(log n + log (log n) - 1) < p(n) < n*(log n + log (log n)), for n >= 6.

So one can use that as the sieving limit, it doesn't exceed the target far.所以可以把它作为筛分极限,它不会超出目标太远。

The O(N) space requirement can be overcome by using a segmented sieve. O(N)空间需求可以通过使用分段筛来克服。 One can then record the primes below √N for O(√N / log N) memory consumption and use segments of increasing length (O(√N) when the sieve is near N).然后,可以记录√N以下的素数以√N O(√N / log N)内存,并在筛子接近 N 时使用长度增加的段 (O(√N))。

There are some easy improvements on the algorithm as stated above:如上所述,算法有一些简单的改进:

  1. start crossing off multiples of p only at , not at 2*p仅在处开始 p倍数,而不是在2*p
  2. eliminate the even numbers from the sieve从筛子中消除偶数
  3. eliminate the multiples of further small primes from the sieve从筛子中消除更多小素数的倍数

None of these reduce the algorithmic complexity, but they all reduce the constant factors by a significant amount (as with trial division, the elimination of multiples of p yields lesser speedup for larger p while increasing the code complexity more than for smaller p ).这些都不降低算法复杂性,但它们都减少由显著量常数因子(如用试除法,的倍数的消除p产率较小的加速比为较大的p同时增加了代码的复杂性超过较小p )。

Using the first two improvements yields使用前两个改进产量

// Entry k in the array represents the number 2*k+3, so we have to do
// a bit of arithmetic to get the indices right.
public static int nthPrime(int n) {
    if (n < 2) return 2;
    if (n == 2) return 3;
    int limit, root, count = 1;
    limit = (int)(n*(Math.log(n) + Math.log(Math.log(n)))) + 3;
    root = (int)Math.sqrt(limit) + 1;
    limit = (limit-1)/2;
    root = root/2 - 1;
    boolean[] sieve = new boolean[limit];
    for(int i = 0; i < root; ++i) {
        if (!sieve[i]) {
            ++count;
            for(int j = 2*i*(i+3)+3, p = 2*i+3; j < limit; j += p) {
                sieve[j] = true;
            }
        }
    }
    int p;
    for(p = root; count < n; ++p) {
        if (!sieve[p]) {
            ++count;
        }
    }
    return 2*p+1;
}

which finds the hundred-millionth prime, 2038074743, in about 18 seconds.它在大约 18 秒内找到了第一亿个素数 2038074743。 This time can be reduced to about 15 seconds (here, YMMV) by storing the flags packed, one bit per flag, instead of as boolean s, since the reduced memory usage gives better cache locality.通过存储打包的标志,每个标志一位,而不是boolean ,这个时间可以减少到大约 15 秒(这里是 YMMV),因为减少的内存使用量提供了更好的缓存局部性。

Packing the flags, eliminating also multiples of 3 and using bit-twiddling for faster faster counting,打包标志,消除 3 的倍数并使用位旋转来更快地计数,

// Count number of set bits in an int
public static int popCount(int n) {
    n -= (n >>> 1) & 0x55555555;
    n = ((n >>> 2) & 0x33333333) + (n & 0x33333333);
    n = ((n >> 4) & 0x0F0F0F0F) + (n & 0x0F0F0F0F);
    return (n * 0x01010101) >> 24;
}

// Speed up counting by counting the primes per
// array slot and not individually. This yields
// another factor of about 1.24 or so.
public static int nthPrime(int n) {
    if (n < 2) return 2;
    if (n == 2) return 3;
    if (n == 3) return 5;
    int limit, root, count = 2;
    limit = (int)(n*(Math.log(n) + Math.log(Math.log(n)))) + 3;
    root = (int)Math.sqrt(limit);
    switch(limit%6) {
        case 0:
            limit = 2*(limit/6) - 1;
            break;
        case 5:
            limit = 2*(limit/6) + 1;
            break;
        default:
            limit = 2*(limit/6);
    }
    switch(root%6) {
        case 0:
            root = 2*(root/6) - 1;
            break;
        case 5:
            root = 2*(root/6) + 1;
            break;
        default:
            root = 2*(root/6);
    }
    int dim = (limit+31) >> 5;
    int[] sieve = new int[dim];
    for(int i = 0; i < root; ++i) {
        if ((sieve[i >> 5] & (1 << (i&31))) == 0) {
            int start, s1, s2;
            if ((i & 1) == 1) {
                start = i*(3*i+8)+4;
                s1 = 4*i+5;
                s2 = 2*i+3;
            } else {
                start = i*(3*i+10)+7;
                s1 = 2*i+3;
                s2 = 4*i+7;
            }
            for(int j = start; j < limit; j += s2) {
                sieve[j >> 5] |= 1 << (j&31);
                j += s1;
                if (j >= limit) break;
                sieve[j >> 5] |= 1 << (j&31);
            }
        }
    }
    int i;
    for(i = 0; count < n; ++i) {
        count += popCount(~sieve[i]);
    }
    --i;
    int mask = ~sieve[i];
    int p;
    for(p = 31; count >= n; --p) {
        count -= (mask >> p) & 1;
    }
    return 3*(p+(i<<5))+7+(p&1);
}

finds the hundred-millionth prime in about 9 seconds, which is not unbearably long.在大约 9 秒内找到第 100 万个质数,这并不是无法忍受的长。

There are other types of prime sieves, of particular interest is the Sieve of Atkin, which exploits the fact that certain congruence classes of (rational) primes are composites in the ring of algebraic integers of some quadratic extensions of ℚ.还有其他类型的素数筛,特别令人感兴趣的是阿特金筛,它利用了这样一个事实,即(有理)素数的某些同余类是 ℚ 的某些二次扩展的代数整数环中的复合物。 Here is not the place to expand on the mathematical theory, suffice it to say that the Sieve of Atkin has lower algorithmic complexity than the Sieve of Eratosthenes and hence is preferable for large limits (for small limits, a not overly optimised Atkin sieve has higher overhead and thus can be slower than a comparably optimised Eratosthenes sieve).这里不是扩展数学理论的地方,只需说阿特金筛比埃拉托色尼筛具有更低的算法复杂性,因此更适合大限制(对于小限制,没有过度优化的阿特金筛具有更高的算法复杂度)开销,因此可能比相对优化的 Eratosthenes 筛慢)。 DJ Bernstein's primegen library (written in C) is well optimised for numbers below 2 32 and finds the hundred-millionth prime (here) in about 1.1 seconds. DJ Bernstein 的primegen库(用 C 编写)针对 2 32以下的数字进行了很好的优化,并在大约 1.1 秒内找到了第 100 万个素数(此处)。

The fast way快捷方式

If we only want to find the n th prime, there is no intrinsic value in also finding all the smaller primes.如果我们只想找到第n素数,那么找出所有较小的素数也没有内在价值。 If we can skip most of them, we can save a lot of time and work.如果我们可以跳过其中的大部分,我们可以节省大量的时间和工作。 Given a good approximation a(n) to the n th prime p(n) , if we have a fast way to calculate the number of primes π(a(n)) not exceeding a(n) , we can then sieve a small range above or below a(n) to identify the few missing or excess primes between a(n) and p(n) .给定一个很好的近似a(n)到第n素数p(n) ,如果我们有一个快速的方法来计算不超过a(n)的素数π(a(n))的数量,那么我们可以筛选一个小的范围高于或低于a(n)以识别a(n)p(n)之间的少数缺失或多余素数。

We have seen an easily computed fairly good approximation to p(n) above, we could take我们已经看到了上面p(n)一个很容易计算的相当好的近似值,我们可以取

a(n) = n*(log n + log (log n))

for example.例如。

A good method to compute π(x) is the Meissel-Lehmer method , which computes π(x) in roughly O(x^0.7) time (the exact complexity depends on the implementation, a refinement by Lagarias, Miller, Odlyzko, Deléglise and Rivat lets one compute π(x) in O(x 2/3 / log² x) time).计算π(x)一个好方法是Meissel-Lehmer 方法,它在大约O(x^0.7)时间内计算π(x) (确切的复杂性取决于实现,由 Lagarias、Miller、Odlyzko、Deléglise 改进并且 Rivat 可以在 O(x 2/3 / log² x) 时间内计算π(x) )。

Starting with the simple approximation a(n) , we compute e(n) = π(a(n)) - n .从简单的近似a(n) ,我们计算e(n) = π(a(n)) - n By the prime number theorem, the density of primes near a(n) is about 1/log a(n) , so we expect p(n) to be near b(n) = a(n) - log a(n)*e(n) and we would sieve a range slightly larger than log a(n)*e(n) .根据素数定理, a(n)附近的素数密度约为1/log a(n) ,因此我们期望p(n)接近b(n) = a(n) - log a(n)*e(n) ,我们会筛选一个比log a(n)*e(n)稍大的范围。 For greater confidence that p(n) is in the sieved range, one can increase the range by a factor of 2, say, which almost certainly will be large enough.为了让p(n)在筛选范围内有更大的信心,可以将范围增加 2 倍,例如,这几乎肯定会足够大。 If the range seems too large, one can iterate with the better approximation b(n) in place of a(n) , compute π(b(n)) and f(n) = π((b(n)) - n . Typically, |f(n)| will be much smaller than |e(n)| . If f(n) is approximately -e(n) , c(n) = (a(n) + b(n)) / 2 will be a better approximation to p(n) . Only in the very unlikely case that f(n) is very close to e(n) (and not very close to 0), finding a sufficiently good approximation to p(n) that the final sieving stage can be done in time comparable to computing π(a(n)) becomes a problem.如果范围似乎过大,一个可以遍历与更好的近似b(n)代替a(n)计算π(b(n))f(n) = π((b(n)) - n . 通常, |f(n)|会比|e(n)|小得多。如果f(n)大约是-e(n) ,则c(n) = (a(n) + b(n)) / 2将是p(n)的更好近似值。只有在f(n)非常接近e(n) (而不是非常接近 0)的极不可能的情况下,才能找到对p(n) ) 的足够好的近似值p(n)最终筛分阶段可以在与计算π(a(n))相当的时间内完成成为一个问题。

In general, after one or two improvements to the initial approximation, the range to be sieved is small enough for the sieving stage to have a complexity of O(n^0.75) or better.一般情况下,对初始近似进行一两次改进后,要筛选的范围足够小,筛选阶段的复杂度为 O(n^0.75) 或更好。

This method finds the hundred-millionth prime in about 40 milliseconds, and the 10 12 -th prime, 29996224275833, in under eight seconds.此方法在大约 40 毫秒内找到第 100 万个素数,在不到 8 秒内找到第 10个 12个素数,29996224275833。


tl;dr: Finding the n th prime can be efficiently done, but the more efficient you want it, the more mathematics is involved. tl;dr:可以有效地找到第n素数,但是您想要的效率越高,涉及的数学就越多。


I have Java code for most of the discussed algorithms prepared here , in case somebody wants to play around with them.我为这里讨论的大多数算法准备Java 代码,以防有人想玩弄它们。


¹ Aside remark for overinterested souls: The definition of primes used in modern mathematics is different, applicable in much more general situations. ¹ 除了对过分感兴趣的灵魂的评论:现代数学中使用的素数的定义是不同的,适用于更一般的情况。 If we adapt the school definition to include negative numbers - so a number is prime if it's neither 1 nor -1 and divisible only by 1, -1, itself and its negative - that defines (for integers) what is nowadays called an irreducible element of ℤ, however, for integers, the definitions of prime and irreducible elements coincide.如果我们修改学校的定义以包含负数——所以如果一个数既不是 1 也不是 -1 并且只能被 1、-1、它本身和它的负数整除,那么它就是素数——这就定义了(对于整数)现在称为不可约元素的东西的ℤ,然而,对于整数,素数和不可约元素的定义是一致的。

int counter = 0;

for(int i = 1; ; i++) {
    if(isPrime(i)
        counter++;

    if(counter == userInput) {
        print(i);
        break;
    }
}

Edit: Your prime function could use a bit of work.编辑:您的主要功能可能需要一些工作。 Here's one that I have written:这是我写的一篇:

private static boolean isPrime(long n) {
    if(n < 2)
        return false;

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

Note - you only need to go up to sqrt(n) when looking at factors, hence the i * i <= n注意 - 在查看因子时,您只需要上升到 sqrt(n),因此i * i <= n

You are trying to do too much in the main method.您试图在 main 方法中做太多事情。 You need to break this up into more manageable parts.您需要将其分解为更易于管理的部分。 Write a method boolean isPrime(int n) that returns true if a number is prime, and false otherwise.编写一个boolean isPrime(int n) ,如果数字是素数则返回真,否则返回假。 Then modify the main method to use isPrime.然后修改main方法使用isPrime。

java.math.BigInteger has a nextProbablePrime() method. java.math.BigInteger 有一个 nextProbablePrime() 方法。 Whilst I'm guessing this is meant for cryptography you could use it for you work.虽然我猜这适用于密码学,但您可以将它用于您的工作。

BigInteger prime = BigInteger.valueOf(0);
for (int i = 0; i < n; i++) {
    prime = prime.nextProbablePrime();
}
System.out.println(prime.intValue());

I just added the missing lines in your own thought process.我只是在您自己的思考过程中添加了缺失的线条。

static int nthPrimeFinder(int n) {

        int counter = 1; // For 1 and 2. assuming n is not 1 or 2.
        int i = 2;
        int x = 2;
        int tempLength = n;

        while (counter <= n) {
            for (; i <= tempLength; i++) {
                for (x = 2; x < i; x++) {
                    if (i % x == 0) {
                        break;
                    }
                }
                if (x == i && counter < n) {
                    //System.out.printf("\n%d is prime", x);
                    counter++;
                    if (counter == n) {
                        System.out.printf("\n%d is prime", x);
                        return counter;
                    }
                }
            }
            tempLength = tempLength+n;
        }
        return 0;
    }
public class prime{
    public static void main(String ar[])
    {
      int count;
      int no=0;
      for(int i=0;i<1000;i++){
        count=0;
        for(int j=1;j<=i;j++){

        if(i%j==0){
          count++;
         }
        }
        if(count==2){
          no++;
          if(no==Integer.parseInt(ar[0])){
            System.out.println(no+"\t"+i+"\t") ;
          }
        }
      }
    }
}

I can see that you have received many correct answers and very detailed one.我可以看到您收到了许多正确的答案,而且非常详细。 I believe you are not testing it for very large prime numbers.我相信你不是在测试非常大的素数。 And your only concern is to avoid printing intermediary prime number by your program.你唯一关心的是避免你的程序打印中间质数。

A tiny change your program will do the trick.对您的程序稍作更改即可解决问题。

Keep your logic same way and just pull out the print statement outside of loop.保持你的逻辑相同,只需在循环之外拉出打印语句。 Break outer loop after n prime numbers.在 n 个素数后中断外循环。

import java.util.Scanner;
/**
 * Calculates the nth prime number
 * @author {Zyst}
 */
public class Prime {
    public static void main(String[] args) {

        Scanner input = new Scanner(System.in);
        int n, 
            i = 2, 
            x = 2;

        System.out.printf("This program calculates the nth Prime number\n");
        System.out.printf("Please enter the nth prime number you want to find:");
        n = input.nextInt();

        for(i = 2, x = 2; n > 0; i++) {
            for(x = 2; x < i; x++) {
                if(i % x == 0) {
                    break;
                }
            }
            if(x == i) {
                n--;
            }
        }
        System.out.printf("\n%d is prime", x);

    }
}

Although many correct and detailed explanations are available.虽然有许多正确和详细的解释。 but here is my C implementation:但这是我的 C 实现:

#include<stdio.h>
#include<conio.h> 

main()
{
int pk,qd,am,no,c=0;
printf("\n Enter the Number U want to Find");
scanf("%d",&no);
for(pk=2;pk<=1000;pk++)
{
am=0;
for(qd=2;qd<=pk/2;qd++)
{
if(pk%qd==0)
{
am=1;
break;
}}
if(am==0)
c++;
if(c==no)
{
printf("%d",pk);
break;
}}
getch();
return 0;
}

This program is an efficient one.这个程序是一个高效的程序。 I have added one more check-in if to get the square root of a number and check is it divisible or not if it's then its not a prime number.如果要获得数字的平方根,我又添加了一个签入,并检查它是否可整除,如果它不是质数。 this will solve all the problems efficiently.这将有效地解决所有问题。

public static void main(String[] args) {

            Scanner sc = new Scanner(System.in);
        int T; // number of test cases
        T = sc.nextInt();
        long[] number = new long[T];
        if(1<= T && T <= 30){
        for(int i =0;i<T;i++){
            number[i]=sc.nextInt(); // read all the numbers
        }
        for(int i =0;i<T;i++){
            if(isPrime(number[i]))
                System.out.println("Prime");
            else
               System.out.println("Not prime");    
        }
    }
    else
      return;
    }
    // is prime or not
    static boolean isPrime(long num){
        if(num==1)
          return false;
        if(num <= 3)
          return true;
        if(num % 2 == 0 || num % 3 == 0 || num % (int)Math.sqrt(num) == 0)
          return false;  
        for(int i=4;i<(int)Math.sqrt(num);i++){
            if(num%i==0)
              return false;
        }
       return true;     
    }

An another solution另一种解决方案

import java.util.Scanner;

public class Prime {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        int[] arr = new int[10000000];
        for(int i=2;i<10000000;i++)
        {
            arr[i]=i;
        }
        for(int i=2;i<10000000;i++)
            for(int j=i+i;j<10000000;j+=i)
                arr[j]=0;

        int t = in.nextInt();
        for(int a0 = 0; a0 < t; a0++){
            int n = in.nextInt();
            int count=0;
            for(int j=2;j<10000000;j++)
            {
                if(arr[j]!=0)
                {
                    count++;
                    if(count==n)
                    {
                        System.out.println(j);
                        break;
                    }
                }
            }
        }
    }
}

Hope this will help for larger numbers...希望这将有助于更大的数字...

Using Java 8 parallelStream would be faster.使用 Java 8 parallelStream 会更快。 Below is my code for finding Nth prime number下面是我查找第 N 个素数的代码

public static Integer findNthPrimeNumber(Integer nthNumber) {
    List<Integer> primeList = new ArrayList<>();
    primeList.addAll(Arrays.asList(2, 3));
    Integer initializer = 4;
    while (primeList.size() < nthNumber) {
        if (isPrime(initializer, primeList)) {
            primeList.add(initializer);
        }
        initializer++;
    }
    return primeList.get(primeList.size() - 1);
}

public static Boolean isPrime(Integer input, List<Integer> primeList) {
    return !(primeList.parallelStream().anyMatch(i -> input % i == 0));
}

@Test
public void findNthPrimeTest() {
    Problem7 inputObj = new Problem7();
    Integer methodOutput = inputObj.findNthPrimeNumber(100);
    Assert.assertEquals((Integer) 541, methodOutput);
    Assert.assertEquals((Integer) 104743, inputObj.findNthPrimeNumber(10001));
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM