포스트

06. Algorithm_Graphs (BFS, DFS, MST, Topological Sort, Shortest Paths)

그래프의 용어와 표현(인접 행렬, 인접 리스트), BFS와 DFS, 최소 신장 트리(Prim, Kruskal), 위상 정렬(TSA1, TSA2), 최단 경로(Dijkstra, Bellman-Ford, DAG). 예제 그래프로 모든 알고리즘을 실행하고 networkx로 교차 검증, 강의 코드도 실행

06. Algorithm_Graphs (BFS, DFS, MST, Topological Sort, Shortest Paths)

학습 목표

  • 그래프의 정의, 용어, 종류, 연결성
  • 인접 행렬과 인접 리스트 표현
  • BFS(큐)와 DFS(스택/재귀)의 동작과 \(\Theta(V + E)\)
  • 최소 신장 트리: Prim과 Kruskal 알고리즘, \(O(E \log V)\)
  • 위상 정렬 두 가지(진입 간선 제거, DFS 완료 역순)
  • 최단 경로: Dijkstra, Bellman-Ford, DAG 최단 경로와 음의 가중치 문제

1. 그래프 기본

  • 그래프 \(G = (V, E)\): 정점(vertex)의 집합 V와, 두 정점을 잇는 간선(edge)의 집합 E
  • 간선의 길이나 정점의 위치는 중요하지 않고, 어떤 정점들이 연결되어 있는지만 중요함
  • 두 정점이 간선으로 연결되어 있으면 인접(adjacent) 하다고 함. 한 정점에 붙은 간선의 수가 그 정점의 차수(degree)
용어뜻
경로 (path)간선으로 이어진 정점들의 나열. 경로의 길이는 지나는 간선 수
단순 경로같은 정점을 두 번 지나지 않는 경로
사이클시작 정점으로 돌아오는 경로
단순 사이클시작 정점을 빼면 같은 정점을 두 번 지나지 않는 사이클
종류설명
완전 그래프모든 정점 쌍이 간선으로 연결
정규 그래프모든 정점의 차수가 같음
순환 그래프정점들이 하나의 고리로 이어진 그래프 (모든 정점의 차수가 2인 정규 그래프)
트리사이클이 없는 연결 그래프
이분 그래프정점을 두 색으로 칠할 때 모든 간선의 양 끝이 서로 다른 색
평면 그래프간선끼리 정점 이외의 곳에서 만나지 않게 그릴 수 있음
  • 연결 그래프: 모든 정점 쌍 사이에 경로가 있음. 비연결 그래프: 경로가 없는 정점 쌍이 하나라도 있음

표현 방법

구분인접 행렬인접 리스트
구조\(N \times N\) 행렬, \((i, j)\)가 1(또는 가중치)이면 간선 있음정점마다 인접한 정점(과 가중치)의 연결 리스트
공간\(\Theta(V^2)\)\(\Theta(V + E)\)
두 정점이 인접한지 확인\(O(1)\)리스트 길이만큼
알맞은 그래프간선이 많은(조밀한) 그래프간선이 적은(희소한) 그래프

무향 그래프의 인접 행렬은 대칭 행렬, 유향 그래프는 일반적으로 비대칭.


2. 그래프 탐색

구분BFS (너비 우선)DFS (깊이 우선)
방식시작점에서 가까운 정점부터 층별로 넓게한 방향으로 갈 수 있는 데까지 깊게 들어갔다가 되돌아옴
자료구조큐스택 (또는 재귀)
용도간선 수 기준 최단 경로모든 정점 방문, 경로 찾기, 위상 정렬
수행 시간\(\Theta(V + E)\)\(\Theta(V + E)\)

모든 정점과 간선을 한 번씩만 확인하므로 둘 다 \(\Theta(V + E)\) (인접 리스트 기준).


3. 최소 신장 트리 (MST)

  • 트리: 사이클이 없는 연결 그래프. 정점이 n개면 간선은 항상 \(n - 1\)개
  • 신장 트리: 그래프 G의 모든 정점을 포함하고 G의 간선만으로 만든 트리 (BFS, DFS 트리도 신장 트리)
  • 최소 신장 트리: 신장 트리 중 간선 가중치의 합이 최소인 것. 무향 연결 그래프에서 정의됨
