Сегментированное решето Эратосфена?

Сделать простое сито достаточно просто:

for (int i=2; i<=N; i++){
    if (sieve[i]==0){
        cout << i << " is prime" << endl;
        for (int j = i; j<=N; j+=i){
            sieve[j]=1;
        }
    }
    cout << i << " has " << sieve[i] << " distinct prime factors\n";
}

Но что делать, если N очень велико, и я не могу хранить такой массив в памяти? Я просмотрел подходы с использованием сегментированного сита, и они, похоже, включают поиск простых чисел до sqrt (N), но я не понимаю, как это работает. Что, если N очень велико (скажем, 10 ^ 18)?


person John Smith    schedule 20.04.2012    source источник
comment
В своем ответе на larsmans вы упомянули, что вам действительно интересно найти количество простых множителей для больших N. В этом случае, предполагая, что N ‹10 ^ 18, вам гораздо лучше множить N, чем просеивать все числа до Н.   -  person user448810    schedule 20.04.2012
comment
Для каждого k до N, а не только для N   -  person John Smith    schedule 20.04.2012


Ответы (6)


Основная идея сегментированного сита состоит в том, чтобы выбрать простые числа рассева, меньшие квадратного корня из n, выбрать достаточно большой размер сегмента, который, тем не менее, уместиться в памяти, а затем просеивать каждый из сегментов по очереди, начиная с самых маленьких. В первом сегменте вычисляется наименьшее кратное каждому простому рассеву, находящемуся внутри сегмента, затем кратные количеству просеивающего стержня обычным образом отмечаются как составные; когда все простые числа просеивания были использованы, оставшиеся немаркированные числа в сегменте являются простыми. Затем, для следующего сегмента, для каждого начального числа рассева вам уже известно первое кратное в текущем сегменте (именно кратное число завершило рассев для этого начального числа в предыдущем сегменте), поэтому вы просеиваете каждое начальное число просеивания и т. Д. пока вы не закончите.

Размер n не имеет значения, за исключением того, что для большего n потребуется больше времени для просеивания, чем для меньшего n; размер имеет значение, это размер сегмента, который должен быть настолько большим, насколько это удобно (скажем, размер кэша первичной памяти на машине).

Вы можете увидеть простую реализацию сегментированного сита здесь. Обратите внимание, что сегментированное решето будет намного быстрее, чем решето очереди приоритетов О'Нила, упомянутое в другом ответе; Если вам интересно, здесь есть реализация.

РЕДАКТИРОВАТЬ: Я написал это для другой цели, но я покажу это здесь, потому что это может быть полезно:

Хотя Сито Эратосфена очень быстрое, для него требуется O (n) места. Это можно уменьшить до O (sqrt (n)) для простых чисел просеивания плюс O (1) для битового массива, выполняя просеивание в последовательных сегментах. В первом сегменте вычисляется наименьшее кратное каждому простому просеиванию, которое находится в сегменте, затем кратные количеству просеивающего стержня отмечаются как составные обычным способом; когда все простые числа просеивания были использованы, оставшиеся немаркированные числа в сегменте являются простыми. Затем для следующего сегмента наименьшее значение, кратное каждой заправке просеивания, является кратным, которое завершило рассев в предыдущем сегменте, и поэтому рассев продолжается до завершения.

Рассмотрим пример сита от 100 до 200 в сегментах по 20. Пять простых чисел рассева - 3, 5, 7, 11 и 13. В первом сегменте от 100 до 120 битовый массив имеет десять слотов, причем слот 0 соответствует 101. , слот k, соответствующий 100 + 2k + 1, и слот 9, соответствующий 119. Наименьшее кратное 3 в сегменте равно 105, что соответствует слоту 2; слоты 2 + 3 = 5 и 5 + 3 = 8 также кратны 3. Наименьшее кратное 5 равно 105 в слоте 2, а слот 2 + 5 = 7 также кратно 5. Наименьшее кратное 7 равно 105 в слоте 2, и слот 2 + 7 = 9 также кратно 7. И так далее.

Функция primesRange принимает аргументы lo, hi и delta; lo и hi должны быть четными, причем lo ‹hi, а lo должно быть больше sqrt (hi). Размер сегмента в два раза больше дельты. Ps - это связанный список, содержащий простые числа, меньшие, чем sqrt (hi), с удалением 2, поскольку четные числа игнорируются. Qs - это связанный список, содержащий переход в битовый массив сита наименьшего кратного в текущем сегменте соответствующего простого числа рассева. После каждого сегмента lo увеличивается на удвоенную дельту, поэтому число, соответствующее индексу i ситового битового массива, равно lo + 2i + 1.

