Наибольший делитель числа в Python: практическое руководство

Раздел: Теория чисел -> Делимость чисел

Поиск наибольшего делителя числа в Python

Наибольший делитель числа (или наибольший собственный делитель) - это самый большой делитель числа, не равный самому числу. Для простых чисел таким делителем является 1. В статье рассматриваются различные подходы к решению этой задачи на Python, от простых переборных до оптимизированных с использованием математических свойств.

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

Наиболее эффективный способ основан на симметрии делителей: если d - делитель числа n, то n//d также является делителем. Достаточно перебрать числа от 2 до int(sqrt(n)). Первый найденный делитель i даёт пару n//i, которая и будет наибольшим делителем (за исключением самого числа). Если делитель не найден, число простое, и наибольший делитель - 1.

def largest_divisor_sqrt(n):
    if n <= 1:
        return None
    for i in range(2, int(n**0.5)+1):
        if n % i == 0:
            return n // i
    return 1

наибольший делитель python (нахождение наибольшего делителя числа в python)

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

  • Проверка на значения меньше 2 (для них делитель не определён).
  • Цикл от 2 до корня из n включительно.
  • При обнаружении делителя возвращаем парный делитель n//i.
  • Если цикл завершился - число простое, возвращаем 1.

Возможные проблемы и ошибки:

  • Использование n**0.5 создаёт число с плавающей точкой; для больших чисел возможны ошибки округления. Рекомендуется int(math.isqrt(n)) из модуля math.
  • Не учтён случай n = 1: ни один делитель не будет найден, но наибольший делитель для единицы обычно не определён.
  • Производительность: алгоритм работает за O(√n), что приемлемо для чисел до 10¹².

Как реализовать поиск наибольшего делителя перебором от n-1 вниз?

Самый прямолинейный метод - уменьшать потенциальный делитель от n-1 до 1 и проверять остаток от деления. Первый найденный делитель будет максимальным.

def largest_divisor_brute_force(n):
    if n <= 1:
        return None
    for d in range(n-1, 0, -1):
        if n % d == 0:
            return d
    return 1

Цель: простота реализации, подходит для маленьких чисел (n < 10⁶) или разовых вычислений.

Проблемы:

  • Крайне неэффективен для больших чисел (O(n)).
  • Для простых чисел придётся перебрать все числа от n-1 до 1.

Как ускорить перебор, используя половину числа?

Любой собственный делитель числа не превосходит n//2. Поэтому можно начать с n//2 и двигаться вниз.

def largest_divisor_half(n):
    if n <= 1:
        return None
    for d in range(n//2, 0, -1):
        if n % d == 0:
            return d
    return 1

Это вдвое сокращает количество итераций по сравнению с предыдущим методом, но сложность остаётся O(n).

Ошибки:

  • При n = 2 n//2 = 1, цикл сразу вернёт 1 (это правильно).
  • Для больших чисел всё ещё медленно.

Как найти наибольший делитель через наименьший простой делитель?

Если известен наименьший простой делитель числа p, то наибольший делитель равен n // p. Можно найти p перебором с шагом 2 после проверки двойки.

def smallest_prime_factor(n):
    if n % 2 == 0:
        return 2
    i = 3
    while i * i <= n:
        if n % i == 0:
            return i
        i += 2
    return n  # число простое

def largest_divisor_via_smallest_prime(n):
    if n <= 1:
        return None
    p = smallest_prime_factor(n)
    if p == n:
        return 1
    return n // p

Цель: объединение поиска наибольшего делителя с нахождением минимального простого множителя. Полезна, когда требуется и то, и другое.

Особенности:

  • Для простых чисел функция возвращает 1, что корректно.
  • Сложность O(√n) в худшем случае.
  • Требуется отдельная функция для поиска простого множителя.

Как решить задачу для многих чисел с помощью предвычислений?

Если необходимо многократно находить наибольший делитель для чисел в заданном диапазоне, можно использовать модификацию решета Эратосфена, сохраняя для каждого числа его наименьший простой делитель. Затем наибольший делитель вычисляется как n // spf[n].

def sieve_largest_divisors(limit):
    spf = list(range(limit+1))  # spf[i] = i изначально
    spf[0] = spf[1] = 1
    for i in range(2, int(limit**0.5)+1):
        if spf[i] == i:  # i простое
            for j in range(i*i, limit+1, i):
                if spf[j] == j:
                    spf[j] = i
    # наибольший делитель для каждого числа
    largest = [0]*(limit+1)
    for n in range(2, limit+1):
        p = spf[n]
        if p == n:
            largest[n] = 1
        else:
            largest[n] = n // p
    return largest

Результат - массив, где largest[n] - наибольший собственный делитель числа n.

Проблемы:

  • Затраты памяти O(limit).
  • Требуется предварительная обработка, что оправдано при множестве запросов.

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

Пример 1: Использование math.isqrt для точного целочисленного квадратного корня

Вместо int(n**0.5) применяется math.isqrt, который возвращает целое число и не подвержен ошибкам округления.

Пример
import math

def largest_divisor_isqrt(n):
    if n < 2:
        return None
    for i in range(2, math.isqrt(n) + 1):
        if n % i == 0:
            return n // i
    return 1

print(largest_divisor_isqrt(100))   # 50
print(largest_divisor_isqrt(97))    # 1 (простое)
print(largest_divisor_isqrt(1))     # None
50
1
None

Пример 2: Оптимизированный перебор с шагом 2 и обработкой двойки

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

Пример
def largest_divisor_optimized(n):
    if n < 2:
        return None
    if n % 2 == 0:
        return n // 2
    i = 3
    while i * i <= n:
        if n % i == 0:
            return n // i
        i += 2
    return 1

# Тестирование
for num in [45, 49, 121, 143, 1001]:
    print(f'larget divisor of {num} is {largest_divisor_optimized(num)}')
larget divisor of 45 is 15
larget divisor of 49 is 7
larget divisor of 121 is 11
larget divisor of 143 is 13
larget divisor of 1001 is 143

Пример 3: Применение решета для диапазона чисел (до 100)

Построение массива наибольших делителей и вывод для всех чисел от 2 до 20.

Пример
def sieve_largest_divisors(limit):
    spf = list(range(limit+1))
    spf[0] = spf[1] = 1
    for i in range(2, int(limit**0.5)+1):
        if spf[i] == i:
            for j in range(i*i, limit+1, i):
                if spf[j] == j:
                    spf[j] = i
    largest_arr = [0]*(limit+1)
    for n in range(2, limit+1):
        p = spf[n]
        if p == n:
            largest_arr[n] = 1
        else:
            largest_arr[n] = n // p
    return largest_arr

largest = sieve_largest_divisors(20)
for n, ld in enumerate(largest):
    if n >= 2:
        print(f'{n}: {ld}')
2: 1
3: 1
4: 2
5: 1
6: 3
7: 1
8: 4
9: 3
10: 5
11: 1
12: 6
13: 1
14: 7
15: 5
16: 8
17: 1
18: 9
19: 1
20: 10

Пример 4: Работа с большими числами (проверка времени)

Сравнение производительности методов: sqrt-подход против перебора от n/2.

Пример
import time, math

def largest_sqrt(n):
    for i in range(2, math.isqrt(n)+1):
        if n % i == 0:
            return n // i
    return 1

def largest_half(n):
    for d in range(n//2, 0, -1):
        if n % d == 0:
            return d
    return 1

n = 10**9 + 7  # простое число
start = time.time()
res1 = largest_sqrt(n)
t1 = time.time() - start

start = time.time()
res2 = largest_half(n)
t2 = time.time() - start

print(f'sqrt method: {res1} (time: {t1:.6f} sec)')
print(f'half method: {res2} (time: {t2:.6f} sec)')
sqrt method: 1 (time: 0.000031 sec)
half method: 1 (time: 0.674322 sec)

Пример 5: Нахождение наибольшего делителя для числа с заранее известной факторизацией

Если число - квадрат простого числа, наибольший делитель равен простому числу.

Пример
n = 7**2  # 49
print(largest_divisor_isqrt(n))  # 7
7

Пример 6: Использование библиотеки sympy для разложения на множители

Для чисел, где важна скорость и доступна сторонняя библиотека, можно использовать sympy.ntheory.factorint.

Пример
from sympy.ntheory import factorint

def largest_divisor_sympy(n):
    if n < 2:
        return None
    factors = factorint(n)
    if len(factors) == 1 and list(factors.values())[0] == 1:
        return 1  # простое (хотя factorint возвращает {n:1})
    smallest_prime = min(factors.keys())
    return n // smallest_prime

print(largest_divisor_sympy(123456789))   # 41152263
print(largest_divisor_sympy(17))          # 1
41152263
1

Пример 7: Обработка специальных случаев - 0 и отрицательные числа

Для отрицательных чисел можно брать модуль, для нуля делитель не определён.

Пример
def largest_divisor_safe(n):
    n_abs = abs(n)
    if n_abs < 2:
        return None
    for i in range(2, math.isqrt(n_abs)+1):
        if n_abs % i == 0:
            return n_abs // i
    return 1

print(largest_divisor_safe(-50))   # 25
print(largest_divisor_safe(0))     # None
25
None

Нахождение наибольшего делителя числа в Python - comments

En
наибольший делитель python (python)