Який найшвидший алгоритм пошуку простих чисел?


183

Який найшвидший алгоритм для пошуку простих чисел за допомогою C ++? Я використовував алгоритм сита, але все одно хочу, щоб це було швидше!


Я знайшов стару статтю, але виглядає цікавою: Fun with Prime Numbers
Mvcoile

29
@Jaider це не вдається для цифр, що становлять 7 (111). Він також не відповідає 1001 = 9. І явно це не вдається майже для всіх праймерів взагалі (не охоплює випадку 2 ^ p - 1, які є простими числами Мерсена - класичні згенеровані приклади, - це завжди буде форма 111 ... 1)
Даніель Кац

1
@Kasperasky - Ви не згадали, яке сито? Ви, мабуть, маєте на увазі сито Ерантосів!
користувач2618142

Решето Ератосфена алгоритму
Імад Aghayi

Відповіді:


79

Дуже швидке втілення Сита Аткіна - першоджерело Дана Бернштейна . Це сито є більш ефективним, ніж сито Ератостена . На його сторінці є деяка орієнтирна інформація.


10
Насправді я не вважаю, що Прейген є найшвидшим або навіть другим найшвидшим; яфу та праймесіве обидва швидше, я думаю, і, звичайно, понад 2 ^ 32. Обидва є (модифікованими) ситами Ератостена, а не ситом Аткіна-Бернштейна.
Чарльз

5
Приміссієве сито Ератостена (SoE) - це найшвидший можливий алгоритм, який завжди буде швидшим, ніж будь-яка реалізація Sieve Atkin SoA, включаючи Бернштейна, як це пов'язано у цій відповіді, оскільки primesieve зменшує кількість операцій порівняно з SoA: Для 32- діапазон бітових чисел (2 ^ 32 - 1), primesieve робить близько 1,2 мільярда чашок, тоді як SoA робить близько 1,4 мільярда комбінованих вільних та квадратних вільних операцій, причому обидві операції мають однакову складність і можуть бути оптимізовані приблизно однаково шлях.
GordonBGood

7
Продовження: Бернштейн лише порівняв SoE, використовуючи таку саму ефективну колісну факторизацію, як і для SoA, що становить 2; 3; 5 колеса, використання якого колеса призводить до приблизно 1,83 мільярда качанів за 32-бітний діапазон чисел; це робить SoA приблизно на 30% швидшим при порівнянні цієї обмеженої версії SoE для еквівалентних інших оптимізацій. Однак алгоритм primesieve використовує колесо 2; 3; 5; 7 у поєднанні з 2; 3; 5; 7; 11; 13; 17 сегментом колеса попередньо відключити, щоб зменшити кількість операцій до приблизно 1,2 мільярда, щоб пробігти приблизно 16,7% швидше, ніж SoA з еквівалентними оптимізаціями циклу роботи.
GordonBGood

6
Продовження2: SoA con не має більш високої коефіцієнтної коефіцієнта колеса, що використовується для значної зміни, оскільки колесо факторизації 2; 3; 5 є частиною алгоритму.
GordonBGood

4
@Eamon Nerbonne, WP правильно; однак, лише дещо краща складність обчислювальної техніки не робить більш швидкими алгоритми для загального використання. У цих коментарях я маю на увазі, що максимальна колісна факторизація сита Ератостена (SoE) (що неможливо для Sieve of Atkin-SoA) передбачає дещо менше операцій для SoE до діапазону близько мільярда. Набагато вище цього пункту, як правило, потрібно використовувати сегментацію сторінок для подолання обмежень пам'яті, і саме там SoA виходить з ладу, приймаючи швидко збільшується кількість постійних накладних витрат із збільшенням дальності.
GordonBGood

29

Якщо це має бути дуже швидким, ви можете включити список простих чисел:
http://www.bigprimes.net/archive/prime/

Якщо ви просто повинні знати, чи є певне число простим числом, у Вікіпедії є різні прості тести . Вони, мабуть, найшвидший метод визначити, чи велика кількість є простими числами, тим більше, що вони можуть сказати вам, якщо число не є простим.


