Идеи реализации

Наиболее наивный подход к поиску простых чисел заключается в следующем. Будем брать по очереди натуральные числа n, начиная с двойки, и проверять их на простоту. Проверка на простоту заключается в следующем: перебирая числа k из диапазона от 2 до n 1 , будем делить n на k с остатком. Если при каком-то k обнаружится нулевой остаток, значит, n делится на k нацело, и число n составное. Если же при делении обнаруживались только ненулевые остатки, значит, число простое; в этом случае выводим его на экран. Ясно, что, получив нулевой остаток (тем самым обнаружив, что n составное), следует отказаться от дальнейших проб на делимость.

Заметим, что все простые числа, за исключением двойки, нечётные. Если обработать особо случай n = 2 , то все последующие числа n можно перебирать с шагом 2. Это даст приблизительно двукратное увеличение производительности программы.

Ещё одно улучшение возникает благодаря следующему утверждению: наименьший делитель составного числа n не превосходит n . Докажем это утверждение от противного. Пускай число k является наименьшим делителем n, причём k > n . Тогда n = k l , где l , причём l n , то есть l также является делителем числа n, кроме того, меньшим, чем k, а это противоречит предположению. Всё это означает, что, перебирая потенциальные делители, можно оборвать перебор, когда k достигнет n : если до этого момента делителей не найдено, то их нет вообще. Кстати, при проверке на простоту числа 11 это наблюдение позволяет сократить перебор более чем в три раза, а для числа 1111111111111111111 — более чем в 1054092553 раза (оба числа — простые).

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

Поскольку при проверке числа n на простоту важен именно наименьший собственный делитель, делители следует искать среди простых чисел, перебирая их по порядку. Но где взять их список? Ответ прост: поскольку наша программа будет искать все простые числа по порядку, кроме вывода на экран будем добавлять их в список найденных простых. Для очередного n будем перебирать потенциальные делители только из этого списка, по-прежнему, вплоть до n .

Издержкой этого подхода является необходимость держать в памяти растущий список найденных простых чисел. Однако объём требуемой для этого памяти будет невелик по сравнению с громадным выигрышем в быстродействии. Следующая таблица даёт представление об экономии при переборе и о затратах памяти:

nколичество k n количество простых k n
1031
100104
10003110
1000010025
10000031665
10000001000168

Другой алгоритм поиска простых чисел приписывают древнегреческому учёному Эратосфену Киренскому (Έρατοσθένης).

Расположим числа от 2 до n в таблицу (решето) и зачеркнём сначала чётные числа, следующие после двойки. Двойку обведём. Затем найдём следующее после двойки незачёркнутое число (это будет тройка), обведём его, и зачеркнём каждое третье число после тройки (начиная с шести). После этого снова найдём первое после тройки незачёркнутое число (пятёрку), и после него зачеркнём каждое пятое (начиная с десяти). Будем повторять подобные действия, пока в таблице не останутся либо обведённые, либо зачёркнутые числа. Обведённые числа будут в точности простыми: 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100

Обратите внимание: количество зачёркиваний у составного числа — это количество простых делителей (без учёта кратности).

Трюк, упомянутый в разделе «Наивный перебор», позволяет вдвое сократить список кандидатов в простые числа — заведомо составными будут все чётные числа кроме двойки. Посмотрим, нельзя ли подобным образом учесть ещё несколько первых простых чисел, чтобы дополнительно уменьшить число кандидатов.

Чисел, делящихся на 2 — половина, а делящихся на 3 — треть. Значит, доля чисел, делящихся хотя бы на одно из этих чисел, равна 1 2 + 1 3 1 2 1 3 = 2 3 (вычитается доля чисел, делящихся и на 2, и на 3, иначе такие числа будут учтены дважды). Для интересной операции, которую мы только что выполнили над дробями 1 2 и 1 3 , введём обозначение: x y = x + y x y .

Очевидно, операция коммутативна: x y = y x . Кроме того, как нетрудно проверить, она ассоциативна: x y z = x y z .

Теперь ясно, что учёт следующего простого числа, пятёрки, увеличивает долю заведомо составных чисел (делящихся на 2, 3, 5) до 1 2 1 3 1 5 = 11 15 . Учёт семёрки даст 1 2 1 3 1 5 1 7 = 11 15 1 7 = 27 35 . Интересно выяснить, какую выгоду можно получить, учитывая следующие простые числа, и каковы будут издержки.

Мы вычислили «суммы» обратных величин для первых k простых чисел и свели результаты в таблицу:

k 1 2 1 3 1 5 1 p k
10,5000…
20,6667…
30,7333…
40,7714…
50,7922…
60,8082…
70,8195…
80,8290…
90,8364…
100,8421…

Числа в правой колонке таблицы растут, но всё медленней.