알고리즘방법수행 시간
Prim정점 하나에서 시작한 집합 S를 키움. 매번 S 안과 밖을 잇는 간선 중 가장 싼 것을 추가최소 힙 사용 시 \(O(E \log V)\)
Kruskal모든 간선을 가중치 순으로 보며, 사이클을 만들지 않으면 추가. 여러 트리가 동시에 자라다 합쳐짐정렬 \(O(E \log E) = O(E \log V)\), Union-Find로 사이클 검사

둘 다 매 순간 가장 좋아 보이는 선택을 하는 그리디 알고리즘이지만, MST에서는 최적해가 보장됨 (07편의 매트로이드).


4. 위상 정렬

사이클이 없는 유향 그래프(DAG) 의 정점들을, 간선 \(x \to y\)가 있으면 x가 y보다 앞에 오도록 일렬로 나열하는 것. 작업들 사이에 선행 관계가 있을 때의 작업 순서. 답이 여러 개일 수 있음.

알고리즘방법수행 시간
TSA1진입 간선이 없는 정점을 하나 골라 결과에 추가하고, 그 정점과 나가는 간선을 지움. 반복 (큐)\(O(V + E)\)
TSA2DFS를 하면서 탐색이 끝난(완료된) 정점을 연결 리스트 맨 앞에 추가 → 완료 순서의 역순\(O(V + E)\)

TSA2에서 가장 먼저 완료되는 정점은 더 갈 곳이 없는 마지막 작업이므로, 맨 앞에 계속 끼워 넣으면 결국 맨 앞에는 가장 먼저 시작해야 할 작업이 옴.


5. 최단 경로

간선에 가중치가 있는 유향 그래프에서 두 정점 사이의 가중치 합이 최소인 경로. 무향 그래프는 간선마다 양방향 간선이 있는 것으로 봄.

유형알고리즘조건수행 시간
단일 시작점Dijkstra음의 가중치 불허\(O(E \log V)\) (힙)
단일 시작점Bellman-Ford음의 가중치 허용, 음의 사이클은 검출\(O(VE)\)
단일 시작점DAG 최단 경로사이클 없는 그래프, 음의 가중치 허용\(O(V + E)\)
모든 쌍Floyd-Warshall음의 사이클 없음\(O(V^3)\)

Dijkstra

  • 거리가 확정된 정점 집합 S를 키움. 매번 S 밖에서 시작점으로부터 거리가 가장 짧은 정점을 S에 넣고, 그 정점에서 나가는 간선으로 이웃의 거리를 이완(relaxation)
  • Prim과 비교 (출제!!): 구조는 거의 같지만 v.dist의 의미가 다름
구분PrimDijkstra
목적모든 정점을 최소 비용으로 연결 (MST)시작점에서 각 정점까지의 최단 경로
v.dist의 뜻v를 현재 트리에 연결하는 간선 하나의 최소 비용시작점에서 v까지 경로의 누적 최소 비용
  • 음의 가중치를 허용하지 않는 이유: S에 들어간 정점은 거리가 확정되었다고 보고 다시 갱신하지 않음. 음의 간선이 있으면 나중에 S 밖의 정점을 거쳐 더 짧은 경로가 생겨도 반영되지 않아 틀린 답이 나옴

Bellman-Ford

  • 모든 간선을 이완하는 일을 \(V - 1\)번 반복
  • 왜 \(V - 1\)번인가: 정점이 V개면 가장 긴 단순 경로의 간선 수는 \(V - 1\)개. i번째 반복이 끝나면 간선 i개 이하로 갈 수 있는 최단 거리가 모두 구해짐
  • \(V\)번째 반복에서도 거리가 줄어들면 음의 사이클이 있다는 뜻 (돌수록 거리가 무한히 줄어들어 최단 경로가 정의되지 않음)

DAG 최단 경로

정점을 위상 정렬 순서대로 처리하며 나가는 간선을 이완. 어떤 정점을 처리할 때 그 정점으로 들어오는 경로는 이미 모두 처리되었으므로 한 번에 끝남.


6. 예제로 실행하기

무향 가중 그래프로 BFS, DFS, Prim, Kruskal을, “라면 끓이기” DAG로 위상 정렬을, 여러 유향 그래프로 최단 경로를 실행하고 networkx 결과와 비교함.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import heapq
from collections import deque

# 무향 가중 그래프 (MST, BFS, DFS용)
UND = {"a": {"b": 8, "c": 11, "d": 9}, "b": {"a": 8, "e": 10}, "c": {"a": 11, "d": 13, "f": 8},
       "d": {"a": 9, "c": 13, "e": 5, "g": 12}, "e": {"b": 10, "d": 5, "h": 8},
       "f": {"c": 8, "g": 7}, "g": {"d": 12, "f": 7, "h": 9}, "h": {"e": 8, "g": 9}}

def bfs(g, s):
    order, seen, q = [], {s}, deque([s])
    while q:
        u = q.popleft(); order.append(u)
        for v in sorted(g[u]):
            if v not in seen: seen.add(v); q.append(v)
    return order

def dfs(g, s, seen=None, order=None):
    seen, order = seen or set(), order if order is not None else []
    seen.add(s); order.append(s)
    for v in sorted(g[s]):
        if v not in seen: dfs(g, v, seen, order)
    return order

def prim(g, r):
    """집합 S를 키워 가며, S와 바깥을 잇는 가장 싼 간선을 하나씩 추가"""
    in_s, edges, pq = {r}, [], [(w, r, v) for v, w in g[r].items()]
    heapq.heapify(pq)
    while pq and len(in_s) < len(g):
        w, u, v = heapq.heappop(pq)
        if v in in_s: continue
        in_s.add(v); edges.append((u, v, w))
        for x, wx in g[v].items():
            if x not in in_s: heapq.heappush(pq, (wx, v, x))
    return edges

def kruskal(g):
    """가중치 순으로 간선을 보며, 사이클을 만들지 않으면 추가 (Union-Find)"""
    parent = {v: v for v in g}
    def find(x):
        while parent[x] != x:
            parent[x] = parent[parent[x]]; x = parent[x]       # 경로 압축
        return x
    edges = sorted((w, u, v) for u in g for v, w in g[u].items() if u < v)
    mst = []
    for w, u, v in edges:
        ru, rv = find(u), find(v)
        if ru != rv:
            parent[ru] = rv; mst.append((u, v, w))
    return mst

print("BFS:", " ".join(bfs(UND, "a")))
print("DFS:", " ".join(dfs(UND, "a")))
p, k = prim(UND, "a"), kruskal(UND)
print("Prim    :", p, "합", sum(w for *_, w in p))
print("Kruskal :", k, "합", sum(w for *_, w in k))

# 유향 비순환 그래프 (위상 정렬, DAG 최단 경로)
DAG = {"냄비에 물 붓기": ["점화"], "라면 봉지 뜯기": ["라면 넣기", "수프 넣기"], "점화": ["라면 넣기", "수프 넣기"],
       "라면 넣기": ["달걀 풀기"], "수프 넣기": ["달걀 풀기"], "달걀 풀기": []}

def tsa1(g):                         # 진입 간선이 없는 정점을 하나씩 꺼내고 그 정점의 간선을 제거 (큐)
    indeg = {v: 0 for v in g}
    for u in g:
        for v in g[u]: indeg[v] += 1
    q, out = deque(v for v in g if indeg[v] == 0), []
    while q:
        u = q.popleft(); out.append(u)
        for v in g[u]:
            indeg[v] -= 1
            if indeg[v] == 0: q.append(v)
    return out

def tsa2(g):                         # DFS가 끝난 정점을 연결 리스트 맨 앞에 추가 (완료 순서의 역순)
    seen, out = set(), deque()
    def visit(u):
        seen.add(u)
        for v in g[u]:
            if v not in seen: visit(v)
        out.appendleft(u)
    for u in g:
        if u not in seen: visit(u)
    return list(out)

print("\n위상 정렬 TSA1:", " → ".join(tsa1(DAG)))
print("위상 정렬 TSA2:", " → ".join(tsa2(DAG)))

# 최단 경로
def dijkstra(g, s):
    d = {v: float("inf") for v in g}; d[s] = 0
    S, pq = set(), [(0, s)]
    while pq:
        du, u = heapq.heappop(pq)
        if u in S: continue
        S.add(u)                                 # u의 거리는 이제 확정
        for v, w in g[u].items():
            if v not in S and du + w < d[v]:     # 확정된 정점은 다시 갱신하지 않음
                d[v] = du + w; heapq.heappush(pq, (d[v], v))
    return d

def bellman_ford(g, s):
    d = {v: float("inf") for v in g}; d[s] = 0
    edges = [(u, v, w) for u in g for v, w in g[u].items()]
    for _ in range(len(g) - 1):                  # 간선 n-1개짜리 경로까지 고려
        for u, v, w in edges:
            if d[u] + w < d[v]: d[v] = d[u] + w
    neg_cycle = any(d[u] + w < d[v] for u, v, w in edges)   # n번째에도 줄면 음의 사이클
    return d, neg_cycle

def dag_shortest(g, s):
    d = {v: float("inf") for v in g}; d[s] = 0
    for u in tsa2(g):                            # 위상 정렬 순서대로 이완
        for v, w in g[u].items():
            if d[u] + w < d[v]: d[v] = d[u] + w
    return d

POS = {"s": {"a": 10, "b": 5}, "a": {"c": 1, "b": 2}, "b": {"a": 3, "c": 9, "d": 2},
       "c": {"d": 4}, "d": {"s": 7, "c": 6}}
print("\n양수 가중치 그래프")
print("  Dijkstra    :", dijkstra(POS, "s"))
print("  Bellman-Ford:", bellman_ford(POS, "s"))

NEG = {"s": {"a": 2, "b": 4}, "a": {"c": 3}, "b": {"a": -3}, "c": {}}
print("\n음의 가중치 간선 b→a = -3")
print("  Dijkstra    :", dijkstra(NEG, "s"), "← a가 먼저 확정되어 s→b→a(1)를 놓침")
print("  Bellman-Ford:", bellman_ford(NEG, "s"))

CYC = {"s": {"a": 1}, "a": {"b": 1}, "b": {"a": -3}}
print("\n음의 사이클 a→b→a (합 -2)")
print("  Bellman-Ford:", bellman_ford(CYC, "s"), "← True면 음의 사이클 존재")

WDAG = {"r": {"s": 5, "t": 3}, "s": {"t": 2, "x": 6}, "t": {"x": 7, "y": 4, "z": 2},
        "x": {"y": -1, "z": 1}, "y": {"z": -2}, "z": {}}
print("\nDAG 최단 경로 (음의 간선 있음):", dag_shortest(WDAG, "s"))
print("  Bellman-Ford와 같은가:", dag_shortest(WDAG, "s") == bellman_ford(WDAG, "s")[0])

# networkx로 교차 검증
import networkx as nx
G = nx.Graph([(u, v, {"weight": w}) for u in UND for v, w in UND[u].items()])
D = nx.DiGraph([(u, v, {"weight": w}) for u in POS for v, w in POS[u].items()])
print("\nnetworkx MST 가중치 합:", int(nx.minimum_spanning_tree(G).size(weight="weight")),
      "/ 최단 거리 일치:", dict(nx.single_source_dijkstra_path_length(D, "s")) == dijkstra(POS, "s"))

실행 결과

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
BFS: a b c d e f g h
DFS: a b e d c f g h
Prim    : [('a', 'b', 8), ('a', 'd', 9), ('d', 'e', 5), ('e', 'h', 8), ('h', 'g', 9), ('g', 'f', 7), ('f', 'c', 8)] 합 54
Kruskal : [('d', 'e', 5), ('f', 'g', 7), ('a', 'b', 8), ('c', 'f', 8), ('e', 'h', 8), ('a', 'd', 9), ('g', 'h', 9)] 합 54

위상 정렬 TSA1: 냄비에 물 붓기 → 라면 봉지 뜯기 → 점화 → 라면 넣기 → 수프 넣기 → 달걀 풀기
위상 정렬 TSA2: 라면 봉지 뜯기 → 냄비에 물 붓기 → 점화 → 수프 넣기 → 라면 넣기 → 달걀 풀기

양수 가중치 그래프
  Dijkstra    : {'s': 0, 'a': 8, 'b': 5, 'c': 9, 'd': 7}
  Bellman-Ford: ({'s': 0, 'a': 8, 'b': 5, 'c': 9, 'd': 7}, False)

음의 가중치 간선 b→a = -3
  Dijkstra    : {'s': 0, 'a': 2, 'b': 4, 'c': 5} ← a가 먼저 확정되어 s→b→a(1)를 놓침
  Bellman-Ford: ({'s': 0, 'a': 1, 'b': 4, 'c': 4}, False)

음의 사이클 a→b→a (합 -2)
  Bellman-Ford: ({'s': 0, 'a': -3, 'b': 0}, True) ← True면 음의 사이클 존재

DAG 최단 경로 (음의 간선 있음): {'r': inf, 's': 0, 't': 2, 'x': 6, 'y': 5, 'z': 3}
  Bellman-Ford와 같은가: True

networkx MST 가중치 합: 54 / 최단 거리 일치: True
  • Prim과 Kruskal이 고른 간선 순서는 다르지만 같은 MST(합 54)를 만들고, networkx의 결과와도 같음
  • 위상 정렬 결과는 두 방법이 다르지만, 둘 다 모든 선행 관계를 지킴 (위상 정렬의 답은 여러 개일 수 있음)
  • 음의 간선 b→a = -3이 있으면 Dijkstra는 a를 먼저 확정해 버려서 a까지 2로 답하지만, 실제 최단 거리는 s→b→a = 4 - 3 = 1. Bellman-Ford는 맞게 구함
  • 음의 사이클이 있으면 Bellman-Ford가 이를 검출함 (True)
  • DAG 최단 경로는 음의 간선이 있어도 위상 순서 한 번으로 Bellman-Ford와 같은 답을 냄

강의 코드 실행

강의록의 01_dijkstra.py, 02_bellman_ford.py는 무작위 그래프를 만드는 클래스라서, 시드를 고정하고 그대로 불러와 실행함 (그림 저장 부분은 networkx와 matplotlib를 씀).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import random, importlib, sys
sys.path.insert(0, ".")
Dijkstra = importlib.import_module("01_dijkstra").Dijkstra          # 강의 코드 그대로 불러오기
BellmanFord = importlib.import_module("02_bellman_ford").BellmanFord

random.seed(1)                                                      # 그래프가 매번 바뀌지 않게 시드 고정
g = Dijkstra(num_nodes=6, edge_prob=0.4)
g.print_graph(); g.dijkstra(start=0); g.print_shortest_paths(start=0)
g.visualize(start=0, filename="dijkstra.png")

random.seed(7)
bf = BellmanFord(num_nodes=6, edge_prob=0.5, weight_range=(-5, 10), negative_edge_ratio=0.05)
bf.print_graph()
if bf.bellman_ford(start=0):
    bf.print_shortest_paths(start=0); bf.visualize(start=0, filename="bellman_ford.png")
else:
    print("음수 사이클이 존재하여 최단 경로를 계산할 수 없습니다.")

실행 결과

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
입력 그래프 (인접 리스트):
정점 0 → 1(가중치=2), 2(가중치=8), 5(가중치=4)
정점 1 → 0(가중치=1), 3(가중치=10), 5(가중치=8)
정점 2 → 0(가중치=4), 3(가중치=6), 4(가중치=1)
정점 3 → 0(가중치=7)
정점 4 → 3(가중치=4)
정점 5 → 


시작 정점: 0

정점 0까지의 최단 거리: 0
경로: 0

정점 1까지의 최단 거리: 2
경로: 0 → 1

정점 2까지의 최단 거리: 8
경로: 0 → 2

정점 3까지의 최단 거리: 12
경로: 0 → 1 → 3

정점 4까지의 최단 거리: 9
경로: 0 → 2 → 4

정점 5까지의 최단 거리: 4
경로: 0 → 5

✅ 그래프가 'dijkstra.png' 파일로 저장되었습니다.
입력 그래프 (간선 리스트):
정점  0 →  1 (가중치=-1)
정점  0 →  2 (가중치= 2)
정점  0 →  4 (가중치= 9)
정점  0 →  5 (가중치= 7)
정점  1 →  0 (가중치= 2)
정점  1 →  2 (가중치= 8)
정점  1 →  3 (가중치= 4)
정점  2 →  0 (가중치= 7)
정점  2 →  1 (가중치= 2)
정점  2 →  3 (가중치= 5)
정점  2 →  4 (가중치=10)
정점  3 →  0 (가중치= 1)
정점  3 →  1 (가중치=10)
정점  3 →  2 (가중치= 1)
정점  3 →  4 (가중치= 8)
정점  4 →  0 (가중치= 9)
정점  4 →  1 (가중치= 2)
정점  4 →  5 (가중치= 4)
정점  5 →  3 (가중치= 2)


시작 정점: 0

정점 0까지의 최단 거리: 0
경로: 0

정점 1까지의 최단 거리: -1
경로: 0 → 1

정점 2까지의 최단 거리: 2
경로: 0 → 2

정점 3까지의 최단 거리: 3
경로: 0 → 1 → 3

정점 4까지의 최단 거리: 9
경로: 0 → 4

정점 5까지의 최단 거리: 7
경로: 0 → 5

✅ 그래프가 'bellman_ford.png' 파일로 저장되었습니다.
  • Dijkstra 결과에서 정점 3은 0→1→3(2 + 10 = 12)이 0→2→4→3(8 + 1 + 4 = 13)보다 짧음
  • Bellman-Ford 그래프에는 음의 간선 0→1(-1)이 있고, 정점 3은 0→1→3(-1 + 4 = 3)이 최단

정리 노트에서 정정한 부분 정리

위치원래정정이유
그래프 정의E: 간선 집합 == 차수E는 간선 집합. 차수는 한 정점에 붙은 간선 수서로 다른 개념
용어경로: 연결된 간선들의 개수경로는 간선으로 이어진 정점의 나열이고, 간선 수는 경로의 길이정의와 길이 구분
용어단순 사이클: 사이클을 포함하지 않는 사이클시작 정점 외에 같은 정점을 반복하지 않는 사이클자기모순적인 정의
연결성비연결: 모든 정점 간에 경로가 존재하지 않음경로가 없는 정점 쌍이 하나라도 있음일부만 끊겨도 비연결
그래프 종류순환 그래프: 모든 꼭짓점이 같은 차수하나의 고리 모양(모든 정점 차수 2)같은 차수는 정규 그래프의 정의
최단 경로 조건가중치의 값이 음수는 허용하지 않는다Dijkstra만 음수 불허. Bellman-Ford, DAG 최단 경로는 음의 간선 허용음의 사이클이 없으면 최단 경로가 정의됨
Bellman-Ford음의 사이클을 허용하지 않음음의 사이클이 있으면 검출해서 알려 줌V번째 반복에서 거리가 줄면 사이클 존재

최종 핵심 정리

  1. 그래프는 \(G = (V, E)\)이고, 희소 그래프는 인접 리스트(\(\Theta(V+E)\) 공간), 조밀 그래프는 인접 행렬(\(\Theta(V^2)\))이 알맞음
  2. BFS는 큐로 가까운 정점부터, DFS는 스택(재귀)으로 깊게 탐색하며 둘 다 \(\Theta(V + E)\)
  3. MST는 간선 합이 최소인 신장 트리이고, n개 정점이면 간선 \(n - 1\)개
  4. Prim은 집합 S를 키우며 S 경계의 최소 간선을, Kruskal은 사이클을 만들지 않는 최소 간선을 추가하며 둘 다 \(O(E \log V)\)
  5. 위상 정렬은 DAG에서 선행 관계를 지키는 나열이며, 진입 간선 제거(TSA1)나 DFS 완료 역순(TSA2)으로 \(O(V + E)\)
  6. Dijkstra는 음의 간선이 없을 때 \(O(E \log V)\)이고, 확정한 정점을 다시 갱신하지 않아 음의 간선이 있으면 틀림
  7. Bellman-Ford는 모든 간선 이완을 \(V - 1\)번 반복해 음의 간선을 처리하고 음의 사이클을 검출하며 \(O(VE)\)
  8. DAG에서는 위상 정렬 순서대로 한 번 이완하면 \(O(V + E)\)에 최단 경로를 구함

End.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.