[Algorithm] 플로이드 와샬(Floyd Warshall) 알고리즘

PikminProtectionAssociation·2024년 6월 14일

행성 탈출기

목록 보기
4/21
post-thumbnail

저번 다익스트라 알고리즘에 이어 이번 시간은 플로이드 와샬 알고리즘!

플로이드 와샬(Floyd Warshall) 알고리즘

정의 및 특징

  • 가중 그래프에서 모든 정점 쌍의 최단 경로를 찾는 알고리즘
  • 다익스트라 알고리즘과 다르게 음의 가중치를 갖는 간선이 있어도 최단 경로 탐색이 가능
    • 단, 음의 사이클이 발생하는 경우에는 불가능
    • 음의 사이클 : 사이클의 모든 경로를 지나 원래 지점으로 돌아왔을 때, 최종적인 비용이 음수가 되는 경우
  • 플로이드 와샬 알고리즘의 기본은 ‘거쳐가는 정점’을 기준으로 최단 거리를 구한다는 것

시간 복잡도

  • O(N^3)
    • for문을 3번 순회하기 때문

Dijkstra vs Floyd Warshall

차이점

  • Floyd Warshall은 음의 가중치를 갖는 간선이 존재할 수 있음
  • Dijkstra는 가장 적은 비용을 가지는 간선을 하나씩 선택하여 알고리즘을 수행
  • Floyd Warshall은 거쳐가는 정점을 기준으로 알고리즘을 수행

공통점

  • 그래프 내의 각 정점에서 정점까지의 최단 거리를 모두 구할 수 있음

예제

백준 11404 플로이드

문제

n(2 ≤ n ≤ 100)개의 도시가 있다. 그리고 한 도시에서 출발하여 다른 도시에 도착하는 m(1 ≤ m ≤ 100,000)개의 버스가 있다. 각 버스는 한 번 사용할 때 필요한 비용이 있다.
모든 도시의 쌍 (A, B)에 대해서 도시 A에서 B로 가는데 필요한 비용의 최솟값을 구하는 프로그램을 작성하시오.

입력

첫째 줄에 도시의 개수 n이 주어지고 둘째 줄에는 버스의 개수 m이 주어진다. 그리고 셋째 줄부터 m+2줄까지 다음과 같은 버스의 정보가 주어진다. 먼저 처음에는 그 버스의 출발 도시의 번호가 주어진다. 버스의 정보는 버스의 시작 도시 a, 도착 도시 b, 한 번 타는데 필요한 비용 c로 이루어져 있다. 시작 도시와 도착 도시가 같은 경우는 없다. 비용은 100,000보다 작거나 같은 자연수이다.
시작 도시와 도착 도시를 연결하는 노선은 하나가 아닐 수 있다.

출력

n개의 줄을 출력해야 한다. i번째 줄에 출력하는 j번째 숫자는 도시 i에서 j로 가는데 필요한 최소 비용이다. 만약, i에서 j로 갈 수 없는 경우에는 그 자리에 0을 출력한다.


문제

  • 문제에서 요구하는 출력은 모든 정점 쌍에 대한 최단 거리이다.
  • 플로이드 와샬과 다익스트라의 가장 큰 차이점은 출발지가 정해져 있느냐, 없느냐 라고 생각한다!
    • 다익스트라는 특정 출발지에서 나머지 정점까지의 최단 거리를 구하는 것
    • 플로이드 와샬은 모든 임의의 출발지에서 나머지 정점까지의 최단 거리를 구하는 것

해결

  • 그러니까 이렇게 모든 정점 쌍에 대한 최단 거리를 구할 때는 플로이드 와샬 알고리즘을 사용할 수 있다!

코드

static void Main(string[] args)
{
	int N = int.Parse(Console.ReadLine());
    int M = int.Parse(Console.ReadLine());
    int[,] dist = new int[N, N];
    for (int i = 0; i < N; i++)
    {
    	for (int j = 0; j < N; j++)
        {
        	if (i == j) dist[i, j] = 0;
            else dist[i, j] = 10000001;
        }
    }

	for (int i = 0; i < M; i++)
    {
    	string[] str = Console.ReadLine().Split(" ");
        int node1 = int.Parse(str[0]) - 1;
        int node2 = int.Parse(str[1]) - 1;
        int cost = int.Parse(str[2]);

		dist[node1, node2] = Math.Min(dist[node1, node2], cost);
    }

	for (int k = 0; k < N; k++)
    {
    	for (int i = 0; i < N; i++)
        {
        	for (int j = 0; j < N; j++)
            {
            	if (dist[i, k] + dist[k, j] < dist[i, j])
                {
                	dist[i, j] = dist[i, k] + dist[k, j];
                }
            }
        }
    }

	StringBuilder sb = new StringBuilder();
    for (int i = 0; i < N; i++)
    {
    	for (int j = 0; j < N; j++)
        {
        	if (dist[i, j] >= 10000001) sb.Append("0");
            else sb.Append(dist[i, j]);
            if (j != N) sb.Append(" ");
        }
        sb.Append("\n");
    }
    Console.WriteLine(sb);
}
  • 플로이드 와샬 알고리즘은 시간 복잡도가 큰 대신 코드가 직관적이라 이해하기는 가장 쉬운 것 같다.
  • 3중 for문의 의미는 정점 i에서 정점 j로 갈 때, 정점 k를 거쳐서 가는 것이 더 빠른가, 아닌가를 체크하는 것!
    • 이런 특징을 다이나믹 프로그래밍의 한 종류로 볼 수 있다.
    • 정점 k를 거쳐가는 것을 기준으로 최단 경로를 계산하여 저장한 뒤, 다음 정점 쌍의 최단 경로를 찾을 때 사용하기 때문
  • 주의할 점은 경로가 없는 정점 쌍을 나타낼 때 적당히 큰 값을 잘 설정해야 한다.
    • 무턱대고 int.MaxValue를 사용하면 오버플로가 발생할 수 있다.
    • 이 문제의 경우 최대 정점 개수는 100이고 최대 간선 개수는 100,000이므로 100*100,000+1 을 INF 값으로 설정하였다.



출처

코딩 테스트 대비: 플로이드-와샬 알고리즘 - 모든 정점 쌍의 최단 경로 찾기 (C#)
24. 플로이드 와샬(Floyd Warshall) 알고리즘



끗~~
과 시작의 아마겟돈
오애오애용

0개의 댓글