Теперь перейдём к изложению сути колёсного метода. Метод позволяет быстро найти собственный делитель заданного числа или убедиться, что число простое. Возьмём первые k простых чисел p 1 , p 2 , , p k . Их произведение P k = p 1 p 2 p k называется примориалом числа k. Затем найдём все числа от 1 до P k , которые не делятся ни на одно из простых p 1 , p 2 , , p k (или, что в данном случае то же самое, взаимно простые с P k ). Все остальные числа от 1 до P k или являются одним из p i , или составные. Кроме того, если к любому числу из тех, что мы назвали «все остальные», прибавить n P k , n , получится составное число. Действительно, любое из «остальных» чисел делится на какое-то p i , и то же самое можно сказать о n P k . Их сумма тоже будет делиться на p i .

Список чисел от 1 до P k , взаимно простых с P k , назовём колесом, а сами такие числа — спицами в колесе. Теперь мы знаем, что любое из простых чисел либо одно из p 1 , p 2 , , p k , либо содержится среди чисел вида s + n P k , где s — спица. Все остальные натуральные числа, кроме единицы, заведомо составные, и их доля, как показывает таблица, довольно велика даже для небольших k.

Для проверки числа N на простоту следует прежде всего поискать N среди чисел p 1 , p 2 , , p k . Если поиск не увенчался успехом, проверяем по очереди, не делится ли N на одно из p i . Если делится, число N — составное. Если же нет, ищем делители N среди спиц колеса s (пропустив, естественно, единицу), затем среди чисел вида s + P k , затем среди чисел вида s + 2 P k , затем — s + 3 P k , и так продолжаем до тех пор, пока квадрат очередного делителя не превысит N.

Построим колёса для первого одного простого числа, первых двух и первых трёх:

kколесо
11
2 1, 5
3 1, 7, 11, 13, 17, 19, 23, 29
4 1, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 121, 127, 131, 137, 139, 143, 149, 151, 157, 163, 167, 169, 173, 179, 181, 187, 191, 193, 197, 199, 209

Возьмём для примера колесо, построенное для двух первых простых чисел — 2 и 3. Проверяя на простоту число N при помощи такого колеса, убедившись, что N не двойка и не тройка, пытаемся делить это число сначала на 2, 3, а затем — на 5, 7, 11, 13, 17, 19, 23, 25, 29, , то есть на числа из арифметических прогрессий 1 + 6 t и 5 + 6 t , t = 0 1 2 3 . При N = 661 имеет смысл остановиться на числе 25, поскольку квадрат следующего в списке, 29, уже больше 661. Теперь можно заключить, что число 661 — простое.

Удобно изображать список возможных делителей в виде таблицы шириной P k (в нашем примере это 2 3 = 6 ): 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 Серые числа заведомо составные. Среди цветных чисел также могут встретиться, хоть и редко, составные числа (синие) — мы помним, что колёсный метод исключает не все составные числа из рассмотрения.

Для проверки того же числа 661 на колесе, построенном для трёх первых простых чисел, нужно проверить его делимость сначала на 2, 3, 5, затем — на 7, 11, 13, 17, 19, 23.

Есть соблазн использовать для построения колеса как можно больше первых простых чисел. Но не стоит этого делать. Выигрыш с добавлением очередного простого числа будет всё меньше и меньше, а количество спиц в k-ом колесе будет расти всё быстрее и быстрее. Можно показать, что количество спиц в k-ом колесе равно p 1 1 p 2 1 p 3 1 p k 1 . Эта последовательность выглядит так: 1, 2, 8, 48, 480, 5760, 92160, 1658880, . Слишком большие колёса только замедлят выполнение программы, к тому же создание списка спиц потребует массу времени. Наши эксперименты показали, что оптимальное количество простых, используемых для построения колеса, равно четырём.

Ах, да. Почему метод называется колёсным? Возьмём колесо со спицами, пронумерованными от 1 до P k , и удалим спицы с номерами, не взаимно простыми с P k . Если прокатить такое колесо по прямой, отмечая следы концов уцелевших спиц, на прямой останутся отметки, принадлежащие арифметическим прогрессиям вида s + P k t . Первые три колеса показаны на рисунке 14.1. «Колёса для проверки чисел на простоту». Следующее колесо уже в семь раз больше самого крупного из показанных, и мы решили воздержаться от его рисования.


Разглядывание этих рисунков навело нас на следующие мысли:

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

Идеи для этого раздела мы нашли на странице The Prime Glossary: wheel factorization.

В разделе «Сравнение разных версий программы» приводятся результаты сравнительных испытаний всех пяти версий программы.

К задаче поиска простых чисел мы вернёмся в главе 32. «Поиск простых чисел с помощью регулярных выражений», где применим не совсем обычный подход: о простоте числа k можно судить по тому, соответствует ли строка, состоящая из k одинаковых символов, определённому шаблону.

Раздел «Простые числа: фильтрация» главы 38. «Объектно-ориентированное программирование» посвящён алгоритму поиска простых чисел, основанном на фильтрации. Постепенно строящаяся цепочка фильтров задерживает составные числа и пропускает простые. В чём-то этот алгоритм напоминает решето Эратосфена, но, строго говоря, не реализует настоящее решето.

В главе 39. «Битовая реализация числового множества» рассматривается другая реализация решета Эратосфена, гораздо более компактная, нежели массив с числами.

Информатика-54© А. Н. Швец