Решето эратосфена кратко и понятно

Обновлено: 07.07.2024

Определение. Целое положительное число называется простым, если оно имеет ровно два различных натуральных делителя — единицу и самого себя. Единица простым числом не считается.

Решето Эратосфена (англ. sieve of Eratosthenes) — алгоритм нахождения всех простых чисел от \(1\) до \(n\) .

Основная идея соответствует названию алгоритма: запишем ряд чисел \(1, 2,\ldots, n\) , а затем будем вычеркивать

сначала числа, делящиеся на \(2\) , кроме самого числа \(2\) ,

потом числа, делящиеся на \(3\) , кроме самого числа \(3\) ,

с числами, делящимися на \(4\) , ничего делать не будем — мы их уже вычёркивали,

потом продолжим вычеркивать числа, делящиеся на \(5\) , кроме самого числа \(5\) ,

Самая простая реализация может выглядеть так:

Этот код сначала помечает все числа, кроме нуля и единицы, как простые, а затем начинает процесс отсеивания составных чисел. Для этого мы перебираем в цикле все числа от \(2\) до \(n\) , и, если текущее число простое, то помечаем все числа, кратные ему, как составные.

Если память позволяет, то для оптимизации скорости лучше использовать не вектор bool , а вектор char — но он займёт в 8 раз больше места. Компьютер не умеет напрямую оперировать с битами, и поэтому при индексации к vector он сначала достаёт нужный байт, а затем битовыми операциями получает нужное значение, что занимает приличное количество времени.

Время работы

Довольно легко показать, что асимптотическое время работы алгоритма хотя бы не хуже, чем \(O(n \log n)\) : даже если бы мы входили в цикл вычёркиваний для каждого числа, не проверяя его сначала на простоту, суммарно итераций было бы

\[ \sum_k \frac = \frac + \frac + \frac + \ldots + \frac = O(n \log n) \]

Здесь мы воспользовались асимптотикой гармонического ряда.

У исходного алгоритма асимптотика должна быть ещё лучше. Чтобы найти её точнее, нам понадобятся два факта про простые числа:

Простых чисел от \(1\) до \(n\) примерно \(\frac<\ln n>\) .

\[ \sum_k \frac <\ln k>\frac \approx n \int \frac = n \ln \ln k \Big |_2^n = O(n \log \log n) \]

Асимптотику алгоритма можно улучшить и дальше, до \(O(n)\) .

Линейное решето

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

Обозначим за \(d(k)\) минимальный простой делитель числа \(k\) и заметим следующий факт: у составного числа \(k\) есть единственное представление \(k = d(k) \cdot r\) , и при этом у числа \(r\) нет простых делителей меньше \(d(k)\) .

Идея оптимизации состоит в том, чтобы перебирать этот \(r\) , и для каждого перебирать только нужные множители — а именно все от \(2\) до \(d(r)\) включительно.

Алгоритм

Немного обобщим задачу — теперь мы хотим посчитать для каждого числа \(k\) на отрезке \([2, n]\) его минимальный простой делитель \(d_k\) , а не только определить его простоту.

Изначально массив \(d\) заполним нулями, что означает, что все числа предполагаются простыми. В ходе работы алгортима этот массив будет постепенно заполняться. Помимо этого, будем поддерживать список \(p\) всех найденных на текущий момент простых чисел.

Теперь будем перебирать число \(k\) от \(2\) до \(n\) . Если это число простое, то есть \(d_k = 0\) , то присвоим \(d_k = k\) и добавим \(k\) в список \(p\) .

Дальше, вне зависимости от простоты \(k\) , начнём процесс расстановки значений в массиве \(d\) — переберем найденные простые числа \(p_i\) , не превосходящие \(d_k\) , и сделаем присвоение \(d_ = p_i\) .

Алгоритм требует как минимум в 32 раза больше памяти, чем обычное решето, потому что требуется хранить делитель ( int , 4 байта) вместо одного бита на каждое число. Линейное решето хоть и имеет лучшую асимптотику, но на практике проигрывает также и по скорости оптимизированному варианту решета Эратосфена.