2
Список усіх простих? Я думаю, ви маєте на увазі список перших кількох
праймерів

9
Якщо ви телефонуєте 100000000 кілька, то так. :)
Георг Шолі

58
напевно 100000000 - це "кілька" порівняно з нескінченністю;)
Тимофей

9
Чому ви вважаєте, що сито Аткіна (SoA) швидше, ніж сито Ератостена (SoE)? Це, звичайно, не тоді, коли хтось просто реалізує програму, використовуючи псевдокод, як у статті, пов’язаній з Вікіпедією. Якщо SoE реалізується з аналогічним рівнем можливих оптимізацій, які використовуються з SoA, то для дуже великих діапазонів просіювання для SoA є трохи менше операцій, ніж для SoE, але цей приріст, як правило, більше, ніж компенсується підвищеною складністю та додатковий постійний коефіцієнт накладних витрат на цю обчислювальну складність, такий, що для практичних застосувань SoE краще.
GordonBGood

26

Він, він, я знаю, я некромант, який відповідає на старі запитання, але я щойно знайшов це запитання в мережі, щоб знайти шляхи для ефективного тестування простих чисел.

Досі я вважаю, що найшвидший алгоритм тестування простих чисел - це сильний ймовірний прайм (SPRP). Я цитую з форумів Nvidia CUDA:

Одна з найбільш практичних нішевих проблем теорії чисел пов'язана з ідентифікацією простих чисел. З огляду на N, як можна ефективно визначити, чи це простий чи ні? Це не просто проблема з тіеретичною проблемою, це може бути справжня потреба в коді, можливо, коли вам потрібно динамічно знаходити основний розмір хеш-таблиці в певних діапазонах. Якщо N - це щось порядком 2 ^ 30, чи дійсно ви хочете зробити 30000 тестів поділу для пошуку будь-яких факторів? Очевидно, що ні.

Поширеним практичним рішенням цієї проблеми є простий тест, який називається ймовірним простим тестом Ейлера, і більш потужне узагальнення, яке називається сильним ймовірним простим рівнем (SPRP). Це тест, який для цілого N може імовірнісно класифікувати його як простий чи ні, а повторні тести можуть збільшити ймовірність правильності. Сама повільна частина тесту включає в себе обчислення значення, схожого на A ^ (N-1) модуль N. Кожен, хто реалізує варіанти шифрування відкритого ключа RSA, використовував цей алгоритм. Це корисно як для величезних цілих чисел (наприклад, 512 біт), так і для звичайних 32 або 64 бітових ints.

Тест можна змінити від імовірнісного відхилення до остаточного доказу первинності, попередньо обчисливши певні вхідні параметри тесту, які, як відомо, завжди вдається для діапазонів N. На жаль, виявлення цих "найвідоміших тестів" є результатом пошуку величезного ( насправді нескінченний) домен. У 1980 р. Був створений перший список корисних тестів Карлом Померансом (відомим тим, що був фактор RSA-129 за допомогою його квадратичного алгоритму Seive.) Пізніше Jaeschke значно покращив результати в 1993 році. У 2004 році Чжан і Тан вдосконалили теорію та межі пошукового домену. Greathouse та Livingstone опублікували найсучасніші результати досі в Інтернеті за адресою http://math.crg4.com/primes.html , найкращі результати величезного пошукового домену.

Дивіться тут для отримання додаткової інформації: http://primes.utm.edu/prove/prove2_3.html та http://forums.nvidia.com/index.php?showtopic=70483

Якщо вам просто потрібен спосіб генерувати дуже великі прості числа, і вам не байдуже генерувати всі прості числа <ціле число n, ви можете використовувати тест Лукаса-Лемера для перевірки простих чисел Мерсена. Просте число Мерсена має форму 2 ^ p -1. Я думаю, що тест Лукаса-Лемера є найшвидшим алгоритмом, виявленим для простих чисел Мерсена.

