μ΅œλ‹¨ 경둜 (Shortest Path)

μ΅œλ‹¨ 경둜 (Shortest Path)

κ°œμš”

κ°€μ€‘μΉ˜κ°€ μžˆλŠ” κ·Έλž˜ν”„μ—μ„œ 두 정점 μ‚¬μ΄μ˜ μ΅œλ‹¨ 경둜λ₯Ό μ°ΎλŠ” μ•Œκ³ λ¦¬μ¦˜μ„ ν•™μŠ΅ν•©λ‹ˆλ‹€. Dijkstra, Bellman-Ford, Floyd-Warshall μ•Œκ³ λ¦¬μ¦˜μ„ λ‹€λ£Ήλ‹ˆλ‹€.


λͺ©μ°¨

  1. μ΅œλ‹¨ 경둜 μ•Œκ³ λ¦¬μ¦˜ 비ꡐ
  2. λ‹€μ΅μŠ€νŠΈλΌ (Dijkstra)
  3. 벨만-ν¬λ“œ (Bellman-Ford)
  4. ν”Œλ‘œμ΄λ“œ-μ›Œμ…œ (Floyd-Warshall)
  5. 0-1 BFS
  6. μ—°μŠ΅ 문제

1. μ΅œλ‹¨ 경둜 μ•Œκ³ λ¦¬μ¦˜ 비ꡐ

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ μ•Œκ³ λ¦¬μ¦˜        β”‚ μ‹œκ°„ λ³΅μž‘λ„ β”‚ 음수 κ°€μ€‘μΉ˜   β”‚ μš©λ„            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ BFS             β”‚ O(V+E)      β”‚ βœ—             β”‚ κ°€μ€‘μΉ˜ μ—†μŒ     β”‚
β”‚ Dijkstra        β”‚ O(E log V)  β”‚ βœ—             β”‚ 단일 좜발점     β”‚
β”‚ Bellman-Ford    β”‚ O(VE)       β”‚ βœ“             β”‚ 단일 좜발점     β”‚
β”‚ Floyd-Warshall  β”‚ O(VΒ³)       β”‚ βœ“             β”‚ λͺ¨λ“  쌍         β”‚
β”‚ 0-1 BFS         β”‚ O(V+E)      β”‚ 0,1만         β”‚ 0/1 κ°€μ€‘μΉ˜      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

2. λ‹€μ΅μŠ€νŠΈλΌ (Dijkstra)

κ°œλ…

단일 μΆœλ°œμ μ—μ„œ λͺ¨λ“  μ •μ κΉŒμ§€μ˜ μ΅œλ‹¨ 거리
쑰건: 음수 κ°€μ€‘μΉ˜ μ—†μŒ

원리:
1. μ‹œμž‘μ  거리 = 0, λ‚˜λ¨Έμ§€ = ∞
2. λ°©λ¬Έν•˜μ§€ μ•Šμ€ 정점 쀑 μ΅œλ‹¨ 거리 정점 선택
3. ν•΄λ‹Ή 정점을 톡해 인접 정점 거리 κ°±μ‹ 
4. λͺ¨λ“  정점 λ°©λ¬Έν•  λ•ŒκΉŒμ§€ 반볡

λ™μž‘ μ˜ˆμ‹œ

        (B)
       / 1 \
      4     2
     /       \
   (A)       (D)
     \       /
      2     1
       \ 3 /
        (C)

μ‹œμž‘: A

단계  λ°©λ¬Έ    dist[A] dist[B] dist[C] dist[D]
----  -----   ------  ------  ------  ------
0     {}      0       ∞       ∞       ∞
1     {A}     0       4       2       ∞       ← A λ°©λ¬Έ, B,C κ°±μ‹ 
2     {A,C}   0       4       2       5       ← C λ°©λ¬Έ, D κ°±μ‹  (2+3)
3     {A,C,B} 0       4       2       5       ← B λ°©λ¬Έ
4     {all}   0       4       2       5       ← D λ°©λ¬Έ

결과: A→B: 4, A→C: 2, A→D: 5

κ΅¬ν˜„ (μš°μ„ μˆœμœ„ 큐)

// C - 인접 리슀트 + λ°°μ—΄ (κ°„λ‹¨ν•œ 버전)
#define INF 1000000000
#define MAX_V 10001

typedef struct {
    int to, weight;
} Edge;

Edge adj[MAX_V][MAX_V];
int adjSize[MAX_V];
int dist[MAX_V];
int visited[MAX_V];
int V, E;

