그래프 · 최단 경로 · 최소 신장 트리
그래프의 기본 용어와 표현 방법을 정리하고, 대표적인 두 문제인 최단 경로(다익스트라)와 최소 신장 트리(프림·크루스칼)를 살펴본다.
그래프
그래프graph
- 각 간선은 순서쌍
로 표현된다(단 ). - 간선을 나타내는 순서쌍에 순서가 있으면 그 그래프는 유향directed이다.
일 때에만 정점 가 정점 에 인접adjacent했다고 한다.- 무향 그래프undirected graph에서 간선
가 있으면 도 성립하며, 두 정점은 서로 인접한다. - 간선은 때때로 세 번째 요소component인 가중치weight 또는 비용cost을 가진다.
경로와 순환 관련 용어를 정리하면 다음과 같다.
- 경로path: 정점들의 나열
으로, ( )를 만족한다. - 경로의 길이length: 경로 위 간선의 수
. 간선이 없는 자기 자신으로의 경로는 길이 이다. - 루프loop: 간선
, 즉 자기 자신으로 향하는 경로. - 단순 경로simple path: 처음과 마지막 정점을 제외하고 방문하는 모든 정점이 서로 다른 경로.
- 순환cycle: 유향 그래프에서 길이가 최소
이고 인 경로. 순환이 없으면 비순환acyclic이다.- 무향 그래프에서
는 순환으로 보지 않는다. 와 가 같은 간선이기 때문이다. - 유향 그래프에서는
와 가 다른 간선이므로 를 순환이라 부를 수 있다.
- 무향 그래프에서
연결성도 그래프의 중요한 성질이다.
- 무향 그래프에서 모든 정점이 서로 연결되어 있으면 연결connected되었다고 한다.
- 유향 그래프가 (방향을 고려해도) 연결되어 있으면 강하게 연결strongly connected되었다고 한다.
- 방향을 모두 없앤 기저 그래프만 연결되어 있으면 약하게 연결weakly connected되었다고 한다.
- 모든 정점 쌍 사이에 간선이 있으면 완전 그래프complete graph라고 한다.
인접 행렬 vs 인접 리스트
그래프를 표현하는 두 가지 대표적 방법이 있다.
- 인접 행렬adjacency matrix: 2차원 배열로, 간선
가 있으면A[u][v]를true, 없으면false로 둔다.- 가중치가 있으면
A[u][v]를 가중치 값으로 두고, 없는 간선은 아주 크거나 아주 작은 값으로 미리 채운다. - 공간이
이므로 간선이 적은 그래프에는 낭비가 크다. 밀집dense( ) 그래프에 적합하다.
- 가중치가 있으면
- 인접 리스트adjacency list: 각 정점마다 인접한 정점들을 나열한다.
- 공간이
로 그래프 크기에 선형 비례하므로, 희소sparse한 그래프에 적절하다.
- 공간이
최소 경로 문제
가중치 그래프에서 간선
- 경로
의 비용은 이며, 이를 가중치 경로 길이weighted path length라고 한다. - 비가중치 경로 길이unweighted path length는 경로 위 간선의 개수
이다. - 비가중치 문제는 모든 간선의 가중치가
인 가중치 최소 경로 문제의 특수한 경우이다.
아래 그래프에서
다익스트라 알고리즘
단일-소스 최소 경로 문제single-source shortest-path problem를 푸는 대표적인 방법이 다익스트라 알고리즘Dijkstra's algorithm이다. 비가중치 경우와 비슷한 아이디어를 쓰되, 각 정점에 다음 정보를 유지한다.
- 각 정점을 known 또는 unknown으로 표기한다.
- 시험적인 거리
를 각 정점에 기록한다. - 이 거리는 해당 정점이 known이 될 때 비로소 최소 경로로 확정된다.
에는 를 마지막으로 바꾸게 만든 정점을 기록한다.
각 단계에서 아직 unknown인 정점 중
위 그래프에서
| 정점 | known | ||
|---|---|---|---|
| T | |||
| T | |||
| T | |||
| T | |||
| T | |||
| T | |||
| T |
음의 간선 주의
그래프에 음의 간선 비용이 있으면 다익스트라 알고리즘은 정상 동작하지 않는다. 보통은 모든 간선에 상수
실습: 다익스트라 알고리즘
위 그래프를 인접 리스트로 구성하고, 우선순위 큐를 이용해
#include <iostream>
#include <vector>
#include <queue>
#include <climits>
using namespace std;
int main() {
const int N = 7; // 정점 v1..v7 (인덱스 1..7)
vector<vector<pair<int,int>>> adj(N + 1); // adj[u] = {(v, cost), ...}
auto add = [&](int u, int v, int w){ adj[u].push_back({v, w}); };
add(1,2,2); add(1,4,1);
add(2,4,3); add(2,5,10);
add(3,1,4); add(3,6,5);
add(4,3,2); add(4,5,2); add(4,6,8); add(4,7,4);
add(5,7,6);
add(7,6,1);
int s = 1;
vector<int> dist(N + 1, INT_MAX);
dist[s] = 0;
// (거리, 정점) 최소 힙
priority_queue<pair<int,int>, vector<pair<int,int>>, greater<>> pq;
pq.push({0, s});
while (!pq.empty()) {
auto [d, u] = pq.top(); pq.pop();
if (d > dist[u]) continue; // 이미 더 짧은 경로로 확정된 정점
for (auto [v, w] : adj[u]) {
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
pq.push({dist[v], v});
}
}
}
cout << "v1 에서 각 정점까지의 최단 거리:" << endl;
for (int v = 1; v <= N; ++v)
cout << " v" << v << " : " << dist[v] << endl;
return 0;
}무가중 · 위상 정렬
모든 간선의 가중치가 같은 무가중 그래프에서는 단순한 BFS로 최단 거리를 구할 수 있다. 또한 방향성 비순환 그래프(DAG)라면 위상 정렬 순서대로 정점을 처리해 음의 가중치가 있어도 한 번의 순회로 최단 경로를 계산할 수 있다.
최소 신장 트리
그래프
프림 알고리즘
프림 알고리즘Prim's algorithm은 트리를 한 정점씩 키워 가는 방식으로, 최소 경로 문제의 다익스트라 알고리즘과 근본적으로 동일하다.
- 각 정점에 대해
, , known/unknown 여부를 기록한다. 는 를 known 정점 집합에 연결하는 최소 간선의 비용, 는 를 바꾼 마지막 정점이다.- 정점
가 선택되면, 인접한 각 unknown 정점 에 대해 로 갱신한다.
다익스트라와 마찬가지로 배열 기반 구현은
크루스칼 알고리즘
크루스칼 알고리즘Kruskal's algorithm은 여러 트리가 모인 포레스트forest에서 출발한다.
- 비용이 작은 간선부터 순서대로 선택하되, 순환을 만들지 않는 간선만 추가한다.
- 알고리즘이 끝나면 트리 하나만 남고, 그것이 최소 신장 트리이다.
- 수행 시간은
이다. 순환 여부 판정 비용은 이보다 작다.
성능 비교
| 알고리즘 | 방식 | 시간 복잡도(배열 기반) |
|---|---|---|
| 프림 | 트리를 한 정점씩 키움(정점 중심) | |
| 크루스칼 | 작은 간선부터 선택, 순환 회피(간선 중심) |
프림은 정점이 적고 간선이 많은 밀집 그래프에, 크루스칼은 간선이 적은 희소 그래프에 유리한 경향이 있다. 크루스칼은 순환 여부 판정을 위해 다음 페이지의 서로소 집합 자료구조를 활용한다.