🔴 Сложный ⏱️ 25 минут

Алгоритм Флойда-Уоршелла: кратчайшие пути между всеми парами

Алгоритм Флойда-Уоршелла: кратчайшие пути между всеми парами

🎯 Зачем это нужно?

Представь, что ты планируешь маршруты доставки для крупной логистической компании 🚚. У тебя есть 1000 городов и нужно знать кратчайший путь между ЛЮБЫМИ двумя городами. Запускать алгоритм Дейкстры 1000 раз? Слишком медленно!

Или другой пример: в социальной сети нужно найти “степень разделения” между всеми пользователями - кто от кого на каком расстоянии. Facebook использует именно такие алгоритмы для рекомендации друзей!

🌍 Где применяется:

  • Google Maps - предвычисление маршрутов между популярными точками
  • Игровая индустрия - поиск путей для NPC в стратегиях
  • Финансы - арбитраж валют (найти цепочку обменов для максимальной прибыли)

📚 История вопроса

Алгоритм назван в честь Роберта Флойда, но на самом деле его независимо открыли трое: Флойд (1962), Уоршелл (1962) и Рой (1959) 📅.

Интересный факт: Стивен Уоршелл изначально решал совсем другую задачу - находил транзитивное замыкание графа (какие вершины достижимы из каких). А уже потом поняли, что тот же принцип работает для кратчайших путей!

💡 Интуиция

Основная идея гениально проста: “А что если пойти через промежуточную вершину?” 🤔

Представь: ты едешь из города A в город C. Прямой путь занимает 100 км. Но есть город B - из A в B всего 30 км, а из B в C тоже 30 км. Итого 60 км через B - выгоднее!

[МЕДИА: image_01] Описание: Треугольник из трех городов A, B, C с расстояниями, показывающий как путь через B короче прямого пути Промпт: “educational illustration showing triangle of three cities A, B, C, direct path 100km vs indirect path through B (30+30=60km), road map style, clear distance labels, modern clean design”

Флойд-Уоршелл делает именно это, но систематически: проверяет ВСЕ возможные промежуточные вершины для ВСЕХ пар городов.

Магия в том, что мы рассматриваем промежуточные вершины по порядку: сначала только через вершину 0, потом через 0 и 1, потом через 0, 1 и 2… И так до конца.

📐 Формальное определение

Пусть dist[i][j][k] = кратчайшее расстояние от i до j, используя только вершины {0, 1, …, k} как промежуточные.

Рекуррентная формула:

dist[i][j][k] = min(
    dist[i][j][k-1],           // не используем k
    dist[i][k][k-1] + dist[k][j][k-1]  // идём через k
)

Базовый случай: dist[i][j][-1] = вес ребра (i,j) если оно есть, иначе ∞

Оптимизация пространства: можем использовать только dist[i][j], обновляя “на месте”:

for k in range(n):
    for i in range(n):
        for j in range(n):
            dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])

Временная сложность: O(n³)
Пространственная сложность: O(n²)

🔍 Примеры с разбором

Пример 1: Простой граф из 4 вершин

Исходная матрица расстояний:

    0  1  2  3
0 [ 0  5  ∞  10]
1 [ ∞  0  3   ∞]
2 [ ∞  ∞  0   1]
3 [ ∞  ∞  ∞   0]

[МЕДИА: image_02] Описание: Граф из 4 вершин с весами рёбер и пошаговое выполнение алгоритма Промпт: “step-by-step Floyd-Warshall algorithm execution, 4 vertices graph with weighted edges, matrix transformations at each k iteration, educational diagram, clean mathematical style”

Итерация k=0 (через вершину 0):

dist[1][3] = min(∞, dist[1][0] + dist[0][3]) = min(∞, ∞ + 10) = ∞
dist[2][1] = min(∞, dist[2][0] + dist[0][1]) = min(∞, ∞ + 5) = ∞

Ничего не изменилось, так как нет путей К вершине 0.

Итерация k=1 (через вершину 1):

dist[0][2] = min(∞, dist[0][1] + dist[1][2]) = min(∞, 5 + 3) = 8
dist[0][3] = min(10, dist[0][1] + dist[1][3]) = min(10, 5 + ∞) = 10

Итерация k=2 (через вершину 2):

dist[0][3] = min(10, dist[0][2] + dist[2][3]) = min(10, 8 + 1) = 9
dist[1][3] = min(∞, dist[1][2] + dist[2][3]) = min(∞, 3 + 1) = 4

Итерация k=3: ничего не меняется.

Финальная матрица:

    0  1  2  3
0 [ 0  5  8  9]
1 [ ∞  0  3  4]
2 [ ∞  ∞  0  1]
3 [ ∞  ∞  ∞  0]

Пример 2: Обнаружение отрицательных циклов