void dijkstra(int start) {
    for (int i = 0; i < V; i++) {
        dist[i] = INF;
        visited[i] = 0;
    }
    dist[start] = 0;

    for (int i = 0; i < V; i++) {
        // μ΅œμ†Œ 거리 정점 μ°ΎκΈ°
        int u = -1;
        int minDist = INF;
        for (int j = 0; j < V; j++) {
            if (!visited[j] && dist[j] < minDist) {
                minDist = dist[j];
                u = j;
            }
        }

        if (u == -1) break;
        visited[u] = 1;

        // 인접 정점 κ°±μ‹ 
        for (int j = 0; j < adjSize[u]; j++) {
            int v = adj[u][j].to;
            int w = adj[u][j].weight;
            if (dist[u] + w < dist[v]) {
                dist[v] = dist[u] + w;
            }
        }
    }
}
// C++ - μš°μ„ μˆœμœ„ 큐 μ‚¬μš© (효율적)
#include <queue>
#include <vector>
using namespace std;

typedef pair<int, int> pii;  // {거리, 정점}

vector<int> dijkstra(const vector<vector<pii>>& adj, int start) {
    int V = adj.size();
    vector<int> dist(V, INT_MAX);
    priority_queue<pii, vector<pii>, greater<pii>> pq;

    dist[start] = 0;
    pq.push({0, start});

    while (!pq.empty()) {
        auto [d, u] = pq.top();
        pq.pop();

        // 이미 처리된 정점이면 μŠ€ν‚΅
        if (d > dist[u]) continue;

        for (auto [v, weight] : adj[u]) {
            if (dist[u] + weight < dist[v]) {
                dist[v] = dist[u] + weight;
                pq.push({dist[v], v});
            }
        }
    }

    return dist;
}

// μ‚¬μš© 예
// adj[u].push_back({v, weight});  // u β†’ v, κ°€μ€‘μΉ˜ weight
// auto dist = dijkstra(adj, 0);
# Python
import heapq
from collections import defaultdict

def dijkstra(graph, start):
    dist = {node: float('inf') for node in graph}
    dist[start] = 0
    pq = [(0, start)]  # (거리, 정점)

    while pq:
        d, u = heapq.heappop(pq)

        # 이미 처리된 정점이면 μŠ€ν‚΅
        if d > dist[u]:
            continue

        for v, weight in graph[u]:
            if dist[u] + weight < dist[v]:
                dist[v] = dist[u] + weight
                heapq.heappush(pq, (dist[v], v))

    return dist

# μ‚¬μš© 예
# graph = defaultdict(list)
# graph[0].append((1, 4))  # 0 β†’ 1, κ°€μ€‘μΉ˜ 4
# graph[0].append((2, 2))  # 0 β†’ 2, κ°€μ€‘μΉ˜ 2
# dist = dijkstra(graph, 0)

경둜 볡원

// C++
pair<vector<int>, vector<int>> dijkstraWithPath(
    const vector<vector<pii>>& adj, int start) {

    int V = adj.size();
    vector<int> dist(V, INT_MAX);
    vector<int> parent(V, -1);
    priority_queue<pii, vector<pii>, greater<pii>> pq;

    dist[start] = 0;
    pq.push({0, start});

    while (!pq.empty()) {
        auto [d, u] = pq.top();
        pq.pop();

        if (d > dist[u]) continue;

        for (auto [v, weight] : adj[u]) {
            if (dist[u] + weight < dist[v]) {
                dist[v] = dist[u] + weight;
                parent[v] = u;  // 경둜 μ €μž₯
                pq.push({dist[v], v});
            }
        }
    }

    return {dist, parent};
}

// 경둜 좜λ ₯
vector<int> getPath(const vector<int>& parent, int end) {
    vector<int> path;
    for (int v = end; v != -1; v = parent[v]) {
        path.push_back(v);
    }
    reverse(path.begin(), path.end());
    return path;
}
# Python
def dijkstra_with_path(graph, start):
    dist = {node: float('inf') for node in graph}
    parent = {node: None for node in graph}
    dist[start] = 0
    pq = [(0, start)]

    while pq:
        d, u = heapq.heappop(pq)

        if d > dist[u]:
            continue

        for v, weight in graph[u]:
            if dist[u] + weight < dist[v]:
                dist[v] = dist[u] + weight
                parent[v] = u
                heapq.heappush(pq, (dist[v], v))

    return dist, parent

def get_path(parent, end):
    path = []
    v = end
    while v is not None:
        path.append(v)
        v = parent[v]
    return path[::-1]