Применения

Массив \(d\) он позволяет искать факторизацию любого числа \(k\) за время порядка размера этой факторизации:

\[ factor(k) = \ \cup factor(k / d(k)) \]

Знание факторизации всех чисел — очень полезная информация для некоторых задач. Ленейное решето интересно не своим временем работы, а именно этим массивом минимальных простых делителей.

Удивительно, но этот алгоритм популярен до сих пор. И сегодня, мы разберемся, как пользоваться тем, что называется решетом Эратосфена.

Занимательно, что поисковик так и норовит подсунуть портрет Евклида, вместо портрета Эратосфена. Хотя, про Евклида и его алгоритм, я уже написала две статьи. Ну, раз великий интернет настаивает, то в конце этой публикации прикреплю ссылку на статью про алгоритм Евклида. Там можно полюбоваться его портретом

Решето Эратосфена

Название, на самом деле, говорящее. Эратосфен, действительно, отбирал простые числа при помощи своеобразного решета.

Натуральное число, большее единицы, называют простым, если оно имеет всего два натуральных делителя: единицу и само это число

Чтобы выделять простые числа, не превосходящие число n , Эратосфен предложил такой алгоритм:

При таком подходе, те числа, которые останутся не вычеркнутыми и будут являться простыми.

На самом деле, им можно пользоваться и для выделения простых чисел из какого-то отрезка натурального ряда, начинающегося не с единицы.

Ну что ж, посмотрим это на примере и вместе ответим на вопросы, которые я задала Вам в начале статьи.


СОВРЕМЕННЫЕ ПРОБЛЕМЫ ШКОЛЬНОГО ОБРАЗОВАНИЯ




Решето Эратосфена в современной жизни


Автор работы награжден дипломом победителя III степени

Текст работы размещён без изображений и формул.
Полная версия работы доступна во вкладке "Файлы работы" в формате PDF

На этот вопрос можно дать два ответа. Первый из них имеет теоретическое значение. Попытки генерации простых чисел ведут к появлению новых интересных инструментов для расчетов, особенно для компьютерных вычислений. Кроме того, наличие большого списка простых чисел позволяет проверять теоремы, которые еще не доказаны. Если кто-то выдвигает гипотезу относительно простых чисел, но оказывается, что одно из миллионов чисел нарушает ее, то вопрос снимается. Это стимулирует поиск простых чисел различных видов: простых чисел Мерсенна, чисел-близнецов и так далее. Иногда такой поиск превращается в соревнование, в котором устанавливаются мировые рекорды и за победы присуждаются большие призы.

Но есть и другая, более практическая причина, связанная с так называемым шифрованием. Электронная почта, банковские операции, кредитные карты и мобильная телефонная связь — все это защищено секретными кодами, непосредственно основанными на свойствах простых чисел.

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

Целью моей работы является исследование применения решета Эратосфена в современной жизни.

Исходя из этой цели, в своем исследовании мне предстоит решить следующие задачи:

выяснить актуальность этой темы;

дать определение понятиям, используемым при изучении данной темы;

изучить биографию и труды Эратосфена;

Для достижения поставленной цели нами использовались следующие методы исследования:

методы экспериментально-теоретического уровня: анализ и синтез, теоретический анализ литературных источников;

методы эмпирического уровня: анкетирование, сравнение.

Этапы проекта: теоретический и практический.

2.1. Используемые понятия, актуальные проблемы в исследовании простых чисел

Для изучения данной темы дадим определение основным понятиям, необходимым для ее исследования.

Натуральные числа – это числа, которые используются при счёте предметов: 1,2,3,4,5,……… Все натуральные числа, записанные в порядке возрастания, образуют ряд натуральных чисел. В натуральном ряду за каждым числом следует еще одно число, большее предыдущего на единицу. Как известно, натуральных чисел – бесконечно много.

