* feat(ko): add the complete Korean online edition * Use UTF-8 for Python CI output on Windows * Remove print-book promotion from the Korean site * fix(ko): preserve emphasis before Korean particles
6.0 KiB
그래프의 기본 연산
그래프의 기본 연산은 '간선'에 대한 연산과 '정점'에 대한 연산으로 나뉜다. '인접 행렬'과 '인접 리스트' 중 어느 표현을 사용하는지에 따라 구현 방법이 달라진다.
인접 행렬 기반 구현
정점 수가 $n$인 무방향 그래프가 주어졌을 때 각 연산은 아래 그림과 같이 구현한다.
- 간선 추가 또는 삭제: 인접 행렬에서 해당 간선을 직접 수정하면 되며,
O(1)시간이 걸린다. 무방향 그래프이므로 두 방향의 간선을 함께 갱신해야 한다. - 정점 추가: 인접 행렬의 끝에 행과 열을 하나씩 추가하고 모두 $0$으로 채운다.
O(n)시간이 걸린다. - 정점 삭제: 인접 행렬에서 행과 열을 하나씩 삭제한다. 첫 번째 행과 열을 삭제할 때가 최악의 경우로,
(n-1)^2개 원소를 '왼쪽 위로 이동'해야 하므로O(n^2)시간이 걸린다. - 초기화: 정점
n개를 전달받아 길이가 $n$인 정점 리스트vertices를 초기화하는 데O(n)시간이 걸린다.n \times n크기의 인접 행렬adjMat을 초기화하는 데는O(n^2)시간이 걸린다.
다음은 인접 행렬로 그래프를 표현한 구현 코드다.
[file]{graph_adjacency_matrix}-[class]{graph_adj_mat}-[func]{}
인접 리스트 기반 구현
무방향 그래프의 전체 정점 수가 n, 전체 간선 수가 $m$이라면 아래 그림의 방법으로 각 연산을 구현할 수 있다.
- 간선 추가: 해당 정점에 대응하는 연결 리스트 끝에 간선을 추가한다.
O(1)시간이 걸린다. 무방향 그래프이므로 두 방향의 간선을 함께 추가해야 한다. - 간선 삭제: 해당 정점에 대응하는 연결 리스트에서 지정한 간선을 찾아 삭제한다.
O(m)시간이 걸린다. 무방향 그래프에서는 두 방향의 간선을 함께 삭제해야 한다. - 정점 추가: 인접 리스트에 연결 리스트 하나를 추가하고 새 정점을 그 연결 리스트의 머리 노드로 둔다.
O(1)시간이 걸린다. - 정점 삭제: 인접 리스트 전체를 순회하며 지정한 정점이 포함된 모든 간선을 삭제한다.
O(n + m)시간이 걸린다. - 초기화: 인접 리스트에 정점
n개와 간선2m개를 만든다.O(n + m)시간이 걸린다.
다음은 인접 리스트의 구현 코드다. 위 그림과 비교하면 실제 코드에는 다음 차이가 있다.
- 정점을 편리하게 추가하고 삭제하며 코드를 간단하게 만들기 위해 연결 리스트 대신 리스트(동적 배열)를 사용한다.
- 해시 테이블로 인접 리스트를 저장한다.
key는 정점 인스턴스이고,value는 그 정점의 인접 정점 리스트(연결 리스트)다.
또한 인접 리스트에서 정점을 나타내기 위해 Vertex 클래스를 사용한다. 인접 행렬처럼 리스트 인덱스로 정점을 구분한다면 인덱스가 $i$인 정점을 삭제할 때 인접 리스트 전체를 순회하면서 $i$보다 큰 모든 인덱스를 $1$씩 줄여야 하므로 매우 비효율적이기 때문이다. 반면 정점마다 고유한 Vertex 인스턴스를 사용하면 어느 정점을 삭제해도 다른 정점을 수정할 필요가 없다.
[file]{graph_adjacency_list}-[class]{graph_adj_list}-[func]{}
효율 비교
그래프에 정점 n 개와 간선 m 개가 있다고 하자. 아래 표는 인접 행렬과 인접 리스트의 시간 효율과 공간 효율을 비교한다. 여기서 인접 리스트(연결 리스트)는 이 절의 구현에 해당하며, 인접 리스트(해시 테이블)는 모든 연결 리스트를 해시 테이블로 바꾼 구현을 뜻한다.
표 인접 행렬과 인접 리스트 비교
| 인접 행렬 | 인접 리스트(연결 리스트) | 인접 리스트(해시 테이블) | |
|---|---|---|---|
| 인접 여부 확인 | O(1) |
O(n) |
O(1) |
| 간선 추가 | O(1) |
O(1) |
O(1) |
| 간선 삭제 | O(1) |
O(n) |
O(1) |
| 정점 추가 | O(n) |
O(1) |
O(1) |
| 정점 삭제 | O(n^2) |
O(n + m) |
O(n) |
| 메모리 사용량 | O(n^2) |
O(n + m) |
O(n + m) |
위 표만 보면 인접 리스트(해시 테이블)의 시간 효율과 공간 효율이 가장 좋아 보인다. 하지만 실제로는 배열 접근이나 대입 한 번이면 되는 인접 행렬의 간선 연산이 더 효율적이다. 종합하면 인접 행렬은 '공간을 더 써서 시간을 절약하는' 원리를, 인접 리스트는 '시간을 더 써서 공간을 절약하는' 원리를 보여 준다.