def floyd_warshall_with_negative_cycles(graph):
    n = len(graph)
    dist = [[float('inf') if i != j else 0 
             for j in range(n)] for i in range(n)]
    
    # Инициализация
    for i in range(n):
        for j in range(n):
            if graph[i][j] != 0:
                dist[i][j] = graph[i][j]
    
    # Основной алгоритм
    for k in range(n):
        for i in range(n):
            for j in range(n):
                if dist[i][k] + dist[k][j] < dist[i][j]:
                    dist[i][j] = dist[i][k] + dist[k][j]
    
    # Проверка на отрицательные циклы
    for i in range(n):
        if dist[i][i] < 0:
            return None, "Найден отрицательный цикл!"
    
    return dist, "OK"

🎮 Практика

Базовый уровень 🟢

Задача 1: Дана матрица смежности графа. Найди кратчайшие расстояния между всеми парами вершин:

    A  B  C
A [ 0  2  ∞]
B [ ∞  0  3]  
C [ 1  ∞  0]
💡 Подсказка Проходи по k от 0 до 2, обновляй dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])

Задача 2: В социальной сети 4 человека: Alice, Bob, Carol, Dave. Связи: Alice→Bob (1), Bob→Carol (1), Carol→Dave (1), Dave→Alice (1). Найди минимальное число “рукопожатий” между всеми парами.

Задача 3: Есть 3 валюты: USD, EUR, RUB. Курсы обмена заданы матрицей. Можно ли получить прибыль от арбитража?

Продвинутый уровень 🟡

Задача 4: Реализуй восстановление самого пути (не только расстояния). Используй матрицу предков.

Задача 5: Модифицируй алгоритм для работы с графом, где рёбра имеют не только вес, но и “пропускную способность”. Нужно найти путь с минимальной стоимостью при ограничении на минимальную пропускную способность.

Задача 6: В игре-стратегии юниты могут двигаться по клеткам. Некоторые клетки - болота (стоимость 3), некоторые - дороги (стоимость 1), некоторые - горы (непроходимы). Найди оптимальные маршруты между всеми важными точками на карте 8×8.

Челлендж 🔴

Задача 7: Netflix Problem: У стримингового сервиса есть серверы в разных городах. Пользователь подключается к ближайшему серверу, но иногда выгоднее маршрутизировать трафик через промежуточные серверы. Учитывая задержки между серверами и пропускную способность, найди оптимальную стратегию маршрутизации.

Задача 8: Crypto Arbitrage: На разных биржах криптовалюты торгуются по разным курсам. Есть комиссии за переводы между биржами. Найди максимально прибыльный цикл арбитража (если он существует).

⚠️ Частые ошибки

Ошибка: Неправильный порядок циклов - внешний цикл не по k

# НЕПРАВИЛЬНО!
for i in range(n):
    for k in range(n):
        for j in range(n):

Правильно: k должно быть внешним циклом - мы рассматриваем промежуточные вершины по очереди 💡 Почему: иначе мы можем использовать вершину k как промежуточную до того, как вычислили оптимальные пути ДО неё

Ошибка: Забыть инициализировать диагональные элементы нулями ✅ Правильно: dist[i][i] = 0 для всех i 💡 Почему: расстояние от вершины до себя всегда 0

Ошибка: Использовать int вместо float(‘inf’) для недостижимых вершин ✅ Правильно: Используй float(‘inf’) или очень большое число 💡 Почему: иначе арифметические операции могут дать неожиданные результаты

Ошибка: Не проверять отрицательные циклы ✅ Правильно: После алгоритма проверь, есть ли dist[i][i] < 0 💡 Почему: отрицательный цикл означает, что можно получить сколь угодно короткий путь

Ошибка: Модифицировать матрицу во время итерации по k-му элементу той же итерации ✅ Правильно: В пределах одной итерации k все обновления независимы 💡 Почему: мы используем значения dist[i][k] и dist[k][j], вычисленные на предыдущих итерациях

🎓 Главное запомнить

Суть: Систематически проверяем все промежуточные вершины для всех пар ✅ Формула: dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
Сложность: O(n³) времени, O(n²) памяти ✅ Применение: Когда нужны кратчайшие пути между ВСЕМИ парами вершин

🔗 Связь с другими темами

Флойд-Уоршелл - это пример динамического программирования на графах. Он связан с:

🔄 Алгоритм Дейкстры - для одной вершины, Флойд-Уоршелл - для всех пар 📊 Умножение матриц - есть связь с возведением матрицы смежности в степень
🔍 Транзитивное замыкание - исходная задача Уоршелла 💰 Арбитраж - поиск отрицательных циклов в логарифмическом пространстве курсов валют 🎮 Игровые алгоритмы - предвычисление расстояний для AI

Понял тему? Закрепи в боте! 🚀

Попрактикуйся на задачах и получи персональные рекомендации от AI

💪 Начать тренировку
💬 Есть вопрос? Спроси бота!