Решето́ Эратосфе́на — алгоритм нахождения всех простых чисел до некоторого целого числа n, который приписывают древнегреческому математику Эратосфену Киренскому[1]. Как и во многих случаях, здесь название алгоритма говорит о принципе его работы, то есть решето подразумевает фильтрацию, в данном случае фильтрацию всех чисел за исключением простых. По мере прохождения списка нужные числа остаются, а ненужные (они называются составными) исключаются.
Название «решето» метод получил потому, что, согласно легенде, Эратосфен писал числа на дощечке, покрытой воском, и прокалывал дырочки в тех местах, где были написаны составные числа. Поэтому дощечка являлась неким подобием решета, через которое «просеивались» все составные числа, а оставались только числа простые. Эратосфен дал таблицу простых чисел до 1000.
Для нахождения всех простых чисел не больше заданного числа n, следуя методу Эратосфена, нужно выполнить следующие шаги:
Теперь все незачёркнутые числа в списке — это все простые числа от 2 до n.
На практике, алгоритм можно улучшить следующим образом. На шаге № 3 числа можно зачеркивать начиная сразу с числа p2, потому что все составные числа меньше него уже будут зачеркнуты к этому времени. И, соответственно, останавливать алгоритм можно, когда p2 станет больше, чем n.[2] Также, все простые числа (кроме 2) — нечётные числа, и поэтому для них можно считать шагами по 2p, начиная с p2.
Запишем натуральные числа, начиная от 2, до 30 в ряд:
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
Первое число в списке, 2 — простое. Пройдём по ряду чисел, зачёркивая все числа, кратные 2 (то есть, каждое второе, начиная с 22 = 4):
2 3456789101112131415161718192021222324252627282930
Следующее незачеркнутое число, 3 — простое. Пройдём по ряду чисел, зачёркивая все числа, кратные 3 (то есть, каждое третье, начиная с 32 = 9):
2 3456789101112131415161718192021222324252627282930
Следующее незачеркнутое число, 5 — простое. Пройдём по ряду чисел, зачёркивая все числа, кратные 5 (то есть, каждое пятое, начиная с 52 = 25):
2 3456789101112131415161718192021222324252627282930
Следующее незачеркнутое число — 7. Его квадрат, 49 — больше 30, поэтому на этом работа завершена. Все составные числа уже зачеркнуты:
2 3 5 7 11 13 17 19 23 29
Оптимизированная реализация (начинающаяся с квадратов) на псевдокоде[3][4]:
Вход: натуральное число n Пусть A — булевый массив, индексируемый числами от 2 до n, изначально заполненный значениями true. для i := 2, 3, 4, ..., пока i2 ≤ n: если A[i] = true: для j := i2, i2 + i, i2 + 2i, ..., пока j ≤ n: A[j] := false Выход: числа i, для которых A[i] = true.
Сложность алгоритма составляет операций при составлении таблицы простых чисел до [5].
При выбранном для каждого простого будет выполняться внутренний цикл, который совершит действий. Сложность алгоритма можно получить из оценки следующей величины:
Так как количество простых чисел, меньших либо равных , оценивается как , и, как следствие, -е простое число примерно равно , то сумму можно преобразовать:
Здесь из суммы выделено слагаемое для первого простого числа, чтобы избежать деления на нуль. Данную сумму можно оценить интегралом
В итоге получается для изначальной суммы:
Более строгое доказательство (и дающее более точную оценку с точностью до константных множителей) можно найти в книге Hardy и Wright «An Introduction to the Theory of Numbers»[6].
В этом варианте простые числа вычисляются последовательно, без ограничения сверху,[7] как числа находящиеся в промежутках между составными числами, которые вычисляются для каждого простого числа p, начиная с его квадрата, с шагом в p (или для нечетных простых чисел 2p)[2]. Может быть представлен символически в парадигме потоков данных как
primes = [2..] \ [[p², p²+p..] for p in primes]
используя нотацию абстракции списков, где \
обозначает разницу между арифметическими прогрессиями.
Первое простое число 2 (среди возрастающих положительных целых чисел) заранее известно, поэтому в этом самореферентном определении нет порочного круга.
Псевдокод поэтапного отсеивания, в неэффективной, для простоты, реализации (ср. с нижеследующими вариантами):
primes = sieve [2..] where sieve [p, ...xs] = [p, ...sieve (xs \ [p², p²+p..])]
Решето Эратосфена часто путают с алгоритмами, которые поэтапно отфильтровывают[en] составные числа, тестируя каждое из чисел-кандидатов на делимость используя по одному простому числу на каждом этапе.[8]
Широко известный функциональный код Дэвида Тёрнера 1975 г.[9] часто принимают за решето Эратосфена,[8] но на самом деле[7] это неоптимальный вариант с перебором делителей (в оптимальном варианте не используются делители, большие квадратного корня тестируемого числа). В псевдокоде,
primes = sieve [2..] where sieve [p, ...xs] = [p, ...sieve [x in xs if x%p > 0]]
Как отмечает Соренсон, главной проблемой реализации решета Эратосфена на вычислительных машинах является не количество выполняемых операций, а требования по объёму занимаемой памяти.[4] При больших значениях n, диапазон простых чисел может превысить доступную память; хуже того, даже при сравнительно небольших n использование кэша памяти далеко от оптимального, так как алгоритм, проходя по всему массиву, нарушает принцип локализованности ссылок[en].
Для решения представленной проблемы используется сегментированное решето, в котором за итерацию просеивается только часть диапазона простых чисел.[10] Данный метод известен с 1970-х годов и работает следующим образом:[4][11]
Если число Δ выбрано равным √n, то сложность алгоритма по памяти оценивается как O(√n), а операционная сложность остаётся такой же, что и у обычного решета Эратосфена.[11]
Для случаев, когда n настолько велико, что все просеиваемые простые числа меньшие √n не могут уместиться в памяти, как того требует алгоритм сегментированного сита, используют более медленные, но с гораздо меньшими требованиями по памяти алгоритмы, например решето Соренсона.[12]
Доказательство тождества Эйлера для дзета-функции Римана содержит механизм отсеивания составных чисел подобный решету Эратосфена, но так, что каждое составное число удаляется из списка только один раз. Схожее решето описано в Gries & Misra 1978 г. как решето с линейным временем работы (см. ниже).
Составляется исходный список начиная с числа 2. На каждом этапе алгоритма первый номер в списке берется как следующее простое число, результаты произведения которого на каждое число в списке помечаются для последующего удаления. После этого из списка убирают первое число и все помеченные числа, и процесс повторяется вновь:
[2] (3) 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53 55 57 59 61 63 65 67 69 71 73 75 77 79 ... [3] (5) 7 11 13 17 19 23 25 29 31 35 37 41 43 47 49 53 55 59 61 65 67 71 73 77 79 ... [4] (7) 11 13 17 19 23 29 31 37 41 43 47 49 53 59 61 67 71 73 77 79 ... [5] (11) 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 ... [...]
Здесь показан пример начиная с нечетных чисел, после первого этапа алгоритма. Таким образом, после k-го этапа рабочий список содержит только числа взаимно простые с первыми k простыми числами (то есть числа не кратные ни одному из первых k простых чисел), и начинается с (k+1)-го простого числа. Все числа в списке, меньшие квадрата его первого числа, являются простыми.
В псевдокоде,
primes = sieve [2..] where sieve [p, ...xs] = [p, ...sieve (xs \ [p², ...[p*x for x in xs]])]
Поскольку все чётные числа, кроме 2, — составные, то можно вообще не обрабатывать никак чётные числа, а оперировать только нечётными числами. Во-первых, это позволит вдвое сократить объём требуемой памяти. Во-вторых, это уменьшит количество выполняемых алгоритмом операций (примерно вдвое).
Это можно обобщить на числа взаимно простые не только с 2 (то есть нечетные числа), но и с 3, 5, и т. д.
Алгоритм Эратосфена фактически оперирует с битами памяти. Следовательно, можно существенно сэкономить потребление памяти, храня переменных булевского типа не как байт, а как бит, то есть байт памяти.
Такой подход — «битовое сжатие» — усложняет оперирование этими битами. Любое чтение или запись бита будут представлять собой несколько арифметических операций. Но с другой стороны существенно улучшается компактность в памяти. Бо́льшие интервалы умещаются в кэш-память которая работает гораздо быстрее обычной так что при работе по-сегментно общая скорость увеличивается.
Этот алгоритм обнаруживает для каждого числа i в отрезке [2...n] его минимальный простой делитель lp[i] (lp от англ. least prime).
Также поддерживается список всех простых чисел — массив pr[], поначалу пустой. В ходе работы алгоритма этот массив будет постепенно заполняться.
Изначально все величины lp[i] заполним нулями.
Дальше следует перебрать текущее число i от 2 до n. Здесь может быть два случая:
Следовательно, надо присвоить lp[i] = i и добавить i в конец списка pr[].
В обоих случаях дальше начинается процесс расстановки значений в массиве lp[i]: следует брать числа, кратные i, и обновлять у них значение lp[]. Однако основная цель — научиться делать это таким образом, чтобы в итоге у каждого числа значение lp[] было бы установлено не более одного раза.
Утверждается, что для этого можно поступить таким образом. Рассматриваются числа вида x = p ⋅ i, где p последовательно равно всем простым числам не превосходящим lp[i] (как раз для этого понадобилось хранить список всех простых чисел).
У всех чисел такого вида проставим новое значение lp[x] — оно должно быть равно p[13].
Вход: натуральное число n Пусть pr - целочисленный массив, поначалу пустой; lp - целочисленный массив, индексируемый от 2 до n, заполненный нулями для i := 2, 3, 4, ..., до n: если lp[i] = 0: lp[i] := i pr[] += {i} для p из pr пока p ≤ lp[i] и p*i ≤ n: lp[p*i] := p Выход: все числа в массиве pr.
Решето Эратосфена является популярным способом оценки производительности компьютера.[14] Как видно из вышеизложенного доказательства сложности алгоритма, избавившись от константы и слагаемого очень близкого к нулю (ln (ln n - ln ln n) - ln ln 2 ≈ ln ln n), временная сложность вычисления всех простых чисел меньше n аппроксимируется следующим соотношением O(n ln ln n). Однако алгоритм имеет экспоненциальную временную сложность в отношении размера входных данных, что делает его псевдополиномиальным алгоритмом. Памяти же для базового алгоритма требуется O(n).[15]
Сегментированная версия имеет ту же операционную сложность O(n ln ln n),[6]. что и несегментированная версия, но уменьшает потребность в используемой памяти до размера сегмента (размер сегмента значительно меньше размера всего массива простых чисел), который равен O(√n/ln n).[16] Также существует очень редко встречающееся на практике оптимизированное сегментированное решето Эратосфена. Оно строится за O(n) операций и занимает O(√n ln ln n/ln n) бит в памяти.[15][17][16]
На практике оказывается, что оценка ln ln n не очень точна даже для максимальных практических диапазонов таких как 1016.[17] Ознакомившись с вышеописанным доказательством сложности, нетрудно понять откуда взялась неточность оценки. Расхождение с оценкой можно объяснить следующим образом: в пределах данного практического диапазона просеивания существенное влияние оказывают постоянные смещения.[6] Таким образом очень медленно растущий член ln ln n не становится достаточно большим, чтобы константами можно было справедливо пренебречь, до тех пор пока n не приблизится к бесконечности, что естественно выходит за границы любого прикладного диапазона просеивания.[6] Данный факт показывает, что для актуальных на сегодняшний день входных данных производительность решета Эратосфена намного лучше, чем следовало ожидать, используя только асимптотические оценки временной сложности.[17]
Следует также отметить, что решето Эратосфена работает быстрее, чем часто сравниваемое с ним решето Аткина только для значений n меньших 10 10 .[18] Сказанное справедливо при условии, что операции занимают примерно одно и то же время в циклах центрального процессора, а это является разумным предположением для одного алгоритма, работающего с огромным битовым массивом.[19] С учетом этого предположения получается, что сито Аткина быстрее чем максимально факторизованное решето Эратосфена для n свыше 10 13 , но при таких диапазонах просеивания потребуется занять огромное пространство в оперативной памяти, даже если была использована «битовая упаковка».[20] Однако раздел о сегментированной версии решета Эратосфена показывает, что предположение о сохранении равенства во времени, затрачиваемом на одну операцию, между двумя алгоритмами не выполняется при сегментации.[11][18] В свою очередь это приводит к тому, что решето Аткина (несегментированное) работает медленнее, чем сегментированное решето Эратосфена с увеличением диапазона просеивания, за счёт уменьшения времени на операцию для второго.
Использование нотации O большого также не является правильным способом сравнения практических характеристик даже для вариаций решета Эратосфена, поскольку данная нотация игнорирует константы и смещения, которые могут быть очень значительными для прикладных диапазонов.[6] Например, одна из вариаций решета Эратосфена известная как решето Притчарда[15] имеет производительность O(n),[17] но её базовая реализация требует либо алгоритма «одного большого массива»[16] (то есть использования обычного массива, в котором хранятся все числа до n), который ограничивает его диапазон использования до объёма доступной памяти, либо он должен быть сегментирован для уменьшения использования памяти. Работа Притчарда уменьшила требования к памяти до предела, но платой за данное улучшение по памяти является усложнение вычислений, что приводит увеличению операционной сложности алгоритмов.[17]
Популярным способом ускорения алгоритмов, работающих с большими массивами чисел, является разного рода факторизация.[21] Применение методов факторизации даёт значительное уменьшение операционной сложности за счёт оптимизации входного массива данных.[22][23] Для индексных алгоритмов часто используют кольцевую факторизацию.[22][24] Рассматриваемые в данной статье алгоритмы нахождения всех простых чисел до заданного n подобные решету Эратосфена относятся к индексным, что позволяет применять к ним метод кольцевой факторизации.[25]
Несмотря на теоретическое ускорение, получаемое с помощью кольцевой факторизации, на практике существуют факторы, которые не учитываются при расчётах, но способны оказать существенное влияние на поведение алгоритма, что в результате может не дать ожидаемого прироста в быстродействии.[26] Рассмотрим наиболее существенные из них:
Для наглядности представления вклада факторизации в производительность алгоритмов просеивания ниже приведены две таблицы. В таблицах приведены результаты измерения реального времени исполнения решета Эратосфена и решета Питчарда в секундах для разных диапазонов n и разных степеней кольцевой факторизации. Ei и Pi обозначения для решета Эратосфена и Питчарда соответственно, где индекс i означает степень кольцевой факторизации. Стоит отметить, что E0 и P0 означают отсутствие факторизации.[28]
n | E0 | E1 | E2 | E3 | E4 | E5 |
---|---|---|---|---|---|---|
500 | 0.00043 | 0.00029 | 0.00027 | 0.00048 | 0.00140 | 0.01035 |
5000 | 0.00473 | 0.00305 | 0.00254 | 0.00293 | 0.00551 | 0.03207 |
50000 | 0.05156 | 0.03281 | 0.02617 | 0.02578 | 0.03164 | 0.10663 |
500000 | 0.55817 | 0.35037 | 0.28240 | 0.28358 | 0.28397 | 0.47028 |
5000000 | 6.06719 | 3.82905 | 3.20722 | 3.25214 | 3.28104 | 3.38455 |
Из таблицы видно, что лучшую производительность имеет решето Эратосфена со средней степенью факторизации E2. Данный факт можно объяснить влиянием кэш-фактора, рассмотренного выше, на алгоритмы с высокой степенью факторизации.
n | E0 | E1 | E2 | E3 | E4 | E5 |
---|---|---|---|---|---|---|
500 | 0.00147 | 0.00074 | 0.00050 | 0.00051 | 0.00095 | 0.00649 |
5000 | 0.01519 | 0.00777 | 0.00527 | 0.00453 | 0.00430 | 0.00973 |
50000 | 0.15507 | 0.08203 | 0.05664 | 0.04843 | 0.04180 | 0.04413 |
500000 | 1.60732 | 0.86254 | 0.61597 | 0.53825 | 0.47146 | 0.43787 |
5000000 | 16.47706 | 9.00177 | 6.57146 | 5.83518 | 5.27427 | 4.88251 |
В заключение стоит отметить, что с увеличением n соотношение времен становится всё больше в пользу решета Эратосфена, а на диапазоне n = 5000000 оно стабильно быстрее при любых факторизациях. Данный факт ещё раз подтверждает проигрыш в быстродействии решета Питчарда из-за сложных вычислений.[17]
primes = sieve [2..]; sieve (p:nos) = p:sieve (remove (multsof p) nos); remove m = filter (not . m); multsof p n = rem n p==0
)Данная страница на сайте WikiSort.ru содержит текст со страницы сайта "Википедия".
Если Вы хотите её отредактировать, то можете сделать это на странице редактирования в Википедии.
Если сделанные Вами правки не будут кем-нибудь удалены, то через несколько дней они появятся на сайте WikiSort.ru .