Простое число — натуральное число, имеющее ровно два различных натуральных делителя — единицу и самого себя. Пр имеры простых чисел: 2,3,5,7,11,13,17,19. Простые числа поставили перед математиками немало сложных вопросов, на многие из которых ответ до сих пор не найден.

Из первой тысячи натуральных чисел 168 чисел являются простыми. Есть простые числа и во второй, третьей, четвертой тысячах. Можно предположить, что среди любых тысячи натуральных чисел, идущих подряд, встречаются простые. Но это не так.

Исследуя таблицы простых чисел, французский математик Жозеф Луи Франсуа Бертран(1822-1900) выдвинул предположение, что при n 1 между числами n и 2 n содержится хотя бы одно простое число. Данный факт смог доказать выдающийся русский математик и механик Пафнутий Львович Чебышёв (1821-1894).

В мире простых чисел существует множество нерешенных задач. Если мы обратимся к учебнику Математика: 6 автора Мерзляка А.Г, то увидим на форзаце таблицу простых чисел, в которой выделены красным цветом простые числа, отличающиеся на 2. Такие пары простых чисел называются близнецами. Но вычислить, сколько пар таких близнецов, конечно это количество или бесконечно, пока не удалось никому.

Также неизвестно, сколько на данный момент существует простых чисел, состоящих только из единиц, и конечно или бесконечно множество простых чисел, в записи которых нет ни одного нуля.

Составным числом называется натуральное число , большее, чем единица, которое не является простым . Каждое составное число является произведением двух или более натуральных чисел, больших 1. Перечислим подряд несколько составных чисел: 4, 6, 8, 9, 10, 12, 14, 15, 16, 18, 20, 21, 22, 24, 25.

А число 1 имеет только один делитель - само это число, поэтому оно не относится ни к простым, ни к составным числам. Таким образом, множество натуральных чисел состоит из простых чисел, составных чисел и единицы. [3]

Разберем, что же такое решето, и почему данное слово используется в математике. Когда в каком-то веществе или продукте необходимо отделить мелкие частички от более крупных, то используют решето. Например, после помола зерна с помощью решета отделяют муку от отрубей.

2.2 История жизни и деятельность великого Эратосфена

Рис. 1 Эратосфен

Эратосфен - древний математик, живший в 276-194 годах до нашей эры. Он является одним из самых разносторонних ученых античности. История сохранила краткие сведения из биографии Эратосфена, однако на него очень часто ссылались авторитетные и знаменитые мудрецы, философы античности: Архимед, Страбон и другие. Родился Эратосфен в Африке, в Кирене, поэтому нет ничего удивительного в том, что своё образование он начал в Александрии. Живой ум Эратосфена пытался постичь практически все известные на тот момент науки. И как все учёные, он наблюдал за природой. [2]

Эратосфен заложил основы математической географии, вычислив с большой точностью величину земного шара, изобрел широту и долготу, а так же придумал високосный день.

Математиков и после смерти Эратосфена волновала проблема нахождения простых чисел. В 1909 году американский математик Деррик Норман Лемер опубликовал таблицы простых чисел в промежутке от 1 до 10.017.000. Книга таблиц имеется в Российской государственной библиотеке в Москве. [1]

Запишем первые сто натуральных чисел в виде таблицы.

Зачеркнём в этой таблице число 1, которое не является простым. Далее обведём число 2, а остальные чётные числа таблицы зачеркнём. (Приложение II )

Первым из оставшихся чисел будет число 3, которое мы тоже обведём. Далее вычеркнем из таблицы все числа, кратные трём, кроме самого числа 3. (Приложение III )

Первым из оставшихся чисел будет число 5. Далее проделаем такую же операцию с числами 5 и 7. (Приложение IV )

На этом этапе просеивание завершено. Все оставшиеся числа в таблице являются простыми. Действительно, числа 4,6,8,9,10 оказались вычеркнутыми. Если составное число больше 10, но меньше 100, то его можно представить в виде произведения двух множителей, один из которых меньше 10. Составное число с таким множителем кратно одному из чисел 2,3,5,7, а значит, будет вычеркнуто.

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

