17 июня 2022
Тэги: Collections, Java, YouTube, алгоритмы, головоломки, руководство.
Ранее я уже приводил Алгоритм поиска простых чисел методом перебора делителей. Эта реализация хороша, если вам нужно ровно N первых простых чисел. Но если вы ищете простые числа в некотором диапазоне (скажем, не превосходящие 1 000 000), то лучше воспользоваться более быстрым алгоритмом, который называется «решето Эратосфена».
Этот алгоритм назван в честь древнегреческого учёного Эратосфена Киренского.
Алгоритм заключается в том, что изначально мы берём всё множество целых чисел в интересующем нас диапазоне, от 2 до N. Затем последовательно проходимся по этому множеству, вычёркивая каждое чётное число, т.к. оно делится на 2. После этого возвращаемся в начало и вычёркиваем все числа, делящиеся на 3, если они ещё не зачёркнуты. Затем делящиеся на следующее простое число – на 5. Затем на 7, на 11 и т. д. То есть мы «просеиваем» исходное множество целых чисел через сито. В итоге у нас останутся только простые числа.
Давайте напишем самую простую реализацию этого алгоритма. Метод getAllPrimesLessThan() на вход будет принимать размер решета sieveSize, который ограничивает сверху наш диапазон поиска. Для начала нам нужно создать массив типа boolean. Этот массив и будет нашим ситом. Если значение массива по индексу N равно true – число N простое. Если false – то нет. В начале алгоритма все элементы массива проставляем в true с помощью метода Arrays.fill(). Элементы с индексами 0 и 1 сразу ставим в false, т.к. ни 0, ни 1 не являются простыми числами.
Затем в цикле, начиная со второго элемента, проверяем значение каждого элемента. Если элемент равен true, то запускаем вложенный цикл опять же со второго элемента до тех пор, пока произведение индексов i и j не превысит размер массива. И все элементы во вложенном цикле помечаем как false, т.е. они не являются простыми.
В конце ещё раз проходимся по «решету» и собираем простые числа в список:
Данный алгоритм работает раза в 3 быстрее, чем перебор делителей, о котором я писал в прошлой статье. Но его можно ещё ускорить.
Количество проверок можно существенно уменьшить, если проверять не все числа, а только те, квадрат которых не превосходит диапазон поиска. Действительно, если N * N больше, чем размер решета и все возможные числа уже вычеркнуты, то дальше N проверять не имеет смысла.
Ну а вложенный цикл мы начинаем как раз с квадрата этого числа и зачёркиваем каждое число, кратное ему.
Данная оптимизация ускоряет алгоритм почти в 2 раза.
Наиболее уязвимым местом алгоритма является тот факт, что нам нужно создавать массив под весь диапазон проверяемых чисел, тогда как в методе перебора делителей мы храним только найденные простые числа. На больших диапазонах вам может просто не хватить памяти для создания такого массива.
Но мы помним, что каждый элемент массива имеет булевый тип, т.е. может принимать только 2 значения. Для хранения такой информации достаточно всего 1 бита. То есть 1 байт памяти позволит хранить 8 элементов!
Заменим наш массив на класс BitSet, который позволяет легко оперировать отдельными битами:
Данная реализация работает ещё чуть быстрее предыдущей и почти в 2 раза быстрее изначальной.
В итоге наша реализация алгоритма поиска целых чисел с помощью «решета Эратосфена» примет следующий вид:
Оптимизации, которые мы применили:
«Решето Эратосфена» позволяет искать простые числа в некотором диапазоне в несколько раз быстрее, чем алгоритм перебора делителей. Однако ему требуется точно знать диапазон поиска, а также нужна дополнительная память, чтобы хранить само «решето».
В случае, если диапазон поиска заранее неизвестен или у вас не хватает памяти для хранения решета и скорость поиска при этом не столь критична – используйте метод перебора делителей.
P.S. На написание статьи меня мотивировал пользователь Nik Nikita, который выступил с конструктивной критикой алгоритма перебора делителей на моём канале на YouTube.
Kotlin, Java, Spring, Spring Boot, Spring Data, SQL, PostgreSQL, Oracle, Linux, Hibernate, Collections, Stream API, многопоточность, файлы, Nginx, Apache, maven, gradle, JUnit, YouTube, новости, руководство, ООП, алгоритмы, головоломки, rest, GraphQL, Excel, XML, json, yaml.