3. 벨만-ν¬λ“œ (Bellman-Ford)

κ°œλ…

단일 μΆœλ°œμ μ—μ„œ λͺ¨λ“  μ •μ κΉŒμ§€μ˜ μ΅œλ‹¨ 거리
νŠΉμ§•: 음수 κ°€μ€‘μΉ˜ ν—ˆμš©, 음수 사이클 탐지 κ°€λŠ₯

원리:
1. μ‹œμž‘μ  거리 = 0, λ‚˜λ¨Έμ§€ = ∞
2. λͺ¨λ“  간선에 λŒ€ν•΄ 거리 κ°±μ‹  (V-1회 반볡)
3. V번째 λ°˜λ³΅μ—μ„œ κ°±μ‹ λ˜λ©΄ 음수 사이클 쑴재

λ™μž‘ μ˜ˆμ‹œ

        (B)
       / 1 \
      4     2
     /       \
   (A)       (D)
     \       /
      2     -5    ← 음수 κ°€μ€‘μΉ˜
       \ 3 /
        (C)

κ°„μ„ : (A,B,4), (A,C,2), (B,D,2), (C,B,1), (C,D,3)

반볡 1: dist = [0, 4, 2, 5]
반볡 2: dist = [0, 3, 2, 5]  ← Cβ†’B둜 B κ°±μ‹  (2+1=3)
반볡 3: dist = [0, 3, 2, 5]  ← λ³€ν™” μ—†μŒ

결과: A→B: 3, A→C: 2, A→D: 5

κ΅¬ν˜„

// C
#define INF 1000000000

typedef struct {
    int from, to, weight;
} Edge;

Edge edges[20001];
int dist[501];
int V, E;

int bellmanFord(int start) {
    for (int i = 0; i < V; i++) {
        dist[i] = INF;
    }
    dist[start] = 0;

    // V-1번 반볡
    for (int i = 0; i < V - 1; i++) {
        for (int j = 0; j < E; j++) {
            int u = edges[j].from;
            int v = edges[j].to;
            int w = edges[j].weight;

            if (dist[u] != INF && dist[u] + w < dist[v]) {
                dist[v] = dist[u] + w;
            }
        }
    }

    // 음수 사이클 확인
    for (int j = 0; j < E; j++) {
        int u = edges[j].from;
        int v = edges[j].to;
        int w = edges[j].weight;

        if (dist[u] != INF && dist[u] + w < dist[v]) {
            return -1;  // 음수 사이클 쑴재
        }
    }

    return 0;
}
// C++
struct Edge {
    int from, to, weight;
};

vector<int> bellmanFord(int V, const vector<Edge>& edges, int start) {
    vector<int> dist(V, INT_MAX);
    dist[start] = 0;

    // V-1번 반볡
    for (int i = 0; i < V - 1; i++) {
        for (const auto& e : edges) {
            if (dist[e.from] != INT_MAX &&
                dist[e.from] + e.weight < dist[e.to]) {
                dist[e.to] = dist[e.from] + e.weight;
            }
        }
    }

    // 음수 사이클 확인
    for (const auto& e : edges) {
        if (dist[e.from] != INT_MAX &&
            dist[e.from] + e.weight < dist[e.to]) {
            return {};  // 음수 사이클
        }
    }

    return dist;
}
# Python
def bellman_ford(V, edges, start):
    dist = [float('inf')] * V
    dist[start] = 0

    # V-1번 반볡
    for _ in range(V - 1):
        for u, v, w in edges:
            if dist[u] != float('inf') and dist[u] + w < dist[v]:
                dist[v] = dist[u] + w

    # 음수 사이클 확인
    for u, v, w in edges:
        if dist[u] != float('inf') and dist[u] + w < dist[v]:
            return None  # 음수 사이클

    return dist

4. ν”Œλ‘œμ΄λ“œ-μ›Œμ…œ (Floyd-Warshall)

κ°œλ…

λͺ¨λ“  정점 쌍 μ‚¬μ΄μ˜ μ΅œλ‹¨ 거리
νŠΉμ§•: 음수 κ°€μ€‘μΉ˜ ν—ˆμš©, DP 기반

원리:
kλ₯Ό κ²½μœ ν•˜λŠ” 경둜 vs 직접 경둜
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])

λͺ¨λ“  k에 λŒ€ν•΄ 반볡

λ™μž‘ μ˜ˆμ‹œ

     1     2
(0) ─→ (1) ─→ (2)
 └──────3──────→

초기:
      0     1     2
  0 [ 0,    1,    3]
  1 [INF,   0,    2]
  2 [INF,  INF,   0]

