找到素数最快的算法是啥?

Posted

技术标签:

【中文标题】找到素数最快的算法是啥?【英文标题】:Which is the fastest algorithm to find prime numbers?找到素数最快的算法是什么? 【发布时间】:2010-10-02 00:19:12 【问题描述】:

使用 C++ 找出素数的最快算法是什么?我已经使用了筛子的算法,但我仍然希望它更快!

【问题讨论】:

我找到的一篇旧文章,但看起来很有趣:Fun With Prime Numbers @Jaider 这对于低至 7 (111) 的数字会失败。对于 1001=9,它也失败了。很明显,它对于几乎所有的素数都失败了(不包括 2^p - 1 的情况,这是梅森素数 - 经典生成的例子 - 总是采用 111...1 的形式) @Kasperasky - 你没有提到哪个筛子?您可能是指 Eranthoses 筛! 埃拉托色尼筛法算法 令人惊讶地看到答案的数量,当不知道要涵盖的数字范围的情况下绝对不可能回答问题时。如果您想要所有个素数,则不需要快速算法。 【参考方案1】:

寻找因素的解决方案:

def divisors(integer):
    result = set()
    i = 2
    j = integer/2
    while(i <= j):
        if integer % i == 0:
            result.add(i)
            #it dont need to 
            result.add(integer//i)
        i += 1
        j = integer//i
    if len(result) > 0:
        return f"not  prime sorted(result)"
    else:
        return f"integer is prime"

--- 测试 ---- 进口时间

start_time = time.time()
print(divisors(180180180180))
print("--- %s seconds ---" % (time.time() - start_time))

--- 0.06314539909362793 秒 ---

start_time = time.time()
print(divs(180180180180180))
print("--- %s seconds ---" % (time.time() - start_time))

--- 1.5997519493103027 秒 ---

start_time = time.time()
print(divisors(1827))
print("--- %s seconds ---" % (time.time() - start_time))

--- 0.0 秒 ---

start_time = time.time()
print(divisors(104729))
print("--- %s seconds ---" % (time.time() - start_time))

--- 0.0 秒 ---

使用此代码:

def divs(integer):
    result = set()
    i = 2
    j = integer / 2
    loops = 0
    while (i <= j):
        if integer % i == 0:
            print(f"loops:loops")
            return f"integer is not a prime"
        i += 1
        j = integer // i
        loops += 1
    print(f"loops:loops")
    
    return f"integer is prime"

--- 测试---

start_time = time.time()
print(divs(180180180180180180180180))
print("--- %s seconds ---" % (time.time() - start_time))

--- 0.0 秒 ---

【讨论】:

【参考方案2】:

这是我一直在玩弄的 Python 中 Eratosthenes 筛的实现。

def eratosthenes(maximum: int) -> list[int | None]:
    """
    Find all the prime numbers between 2 and `maximum`.

    Args:
        maximum: The maximum number to check.

    Returns:
        A list of primes between 2 and `maximum`.
    """

    if maximum < 2:
        return []

    # Discard even numbers by default.
    sequence = dict.fromkeys(range(3, maximum+1, 2), True)

    for num, is_prime in sequence.items():
        # Already filtered, let's skip it.
        if not is_prime:
            continue

        # Avoid marking the same number twice.
        for num2 in range(num ** 2, maximum+1, num):
            # Here, `num2` might contain an even number - skip it.
            if num2 in sequence:
                sequence[num2] = False

    # Re-add 2 as prime and filter out the composite numbers.
    return [2] + [num for num, is_prime in sequence.items() if is_prime]

在简陋的三星 Galaxy A40 上,10000000 个数字的代码似乎需要大约 16 秒。

欢迎提出建议!

【讨论】:

【参考方案3】:

我发现这个解决方案非常快,但它会带来后果,所以这被称为 费马小定理。如果我们取任何数字p 并将其放入(1^p)-1(2^p)-2...(n^p)-n 中,并且我们得到的数字可以被p 整除,那么它就是质数。谈论后果,这不是 100% 正确的解决方案。有一些像341(不是素数)这样的数字,它会通过(2^341)-2 的测试,但在(3^341)-3 上失败,所以它被称为合数。我们可以进行两项或多项检查,以确保他们通过所有检查。还有一种不是素数但也通过了所有测试用例的数字:( 561, 1729 Ramanujan tax no 等。

好消息:(2^p)-2 在前 250 亿个数字中只有 2183 个失败了 案例。

#include <iostream>
#include <math.h>
using namespace std;

int isPrime(int p)

    int tc = pow(2, p) - 2;
    if (tc % p == 0)
    
        cout << p << "is Prime ";
    
    else
    
        cout << p << "is Not Prime";
    
    return 0;


int main()

    int p;
    cin >> p;
    isPrime(p);
    return 0;
 

【讨论】:

【参考方案4】:

我最近编写了这段代码来求数字的总和。可以轻松修改它以查找数字是否为素数。基准在代码之上。

// built on core-i2 e8400
// Benchmark from PowerShell
// Measure-Command  ExeName.exe 
// Days              : 0
// Hours             : 0
// Minutes           : 0
// Seconds           : 23
// Milliseconds      : 516
// Ticks             : 235162598
// TotalDays         : 0.00027217893287037
// TotalHours        : 0.00653229438888889
// TotalMinutes      : 0.391937663333333
// TotalSeconds      : 23.5162598
// TotalMilliseconds : 23516.2598
// built with latest MSVC
// cl /EHsc /std:c++latest main.cpp /O2 /fp:fast /Qpar

#include <cmath>
#include <iostream>
#include <vector>

inline auto prime = [](std::uint64_t I, std::vector<std::uint64_t> &cache) -> std::uint64_t 
    std::uint64_t rootstatic_cast<std::uint64_t>(std::sqrtl(I));
    for (std::size_t i; cache[i] <= root; ++i)
        if (I % cache[i] == 0)
            return 0;

    cache.push_back(I);
    return I;
;

inline auto prime_sum = [](std::uint64_t S) -> std::uint64_t 
    std::uint64_t R5;
    std::vector<std::uint64_t> cache;
    cache.reserve(S / 16);
    cache.push_back(3);

    for (std::uint64_t I5; I <= S; I += 8)
    
        std::uint64_t UI % 3;
        if (U != 0)
            R += prime(I, cache);
        if (U != 1)
            R += prime(I + 2, cache);
        if (U != 2)
            R += prime(I + 4, cache);
        R += prime(I + 6, cache);
    
    return R;
;

int main()

    std::cout << prime_sum(63210123);

【讨论】:

【参考方案5】:

我今天用 C 语言编写了它,用 tcc 编译,几年前在准备竞争性考试时发现了它。不知道有没有人已经写过。它真的很快(但你应该决定它是否很快)。在平均 32% 的 CPU 使用率的 i7 处理器上,花了一两分钟的时间找出了 10 到 1,00,00,000 之间的大约 1,00,004 个素数。 如您所知,只有最后一位数字为 1、3、7 或 9 的才是素数 并且要检查该数字是否为素数,您必须将该数字除以先前找到的素数。 所以首先取一组四个数字 = 1,3,7,9, 通过除以已知素数来测试它, 如果提醒不为零,则数字是素数,将其添加到素数数组。 然后将 10 添加到组中,使其变为 11,13,17,19 并重复该过程。

#include <stdio.h>
int main()     
    int nums[4]=1,3,7,9;
    int primes[100000];
    primes[0]=2;
    primes[1]=3;
    primes[2]=5;
    primes[3]=7;
    int found = 4;
    int got = 1;
    int m=0;
    int upto = 1000000;
    for(int i=0;i<upto;i++)
        //printf("iteration number: %d\n",i);
        for(int j=0;j<4;j++)
            m = nums[j]+10;
            //printf("m = %d\n",m);
            nums[j] = m;
            got = 1;
            for(int k=0;k<found;k++)
                //printf("testing with %d\n",primes[k]);
                if(m%primes[k]==0)
                    got = 0;
                    //printf("%d failed for %d\n",m,primes[k]);
                    break;
                
            
            if(got==1)
                //printf("got new prime: %d\n",m);
                primes[found]= m;
                found++;
            
        
    
    printf("found total %d prime numbers between 1 and %d",found,upto*10);
    return 0;

【讨论】:

【参考方案6】:

有一个 100% 的数学测试将检查数字 P 是素数还是合数,称为 AKS Primality Test。

概念很简单:给定一个数P,如果(x-1)^P - (x^P-1)的所有系数都可以被P整除,那么P是质数,否则是合数。

例如,给定P = 3,将给出多项式:

   (x-1)^3 - (x^3 - 1)
 = x^3 + 3x^2 - 3x - 1 - (x^3 - 1)
 = 3x^2 - 3x

并且系数都可以被3整除,因此这个数是素数。

例如P = 4,它不是一个素数会产生:

   (x-1)^4 - (x^4-1)
 = x^4 - 4x^3 + 6x^2 - 4x + 1 - (x^4 - 1)
 = -4x^3 + 6x^2 - 4x

在这里我们可以看到系数6 不能被4 整除,因此它不是素数。

多项式(x-1)^PP+1 项,可以使用组合找到。所以,这个测试将在 O(n) 运行时运行,所以我不知道这会有多大用处,因为您可以简单地从 0 到 p 迭代 i 并测试剩余部分。

【讨论】:

AKS 在实践中是一种非常缓慢的方法,与其他已知方法相比没有竞争力。您描述的方法不是 AKS,而是一个比未优化的试验划分慢的开放引理(正如您所指出的那样)。 你好@Kousha,x 代表什么?在(x-1)^P - (x^P-1)。你有这个示例代码吗?在 C++ 中确定整数是否为素数? @kiLLua X 只是一个变量。 X 的系数决定了这个数是否是素数。不,我没有代码。我不建议实际使用这种方法来确定一个数字是否为素数。这只是素数的一个非常酷的数学行为,但除此之外它的效率非常低。【参考方案7】:

我会让你决定它是否是最快的。

using System;
namespace PrimeNumbers


public static class Program

    static int primesCount = 0;


    public static void Main()
    
        DateTime startingTime = DateTime.Now;

        RangePrime(1,1000000);   

        DateTime endingTime = DateTime.Now;

        TimeSpan span = endingTime - startingTime;

        Console.WriteLine("span = 0", span.TotalSeconds);

    


    public static void RangePrime(int start, int end)
    
        for (int i = start; i != end+1; i++)
        
            bool isPrime = IsPrime(i);
            if(isPrime)
            
                primesCount++;
                Console.WriteLine("number = 0", i);
            
        
        Console.WriteLine("primes count = 0",primesCount);
    



    public static bool IsPrime(int ToCheck)
    

        if (ToCheck == 2) return true;
        if (ToCheck < 2) return false;


        if (IsOdd(ToCheck))
        
            for (int i = 3; i <= (ToCheck / 3); i += 2)
            
                if (ToCheck % i == 0) return false;
            
            return true;
        
        else return false; // even numbers(excluding 2) are composite
    

    public static bool IsOdd(int ToCheck)
    
        return ((ToCheck % 2 != 0) ? true : false);
    


在配备 2.40 GHz 处理器的 Core 2 Duo 笔记本电脑上,查找和打印 1 到 1,000,000 范围内的质数需要大约 82 秒。它找到了 78,498 个素数。

【讨论】:

这太慢了。问题是i &lt;= (ToCheck / 3)。它应该是i &lt;= (ToCheck / i)。有了它,它可能会在 0.1 秒内运行。 无需多想就能看到这段代码非常慢。你积累了很多错误,例如这个除以 3 而不是 i 以及尝试范围内的偶数。【参考方案8】:

Rabin-Miller 是一个标准的概率素性测试。 (你运行它 K 次,输入数字要么肯定是复合的,要么可能是素数,错误概率为 4-K。(几百次迭代,它几乎肯定会告诉你真相)

有一个非概率(确定性)variant of Rabin Miller。

Great Internet Mersenne Prime Search (GIMPS) 发现了世界上已证明的最大素数记录(274,207,281 - 截至 2017 年 6 月为 1 个),它使用 several algorithms,但这些是特殊形式的素数.然而,上面的 GIMPS 页面确实包含一些通用的确定性素性测试。它们似乎表明哪种算法“最快”取决于要测试的数字的大小。如果您的数字适合 64 位,那么您可能不应该使用旨在处理数百万位素数的方法。

【讨论】:

【参考方案9】:

如果它必须非常快,您可以包含一个素数列表:http://www.bigprimes.net/archive/prime/

如果您只需要知道某个数字是否为质数,则有各种prime tests listed on wikipedia。它们可能是确定大数是否为素数的最快方法,尤其是因为它们可以告诉您一个数字是否不是素数。

【讨论】:

所有素数的列表?我认为您的意思是前几个素数的列表... :) 如果您拨打 100000000 几个电话,那么可以。 :) 与无穷大相比,100000000 肯定是“少数”;) 为什么您认为阿特金筛 (SoA) 比埃拉托色尼筛 (SoE) 快?这肯定不是当一个人只是使​​用您链接的***文章中的伪代码来实现一个程序时。如果 SoE 采用与 SoA 类似的可能优化级别来实现,那么 SoA 的非常大筛选范围的操作比 SoE 的操作略少,但这种增益通常会被增加的复杂性和这种计算复杂性的额外常数因子开销,因此对于实际应用,SoE 更好。 素数的好处在于,它们不会改变。一旦你有了一个列表,你就可以永远重复使用它。【参考方案10】:

我总是使用这种方法来计算使用筛算法的素数。

void primelist()
 
   for(int i = 4; i < pr; i += 2) mark[ i ] = false;
   for(int i = 3; i < pr; i += 2) mark[ i ] = true; mark[ 2 ] = true;
   for(int i = 3, sq = sqrt( pr ); i < sq; i += 2)
       if(mark[ i ])
          for(int j = i << 1; j < pr; j += i) mark[ j ] = false;
  prime[ 0 ] = 2; ind = 1;
  for(int i = 3; i < pr; i += 2)
    if(mark[ i ]) ind++; printf("%d\n", ind);
 

【讨论】:

【参考方案11】:

他,他我知道我是一个回答旧问题的问题死灵法师,但我刚刚发现这个问题正在网上搜索实现有效素数测试的方法。

到目前为止,我认为最快的素数测试算法是强概率素数 (SPRP)。我引用了 Nvidia CUDA 论坛:

数论中更实际的利基问题之一必须做 与素数的识别。给定 N,你如何有效地 确定它是否是素数?这不仅仅是理论上的 问题,它可能是代码中真正需要的,也许当你需要的时候 动态查找特定范围内的素数哈希表大小。如果 N 是 2^30 量级的东西,你真的想做 30000 划分测试以寻找任何因素?显然不是。