На данный момент проведение соревнований для школьников по различным предметам снова набирают популярность. Лауреаты и победители таких мероприятий выходят на новый уровень обучения и могут получить неплохие перспективы в дальнейшей деятельности, в том числе материальные гранды.

Олимпиады по информатике включают в себя не только сложные задачи, но и нахождение таких известных понятий, как простые числа. Решето Эратосфена при этом используется в качестве наиболее актуального способа вычисления последовательностей, путем интегрирования аксиомы в программный код. Несмотря на древность открытия, данная теория помогает быстро и эффективно освоиться в труднодоступных расчетах.

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

2.4 Практическая часть

Я провела анкетирование в двух классах шестой параллели МАОУ СОШ № 73 г. Челябинска, чтобы узнать, насколько актуальна данная тема, и что учащиеся вообще об этом знают. В анкетировании приняли участие 43 человека. Учащимся было задано три вопроса, на которые было предложено дать краткий ответ: да или нет. Результаты анкетирования следующие:

Знаете ли Вы, что представляет собой решето, для чего оно используется?

Да: 5 человек, нет: 38 человек.

Знаете ли Вы, что такое простые числа, составные числа?

Да: 21 человек, нет: 22 человека.

Да: 9 человек, нет: 34 человека.

Рис.2 Доска для просеивания простых чисел

Математика – наука, которая появилась несколько тысяч лет и активно использовалась уже в Древней Греции. При этом многие ученые-теоретики, жившие в то время, делали открытия, ставшие великими и гениальными, но настоящее признание получили несколько веков спустя, когда технологии позволили понять весь потенциал исследований античных арифметиков.

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

Список используемой литературы:

Мерзляк А.Г. Математика: 6 класс: учебник / А.Г. Мерзляк, В.Б. Полонский, М.С. Якир; под ред. В.Е. Подольского. - М. : Вентана - Граф, 2019. – 334 с.

Простое число // Математическая энциклопедия (в 5 томах) . — М. : Советская Энциклопедия, 1977. — Т. 4.

Толково-образовательный. Новый словарь русского языка Ефремова Т.Ф. - М.: Рус. яз., 2000.- 1209 с.

Ушаков Д.Н Толковый словарь / Д.Н. Ушаков. - М.: Славянский Дом Книги , 2014 г. – 960с.

Об античном ученом Эратосфене Киренском, которому приписывают наш метод, википедия говорит следующее [1]:

Обычно метод Эратосфена описывают при помощи картинки, где изображено некоторое количество первых чисел натурального ряда (единицу, как и договорились, мы сразу выбросим из рассмотрения):

Первые 50 натуральных чисел

Первые 50 натуральных чисел

Идея в том, что мы берем подряд числа из натурального ряда. Для каждого взятого числа k добавляем это же число k и полученный результат вычеркиваем. Потом снова добавляем и снова вычеркиваем. Делаем так, пока не дойдем до конца интервала. Почему мы вычеркиваем эти числа? Потому что это каждое такое число кратно k, оно заведомо составное, а не простое.

Но мы идем дальше. К двойке прибавляем само это число 2+2. Получилось 4. Надо ли здесь задавать себе вопрос: в первый раз или не в первый мы встретили это число? Нет, не надо. Потому что мы работаем с двойкой! Получившееся число 4 вычеркиваем.

Кстати, как долго нам надо идти вперед? Это зависит от точной постановки задачи, а мы пока этого не сделали, и подходили к задаче по-простецки - находили простые числа. Но мы не уточняли, сколько именно простых чисел мы хотим найти. В самом деле – сколько?

Поиск простых чисел на заданном интервале

Наверное, имеет смысл искать N первых простых чисел. Но это не очень простая постановка задачи – вы это скоро увидите. Давайте для начала рассмотрим более простую задачу: найти все простые числа, меньше или равные заданного числа M.