function primesRange(lo, hi, delta)
    function qInit(p)
        return (-1/2 * (lo + p + 1)) % p
    function qReset(p, q)
        return (q - delta) % p
    sieve := makeArray(0..delta-1)
    ps := tail(primes(sqrt(hi)))
    qs := map(qInit, ps)
    while lo < hi
        for i from 0 to delta-1
            sieve[i] := True
        for p,q in ps,qs
            for i from q to delta step p
                sieve[i] := False
        qs := map(qReset, ps, qs)
        for i,t from 0,lo+1 to delta-1,hi step 1,2
            if sieve[i]
                output t
        lo := lo + 2 * delta

При вызове primesRange (100, 200, 10) простые числа рассева ps равны [3, 5, 7, 11, 13]; qs изначально имеет значение [2, 2, 2, 10, 8], соответствующее наименьшим кратным 105, 105, 105, 121 и 117, и сбрасывается для второго сегмента на [1, 2, 6, 0, 11], соответствующее наименьшему кратные 123, 125, 133, 121 и 143.

Вы можете увидеть эту программу в действии на странице http://ideone.com/iHYr1f. И в дополнение к приведенным выше ссылкам, если вы интересуетесь программированием с простыми числами, я скромно рекомендую это эссе в моем блоге.

person user448810    schedule 20.04.2012
comment
Я уже видел множество реализаций - опять же, я не понимаю их полностью, потому что для меня это слишком абстрактно. Ищу примеры. - person John Smith; 20.04.2012
comment
Вы смотрели? Реализация, на которую я указал, включает довольно хорошее объяснение. - person user448810; 20.04.2012
comment
Да, я уже был на этом сайте, но я начинаю тускнеть, когда они волей-неволей начинают разбрасывать переменные. Мне просто нужно что-то чисто числовое, чтобы я мог получить интуицию - person John Smith; 20.04.2012
comment
Вы просили привести примеры. На указанном сайте показано, как точно просеять диапазон от 100 до 200 в пяти сегментах, в том числе как выбрать простые числа рассева и как сбросить значения просеивания для каждого сегмента. Вы сами разработали этот пример? Что вы до сих пор не понимаете? - person user448810; 20.04.2012
comment
Когда они начинают заниматься этим PQB, и я не могу все исправить, не говоря уже о том, чтобы делать это вручную. Я не знаю, почему они выбирают те ценности, которые они делают, что именно они делают и т. Д. - person John Smith; 20.04.2012
comment
Смотрим на пример. Простые числа рассева, меньшие квадратного корня из 200, равны 3, 5, 7, 11 и 13. Рассмотрим первый сегмент, который имеет десять значений {101 103 105 107 109 111 113 115 117 119}. Наименьшее кратное 3 в сегменте - 105, поэтому вычеркните 105 и каждое третье число после: 111, 117. Наименьшее кратное 5 в сегменте - 105, поэтому вычеркните 105 и пятое число после: 115. Наименьшее кратное из 7 в сегменте - 105, поэтому вычеркните 105 и седьмое число после: 119. В сегменте нет числа, кратного 11, поэтому делать нечего. Наименьшее число, кратное 13 - person user448810; 20.04.2012
comment
в сегменте 117, так что бей. Оставшиеся числа {101 103 107 109 113} простые. Для второго сегмента {121, 123, 125, 127, 129, 131, 133, 135, 137 139} наименьшие кратные числа каждого простого числа равны 123, 125, 133 (за пределами сегмента), 121 и 143 (за пределами сегмента), и все они могут быть вычислены путем подсчета следующее кратное заполнению просеивания после конца первого сегмента. Это поможет? - person user448810; 20.04.2012
comment
То есть в основном он использует простые числа, меньшие квадратного корня из верхней границы, чтобы вычеркнуть записи в каждом сегменте? Работает ли это, если я тоже ищу простые факторизации? (скажем, я меняю решето [j] = 1 в моем коде на решето [j] + = 1) - person John Smith; 20.04.2012
comment
+1 за отличное описание сегментированных сит и ссылку на программирование. - person NealB; 20.04.2012
comment
Да, для вычеркивания композитов используются простые числа, меньшие квадратного корня из верхней границы. Чтобы вычислить простые множители, сохраните первое простое просеивающее число, которое вычеркивает каждое число, которое является наименьшим простым множителем числа, затем разделите на наименьшее число простых множителей, чтобы получить кофактор, и найдите кофактор в своей таблице наименьших простых множителей. . И так далее рекурсивно. Но это не приведет вас к 10 ^ 18, так как таблица наименьших простых множителей будет слишком большой. - person user448810; 20.04.2012
comment
NeilB: Спасибо. Кстати, я пишу Programming Praxis. - person user448810; 20.04.2012
comment
Есть ли способ достичь 10 ^ 18 с помощью просеивания? Имеет ли смысл мой исходный код? Я отредактировал его, чтобы обеспечить некоторый вывод. РЕДАКТИРОВАТЬ: Ого, я не знал, что это ваш сайт! Потрясающий - person John Smith; 20.04.2012
comment
да. Но вам понадобится много памяти (для всех простых чисел просеивания меньше 10 ^ 9), и вам придется сохранять результат на диске. Дэниел Фишер, который тусуется здесь, на Stack Overflow, сделал это. Tomas Oliveira e Silva сделал это, и у него есть свой веб-сайт. Но вычисление большого количества простых чисел - это почти наверняка неправильный способ делать то, что вы хотите, - это неправильный способ делать что угодно. Почему бы вам не рассказать нам, что вы действительно пытаетесь сделать, и, возможно, мы сможем предложить лучший метод. - person user448810; 20.04.2012
comment
Я предполагаю, что вы имеете в виду omega (k) для 2 ‹= k‹ = N; то есть вам нужен результат для всех k в диапазоне, а не только для некоторых k в диапазоне. В этом случае сито подойдет: просто проведите через простые числа просеивания до квадратного корня из n, добавляя 1 в каждом месте базового массива. Он отлично работает, если сито сегментировано; Например, в этом примере мы нашли 3 простых числа просеивания, которые делили 105. Похоже на странное хобби. - person user448810; 20.04.2012
comment
Конечно, если вам просто нужна омега (k) для единственного значения k, вы должны просто множить k и удалять повторяющиеся множители. - person user448810; 20.04.2012
comment
Правильно ли я сделал это в исходном посте? Базовый массив для 2 ‹= k‹ = квадратный корень из N. И тогда я полагаю, что могу использовать его для остальной части N? - person John Smith; 20.04.2012
comment
Полагаю, ваше решето где-то еще. Подсчет, который вы дали, должен работать. Попробуйте это для N = 1000. Протестируйте это, написав функцию, которая множит k и подсчитывает неповторяющиеся факторы, а затем сравнивает их. Дайте мне знать, как у вас дела (электронная почта в моем профиле). - person user448810; 20.04.2012
comment
Какое эмпирическое правило выбора размера блока сегментирования? - person John Smith; 20.04.2012
comment
Как можно больше. Но он должен уместиться в памяти, желательно в вашем самом маленьком кэше. Помните, что вам нужны биты, а не байты. - person user448810; 20.04.2012
comment
Думаю, здесь я впервые наткнулся на камень преткновения. А как насчет простых множителей, превышающих sqrt (N), которые множители k больше, чем sqrt (N)? Их пропустили. - person John Smith; 20.04.2012
comment
Нет делителей на k ‹N больше квадратного корня из N. Однако между квадратным корнем из N и N есть простые числа, которые вам нужно прибавить к своему счету. - person user448810; 20.04.2012
comment
Я имею в виду, что рассмотрим случай N = 100. Простые числа в нашем начальном сите будут 2,3,5,7. Допустим, мы берем B = 10. Когда мы дойдем до сегмента 40-50, у 46 будет 23 (простое число) в качестве одного из факторов, даже если он не является частью нашего первоначального простого списка. - person John Smith; 20.04.2012
comment
Я ошибся. Просеивая простые числа, вы можете остановиться по крайней мере по первому множителю. При подсчете факторов вы должны учитывать все основные факторы, а не только наименьший простой фактор. Это означает, что вам нужно просеять до n / 2 вместо sqrt (n). Это замедлит итерацию до 10 ^ 18. Код доступен по адресу http://ideone.com/x1Sxw, но он использует обычное сито, а не сегментированное сито. - person user448810; 20.04.2012
comment
Как улучшить этот алгоритм, чтобы он работал на малых lo? Например, при его тестировании в диапазоне 4 to 50 перечислены простые числа больше 11, а в диапазоне 4 to 10 перечислены также 11. Более того, передача нечетного целого числа в качестве lo заставляет этот алгоритм печатать случайные значения. - person tomi.lee.jones; 31.08.2013
comment
В тексте приводятся различные ограничения на lo и hi, которые нарушаются в ваших примерах. Для столь малых диапазонов лучше использовать стандартное сито Эратосфена и отбрасывать любые простые числа, которые вам не нужны. - person user448810; 31.08.2013
comment
Какова логика шага Q = (-1/2 * (L + Pk + 1))% Pk? Как он вычисляет первое число (L + Q), большее L, которое делится на Pk? - person sanchit.h; 05.09.2013
comment
Это не первое число больше L, делимое на P, а первое смещение в решето. Рассмотрим окно длины P, скользящее по числовой прямой; в какой-то момент одна конечная точка будет больше L, а другая меньше или равна L. Расстояние от левой конечной точки до L равно L% P, а оставшееся -L% P над L - это смещение, в котором мы хотим начать просеивать. Сложите 1 и разделите на 2, потому что наше сито имеет только нечетные числа, и переставьте члены, чтобы вывести операцию mod P за скобки. - person user448810; 05.09.2013
comment
Спасибо! Теперь это имеет смысл! Кроме того, сито в настоящее время пропускает четные числа, как мы можем изменить его, чтобы включить в него большее колесо 2,3,5 и т. Д. - person sanchit.h; 09.09.2013
comment
Поскольку единственное четное простое число - 2, вы можете игнорировать все четные числа на своем сите и обрабатывать 2 специально. - person user448810; 09.09.2013
comment
Я хотел спросить, как мы можем игнорировать кратные 2,3 и 5 так же, как мы игнорируем здесь кратные 2. Тогда нам нужно проверять только каждые 8 ​​чисел на каждые 30 чисел вместо 15 (когда мы игнорируем числа, кратные 2). - person sanchit.h; 12.09.2013
comment
Я разместил простое колесное сито http://codepad.org/i22jgU9W. Не ждите улучшения скорости; бухгалтерия, необходимая для подсчета пропусков, стоит больше, чем экономия от уменьшенного просеивания. Также поищите решето-колесо Притчарда в моем блоге. Я оставлю вас сегментировать самостоятельно. Между этим и предыдущим вопросом вы, вероятно, должны мне награду. - person user448810; 12.09.2013
comment
Спасибо, я попробую снова сегментировать свое сито, я на самом деле застрял в реализации сита с колесной факторизацией и сегментацией. В вашем сите я вижу, что вы сжали обычные {6, 4, 2, 4, 2, 4, 6, 2} в массив «половинок», чтобы уменьшить пространство, но его скорость можно улучшить, отметив (p * x- 7) / 2 [x = p] как составное и увеличивающее x на whlptrn [m] вместо пропуска, кратного p, 2p, 3p (логику которой я не понял). Мое сито, которое использует это, вычисляет пи (10 ^ 8) менее чем за секунду, я надеялся улучшить его до пи (10 ^ 9) менее чем за 1 с, также включив сегментацию. - person sanchit.h; 14.09.2013
comment
Я не знаю здесь о наградах, нужно ли мне для этого начинать новый вопрос? - person sanchit.h; 14.09.2013
comment
Я бы не рекомендовал просеивать до 10 ^ 18 для всего диапазона для завершения в ближайшее время на текущих настольных компьютерах: хранение не является проблемой, поскольку можно просто сохранить битовый массив базовых простых чисел, который при сжатии с использованием колесо факторизации составляет всего около 28 Мегабайт; проблема заключается во времени выполнения - даже если на составную операцию отбраковки уходит 3 тактовых цикла ЦП, примерно 4 на 10 ^ 17 отбраковок потребуются годы, чтобы вычислить только количество простых чисел, не говоря уже об использовании этих простых чисел для выполнения чего-то, например, факторизации грубой силы. Хотя достаточно простых чисел до миллиарда, чтобы разложить на множители до 10 ^ 18 ... - person GordonBGood; 17.04.2014
comment
продолжение: указанная выше работа в интерпретируемом Python выполняется еще медленнее, если просеивать весь диапазон до 10 ^ 18, так как это будет 100 тактовых циклов на выбор составного числа. а не только три, как в моей оценке выше, сделанной на основе реализации компилятора собственного кода. Тем не менее, я не вижу необходимости просеивать до N 10 ^ 18, если кто-то просто хочет, чтобы простые множители были до этого предела, хотя определение количества множителей путем деления грубой силы для каждого числа в этом диапазоне по-прежнему является большой задачей, даже с учетом от 50 миллионов простых чисел до миллиарда - миллион триллионов делений при десятках тактовых частот процессора на ... - person GordonBGood; 17.04.2014
comment
@ sanchit.h Нет, вы можете просто нажать кнопку запуска награды под вопросом, а затем назначить эту награду на любой ответ, который вам нравится. Подробнее о наградах здесь. Прежде чем задавать вопрос, лучше сначала погуглить (так как это экономит время), эта ссылка появляется в верхней части результатов. - person Adam Stelmaszczyk; 19.06.2017
comment
GordonBGood: Теперь у вас есть все эти простые числа меньше 10 ^ 17, что вы собираетесь с ними делать? На самом деле большинство математиков используют эти таблицы для подсчета простых чисел и соотносят их, например, с дзета-функция Римана. Если вы просто считаете простые числа, есть и другие решения. Я собрал некоторые из них в github.com/albertvanderhorst/primecounting - person Albert van der Horst; 31.10.2018
comment
@AlbertvanderHorst, извините, я пропустил ваш комментарий. Ваша точка зрения, ставящая под сомнение полезность предоставления всех простых чисел в каком-то огромном диапазоне, также является моей точкой (а также почти невозможным фактическим выполнением задачи с использованием общедоступных компьютеров или даже суперкомпьютеров с использованием методов просеивания). Что касается простого подсчета простых чисел, кроме решений, перечисленных в вашем репозитории GitHub, наиболее полезным для огромных диапазонов является использование методов численного анализа, как это сделано в простое число Кима Валиша и может быть расширено до суммирования простых чисел. - person GordonBGood; 01.07.2020