这个问题的常见实际解决方案是一个简单的测试,称为 欧拉可能素数检验和更强大的概括 称为强可能素数 (SPRP)。这是一个测试 整数 N 可以概率性地将其分类为素数或非素数,并且 重复测试可以增加正确率。缓慢的部分 测试本身的主要涉及计算一个类似于 A^(N-1) 模 N。任何实现 RSA 公钥加密的人 变体使用了这种算法。它对大整数都很有用 (如 512 位)以及普通的 32 或 64 位整数。

测试可以从概率拒绝更改为 通过预先计算某些测试输入来确定素数证明 已知在 N 范围内总是成功的参数。 不幸的是,这些“最知名的测试”的发现是有效的 搜索一个巨大的(实际上是无限的)域。 1980年,第一个名单 有用的测试是由 Carl Pomerance 创建的(以 用他的 Quadratic Seive 算法分解 RSA-129。)后来 Jaeschke 1993 年成绩显着提高。2004 年,Zhang 和 Tang 改进了搜索域的理论和限制。豪宅和 利文斯通已经发布了迄今为止最现代的结果 网络,http://math.crg4.com/primes.html,一个巨大的最佳结果 搜索域。

查看此处了解更多信息: http://primes.utm.edu/prove/prove2_3.html 和 http://forums.nvidia.com/index.php?showtopic=70483