Пусть это M равно 50. Значит, надо добавлять нашу двойку, пока не дойдем до 50.

Начав с 2, и добавляя по 2, мы получим следующую картину (красным выделены вычеркнутые числа):

Вычеркнули от 2 по +2

Вычеркнули от 2 по +2

Вычеркнули от 3 по +3

Вычеркнули от 3 по +3

Нетрудно видеть, что какие-то числа нам пришлось вычеркивать по второму разу, начиная с той же шестерки – ведь мы уже вычеркнули 6, когда работали с числом 2. Есть ли в этом что-то нехорошее? Если рассматривать эту работу с вычислительной точки зрения, то как будто бы это нехорошо. Вообще любая повторная работа, которую можно избежать – это нехорошо. Потому что мы тратим вычислительное время. Но в данном случае лучше все же закрыть глаза на эту избыточность. Потому что иначе нам пришлось бы как-то проверять, а не вычеркивали ли мы это число раньше? А это, само по себе, тоже лишняя вычислительная работа.

Вычеркнули от 5 по +5

Вычеркнули от 5 по +5

Как долго следует продолжать этот процесс? В самом начале мы сказали, что надо перебирать все числа в заданном интервале [2; M]. Но нетрудно догадаться, что на самом деле достаточно остановиться на числе M/2 (или его целой части для нечетных M). Ведь когда мы перейдем к следующему за ним числу K = [M/2] + 1, то число K + K заведомо окажется вне диапазона [2; M].

В любом случае, когда мы закончим перебирать все числа в интервале [2; M] (или только первую половину этого интервала), мы получим, что больше нам вычеркивать нечего. Это значит, что числа, оставшиеся незакчеркнуыми – и есть простые:

Всего мы нашли 16 простых чисел на нашем интервале, причем заранее мы не могли знать, сколько именно мы найдем. В этом и состоит главная прелесть и загадка простых чисел: не существует простого понимания того, насколько плотно простые числа расположены на числовой оси. Вспомним, какую постановку задачи мы сами для себя выбрали – найти все простые числа на заданном отрезке, – то есть, не превосходящие некоторого числа M.

Давайте для начала запрограммируем на Python первую задачу – для заданного интервала, а потом станем думать, как это трансформировать в решение второй задачи.

nums = list(range(maxNum + 1))

for j in range(i + i, len(nums), i):

В результате чего убедимся, что на отрезке [1; 100] всего 25 простых чисел. А теперь представим, что нашему заказчику совершенно все равно, какой интервал мы рассматриваем. Ему важно, чтобы мы нашли ему 100 первых простых чисел. А уж на каком интервале они лежат – как получится. Как нам нужно действовать?

Поиск заданного количества простых чисел

Кстати, почему это так? Это очень интересный вопрос, но им надо подробно заниматься не здесь, а в теории чисел. Но на самом деле, интуитивно, это и так ясно. Ведь чем больше число, тем больше шансов, что у него окажутся делители, большие единицы, но меньшие его самого.

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

В каком-то смысле, на этом месте можно было бы остановиться и считать задачу решенной. Мы нашли принципиальный способ находить требуемое количество первых простых чисел. Но, к сожалению, с практической точки зрения, этот способ очень плохой. Все дело в том, что нам придется многократно повторять одну и ту же работу. Представьте, что мы уже проделали все нужные вычисления на отрезке [1; M] и не нашли требуемое количество N простых чисел. Мы говорим: не беда! – и начинаем работу заново на отрезке [1; 2M]. И нам неизбежно придется пройти сначала по отрезку [1; M], но ведь мы это уже делали! Получается, мы сделаем эту работу повторно.

Вообще, такие оценки далеко не всегда легко выполнять, но программист постепенно должен этому учиться. Кроме этого, как и почти все в программировании, такие оценки можно делать по-разному. Конечно – при правильно проведенной оценке конечный результат не должен изменяться. Поэтому автор сделает так, как нравится ему. Изобразим наши поисковые интервалы геометрически:

Удвоили поисковый интервал

Удвоили поисковый интервал

Мы видим казалось бы - простую вещь: при увеличении поискового интервала в 2 раза объем вычислений увеличился в 2 раза. Однако – не совсем так. Ведь мы уже один раз проделали кусок работы на начальном интервале [1; M], а потом мы еще дважды ее выполнили. Всего получается уже
m + 2m = 3m.

Пусть нам снова не хватило простых чисел, найденных на интервале [1; 2M]. Тогда мы переходим к интервалу [1; 4M]. И мы видим, что полное количество проделанных нами вычислений от самого начала работы равно m + 2m +4m = 7m.

Еще раз удвоили поисковый интервал

Еще раз удвоили поисковый интервал

Теперь мы уже можем разглядеть, по какому закону нарастает объем вычислений – это закон геометрической прогрессии. Поскольку знаменатель этой прогрессии равен 2, сумма k членов прогрессии равна m * (2^k – 1) / (2 -1), то есть – m * (2^k - 1). Напомним: k слагаемых в нашей сумме означает, что мы k-1 раз удваивали размер поискового интервала.

Это не просто очень сильный рост, это самый плохой рост, какой только можно вообразить, и любой ИТ-специалист сразу схватится за голову: такой алгоритм просто никуда не годится. Надо искать какой-то другой метод.

Нам снова поможет довольно простая идея: давайте, вместо того, чтобы все время начинать работу заново, попробуем просто продолжать эту работу. Легко сказать, а как сделать?

Если бы мы проверяли только деление, скажем на 2 или 3, или любое другое, но одно конкретное число, мы бы шли и шли вдоль первого интервала, потом увидели бы, что одного этого интервала нам не хватае, и пошли бы дальше, и нет проблем. Но проблема в том, что проработав число 2, мы его бросаем, и работаем с числом 3, и так далее. А когда приходит время расширить интервал – мы уже ничего не помним, и поэтому не знаем, за что хвататься.

Какое число не возьми – ту же двойку – мы не знаем, откуда надо начинать новый отсчет.

Хорошая формулировка проблемы часто содержит, частично или даже полностью, путь к ее решению, и это именно наш случай. Если мы не знаем, откуда нам начинать прыгать через 2 (3, 5, 7 - и так далее) – давайте просто будем это записывать, чтобы не забыть.

Запоминать лучше в виде словаря. Словарь – это простейший объект в языках Python и JavaScript. Возможно, и в каких-то других языках тоже, но кругозор автора не бесконечен. А в этих двух языках словарь представляет собой перечень пар : , заключенный в фигурные скобки <>:

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

В языках Си и С++ существует похожий тип данных, называемый структурой (struct), но это менее гибкий тип данных, чем словарь Python. Любопытно, что в JavaScript вообще все объекты, включая очень сложные, строятся на основе словарей, а классов, как в С++, Java или Python, нет вообще, но можно наследовать объекты. В этом отношении, JavaScript – самый необычный объектно-ориентированный язык.

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

Возможно, это не самая простая и понятная формулировка, поэтому давайте повторим ее другими словами. Допустим, мы нашли простое число 2. Мы последовательно вычеркивали, в нашем текущем интервале, числа: 4, 6, 8, … и остановились, например, на числе 240. А наш интервал поиска заканчивался, например, на числе 241. Дальше нам идти некуда – в рамках данного интервала.

Запоминать эту пару чисел мы будем в виде одной записи в словаре:

Вообще, всегда надо стремиться к тому, чтобы имена переменных максимально отражали содержательный смысл тех данных, которые эта переменная описывает. Это не всегда удается в полной мере, но стараться все равно надо.

Дополним наш предыдущий код заполнением словаря pev_primes по мере того, как мы проходим по начальному поисковому диапазону [1; maxNum]:

nums = list(range(maxNum + 1))

for j in range(i + i, len(nums), i):

Последнее значение j и есть то число, последнее для i, которое мы вычеркнули в текущем интервале, поэтому его надо запомнить.

