Нахождение простых чисел: полный разбор с программным кодом
Основные алгоритмы поиска простых чисел
Как эффективно найти все простые числа до заданного числа N?
Наиболее производительное решение - решето Эратосфена. Алгоритм работает за O(N log log N) и потребляет O(N) памяти.
def sieve_of_eratosthenes(n):
if n < 2:
return []
is_prime = [True] * (n + 1)
is_prime[0] = is_prime[1] = False
for i in range(2, int(n ** 0.5) + 1):
if is_prime[i]:
for j in range(i * i, n + 1, i):
is_prime[j] = False
return [i for i, prime in enumerate(is_prime) if prime]
primes = sieve_of_eratosthenes(50)
print(primes)
алгоритм решения задачи python (алгоритм решения задачи на python)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
базовые задачи python (базовые задачи python)
Пояснение шагов:
- Создание списка:
[True] * (n+1)- все числа от 0 до N считаются простыми. - Исключение 0 и 1: они не являются простыми.
- Внешний цикл: перебор от 2 до квадратного корня из N. Если число простое, то вычёркиваются его кратные.
- Внутренний цикл: начинается с
i*i, чтобы не повторять вычёркивание ранее обработанных множителей. - Формирование результата: генератор списка собирает индексы, где значение
True.
Типичные ошибки:
- Забыть исключить 0 и 1 - они будут ошибочно включены в результат.
- Внутренний цикл начинать с
iвместоi*i- это избыточно, но не ломает логику, только замедляет. - Использование
range(n+1)без учёта границ - возможен выход за пределы.
Как проверить, является ли конкретное число простым?
Простой перебор делителей от 2 до n-1. Работает медленно для больших чисел, но подходит для обучения.
def is_prime_naive(n):
if n < 2:
return False
for i in range(2, n):
if n % i == 0:
return False
return True
print(is_prime_naive(17))
print(is_prime_naive(18))
задачи для обучения python (задачи для обучения python)
True False
задачи на классы в python (задачи на классы в python)
Оптимизация: достаточно проверять делители до sqrt(n).
def is_prime_optimized(n):
if n < 2:
return False
if n == 2:
return True
if n % 2 == 0:
return False
i = 3
while i * i <= n:
if n % i == 0:
return False
i += 2
return True
print([i for i in range(20) if is_prime_optimized(i)])
множество python задачи (задачи на множества в python)
[2, 3, 5, 7, 11, 13, 17, 19]
задачи на модули python (задачи на модули в python)
Проблемы:
- Число 2 - чётное, но простое. Проверка на чётность может его исключить, поэтому нужна отдельная обработка.
- При
nблизком к 1e12 цикл до sqrt(n) всё равно может быть долог, если не используется решето.
Как получить простые числа с помощью List Comprehension и all?
Функциональный стиль, но неэффективен для больших диапазонов.
def primes_upto_lc(n):
return [x for x in range(2, n+1) if all(x % y != 0 for y in range(2, int(x**0.5)+1))]
print(primes_upto_lc(30))
задачи на операторы в python (задачи на операторы в python)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
задачи на последовательности python (задачи на последовательности в python)
Недостаток: для каждого числа заново вычисляется квадратный корень и выполняется цикл. При N=100000 алгоритм работает заметно дольше решета.
Как сгенерировать простые числа с помощью генератора?
Экономит память, возвращая числа по одному.
def prime_generator():
yield 2
primes = [2]
candidate = 3
while True:
is_prime = True
sqrt_candidate = candidate ** 0.5
for p in primes:
if p > sqrt_candidate:
break
if candidate % p == 0:
is_prime = False
break
if is_prime:
primes.append(candidate)
yield candidate
candidate += 2
# Первые 10 простых чисел
gen = prime_generator()
first_ten = [next(gen) for _ in range(10)]
print(first_ten)
задачи на списки python (задачи на списки в python)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
Особенности: генератор запоминает уже найденные простые числа, что ускоряет проверку. Однако он не может работать с бесконечным списком в памяти, поэтому используется для поточного получения.
Расширенные примеры и нестандартные реализации
Решето Эратосфена с битовыми операциями (экономия памяти)
Вместо списка булевых значений используется целое число или массив байтов. Каждый бит соответствует одному числу.
def sieve_bitarray(n):
if n < 2:
return []
# Используем bytearray для хранения флагов (1 байт на число)
is_prime = bytearray(b'\x01') * (n + 1)
is_prime[0] = is_prime[1] = 0
for i in range(2, int(n ** 0.5) + 1):
if is_prime[i]:
step = i
start = i * i
is_prime[start:n+1:step] = b'\x00' * ((n - start)//step + 1)
return [i for i, flag in enumerate(is_prime) if flag]
print(sieve_bitarray(40))
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]
Пояснение: bytearray занимает 1 байт на элемент вместо 28 байт (для bool в Python). Операция присваивания среза b'\x00' * количество устанавливает все кратные в ноль за один вызов.
Решето Эратосфена с оптимизацией чётных чисел
Можно хранить информацию только для нечётных чисел, сократив память вдвое.
def sieve_odd_only(n):
if n < 2:
return []
if n == 2:
return [2]
# is_prime[i] соответствует числу 2*i+3
size = (n - 1) // 2
is_prime = [True] * size
for i in range(int(n ** 0.5) // 2):
if is_prime[i]:
# число = 2*i+3
p = 2 * i + 3
# начинаем с p*p = (2*i+3)^2 = 4*(i^2) + 12*i + 9 = 2*(2*i^2+6*i+4)+1
step = p
start = (p * p - 3) // 2 # индекс для p^2
is_prime[start::step] = [False] * ((size - start + step - 1) // step)
result = [2] + [2*i+3 for i, val in enumerate(is_prime) if val]
return result
print(sieve_odd_only(50))
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
Здесь сложнее индексация, но выигрыш в памяти и скорости за счёт меньшего количества операций.
Параллельное вычисление простых чисел (multiprocessing)
Для больших N (например, до 10^7) можно разделить диапазон на части и обработать параллельно.
import multiprocessing as mp
def sieve_segment(start, size):
# Решето на отрезке [start, start+size)
limit = int((start + size) ** 0.5) + 1
# сначала найдём все простые до limit
is_prime_base = [True] * (limit + 1)
is_prime_base[0] = is_prime_base[1] = False
for i in range(2, int(limit ** 0.5) + 1):
if is_prime_base[i]:
for j in range(i*i, limit+1, i):
is_prime_base[j] = False
bases = [i for i, v in enumerate(is_prime_base) if v]
# Теперь работаем с сегментом
segment = [True] * size
first = start
for p in bases:
start_val = max(p*p, (first + p - 1)//p * p)
for j in range(start_val, first+size, p):
segment[j - first] = False
return [first + i for i, v in enumerate(segment) if v]
def parallel_primes(n, num_workers=4):
if n < 2:
return []
chunk_size = (n - 1) // num_workers + 1
ranges = []
for i in range(num_workers):
start = 2 + i * chunk_size
end = min(start + chunk_size, n + 1)
if start < end:
ranges.append((start, end - start))
with mp.Pool(num_workers) as pool:
results = pool.starmap(sieve_segment, ranges)
# объединение результатов (могут быть дубли? нет)
primes = []
for r in results:
primes.extend(r)
return sorted(primes)
# для демонстрации возьмём небольшое n
print(parallel_primes(50, 2))
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
Внимание: при малых N параллелизм не даёт выигрыша из-за накладных расходов. Для N=10^7 может ускорить в 2-3 раза на многоядерном процессоре.
Использование библиотеки numpy для векторизованного решета
Numpy позволяет выполнять операции над массивами быстрее, чем чистые циклы Python.
import numpy as np
def sieve_numpy(n):
if n < 2:
return []
is_prime = np.ones(n+1, dtype=bool)
is_prime[0:2] = False
for i in range(2, int(n**0.5)+1):
if is_prime[i]:
is_prime[i*i:n+1:i] = False
return np.where(is_prime)[0].tolist()
print(sieve_numpy(50))
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
Скорость может быть выше решета на чистых списках для больших N за счёт низкоуровневой реализации. Однако импорт numpy оправдан только если библиотека уже используется в проекте.
Решето Эратосфена с Wheel factorization (оптимизация путём пропуска чисел, кратных 2,3,5)
Сначала предварительно отсеиваются числа с малыми простыми делителями, затем решето применяется к остаткам. Пример для wheel 2*3=6.
import math
def wheel_sieve(n):
if n < 2:
return []
# малые простые
small_primes = [2, 3, 5]
limit = int(math.isqrt(n))
# отсеиваем маленькие
marks = [False] * (n+1)
for p in small_primes:
for multiple in range(p, n+1, p):
marks[multiple] = True
# теперь решето среди чисел вида 6k±1 (кроме кратных 2,3)
for i in range(7, limit+1, 2):
if not marks[i]:
step = i if i%6==1 else i*2 # упрощение
# на самом деле нужно вычёркивать кратные
start = i*i
if start > n:
break
for j in range(start, n+1, i):
marks[j] = True
# собираем непомеченные
return [i for i in range(2, n+1) if not marks[i]]
print(wheel_sieve(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]
Реализация выше не совершенна, но демонстрирует идею. Полное Wheel factorization (2*3*5 = 30) даёт дополнительное ускорение.