Флойд - 1#974

Флойд - 1 (Розбір)

У задачі потрібно знайти найкоротші відстані між усіма парами вершин. Це класична задача, яка розв'язується алгоритмом Флойда — Уоршелла. Алгоритм ґрунтується на динамічному програмуванні та коректно працює з ребрами від'ємної ваги за умови, що граф не містить циклів від'ємної ваги. Ця умова гарантована в задачі.

Ідея алгоритму. Пронумеруємо вершини від до . Позначимо через d_k[i][j] довжину найкоротшого шляху з вершини до вершини , усі проміжні вершини якого належать множині \{1, 2, \ldots, k\}. При k = 0 проміжних вершин немає, тому d_0[i][j] дорівнює вазі ребра i \to j, тобто збігається з матрицею суміжності.

Розглянемо найкоротший шлях з до , проміжні вершини якого лежать серед \{1, \ldots, k\}. Можливі два випадки:

Обираючи кращий з двох варіантів, отримуємо рекурентне співвідношення:

d_k[i][j] = \min(d_{k-1}[i][j], d_{k-1}[i][k] + d_{k-1}[k][j])

Матриця d_n містить потрібні найкоротші відстані, оскільки в ній проміжною може бути будь-яка вершина графа.

Обчислення в одній матриці. Немає потреби зберігати всі матриці d_0, d_1, \ldots, d_n: усі обчислення можна виконувати безпосередньо в матриці суміжності . Справді, на кроці значення g[i][k] та g[k][j] не змінюються, оскільки g[k][k] = 0. Наприклад, g[i][k] = \min(g[i][k], g[i][k] + g[k][k]) = g[i][k]. Тому не має значення, чи вже оновлено інші елементи матриці на поточному кроці.

Важливо, щоб цикл за проміжною вершиною був зовнішнім: на момент обробки вершини усі відстані з проміжними вершинами з \{1, \ldots, k - 1\} мають бути вже обчислені.

Складність. Алгоритм складається з трьох вкладених циклів по ітерацій кожен, тому час його роботи становить O(n^3). Для n \le 100 це близько 10^6 операцій.

Приклад

Граф, наведений у прикладі, зображено нижче. Граф повний, але ребра ваги на малюнку не показані: як ми побачимо далі, жодне з них не належить жодному найкоротшому шляху.

Просимулюємо роботу алгоритму. Початкова матриця збігається з матрицею суміжності:

\boldsymbol{k = 1}. Шляхи з вершини стають коротшими через вершину : g[4][2] = g[4][1] + g[1][2] = 4 + 5 = 9 та g[4][3] = g[4][1] + g[1][3] = 4 + 9 = 13.

\boldsymbol{k = 2}. Через вершину стають коротшими такі шляхи: g[1][3] = g[1][2] + g[2][3] = 5 + 2 = 7, g[1][4] = g[1][2] + g[2][4] = 5 + 8 = 13 та g[4][3] = g[4][2] + g[2][3] = 9 + 2 = 11.

\boldsymbol{k = 3}. Проходження через вершину не скорочує жодного шляху. Наприклад, g[1][3] + g[3][4] = 7 + 7 = 14 > g[1][4] = 13.

\boldsymbol{k = 4}. Через вершину стають коротшими шляхи до вершин та : g[2][1] = g[2][4] + g[4][1] = 8 + 4 = 12, g[3][1] = g[3][4] + g[4][1] = 7 + 4 = 11 та g[3][2] = g[3][4] + g[4][2] = 7 + 9 = 16.

Зверніть увагу на останнє оновлення. Значення g[4][2] = 9 було отримано на кроці k = 1 і відповідає шляху 4 \to 1 \to 2. Тому g[3][2] = 16 відповідає шляху 3 \to 4 \to 1 \to 2, який має дві проміжні вершини.

Остаточна матриця найкоротших відстаней збігається з відповіддю з прикладу:

Реалізація алгоритму

Збережемо матрицю суміжності графа в масиві .

#define MAX 101
int g[MAX][MAX];

Функція floyd реалізує алгоритм Флойда — Уоршелла.

void floyd(void)
{
  int i, j, k;
  for(k = 0; k < n; k++)
  for(i = 0; i < n; i++)
  for(j = 0; j < n; j++)
    if (g[i][k] + g[k][j] < g[i][j]) g[i][j] = g[i][k] + g[k][j];
}

Основна частина програми. Зчитаємо вхідний граф.

scanf("%d",&n);
for(i = 0; i <  n; i++)
for(j = 0; j <  n; j++)
  scanf("%d",&g[i][j]);

Запустимо алгоритм Флойда — Уоршелла.

floyd();

Виведемо матрицю найкоротших відстаней між усіма парами вершин.

for(i = 0; i <  n; i++)
{
  for(j = 0; j <  n; j++)
    printf("%d ",g[i][j]);
  printf("\n");
}

Реалізація на Java

import java.util.*;

public class Main 
{
  static int g[][];
  static int n;
  
  static void floyd()
  {
    for(int k = 0; k < n; k++)
    for(int i = 0; i < n; i++)
    for(int j = 0; j < n; j++)
      if (g[i][k] + g[k][j] < g[i][j]) 
        g[i][j] = g[i][k] + g[k][j];
  }
  
  public static void main(String[] args) 
  {
    Scanner con = new Scanner(System.in);	  
    n = con.nextInt();
    g = new int[n][n];

    for(int i = 0; i < n; i++)
    for(int j = 0; j < n; j++)
      g[i][j] = con.nextInt();

    floyd();

    for (int i = 0; i < n; i++)
    {
      for (int j = 0; j < n; j++)
    	  System.out.print(g[i][j] + " ");
      System.out.println();
    }
    con.close();
  }
}

Реалізація на Python

Функція floyd реалізує алгоритм Флойда — Уоршелла.

def floyd():
  for k in range(n):
    for i in range(n):
      for j in range(n):
        if g[i][k] + g[k][j] < g[i][j]:
           g[i][j] = g[i][k] + g[k][j]

Основна частина програми. Зчитаємо вхідний граф.

n = int(input())
g = [[] for _ in range(n)]
for i in range(n):
  g[i] = list(map(int, input().split()))

Запустимо алгоритм Флойда — Уоршелла.

floyd()

Виведемо матрицю найкоротших відстаней між усіма парами вершин.

for i in range(n):
  print(*g[i])