如果您只需要一种方法来生成非常大的素数并且不关心生成所有小于整数 n 的素数,您可以使用 Lucas-Lehmer 测试来验证 Mersenne 素数。梅森素数的形式为 2^p -1。我认为 Lucas-Lehmer 检验是发现梅森素数最快的算法。

如果你不仅想使用最快的算法,还想使用最快的硬件,尝试使用 Nvidia CUDA 来实现,为 CUDA 编写内核并在 GPU 上运行。

如果您发现足够大的素数,您甚至可以赚取一些钱,EFF 提供的奖金从 5 万美元到 25 万美元不等: https://www.eff.org/awards/coop

【讨论】:

【参考方案12】:

我知道有点晚了,但这可能对通过搜索到达这里的人有用。无论如何,这里有一些 javascript 依赖于只需要测试素数的事实,因此代码生成的早期素数被重新用作以后的测试因子。当然,首先过滤掉所有偶数和 mod 5 值。结果将在数组 P 中,并且此代码可以在 i7 PC 上在 1.5 秒内处理 1000 万个素数(或大约 20 秒内处理 1 亿个)。用C重写应该很快。

var P = [1, 2], j, k, l = 3

for (k = 3 ; k < 10000000 ; k += 2)

  loop: if (++l < 5)
  
    for (j = 2 ; P[j] <= Math.sqrt(k) ; ++j)
      if (k % P[j] == 0) break loop

    P[P.length] = k
  
  else l = 0

