← Назад к вопросам
Что быстрее O(n) или O(log(n))?
2.0 Middle🔥 81 комментариев
#Python Core#Soft Skills#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Анализ сложности: O(n) vs O(log n)
Быстрый ответ
O(log n) всегда быстрее O(n). Логарифмическая сложность означает, что операция выполняется намного меньше раз при увеличении размера входных данных.
Наглядное сравнение
import math
# Для массива из 1 000 000 элементов
n = 1_000_000
linear = n # 1,000,000 операций
logarithmic = math.log2(n) # ~20 операций
print(f"O(n) операций: {linear}")
print(f"O(log n) операций: {logarithmic}")
print(f"Разница: {linear / logarithmic:.0f}x раз")
# Выход:
# O(n) операций: 1000000
# O(log n) операций: 19.93
# Разница: 50128x раз
Графическое представление
n = 10: O(n)=10 vs O(log n)=3.3
n = 100: O(n)=100 vs O(log n)=6.6
n = 1000: O(n)=1000 vs O(log n)=10
n = 1000000: O(n)=1000000 vs O(log n)=20
С ростом n разница становится астрономической.
Примеры O(log n): бинарный поиск
def binary_search(arr, target):
"""Поиск элемента в отсортированном массиве — O(log n)"""
left, right = 0, len(arr) - 1
while left <= right:
mid = (left + right) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
left = mid + 1 # Исключаем левую половину
else:
right = mid - 1 # Исключаем правую половину
return -1
# Даже в массиве из 1 млн элементов максимум 20 операций
data = list(range(1_000_000))
result = binary_search(data, 999_999)
print(f"Найден индекс: {result}")
Примеры O(n): линейный поиск
def linear_search(arr, target):
"""Поиск элемента перебором — O(n)"""
for i, element in enumerate(arr):
if element == target:
return i
return -1
# На массиве из 1 млн элементов может быть 1 млн операций
data = list(range(1_000_000))
result = linear_search(data, 999_999) # Худший случай — 1 млн итераций
print(f"Найден индекс: {result}")
Реальный тест производительности
import time
import math
def time_binary_search(n):
"""Время бинарного поиска"""
arr = list(range(n))
target = n - 1 # Худший случай
start = time.perf_counter()
left, right = 0, len(arr) - 1
while left <= right:
mid = (left + right) // 2
if arr[mid] == target:
break
elif arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return time.perf_counter() - start
def time_linear_search(n):
"""Время линейного поиска"""
arr = list(range(n))
target = n - 1 # Худший случай
start = time.perf_counter()
for i in range(len(arr)):
if arr[i] == target:
break
return time.perf_counter() - start
# Тестирование на разных размерах
for n in [1_000, 10_000, 100_000, 1_000_000, 10_000_000]:
log_time = time_binary_search(n)
lin_time = time_linear_search(n)
ratio = lin_time / log_time if log_time > 0 else float('inf')
print(f"n={n:8d}: O(log n)={log_time*1e6:6.2f}μs, O(n)={lin_time*1e6:8.2f}μs, разница={ratio:.0f}x")
# Примерный результат:
# n= 1000: O(log n)= 0.10μs, O(n)= 10.50μs, разница=105x
# n= 10000: O(log n)= 0.15μs, O(n)= 110.00μs, разница=733x
# n= 100000: O(log n)= 0.20μs, O(n)= 1100.00μs, разница=5500x
# n= 1000000: O(log n)= 0.25μs, O(n)= 11000.00μs, разница=44000x
Другие примеры O(log n)
# 1. Быстрое возведение в степень
def power(x, n):
"""x^n за O(log n) вместо O(n)"""
result = 1
while n > 0:
if n % 2 == 1:
result *= x
x *= x
n //= 2 # Каждый раз n уменьшается в 2 раза
return result
# 2. Поиск в бинарном дереве поиска
class BST:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def search(self, target): # O(log n) в сбалансированном дереве
if self.value == target:
return self
elif target < self.value and self.left:
return self.left.search(target)
elif target > self.value and self.right:
return self.right.search(target)
return None
# 3. Merge sort, quick sort — O(n log n)
def merge_sort(arr):
"""O(n log n) — существует n уровней, на каждом O(n) работы"""
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid]) # Разделение — O(log n) уровней
right = merge_sort(arr[mid:]) # На каждом уровне O(n) слияния
return merge(left, right)
def merge(left, right):
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
Сравнение разных сложностей
# Для n = 1,000,000:
O(1) - константная: 1 операция
O(log n) - логарифмическая: ~20 операций (лучшее)
O(sqrt(n)) - корень: ~1,000 операций
O(n) - линейная: 1,000,000 операций
O(n log n) - линейно-логарифмическая: ~20,000,000 операций
O(n^2) - квадратичная: 1,000,000,000,000 операций (триллион!)
O(2^n) - экспоненциальная: 2^1000000 операций (невычислимо)
O(n!) - факториальная: (очень плохо)
Когда O(n) может быть приемлемо
# Если операция ОЧЕНЬ дешёвая, O(n) может быть быстрее O(log n)
def cheap_linear():
"""Простое сложение в цикле"""
result = 0
for i in range(1_000_000):
result += i # Очень дешёвая операция
return result
def complex_logarithmic():
"""Сложные вычисления"""
result = 1
for i in range(20): # log(1_000_000) ~ 20
result *= expensive_function() # Очень дорогая операция
return result
# Несмотря на O(n) vs O(log n), cheap_linear может быть быстрее
# Потому что константные множители имеют значение!
# O(n) с маленькой константой может быть быстрее O(log n) с большой
Почему асимптотическая сложность важна
# O(n) с константой C1
time_linear = C1 * n
# O(log n) с константой C2
time_log = C2 * log(n)
# Даже если C2 > C1, для больших n время_log всегда меньше
# Потому что log(n) растёт медленнее n
C1, C2 = 1, 100 # log(n) имеет большую константу
for n in [10, 100, 1_000, 10_000, 100_000, 1_000_000]:
t_linear = C1 * n
t_log = C2 * math.log2(n)
print(f"n={n:7d}: O(n)={t_linear:8.0f}ns, O(log n)={t_log:8.2f}ns")
# Выход показывает, что даже с большой константой, O(log n) выигрывает:
# n= 10: O(n)= 10ns, O(log n)= 333.22ns
# n= 100: O(n)= 100ns, O(log n)= 664.39ns
# n= 1000: O(n)= 1000ns, O(log n)= 997.24ns <- начинает выигрывать
# n= 10000: O(n)= 10000ns, O(log n)= 1329.20ns
# n= 100000: O(n)= 100000ns, O(log n)= 1661.36ns
# n=1000000: O(n)= 1000000ns, O(log n)= 1993.28ns
Заключение
O(log n) всегда быстрее O(n) для больших данных:
- O(log n) — логарифмическая, растёт очень медленно
- O(n) — линейная, растёт пропорционально входным данным
- При n = 1 млн разница уже в десятки тысяч раз
- Используйте бинарный поиск вместо линейного где возможно
- Выбирайте алгоритмы с лучшей асимптотической сложностью
Правило: O(log n) > O(n) в плане производительности.