面试问题:如何递归生成质数的最快方法?

6

生成质数很简单,但是找到并递归生成(质数)的最快方法是什么?

这是我的解决方案。然而,它不是最佳选择。我认为它的时间复杂度为O(N*sqrt(N))。如果我错了,请纠正我。

    public static boolean isPrime(int n) {
        if (n < 2) {
            return false;
        } else if (n % 2 == 0 & n != 2) {
            return false;
        } else {
            return isPrime(n, (int) Math.sqrt(n));
        }
    }

    private static boolean isPrime(int n, int i) {
        if (i < 2) {
            return true;
        } else if (n % i == 0) {
            return false;
        } else {
            return isPrime(n, --i);
        }
    }

   public static void generatePrimes(int n){
       if(n < 2) {
            return ;
       } else if(isPrime(n)) {
            System.out.println(n);
       } 

       generatePrimes(--n);

   }

   public static void main(String[] args) {

        generatePrimes(200);
   }

你正在测试质数,而不是生成质数。 - Matthew Flaschen
1
如果你想生成小于n的质数,可以使用埃拉托色尼筛法。 - algo-geeks
不确定面试官是否要求最佳渐进时间复杂度,还是只需要最佳时间复杂度,但你可以通过不将偶数发送到函数中来进行局部优化。只需检查200是否为偶数,减去1,然后每次调用generatePrimes(n-=2)即可。 - kralco626
但实际上这并没有给你带来太多,因为你无论如何都要检查它是否在isPrime方法中...但我只是说...你可以进行一些常数时间的工作,并将对generatePrimes函数的调用减少一半。 - kralco626
这似乎是一个非常愚蠢的面试问题。 - Nick Johnson
5个回答

4
在数学中,Atkin筛法是一种快速、现代的算法,用于查找指定整数范围内的所有质数。 Wikipedia文章(包含伪代码)
为了递归地解决这个问题,也许可以实现Eratosthenes筛法的递归版本。这个页面可能会有所帮助,因为它似乎讨论了一个递归实现。

1
但在问题中提到:要递归地找到并生成(质数)。 - user467871
也许我的编辑解决了递归要求,但我不确定。 - Kyle
我刚刚再次编辑了它,加入了一个链接到CMU网站上可能有用的页面。 - Kyle
有趣的CMU链接加1分,建议使用Atkin筛法扣1分(抱歉:))- 它的理论复杂度可能更好,但实际上很难实现(维基百科文章中的伪代码是错误的,在讨论页面上也是如此),那些尝试过的人说(在SO上)涉及的常数因素使它仍然比适当的wheel-erized埃拉托斯特尼筛法慢,对于32位数的范围来说肯定如此。 - Will Ness
@WillNess:同意。在实践中,我不知道Atkin-Bernstein筛法在任何数字范围内都能优于适当优化的SoE。primegen在$2^{32}$以外的表现非常糟糕,我也不知道其他任何严肃的实现方式。(谷歌可以找到很多玩具实现方式。) - Charles

3
你需�的是永久筛法,这里是递归素数测试器的代�,我认为它相当高效,因为它�需�测试素数因�,让我知�你的想法😉
顺便说一下,我�会�试使用超过一个字节的任何东西,因为使用超过100的数字似�需�等待一段时间。
public boolean isPrime(byte testNum)
{
    if ( testNum <= 1 )
        return false;
    for ( byte primeFactor = 2; primeFactor < testNum; primeFactor++ )
        if ( isPrime(primeFactor) )
            if ( testNum % primeFactor == 0 )
                return false;
    return true;
}

2? -- 3?2? -- 4?2? -- 5?2?3?2?4?2? -- 6?2? -- 7?2?3?2?4?2?5?2?3?2?4?2?6?2? -- 8?2? -- 9?2?3?2? -- 10?2? -- 11?2?3?2?4?2?5?2?3?2?4?2?6?2?7?2?3?2?4?2?5?2?3?2?4?2?6?2?8?2?9?2?3?2?10?2? -- ... 这远非快速,更不用说最快的了。 :) - Will Ness
不,这完全不有效率。它极其低效。你需要做巨量的工作,仅仅为了一个微不足道的余数测试。这可能是我见过的最低效的实现方式了,真的。:) - Will Ness
2
这个速度非常慢,但我喜欢它。ECPP和AKS是多对数的,试除法是多项式的,而这个则是指数级别的。在哪里还能看到这样的差距呢? - Charles
所以,它的代码是 isPrime n = n > 1 && []==[f | f <- [2..n-1], isPrime f && rem n f == 0]。(当然,调用 isPrime f 是完全多余的)。仍然是速度最慢的冠军。 - Will Ness