【讨论】:

如果你生成大量素数,这会给你带来很多麻烦,为了比较,最好使用 P[j]*P[j] @Simon sqrt 可以被提升出循环并且只计算一次,而P[j]*P[j] 必须在每次迭代时计算。如果没有测试,我不会认为其中一个比另一个更快。【参考方案13】:
#include<iostream>
using namespace std;

void main()

    int num,i,j,prime;
    cout<<"Enter the upper limit :";
    cin>>num;

    cout<<"Prime numbers till "<<num<<" are :2, ";

    for(i=3;i<=num;i++)
    
        prime=1;
        for(j=2;j<i;j++)
        
            if(i%j==0)
            
                prime=0;
                break;
            
        

        if(prime==1)
            cout<<i<<", ";

    

【讨论】:

这是你能做到的最慢的。 这很慢,如果上限是 10000000 那么这段代码会消耗很多时间!! 此代码为 O(N^2/log N)。如果没有break;,它会更慢,O(N^2),但这可能已经被视为编码错误。通过素数保存和测试是 O(N^2/(log N)^2),并且仅通过低于数字平方根的素数进行测试是 O(N^1.5/(log N)^2)。 @WillNess 也许有点夸张。他可以很容易地从 1 而不是 2 开始 for 循环,并添加一个 j 我认为这篇文章不应该被删除,它是一个有价值的反例。【参考方案14】:
#include <iostream>

using namespace std;

int set [1000000];

int main ()

    for (int i=0; i<1000000; i++)
        set [i] = 0;
    
    int set_size= 1000;
    set [set_size];
    set [0] = 2;
    set [1] = 3;
    int Ps = 0;
    int last = 2;

    cout << 2 << " " << 3 << " ";

    for (int n=1; n<10000; n++)
        int t = 0;
        Ps = (n%2)+1+(3*n);
        for (int i=0; i==i; i++)
            if (set [i] == 0) break;
            if (Ps%set[i]==0)
                t=1;
                break;
            
        
        if (t==0)
            cout << Ps << " ";
            set [last] = Ps;
            last++;
        
    
    //cout << last << endl;


    cout << endl;

    system ("pause");
    return 0;

【讨论】:

这应该是关于“如何在不实际使用 GOTO 的情况下编写非结构化代码”的答案。这一切的混乱只是为了编写一个简单的试用除法! (n%2)+1+(3*n) 不过还是不错的。 :) @Will Ness 我会否决这个问题的答案;为什么在宏可以使用时使用 for 循环? :)【参考方案15】:
#include<stdio.h>
main()

    long long unsigned x,y,b,z,e,r,c;
    scanf("%llu",&x);
    if(x<2)return 0;
    scanf("%llu",&y);
    if(y<x)return 0;
    if(x==2)printf("|2");
    if(x%2==0)x+=1;
    if(y%2==0)y-=1;
    for(b=x;b<=y;b+=2)
    
        z=b;e=0;
        for(c=2;c*c<=z;c++)
        
            if(z%c==0)e++;
            if(e>0)z=3;
        
        if(e==0)
        
            printf("|%llu",z);
            r+=1;
        
    
    printf("|\n%llu outputs...\n",r);
    scanf("%llu",&r);
    