Существует версия сита, основанная на очередях приоритетов, которая дает столько простых чисел, сколько вы запрашиваете, а не все из них до верхней границы. Это обсуждается в классической статье «Подлинное сито Эратосфена» а поиск в Google "сита очереди приоритетов эратосфена" обнаруживает довольно много реализаций на различных языках программирования.

person Fred Foo    schedule 20.04.2012
comment
Я сталкивался с реализациями, но проблема в том, что я их не понимаю. Эти бумаги всегда довольно плотные. В основном я ищу примеры, потому что считаю, что с ними легче всего работать и понимать. Технически я использую решето, чтобы получить количество уникальных простых множителей на значение k для больших N. - person John Smith; 20.04.2012
comment
Инкрементное сито, используемое Мелиссой О'Нил в связанной статье, довольно медленное по сравнению с ситом на основе массивов и, что еще хуже, имеет асимптотическую вычислительную сложность, которая растет значительно быстрее, чем линейно с диапазоном, поэтому может не подходить для этого проблема. Что касается необходимой квалификации без верхней границы, сегментированное решето страницы также не обязательно должно иметь указанную верхнюю границу, если базовые простые числа меньше квадратного корня текущего диапазона) реализованы как расширяемый массив или как форма списка . - person GordonBGood; 17.04.2014
comment
@gordonbgood для меня не совсем правильно, что сито на основе итераторов и очередей с приоритетами работает довольно медленно по сравнению с ситом на основе массивов. Время выполнения, насколько я могу судить: O (сумма от i = 2 до n из log (π (i) -1) ω (i)) (где π - количество простых чисел, меньшее или равное заданному числу , ω - количество уникальных простых множителей данного числа). Сравнительно наивная реализация решета на основе массива - это O (сумма от i = 2 до n из (n / i, если i простое, или 1, если i не простое)). - person mtraceur; 01.03.2021
comment
@gordonbgood По сути, у меня нет математических навыков, чтобы быстро это обдумать, но в настоящее время я думаю, что вы правы в том, что первое происходит медленнее, чем второе, и что у первого бессимптомный рост хуже, чем у второго. Но это не очень тривиальный анализ, моя первоначальная интуиция заключалась в том, чтобы усомниться в вашем комментарии. Мне пришлось сделать так, чтобы сложность каждой итерации была явной, чтобы я увидел, что вы в целом правы (субъективные нечеткие усиливающие слова вроде «совсем в стороне»). - person mtraceur; 01.03.2021
comment
@gordonbgood Но при дальнейшем анализе становится еще менее ясно. Давайте посмотрим на эти термины в сумме: n / i в массиве vs log (π (i) -1) ω (i). Прежние тенденции от n, деленного на небольшую константу, в сторону единицы. Последняя стремится от единицы к log (π (n) -1) ω (n). Это искушает интуицию, что первое сжимается, второе растет, поэтому очевидно, что первое быстрее, а второе медленнее. Но я счел полезным заметить, что если мы возьмем все члены этих сумм для данного n и отсортируем их от наименьшего к наибольшему, оба начнутся с 1 и увеличатся до n / 2 и log (π (n) -1) ω (n) соответственно. - person mtraceur; 01.03.2021
comment
Итак, первая реализация состоит в том, что n / 2, очевидно, намного больше, чем log (π (n) -1) ω (n) для больших n. Мы можем сделать очевидную оптимизацию, например, игнорировать все четные числа, чтобы превратить эти два в три, и я думаю, что мы можем обобщить на факторизацию колеса, чтобы увеличить это число. Но даже если мы будем рассматривать факторизацию колеса как бесплатную, она все равно сделает ее n / C только для некоторой константы C, и независимо от того, насколько большим мы сделаем C, log и ω будут давать меньшие результаты, чем для достаточно большого n. Итак, если это быстрее, то это потому, что мы платим n / i только за простые числа i, но мы платим log (π (i) -1) ω (i) за все i. - person mtraceur; 01.03.2021
comment
@gordonbgood И снова, для ясности, я не говорю, что вы ошибаетесь, я просто говорю, что я не могу доказать правильность вашего утверждения и что с точки зрения только моих собственных ограниченных возможностей для анализа этого проблемного пространства я бы не чувствовать себя комфортно, уверенно принимая вывод о том, что он обязательно медленнее. - person mtraceur; 01.03.2021
comment
@mtaceur, я постараюсь свести это к вам по двум причинам: 1) сложность асимптотических вычислений O, которая вас больше всего беспокоит, и 2) количество циклов ЦП = время, необходимое для выполнения каждой операции. Что касается первого, то хорошо известно, что сито Эратосфена имеет O (n log log n) операций, где n - это диапазон просеивания для очень больших диапазонов согласно статья в Википедии, и из более точных выводов этой статьи, что существует большое постоянное смещение, которое делает практические диапазоны еще меньше. - person GordonBGood; 02.03.2021
comment
@mtraceur, Кроме того, хорошо известно, что время доступа для приоритетной очереди на основе кучи составляет O (log n) из ее структуры на основе двоичного дерева, где n - это количество элементов в очереди. Теперь, в реализации Мелисы О'Нил, количество элементов в очереди - это количество базовых простых чисел до квадратного корня из текущего диапазона (из более поздних улучшенных версий) и, следовательно, охватывается статьей в Википедии, поэтому он добавил log n коэффициент сложности базовой SoE; О'Нил подтверждает это в своей статье. Как вы говорите, уровни оптимизации колес только добавляют постоянные улучшения коэффициентов. - person GordonBGood; 02.03.2021
comment
@mtraceur, Значит, в этих функциональных инкрементальных ситах много раз и увеличивается с диапазоном количества операций. Кроме того, они не могут быть многопоточными, поскольку каждый шаг зависит от предыдущего, поэтому нельзя использовать преимущества современных многоядерных процессоров. 2) Наконец, что наиболее серьезно, их время на операцию медленное, при сотнях тактовых циклов ЦП на исключение составного числа, тогда как реализация на основе массива может занимать всего один и в среднем всего несколько тактовых циклов на выборку например, сито Кима Валиша. - person GordonBGood; 02.03.2021
comment
@mtraceur, Наконец, что касается, возможно, более понятной серии ответов в руководстве, см. мой ответ на страницу-сегментированную SoE в JavaScript и ответы, ведущие к этому, который использует отбраковку массивов для отсеивания миллиардов всего за пять или шесть тактовых циклов ЦП за операцию отсева (запускается в браузере Google Chrome). Этот фрагмент можно запускать даже на смартфоне, если рассматривать его как сайт для настольных компьютеров. По обеим причинам, связанным с уменьшением количества операций и снижением затрат на операцию, ни одно функциональное сито не может приблизиться к этому в данном языке. - person GordonBGood; 02.03.2021
comment
@gordonbgood Я согласен с вами в отношении огромной разницы в количестве фактических инструкций ЦП на количество исключенных, но что касается алгоритмической сложности ... вы просто рассматривали два разных n из двух разных контекстов, как если бы они были одинаковыми n. Когда n - это число, до которого мы просеиваем, это π имеет решающее значение для учета количества простых чисел, меньших или равных n, которые фактически попадают в кучу. Мы не можем просто отбросить его (если вы не хотите аргументировать, что функция подсчета простых чисел является приблизительно линейной или эффективно подавляется другими факторами). - person mtraceur; 02.03.2021
comment
@gordonbgood У меня также сложилось впечатление из ваших замечаний, что вы ... либо не осознавали, либо не сочли целесообразным признать, что O(n log log n), который хорошо известен благодаря решето на основе массивов, на самом деле является бессимптомным приближением более точная сумма, которую я привел в своих комментариях. То есть, может быть, я ошибаюсь, но у меня есть книга, написанная людьми намного умнее меня, в которых говорится, что время выполнения O(n/3 + n/5 + n/7 + n/11 + ...) и что с помощью некоторой причудливой математики можно показать, что она имеет тенденцию к O(n log log n). Я просто увеличил сумму обратно, чтобы облегчить сравнение. - person mtraceur; 02.03.2021
comment
@mtraceur, если так будет продолжаться, может, нам лучше переместить это в чат. Однако второй верен, и я резюмировал его, сославшись на полный вывод из статьи в Википедии. Сложность SoE не имеет ничего общего с тем, почему реализации, подобные O'Neill, работают медленнее, и даже если исключить дополнительный коэффициент log n с помощью хеш-таблицы с амортизированным временем доступа O (1), медленность связана с сотнями или тысячами для их реализации требуется большее количество тактовых циклов ЦП по сравнению с необработанным отбраковкой битов в массиве. Извините, если мои предыдущие комментарии произвели другое впечатление. - person GordonBGood; 02.03.2021