k=1 (1을 경유):
  dist[0][2] = min(3, dist[0][1]+dist[1][2])
             = min(3, 1+2) = 3

κ²°κ³Ό: 0β†’2 μ΅œλ‹¨κ±°λ¦¬ = 3

κ΅¬ν˜„

// C
#define INF 1000000000
#define MAX_V 500

int dist[MAX_V][MAX_V];
int V;

void floydWarshall() {
    // kλ₯Ό 경유점으둜
    for (int k = 0; k < V; k++) {
        for (int i = 0; i < V; i++) {
            for (int j = 0; j < V; j++) {
                if (dist[i][k] != INF && dist[k][j] != INF) {
                    if (dist[i][k] + dist[k][j] < dist[i][j]) {
                        dist[i][j] = dist[i][k] + dist[k][j];
                    }
                }
            }
        }
    }
}

// μ΄ˆκΈ°ν™”
void initGraph() {
    for (int i = 0; i < V; i++) {
        for (int j = 0; j < V; j++) {
            if (i == j) dist[i][j] = 0;
            else dist[i][j] = INF;
        }
    }
}
// C++
void floydWarshall(vector<vector<int>>& dist) {
    int V = dist.size();

    for (int k = 0; k < V; k++) {
        for (int i = 0; i < V; i++) {
            for (int j = 0; j < V; j++) {
                if (dist[i][k] != INT_MAX && dist[k][j] != INT_MAX) {
                    dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
                }
            }
        }
    }
}

// μ΄ˆκΈ°ν™”
vector<vector<int>> initDist(int V) {
    vector<vector<int>> dist(V, vector<int>(V, INT_MAX));
    for (int i = 0; i < V; i++) {
        dist[i][i] = 0;
    }
    return dist;
}
# Python
def floyd_warshall(V, edges):
    INF = float('inf')

    # μ΄ˆκΈ°ν™”
    dist = [[INF] * V for _ in range(V)]
    for i in range(V):
        dist[i][i] = 0

    for u, v, w in edges:
        dist[u][v] = w

    # ν”Œλ‘œμ΄λ“œ-μ›Œμ…œ
    for k in range(V):
        for i in range(V):
            for j in range(V):
                if dist[i][k] + dist[k][j] < dist[i][j]:
                    dist[i][j] = dist[i][k] + dist[k][j]

    return dist

경둜 볡원

// C++
void floydWarshallWithPath(vector<vector<int>>& dist,
                           vector<vector<int>>& next) {
    int V = dist.size();

    // μ΄ˆκΈ°ν™”
    for (int i = 0; i < V; i++) {
        for (int j = 0; j < V; j++) {
            if (dist[i][j] != INT_MAX && i != j) {
                next[i][j] = j;
            } else {
                next[i][j] = -1;
            }
        }
    }

    for (int k = 0; k < V; k++) {
        for (int i = 0; i < V; i++) {
            for (int j = 0; j < V; j++) {
                if (dist[i][k] != INT_MAX && dist[k][j] != INT_MAX &&
                    dist[i][k] + dist[k][j] < dist[i][j]) {
                    dist[i][j] = dist[i][k] + dist[k][j];
                    next[i][j] = next[i][k];
                }
            }
        }
    }
}

vector<int> getPath(const vector<vector<int>>& next, int from, int to) {
    if (next[from][to] == -1) return {};

    vector<int> path = {from};
    while (from != to) {
        from = next[from][to];
        path.push_back(from);
    }
    return path;
}

5. 0-1 BFS

κ°œλ…

κ°„μ„  κ°€μ€‘μΉ˜κ°€ 0 λ˜λŠ” 1인 κ·Έλž˜ν”„μ—μ„œ μ΅œλ‹¨ 거리
β†’ 덱(Deque) μ‚¬μš©ν•˜μ—¬ O(V+E)에 ν•΄κ²°

원리:
- κ°€μ€‘μΉ˜ 0인 κ°„μ„ : 덱 μ•žμ— μΆ”κ°€
- κ°€μ€‘μΉ˜ 1인 κ°„μ„ : 덱 뒀에 μΆ”κ°€

효과: 항상 μ΅œμ†Œ 거리 정점이 μ•žμ— μœ„μΉ˜

κ΅¬ν˜„