Якщо ви хочете використовувати не найшвидший алгоритм, а й найшвидший апарат, спробуйте реалізувати його за допомогою Nvidia CUDA, напишіть ядро ​​для CUDA та запустіть його на GPU.

Ви навіть можете заробити трохи грошей, якщо виявите досить великі прості номери, EFF дає призи від 50 000 до 250 000 доларів: https://www.eff.org/awards/coop


17

Існує 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)^Pбуде P+1терміном і його можна знайти за допомогою комбінації. Отже, цей тест буде працювати під O(n)час виконання, тому я не знаю, наскільки це було б корисно, оскільки ви можете просто повторити його iвід 0 до pтесту на решту.


5
AKS - це дуже повільний на практиці метод, не конкуруючий з іншими відомими методами. Описаний вами метод - це не AKS, а початкова лема, що повільніше, ніж неоптимізований поділ проб (як ви зазначаєте).
DanaJ

привіт @Kousha, для чого xстенди? в (x-1)^P - (x^P-1). у вас є зразок коду для цього? в C ++ для визначення, чи є ціле число простим чи ні?
kiLLua

@kiLLua X - просто змінна. Саме коефіцієнт X визначає, чи є число простим чи ні. І ні у мене немає коду. Я не рекомендую реально використовувати цей метод для визначення того, чи є число простим чи ні. Це просто дуже класна математична поведінка простих чисел, але в іншому випадку це неймовірно неефективно.
Куша

5

Чи є ваша проблема вирішити, чи є певна кількість простим? Тоді вам потрібен тест на первинність (легкий). Або вам потрібні всі праймери до заданої кількості? У цьому випадку провідні сита хороші (легкі, але потребують пам'яті). Або вам потрібні прості множники? Для цього знадобиться факторизація (складно для великої кількості, якщо ви дійсно хочете найефективніших методів). Наскільки великі цифри ви дивитесь? 16 біт? 32 біта? більший?

Один розумний і ефективний спосіб - попередньо обчислити таблиці простих ліній і зберегти їх у файлі, використовуючи кодування на рівні бітів. Файл вважається одним довгим бітовим вектором, тоді як біт n являє собою ціле n. Якщо n є простим, його біт встановлюється в один, а в іншому - до нуля. Пошук дуже швидкий (ви обчислюєте зміщення байтів і трохи маску) і не вимагає завантаження файлу в пам'ять.


Хороший тест на первинність є конкурентоспроможним затримкою основної пам’яті для простих таблиць, які можуть розумно вміститися, тому я б не використовував це, якщо б він не міг вписатися в L2.
Чарльз

3

Рабін-Міллер - це стандартний імовірнісний тест первинності. (ви запускаєте його K разів, а число вводу або однозначно складене, або, ймовірно, просте з вірогідністю помилки 4- К . (кілька сотень ітерацій, і це майже напевно говорить вам правду)

Існує неімовірнісний (детермінований) варіант Рабіна Міллера .

Great Internet Mersenne Prime Search (GIMPS) , який знайшов світовий рекорд за величиною доведеним штрихом (2 74,207,281 - 1 станом на червень 2017 року), використовує кілька алгоритмів , але це прості числа в спеціальних формах. Однак на сторінці GIMPS наведені деякі загальні тести детермінованої первинності. Здається, вони вказують на те, який алгоритм "найшвидший" залежить від розміру тестуваного числа. Якщо ваше число відповідає 64 бітам, вам, ймовірно, не слід застосовувати метод, призначений для роботи на прайметах в кілька мільйонів цифр.


2

Це залежить від вашої заявки. Є деякі міркування:

  • Вам потрібна лише інформація, чи є декілька чисел простими, чи потрібні всі прості числа до певної межі, чи потрібні (потенційно) всі прості числа?
  • Наскільки великі цифри, з якими ви маєте мати справу?

Тести Міллера-Рабіна та аналога лише швидше, ніж сито для чисел певного розміру (я вважаю, десь кілька мільйонів). Нижче, використовуючи пробний поділ (якщо у вас просто кілька номерів) або сито швидше.


-1

Я завжди використовую цей метод для обчислення чисел чисел, що слідують за алгоритмом сита.

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);
 }

-1

Я дозволю вам вирішити, швидше це чи ні.

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);
    }
}
}

На пошуку мого ноутбука Core 2 Duo з процесором 2,40 ГГц потрібно приблизно 82 секунди, щоб знайти та роздрукувати прості номери в межах від 1 до 1 000 000. І виявлено 78 498 простих чисел.


3
це шлях занадто повільний. проблема в тому i <= (ToCheck / 3). так і має бути i <= (ToCheck / i). з ним він може працювати замість 0,1 секунди.
Буде Несс

-3
#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 використовується до ініціалізації
zumalifeguard

-3

Я не знаю про жодний заздалегідь заданий алгоритм, але я створив свій власний, який дуже швидко. Він може обробити 20 цифр цифр менше ніж за 1 секунду. Максимум можливостей цієї програми - 18446744073709551615. Програма:

#include <iostream>
#include <cmath>
#include <stdlib.h>

using namespace std;

unsigned long long int num = 0;

bool prime() {
    if (num % 2 == 0 || num == 1) {
        return false;
    }

    unsigned long int square_root = sqrt(num);
    for (unsigned long int i = 3; i <= square_root; i += 2) {
        if (num % i == 0) {
            return false;
        }
    }

    return true;
}

int main() {
    do {
        system("cls");
        cout << "Enter number : ";
        cin >> num;

        if (prime()) {
            cout << "The number is a prime number" << endl << endl << endl << endl;
        } else {
            cout << "The number is not a prime number" << endl << endl << endl << endl;
        }
        system("pause");
    } while (1);

    return 0;
}

-4
#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;
}

12
це має бути відповідь на тему "Як написати неструктурований код без фактичного використання GOTO". Все це плутанина просто для кодування простого пробного поділу! (n%2)+1+(3*n)Це щось добре, хоча. :)
Чи буде Несс

1
@Will Ness я б сказав це як відповідь на це питання; навіщо використовувати цикл, коли макрос буде робити? :)
Роб Грант

-4

Я знаю, що це трохи пізніше, але це може бути корисно людям, які приїжджають сюди з пошуків. У будь-якому випадку, ось деякий JavaScript, який спирається на те, що потрібно перевірити лише основні фактори, тому попередні праймери, згенеровані кодом, повторно використовуються як тестові фактори для пізніших. Звичайно, усі рівні і mod 5 значення відфільтровані спочатку. Результат буде в масиві P, і цей код може розчавити 10 мільйонів праймів за 1,5 секунди на ПК i7 (або 100 мільйонів приблизно за 20). Переписане на 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
}

2
Це доставить вам багато клопотів, якщо ви генеруєте велику кількість простих ліній, а для порівнянь краще використовуйте P [j] * P [j] <= k, тому що sqrt є досить повільним
Саймон

-11
#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<<", ";

    }
}

60
це про найповільніших, про які можна піти.
Чи буде Несс

1
Це дуже повільно, якщо верхня межа дозволяє сказати 10000000, то цей код забирає багато часу !!
Dixit Singla

цей код - O (N ^ 2 / log N). без break;цього було б ще повільніше, O (N ^ 2), але це вже можна розглядати як помилку кодування. збереження та тестування на праймери - O (N ^ 2 / (log N) ^ 2), а тестування на праймери нижче квадратного кореня числа - O (N ^ 1,5 / (log N) ^ 2).
Буде Несс

@WillNess Можливо, трохи гіперболічний. Він міг легко запустити цикл for з 1 замість 2, і додав j <= i замість j <i. :)
Кенні Кейсон

3
Я не думаю, що цю публікацію не слід видаляти, вона слугує цінним контрприкладом.
Буде Несс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.