Давайте посмотрим, что мы сохранили:

Что-то пошло не так

Что-то пошло не так

Это только первый вопрос, но есть еще и другой: почему значение 94 у ключа 53 такое же, как у ключа 47. Есть и третий вопрос: почему значение 94 так и повторяется до самого конца словаря pev_primes.

Обнаружить причину произошедшего на самом деле несложно. Число 53 – первое, большее половины нашего диапазона. Если мы добавим 53 к 53, мы сразу выходим за пределы поискового диапазона. Это значит, что цикл

for j in range(i + i, len(nums), i):

- даже не начнет выполняться. Тогда, что же мы будем записывать в наш словарь в инструкции prev_primes[i] = j ? Это будет то значение j, которое осталось от предыдущего i, при котором цикл for еще выполнялся – как раз 94.

to_remove = range(i + i, len(nums)+1,

for j in to_remove :

if len(to_remove) > 0:

Теперь стало лучше

Теперь стало лучше

def add_primes(minNum, maxNum, prev_primes) :

nums = list(range(minNum, maxNum+1))

for prime, last_removed in prev_primes.items():

to_remove = range(last_removed + prime, maxNum+1, prime)

И удалять числа придется тоже чуть похитрее, чем раньше: чтобы получить индекс нужного числа в списке nums, нужно из этого числа вычесть начало диапазона:

for num in to_remove :

nums[num - minNum] = 0

Итак, что мы сделали. Мы продолжили вычеркивание чисел, кратным тем простым, которые мы нашли на предыдущем интервале, точнее – на всех предыдущих интервалах. Мы же будем продолжать расширять поисковый интервал неопределенно долго. Но на текущем интервале должны найтись какие-то новые простые числа в списке nums!

def add_primes(minNum, maxNum, prev_primes) :

nums = list(range(minNum, maxNum+1))

for num in nums :

Первое же встречное, но обязательно ненулевое – т.е. не вычеркнутое число, и есть простое, как и раньше.

Как и раньше, удалять надо с числа next_prime + next_prime, с шагом next_prime.

to_remove = range(next_prime * 2, maxNum+1, next_prime)

for num1 in to_remove:

nums[num1 - minNum] = 0

Как и раньше, к данному простому числу next_prime привязываем то число, которое мы вычеркнули последним:

if len(to_remove) > 0:

add_primes(minNum, maxNum, prev_primes)

minNum = maxNum + 1

Наконец, выполняем все вместе:

Выше уже говорилось, что мы не можем так подгадать, чтобы найти ровно требуемое (например, 100) количество простых чисел. Но мы можем проверить, что нашли не меньше, и в этом месте прекращаем развдигать наше раздвижное сито. И, чтобы вывести ровно 100 чисел, просто возьмем срез от найденного списка:

[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]

[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]

[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]

for num in nums :

if len(to_remove) > 0:

Получаем такую трассировку нашей программы:

101 : 103 : 107 : 109 : 113 : …

Точнее говоря – работа идет, но недостаточная: мы вычеркиваем числа, кратные только ранее найденным простым, а новые простые мы не находим и не и не используем.

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

def add_primes(minNum, maxNum, prev_primes):

'''добавляем простые числа и последние вычеркнутые числа'''

nums = list(range(minNum, maxNum+1)

for prime, last_removed in prev_primes.items():

to_remove = range(last_removed + prime, maxNum+1, prime)

to_remove = range(last_removed, maxNum+1, prime)

for num in to_remove :

nums[num - minNum] = 0

for num in nums :

to_remove = range(next_prime * 2, maxNum+1, next_prime)

for num1 in to_remove:

nums[num1 - minNum] = 0

if len(to_remove) > 0:

prev_primes[next_prime] = 2 * next_prime

add_primes(minNum, maxNum, prev_primes)

minNum = maxNum + 1

Проверяем результат: (можете сравнить с готовыми таблицами простых чисел):.

[2, 3, 5, 7, 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, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541]

Читайте также: