Что такое алгоритм «разделяй и властвуй»?
На лекции упомянули «разделяй и властвуй» и сказали, что merge sort и бинарный поиск — это про него. Но я не уловил саму идею. Это просто рекурсия или что-то большее? И как из этого принципа потом выводят сложность вроде O(n log n)? Хотелось бы пример с кодом, чтобы прям увидеть.
2 ответа
Хорошо, что задал — это один из самых универсальных приёмов в алгоритмах.
«Разделяй и властвуй» (divide and conquer) — это стратегия из трёх шагов:
- Разделить задачу на несколько подзадач поменьше (обычно того же типа).
- Решить каждую подзадачу рекурсивно (а совсем маленькие — напрямую, это база рекурсии).
- Объединить ответы подзадач в ответ для исходной.
Это больше чем просто рекурсия. Рекурсия — инструмент; «разделяй и властвуй» — это конкретный паттерн, где задача дробится на независимые части, а потом результаты собираются вместе.
Классический пример — merge sort:
def merge_sort(arr):
if len(arr) <= 1: # база: 0 или 1 элемент уже отсортирован
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid]) # делим и решаем левую половину
right = merge_sort(arr[mid:]) # и правую
return merge(left, right) # объединяем
def merge(a, b):
result, i, j = [], 0, 0
while i < len(a) and j < len(b):
if a[i] <= b[j]:
result.append(a[i]); i += 1
else:
result.append(b[j]); j += 1
result.extend(a[i:])
result.extend(b[j:])
return result
print(merge_sort([5, 2, 8, 1, 9, 3])) # [1, 2, 3, 5, 8, 9]
Откуда берётся O(n log n)? Рассуждаем простыми словами:
- На каждом уровне рекурсии мы делим массив пополам → всего получается log n уровней (сколько раз n можно делить на 2 до единицы).
- На каждом уровне суммарно мы сливаем все n элементов → O(n) работы на уровень.
- Перемножаем: log n уровней × n работы = O(n log n).
То же рассуждение формализуют через рекуррентное соотношение: T(n) = 2·T(n/2) + O(n). «2 подзадачи размера n/2 плюс O(n) на слияние» — решение как раз даёт O(n log n).
Другие примеры:
- Бинарный поиск: делим диапазон пополам, спускаемся в одну половину → O(log n).
- Quick sort: делим по опорному элементу, сортируем части → в среднем O(n log n).
Итог: увидел «разбить → решить рекурсивно → объединить» — это он.
Добавлю нюанс про бинарный поиск как пример. Он формально тоже «разделяй и властвуй», но с важным отличием: после деления пополам мы спускаемся только в одну половину, а не в обе, и шага «объединить» по сути нет.
Именно поэтому у него рекуррентность T(n) = T(n/2) + O(1), что даёт O(log n), а не O(n log n). Сравни с merge sort, где мы обрабатываем обе половины и потом сливаем. Полезно держать это различие в голове — оно объясняет, почему сложности у них разные.