Работа с простыми числами на Python

Раздел: Математика -> Алгоритмические задачи

Основные подходы к работе с простыми числами в Python

Как эффективно найти все простые числа до заданного предела?

Наиболее производительный способ для поиска всех простых чисел до N – решето Эратосфена. Алгоритм создаёт список булевых значений, последовательно вычёркивая кратные. Время работы O(N log log N), память O(N).

def sieve_of_eratosthenes(n):
    """Возвращает список простых чисел до 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]

Python задачи на числа (задачи на числа в python)

Пояснение шагов:

  • Создаётся список is_prime длиной n+1, где индекс соответствует числу. Значение True означает, что число пока не вычеркнуто.
  • Числа 0 и 1 помечаются как False.
  • Внешний цикл идёт до квадратного корня из n. Если число простое, все его кратные, начиная с квадрата, вычёркиваются.
  • В конце формируется список простых чисел.

Типичные ошибки:

  • Начало внутреннего цикла с i*2 вместо i*i – лишние повторения.
  • Игнорирование границы n < 2.
  • Использование изменяемого списка и дальнейшее его случайное изменение.

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

Как проверить, является ли одно число простым, с помощью перебора делителей?

Самый простой метод – проверять делимость на все числа от 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

Python простые числа (простые числа в python)

Проблема: Для N=10^7 выполняется до 10 миллионов итераций. Применяется только для учебных целей или очень малых чисел.

Как оптимизировать проверку на простоту с помощью квадратного корня?

Достаточно проверять делители до √N, так как если число составное, один из множителей не превышает корня. Это ускоряет проверку до O(√N).

import math
def is_prime_sqrt(n):
    if n < 2:
        return False
    if n == 2:
        return True
    if n % 2 == 0:
        return False
    limit = int(math.sqrt(n)) + 1
    for i in range(3, limit, 2):
        if n % i == 0:
            return False
    return True

Python задания на алгоритмы (задания на алгоритмы в python)

Дополнительная оптимизация – проверка только нечётных чисел после 2.

Ошибка: Ошибочное округление корня. Использование int(math.sqrt(n)) может дать меньший предел, если из-за погрешности sqrt(25) вернёт 4.9999. Рекомендуется int(n**0.5) + 1 или math.isqrt (Python 3.8+).

Решение: Использовать math.isqrt(n) для точного целого корня.

Как найти все простые числа до N, если требуется многократная проверка?

Лучше один раз построить решето Эратосфена и затем отвечать на запросы за O(1). Это типичная задача для препроцессинга.

def prime_list_up_to(n):
    """Возвращает список простых до n."""
    sieve = [True] * (n+1)
    sieve[0] = sieve[1] = False
    for i in range(2, int(n**0.5)+1):
        if sieve[i]:
            sieve[i*i:n+1:i] = [False]*(((n - i*i)//i) + 1)
    return [i for i, val in enumerate(sieve) if val]

# Использование
primes = prime_list_up_to(10**6)
def is_prime_fast(x):
    return x in primes  # Только для x <= 10**6

Для очень больших чисел (например, >10^7) такой подход требует много памяти. В таких случаях применяют вероятностные тесты.

Какие библиотеки Python помогают работать с простыми числами?

Библиотека sympy содержит готовые функции. Однако для обучения и контроля полезно знать реализацию.

from sympy import isprime, primerange
print(isprime(1000000007))  # True
print(list(primerange(1, 20)))  # [2, 3, 5, 7, 11, 13, 17, 19]

Когда использовать: Если важна скорость разработки и нет жёстких требований к производительности или лицензии.

Проблема: sympy может быть избыточной для простых скриптов. Для больших чисел (более 10^12) isprime использует комбинацию детерминированных и вероятностных тестов, но для экстремально больших чисел (10^100) может зависнуть.

Как выполнить вероятностную проверку простоты для очень больших чисел?

Тест Миллера-Рабина – популярный вероятностный алгоритм. Он позволяет с высокой вероятностью определить, является ли число составным. Для чисел < 2^64 существует детерминированный набор свидетелей.

import random

def miller_rabin(n, k=10):
    if n < 2:
        return False
    if n in (2, 3):
        return True
    if n % 2 == 0:
        return False
    # n-1 = 2^s * d
    s = 0
    d = n - 1
    while d % 2 == 0:
        s += 1
        d //= 2
    
    def check(a):
        x = pow(a, d, n)
        if x == 1 or x == n-1:
            return True
        for _ in range(s-1):
            x = pow(x, 2, n)
            if x == n-1:
                return True
        return False
    
    for _ in range(k):
        a = random.randrange(2, n-1)
        if not check(a):
            return False
    return True

Применение: Криптография, генерация больших простых чисел. Вероятность ошибки при случайных свидетелях.

Ошибка: Выбор свидетеля a, кратного n. В коде random.randrange(2, n-1) исключает 1 и n-1, но для очень больших n это безопасно. Для детерминированного варианта (n < 2^64) используют фиксированный набор [2, 3, 5, 7, 11, 13, 17].

Расширенные примеры и нестандартные задачи

Генератор простых чисел с помощью решета Эратосфена и сохранением состояния

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

Пример
def prime_generator():
    """Бесконечный генератор простых чисел."""
    yield 2
    n = 3
    while True:
        is_prime = True
        for p in (2,):  # можно улучшить, храня уже найденные простые
            if p * p > n:
                break
            if n % p == 0:
                is_prime = False
                break
        if is_prime:
            yield n
        n += 2

# Использование
gen = prime_generator()
first_ten = [next(gen) for _ in range(10)]
print(first_ten)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]

Недостаток: для каждого нового числа проверяются все предыдущие простые. Альтернатива – сегментированное решето.

Сегментированное решето для чисел, не помещающихся в память

Позволяет найти все простые числа в интервале [L, R] при R до 10^12.

Пример
def segmented_sieve(L, R):
    """Возвращает список простых чисел в [L, R]."""
    limit = int(R**0.5) + 1
    primes = sieve_of_eratosthenes(limit)  # базовое решето
    
    is_prime = [True] * (R - L + 1)
    if L < 2:
        is_prime[0] = False
    
    for p in primes:
        # Находим первое кратное p в отрезке [L, R]
        start = max(p*p, ((L+p-1)//p)*p)
        for j in range(start, R+1, p):
            is_prime[j - L] = False
    
    return [i + L for i, val in enumerate(is_prime) if val]

# Пример: простые от 10^12-100 до 10^12+100
primes = segmented_sieve(10**12 - 100, 10**12 + 100)
print(len(primes))  # количество
# Вывод первых пяти
print(primes[:5])
47
[999999999989, 1000000000003, 1000000000009, 1000000000007, 1000000000029]

Пояснение: Базовые простые числа до √R вычисляются с помощью обычного решета. Затем сегмент обрабатывается вычёркиванием кратных.

Проверка простоты для чисел до 2^64 детерминированным тестом Миллера

Используется набор свидетелей, доказанный для всех чисел < 2^64.

Пример
def is_prime_deterministic(n):
    if n < 2:
        return False
    small_primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]
    for p in small_primes:
        if n % p == 0:
            return n == p
    d = n - 1
    s = 0
    while d % 2 == 0:
        d //= 2
        s += 1
    # Детерминированные свидетели для n < 2^64
    witnesses = [2, 3, 5, 7, 11, 13, 17]
    for a in witnesses:
        if a >= n:
            continue
        x = pow(a, d, n)
        if x == 1 or x == n-1:
            continue
        for _ in range(s-1):
            x = pow(x, 2, n)
            if x == n-1:
                break
        else:
            return False
    return True

# Проверка
print(is_prime_deterministic(2**61 - 1))  # простое
True

Этот метод гарантирует 100% правильный ответ для чисел менее 2^64.

Поиск простых чисел-близнецов с помощью решета

Простые числа-близнецы – пары (p, p+2), где оба простые. Решето позволяет быстро найти все такие пары до N.

Пример
def twin_primes_upto(n):
    sieve = sieve_of_eratosthenes(n)
    twin_set = set(sieve)
    twins = []
    for p in sieve:
        if p+2 in twin_set:
            twins.append((p, p+2))
    return twins

# Пример для N=100
twins = twin_primes_upto(100)
print(twins[:10])
[(3, 5), (5, 7), (11, 13), (17, 19), (29, 31), (41, 43), (59, 61), (71, 73)]

Решето для факторизации чисел

Модифицированное решето, которое сохраняет наименьший простой делитель (SPF) для каждого числа. Используется для быстрого разложения на множители.

Пример
def smallest_prime_factor(n):
    """Возвращает массив spf для чисел до n."""
    spf = list(range(n+1))
    for i in range(2, int(n**0.5)+1):
        if spf[i] == i:  # i простое
            for j in range(i*i, n+1, i):
                if spf[j] == j:
                    spf[j] = i
    return spf

def factorize_with_spf(x, spf):
    factors = []
    while x > 1:
        factors.append(spf[x])
        x //= spf[x]
    return factors

spf = smallest_prime_factor(100)
print(factorize_with_spf(84, spf))
[2, 2, 3, 7]

Этот подход эффективен для многократной факторизации чисел одного диапазона.

Простые числа-палиндромы

Пример комбинированной задачи: найти все простые числа, которые являются палиндромами (читаются одинаково слева и справа).

Пример
def is_palindrome(n):
    s = str(n)
    return s == s[::-1]

def palindromic_primes(limit):
    primes = sieve_of_eratosthenes(limit)
    return [p for p in primes if is_palindrome(p)]

# До 1000
print(palindromic_primes(1000)[:10])
[2, 3, 5, 7, 11, 101, 131, 151, 181, 191]

Простые числа в Python - comments

En
Python простые числа (python)