Просто мы делаем сегментированное сито, которое у нас есть. Основная идея состоит в том, что, скажем, нам нужно найти простые числа от 85 до 100. Мы должны применить традиционное сито, но способом, описанным ниже:

Итак, мы берем первое простое число 2, делим начальное число на 2 (85/2) и, округляя до меньшего числа, получаем p = 42, теперь снова умножаем на 2, получаем p = 84, с этого момента начинаем прибавлять 2 Итак, мы удалили все множители 2 (86,88,90,92,94,96,98,100) в диапазоне.

Мы берем следующее простое число 3, делим начальное число на 3 (85/3) и, округляя до меньшего числа, получаем p = 28, теперь снова умножаем на 3, получаем p = 84, с этого момента начинаем прибавлять 3 до Последнее число. Итак, мы удалили все множители 3 (87,90,93,96,99) в диапазоне.

Возьмите следующее простое число = 5 и так далее .................. Продолжайте выполнять указанные выше шаги. Вы можете получить простые числа (2,3,5,7, ...), используя традиционное сито до sqrt (n), а затем используйте его для сегментированного сита.

person OneWhoKnocks    schedule 28.04.2015

Если кто-то хочет увидеть реализацию на C ++, вот моя:

void sito_delta( int delta, std::vector<int> &res)
{

std::unique_ptr<int[]> results(new int[delta+1]);
for(int i = 0; i <= delta; ++i)
    results[i] = 1;

int pierw = sqrt(delta);
for (int j = 2; j <= pierw; ++j)
{
    if(results[j])
    {
        for (int k = 2*j; k <= delta; k+=j)
        {
            results[k]=0;
        }
    }
}

for (int m = 2; m <= delta; ++m)
    if (results[m])
    {
        res.push_back(m);
        std::cout<<","<<m;
    }
};
void sito_segment(int n,std::vector<int> &fiPri)
{
int delta = sqrt(n);

if (delta>10)
{
    sito_segment(delta,fiPri);
   // COmpute using fiPri as primes
   // n=n,prime = fiPri;
      std::vector<int> prime=fiPri;
      int offset = delta;
      int low = offset;
      int high = offset * 2;
      while (low < n)
      {
          if (high >=n ) high = n;
          int mark[offset+1];
          for (int s=0;s<=offset;++s)
              mark[s]=1;

          for(int j = 0; j< prime.size(); ++j)
          {
            int lowMinimum = (low/prime[j]) * prime[j];
            if(lowMinimum < low)
                lowMinimum += prime[j];

            for(int k = lowMinimum; k<=high;k+=prime[j])
                mark[k-low]=0;
          }

          for(int i = low; i <= high; i++)
              if(mark[i-low])
              {
                fiPri.push_back(i);
                std::cout<<","<<i;
              }
          low=low+offset;
          high=high+offset;
      }
}
else
{

std::vector<int> prime;
sito_delta(delta, prime);
//
fiPri = prime;
//
int offset = delta;
int low = offset;
int high = offset * 2;
// Process segments one by one 
while (low < n)
{
    if (high >= n) high = n;
    int  mark[offset+1];
    for (int s = 0; s <= offset; ++s)
        mark[s] = 1;

    for (int j = 0; j < prime.size(); ++j)
    {
        // find the minimum number in [low..high] that is
        // multiple of prime[i] (divisible by prime[j])
        int lowMinimum = (low/prime[j]) * prime[j];
        if(lowMinimum < low)
            lowMinimum += prime[j];

        //Mark multiples of prime[i] in [low..high]
        for (int k = lowMinimum; k <= high; k+=prime[j])
            mark[k-low] = 0;
    }

    for (int i = low; i <= high; i++)
        if(mark[i-low])
        {
            fiPri.push_back(i);
            std::cout<<","<<i;
        }
    low = low + offset;
    high = high + offset;
}
}
};

int main()
{
std::vector<int> fiPri;
sito_segment(1013,fiPri);
}
person Tomasz Andel    schedule 30.04.2017

Основываясь на ответе Swapnil Kumar, я выполнил следующий алгоритм на C. Он был построен с помощью mingw32-make.exe.

#include<math.h>
#include<stdio.h>
#include<stdlib.h>

int main()
{
    const int MAX_PRIME_NUMBERS = 5000000;//The number of prime numbers we are looking for
    long long *prime_numbers = malloc(sizeof(long long) * MAX_PRIME_NUMBERS);
    prime_numbers[0] = 2;
    prime_numbers[1] = 3;
    prime_numbers[2] = 5;
    prime_numbers[3] = 7;
    prime_numbers[4] = 11;
    prime_numbers[5] = 13;
    prime_numbers[6] = 17;
    prime_numbers[7] = 19;
    prime_numbers[8] = 23;
    prime_numbers[9] = 29;
    const int BUFFER_POSSIBLE_PRIMES = 29 * 29;//Because the greatest prime number we have is 29 in the 10th position so I started with a block of 841 numbers
    int qt_calculated_primes = 10;//10 because we initialized the array with the ten first primes
    int possible_primes[BUFFER_POSSIBLE_PRIMES];//Will store the booleans to check valid primes
    long long iteration = 0;//Used as multiplier to the range of the buffer possible_primes
    int i;//Simple counter for loops
    while(qt_calculated_primes < MAX_PRIME_NUMBERS)
    {
        for (i = 0; i < BUFFER_POSSIBLE_PRIMES; i++)
            possible_primes[i] = 1;//set the number as prime

        int biggest_possible_prime = sqrt((iteration + 1) * BUFFER_POSSIBLE_PRIMES);

        int k = 0;

        long long prime = prime_numbers[k];//First prime to be used in the check

        while (prime <= biggest_possible_prime)//We don't need to check primes bigger than the square root
        {
            for (i = 0; i < BUFFER_POSSIBLE_PRIMES; i++)
                if ((iteration * BUFFER_POSSIBLE_PRIMES + i) % prime == 0)
                    possible_primes[i] = 0;

            if (++k == qt_calculated_primes)
                break;

            prime = prime_numbers[k];
        }
        for (i = 0; i < BUFFER_POSSIBLE_PRIMES; i++)
            if (possible_primes[i])
            {
                if ((qt_calculated_primes < MAX_PRIME_NUMBERS) && ((iteration * BUFFER_POSSIBLE_PRIMES + i) != 1))
                {
                    prime_numbers[qt_calculated_primes] = iteration * BUFFER_POSSIBLE_PRIMES + i;
                    printf("%d\n", prime_numbers[qt_calculated_primes]);
                    qt_calculated_primes++;
                } else if (!(qt_calculated_primes < MAX_PRIME_NUMBERS))
                    break;
            }

        iteration++;
    }

    return 0;
}

Он устанавливает максимальное количество простых чисел, которые необходимо найти, затем массив инициализируется известными простыми числами, такими как 2, 3, 5 ... 29. Итак, мы делаем буфер, который будет хранить сегменты возможных простых чисел, этот буфер не может быть больше, чем степень наибольшего начального простого числа, которое в данном случае равно 29.

Я уверен, что существует множество оптимизаций, которые можно сделать для повышения производительности, например, распараллелить процесс анализа сегментов и пропустить числа, кратные 2, 3 и 5, но это служит примером низкого потребления памяти.

person Bruno Simas Hadlich    schedule 11.08.2016

Число является простым, если ни одно из меньших простых чисел не делит его. Поскольку мы перебираем простые числа по порядку, мы уже отметили все числа, которые делятся хотя бы на одно из простых чисел, как делимые. Следовательно, если мы достигаем ячейки, а она не отмечена, то она не делится на меньшее простое число и, следовательно, должна быть простым.

Запомните эти моменты: -

// Generating all prime number up to  R
 
// creating an array of size (R-L-1) set all elements to be true: prime && false: composite
     

#include<bits/stdc++.h>

using namespace std;

#define MAX 100001

vector<int>* sieve(){
    bool isPrime[MAX];

    for(int i=0;i<MAX;i++){
        isPrime[i]=true;
    }
 for(int i=2;i*i<MAX;i++){
     if(isPrime[i]){
         for(int j=i*i;j<MAX;j+=i){
             isPrime[j]=false;
         }
     }
 }

 vector<int>* primes = new vector<int>();
 primes->push_back(2);
 for(int i=3;i<MAX;i+=2){
     if(isPrime[i]){
     primes->push_back(i);
     }
}

 return primes;
}

void printPrimes(long long l, long long r, vector<int>*&primes){
      bool isprimes[r-l+1];
      for(int i=0;i<=r-l;i++){
          isprimes[i]=true;
      }

      for(int i=0;primes->at(i)*(long long)primes->at(i)<=r;i++){

          int currPrimes=primes->at(i);
          //just smaller or equal value to l
          long long base =(l/(currPrimes))*(currPrimes);
      

          if(base<l){
              base=base+currPrimes;
          }
    
    //mark all multiplies within L to R as false

          for(long long j=base;j<=r;j+=currPrimes){
              isprimes[j-l]=false;
          }

   //there may be a case where base is itself a prime number

          if(base==currPrimes){
              isprimes[base-l]= true;
          }
    }

          for(int i=0;i<=r-l;i++){
              if(isprimes[i]==true){
                  cout<<i+l<<endl;
              }
          

      }
}
int main(){
    vector<int>* primes=sieve();
   
   int t;
   cin>>t;
   while(t--){
       long long l,r;
       cin>>l>>r;
       printPrimes(l,r,primes);
   }

    return 0;

}
person Amitesh mani tiwari    schedule 17.01.2021