【讨论】:

r 在被初始化之前使用【参考方案16】:

这取决于您的应用程序。有一些注意事项:

您是否只需要几个素数的信息,您是否需要达到一定限度的所有素数,或者您是否需要(可能)所有素数? 您需要处理的数字有多大?

Miller-Rabin 和模拟测试只比筛子更快地处理超过一定大小的数字(我相信大约几百万)。在此之下,使用试除法(如果您只有几个数字)或筛子会更快。

【讨论】:

【参考方案17】:

你的问题是决定一个特定的数字是否是素数吗?然后你需要一个素数测试(简单)。或者你需要给定数字的所有素数?在这种情况下,初筛很好(容易,但需要记忆)。或者你需要一个数字的质因数吗?这将需要分解(如果您真的想要最有效的方法,则很难处理大量数据)。您正在查看的数字有多大? 16位? 32位?更大?

一种聪明而有效的方法是预先计算素数表,并使用位级编码将它们保存在文件中。该文件被认为是一个长位向量,而位 n 表示整数 n。如果 n 是素数,则其位设置为 1,否则设置为 0。查找速度非常快(您计算字节偏移量和位掩码)并且不需要将文件加载到内存中。

【讨论】:

一个好的素数测试可以与主内存延迟竞争,因为它可以合理地适合素数表,所以除非它适合 L2,否则我不会使用它。【参考方案18】:

Sieve of Atkin 的一个非常快速的实现是 Dan Bernstein 的 primegen。这种筛子比Sieve of Eratosthenes 更有效。他的页面有一些基准信息。

【讨论】:

其实我不认为 primegen 是最快的,甚至不是第二快的;我认为,yafu 和 primesieve 通常都更快,而且肯定超过 2^32。两者都是 Eratosthenes 的(改进的)筛子,而不是 Atkin-Bernstein 筛子。 Primesieve Eratosthenes (SoE) 的筛子是可能的最快的算法,并且总是比阿特金筛子 SoA 的任何实现更快,包括本答案中链接的伯恩斯坦,因为 primesieve 减少了数量与 SoA 相比的操作数:对于 32 位数字范围 (2^32 - 1),primesieve 进行约 12 亿次剔除,而 SoA 总共进行约 14 亿次组合切换和无平方操作,这两种操作大致相同复杂性并且能够以大致相同的方式进行优化。 续:Bernstein 仅使用与 SoA 相同的有效轮分解比较 SoE,SoA 是一个 2;3;5 轮,使用该轮在 32-位数范围;这使得 SoA在比较这个受限版本的 SoE 时快了大约 30%,以实现同等的其他优化。然而,primesieve 算法使用 2;3;5;7 轮与 2;3;5;7;11;13;17 轮段预剔除以将运算次数减少到大约 12 亿次运行比具有同等操作循环优化的 SoA 快 16.7%。 Continued2:SoA con 没有用于产生很大差异的更高因子轮分解,因为 2;3;5 分解轮是算法的“烘焙”部分。 @Eamon Nerbonne,WP 是正确的;然而,仅仅具有稍微好一点的计算复杂度并不能为一般使用提供更快的算法。在这些 cmets 中,我指的是埃拉托色尼筛 (SoE) 的最大轮因式分解(这对于 Atkin-SoA 筛来说是不可能的)使得 SoE 的操作稍微减少了大约 10 亿。远高于这一点,通常需要使用页面分段来克服内存限制,这就是 SoA 失败的地方,随着范围的增加,不断增加的开销会迅速增加。

以上是关于找到素数最快的算法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

素数测试的最快算法[关闭]

求一个最快的C语言算素数程序

我有一个新算法可以在线性时间内找到因子或素数 - 需要对此进行验证

列出 N 以下所有素数的最快方法

为素数算法找到大哦[重复]

找出所有低于 40 亿的素数的最快方法