3
对于递归,您应该使用 记忆化 来改进您的递归函数,这意味着如果您正在寻找质数,则将其保存在数组中,并在调用 isPrime(n) 时首先检查数字是否存在于数组中,如果不存在,则调用 isPrime(n, (int) Math.sqrt(n))。此外,如果 isPrime(n,i) 返回 true,请将其添加到质数列表中,最好将数组排序以进行二进制搜索,在 C# 中有排序列表和二进制搜索操作 [制作 n 项列表需要 O(n log n),搜索为 O(log(n))] 我不知道 Java [但是您可以实现它]。 编辑:您当前的方法是 O(n sqrt(n)) ,但使用我的方法可以达到相同的顺序!但是性能更好,事实上,顺序为 O(n sqrt(n) / log (n) + n log(n/log(n))) ,因为 log(n) 小于 n^Epsilon,所以最好说它是 O(n sqrt(n)) ,但是正如您所看到的,它将运行 log(n) 次更快。
此外,在启动时进行一些额外的检查可以使算法运行快2*log(n)倍。最好使用i-2而不是i--。

@IVlad(近4年过去了,但仍然...)实际上,n / log(n) ~ o(n),带有o。O(n/log(n))是一个独立的复杂度类。当近似为n^a(n)时,a(n)始终小于2.0,从下面收敛到2.0。 - Will Ness
@WillNess - 哦,我以为你在谈论另一种情况。在这种情况下,我不明白为什么说 n / log nO(n) 是错误的 - 对于足够大的 n 值,n / log n 不是被 n 限制吗?这就是大 O 符号所处理的上界。我的观点是 log n 不是它的上界,而 n 是。 - IVlad
@IVlad,这是O和o之间的区别。确实有区别n/log n不会从上方限制为n(对于某些k,对于足够大的n,< k*n),它被n所支配(对于任何k,无论多小,对于足够大的n,< k*n)。 - Will Ness
如果fO(n^a)中可以被k*n^b近似,那么它将满足b <= a;但是如果f在*o(n^a)*中,它的近似值k*n^b将始终满足b < a。我想是这样的。 - Will Ness
@IVlad O(n)是夸大其词的,就像O(n^2)一样。例如2n不在o(n)中,仅在O(n)中。n/log(n)在O(n)和o(n)都存在,并且说一个包含另一个(但反过来不行),所以说它在o(n)中是“更具说明性的”,就像说2n在O(n)中比O(n^2)更具说明性,因为一个包含另一个(但反过来不行)。o(n)是比O(n)更紧的上限。无论这是否与您最初的观点相关,我都不知道。:) 我只知道两者是不同的。在实践中,o(n)中的算法将明显 - Will Ness
显示剩余16条评论

1

首先,如果你想要生成大的质数(而不是测试整数是否为质数),那么Pocklington's theorem就非常有用了。这个定理允许在你知道足够多的p-1的质因子时,对候选p进行快速的素性检验。因此,可以采用以下方法:生成一些质数,计算它们乘积的适当倍数,并使用Pocklington's定理进行测试。如果你想要找到大的质数(例如用于RSA加密系统),那么你将不得不递归地应用这种方法来生成p-1的因子。

上面的描述缺少很多细节。但是这种方法已经被深入分析过了。我认为这篇论文是最快的方法,尽管自那时以来已经过去了一段时间,可能有人已经改进了它。

P.Mihailescu. "Fast Generation of Provable Primes using Search in Arithmetic Progressions", Proceedings CRYPTO 94, Lecture Notes in Computer Science vol 939, Springer 1994, pp. 282-293。


0

为什么要使用递归?

使用更好的质数生成算法,如埃拉托斯特尼筛法或更好的阿特金筛法。


在问题中提到:递归地查找并生成(质数)。 - user467871

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接