// C++
vector<int> zeroOneBFS(const vector<vector<pii>>& adj, int start) {
    int V = adj.size();
    vector<int> dist(V, INT_MAX);
    deque<int> dq;

    dist[start] = 0;
    dq.push_front(start);

    while (!dq.empty()) {
        int u = dq.front();
        dq.pop_front();

        for (auto [v, weight] : adj[u]) {
            if (dist[u] + weight < dist[v]) {
                dist[v] = dist[u] + weight;

                if (weight == 0) {
                    dq.push_front(v);
                } else {
                    dq.push_back(v);
                }
            }
        }
    }

    return dist;
}
# Python
from collections import deque

def zero_one_bfs(graph, start, n):
    dist = [float('inf')] * n
    dist[start] = 0
    dq = deque([start])

    while dq:
        u = dq.popleft()

        for v, weight in graph[u]:
            if dist[u] + weight < dist[v]:
                dist[v] = dist[u] + weight

                if weight == 0:
                    dq.appendleft(v)
                else:
                    dq.append(v)

    return dist

6. μ—°μŠ΅ 문제

문제 1: νŠΉμ • 거리의 λ„μ‹œ μ°ΎκΈ°

μ‹œμž‘ λ„μ‹œμ—μ„œ μ •ν™•νžˆ K 거리인 λͺ¨λ“  λ„μ‹œλ₯Ό μ°ΎμœΌμ„Έμš”.

μ •λ‹΅ μ½”λ“œ
import heapq

def cities_at_distance_k(n, edges, start, k):
    graph = [[] for _ in range(n + 1)]
    for a, b in edges:
        graph[a].append(b)  # 단방ν–₯

    dist = [float('inf')] * (n + 1)
    dist[start] = 0
    pq = [(0, start)]

    while pq:
        d, u = heapq.heappop(pq)
        if d > dist[u]:
            continue
        for v in graph[u]:
            if dist[u] + 1 < dist[v]:
                dist[v] = dist[u] + 1
                heapq.heappush(pq, (dist[v], v))

    result = [i for i in range(1, n + 1) if dist[i] == k]
    return sorted(result) if result else [-1]

문제 2: 음수 사이클 탐지

음수 사이클이 μ‘΄μž¬ν•˜λŠ”μ§€ ν™•μΈν•˜μ„Έμš”.

μ •λ‹΅ μ½”λ“œ
def has_negative_cycle(V, edges):
    dist = [0] * V  # λͺ¨λ“  μ •μ μ—μ„œ μ‹œμž‘ κ°€λŠ₯

    for _ in range(V - 1):
        for u, v, w in edges:
            if dist[u] + w < dist[v]:
                dist[v] = dist[u] + w

    for u, v, w in edges:
        if dist[u] + w < dist[v]:
            return True

    return False

μΆ”μ²œ 문제

λ‚œμ΄λ„ 문제 ν”Œλž«νΌ μ•Œκ³ λ¦¬μ¦˜
⭐⭐ μ΅œλ‹¨κ²½λ‘œ λ°±μ€€ Dijkstra
⭐⭐ Network Delay Time LeetCode Dijkstra
⭐⭐⭐ νƒ€μž„λ¨Έμ‹  λ°±μ€€ Bellman-Ford
⭐⭐⭐ ν”Œλ‘œμ΄λ“œ λ°±μ€€ Floyd-Warshall
⭐⭐⭐ 경둜 μ°ΎκΈ° λ°±μ€€ Floyd-Warshall
⭐⭐⭐⭐ Cheapest Flights LeetCode λ³€ν˜• Dijkstra

μ•Œκ³ λ¦¬μ¦˜ 선택 κ°€μ΄λ“œ

질문 1: κ°€μ€‘μΉ˜κ°€ μžˆλŠ”κ°€?
β”œβ”€β”€ No β†’ BFS (O(V+E))
└── Yes ↓

질문 2: 음수 κ°€μ€‘μΉ˜κ°€ μžˆλŠ”κ°€?
β”œβ”€β”€ No β†’ Dijkstra (O(E log V))
└── Yes ↓

질문 3: 음수 사이클 탐지가 ν•„μš”ν•œκ°€?
β”œβ”€β”€ Yes β†’ Bellman-Ford (O(VE))
└── No ↓

질문 4: λͺ¨λ“  쌍 μ΅œλ‹¨κ±°λ¦¬κ°€ ν•„μš”ν•œκ°€?
β”œβ”€β”€ Yes β†’ Floyd-Warshall (O(VΒ³))
└── No β†’ Bellman-Ford (O(VE))

μΆ”κ°€: κ°€μ€‘μΉ˜κ°€ 0, 1만 있으면 β†’ 0-1 BFS (O(V+E))

λ‹€μŒ 단계


참고 자료

to navigate between lessons