그래프
정의
- 현상이나 사물을 정점vertex과 간선edge으로 표현한 것
- Graph G = (V, E)
- 두 정점이 간선으로 연결되어 있으면 인접adjacent하다고 한다.
그래프의 예
그래프의 표현
인접행렬
- N * N 행렬로 표현 (N : 정점의 총 수)
- 원소 (i, h) = 1 : 정점 i와 정점 j사이에 간선이 있음
- 원소 (i, j) = 0 : 정점 i와 정점 j사이에 간선이 없음
- 유향 그래프의 경우
- 원소 (i, j)는 정점 i로부터 정점 j로 연결되는 간선이 있는지를 나타냄
- 가중치 있는 그래프의 경우
장점 : 인접행렬은 간선간의 밀도가 높은 경우에 유용하고, n^2 이상의 시간이 걸리는 알고리즘에서 적합하다.
단점 :반대의 경우 시간과 공간의 낭비가 발생한다.
인접리스트
- N개의 연결 리스트로 표현
- i번째 리스트는 정점 i에 인접한 정점들을 리스트로 연결해 놓음
- 가중치 있는 그래프의 경우
장점 : 행렬 표현에 비해 공간의 낭비가 없다. 모든 가능한 정점 쌍에 비해 간선의 수가 적을 때 유용하다.
단점 : 간선의 밀도가 높은 경우 리스트를 만드는 오버헤드가 커지고, 리스트에 탐색 시간도 존재한다.
인접 배열
- N개의 연결 배열로 표현
- i번째 배열은 정점 i에 인접한 정점들의 집합
- 가중치 있는 그래프의 경우
그래프에서 모든 정점 방문하기
- 대표적 두가지 방법
- 깊이우선탐색 DFS (Depth-First Search)
- 너비우선탐색 BFS (Breadth-First Search)
✨✨✨✨✨ 그래프 알고리즘의 기본 ✨✨✨✨✨
동일한 트리를 각각 DFS/BFS로 방문하기
BFS 너비우선탐색
BFS(G, v) {
for each v ∈ V - {s}
visited[v] <- NO;
visited[s] <- YES; // s : 시작 정점
enqueue(Q, s); // Q : 큐
while (Q != ∅) {
u <- dequeue(Q);
for each v ∈ L(u) // L(u): 정점 u의 인접 리스트
if (visited[v] = NO) then {
visited[u] <- YES;
enqueue(Q, V);
}
}
}
}
}
- Queue를 이용하여 구현
- 방문여부를 알려주는 visit 배열을 초기화
- 시작 정점에 방문 기록, 큐에 삽입
- 반복문 시작
- 큐에서 반출
- 반출한 정점에 인접한 정점을 탐색
- 방문하지 않았던 정점이라면 방문 기록, 큐에 삽입
✔️ 수행시간 : ⊝(|V|+|E|)
DFS 깊이우선탐색
DFS (v) {
visited[v] <- YES;
for each x ∈ L(v) // L(v) : 정점 v의 인접리스트
if (visited[x] = NO) then DFS(x);
✔️ 수행시간 : ⊝(|V|+|E|)
최단경로
- 조건
- 간선 가중치가 있는 유향 그래프
- 무향 그래프는 각 간선에 대해 양쪽으로 유향 간선이 있는 유향 그래프로 생각할 수 있다.
- 즉, 무향 간선 (u,v)는 유향 간선 (u,v)와 (v,u)를 의미한다고 가정하면 된다.
- 두 정점 사이의 최단경로
- 두 정점 사이의 경로들 중 간선의 가중치 합이 최소인 경로
- 간선 가중치의 합이 음인 싸이클이 있으면 문제가 정의되지 않는다.
- 단일 시작점 최단경로
- 단일 시작점으로부터 각 정점에 이르는 최단경로를 구한다.
다익스트라 알고리즘
- 음의 가중치를 허용하지 않는 최단경로
- 그리디 알고리즘
벨만-포드 알고리즘
- 모든 쌍 최단경로
- 모든 정점 쌍 사이의 최단경로를 모두 구한다.
-> 플로이드-워샬 알고리즘
(Floyd's 알고리즘)
모든 쌍 최단경로
- 모든 정점들간의 상호 최단거리 구하기
- 구해야 할 최단 경로가 모두 n2개
(자신에게서 자신으로 경로를 포함)
- 응용 예
- 도로 지도
- 네비게이션 시스템
- 네트워크 커뮤니케이션
1. 동적프로그래밍을 이용
- 사용하는 간선의 수와 관계되어 변수를 정의
- dijm : 최대 m개의 간선을 사용해서 정점 i에서 정점 j에 이르는 최단 거리
- 최대 m개의 간선을 사용해서 정점 i에서 정점 j에 이르는 최단 거리 dijm 구하기
--> 모든 정점 k에 대해 최대 m-1개의 간선을 사용해서 정점 k에 이르는 최단거리 dijm−1 에 wkj를 더한 값을 구한 다음 가장 짧은 것을 선택
- 위 알고리즘은 계산과 구현이 쉬우나 시간이 오래 걸린다.
- 아래의 알고리즘은 사용하면 변수를 덜 사용하여 구현이 가능하다.
- dijk : 정점 집합 {1,2,3,...,k}에 속하는 정점들만 중간 정점으로 거쳐서 i에서 j에 이르는 최단 거리
- 위 알고리즘에서는 간선의 수와 관계지어 변수를 정의하였으나 여기서는 사용한 정점의 집합과 관계를 지었다. 관계식은 다음과 같다.
2. 플로이드 알고리즘
-
그래프에서 최단경로의 길이의 표현 : 0<=k<=n인, dk
dijm = {v1,v2,...,vk}의 정점들 만을 통해서
vi에서 vj로 가는 최단경로의 길이
-
사용한 접점의 집합과 관계를 설정
-
동적계획식 설계전략 - 자료구조
여기서, 0<=k<=5 일때, Dk[2][5]를 구해보자.
- D0 = W이고, Dn=D 임은 분명하다. 따라서 D를 구하기 위해서는
D0을 가지고 Dn을 구할 수 있는 방법을 고안해야 한다.
Dn: n개의 정점들 사이에서의 최단 거리
- 재귀관계식 구하기
- Dk−1을 가지고 Dk 를 계산할수 있는 재귀 관계식을 정립
경우 1:{v1,v2,...,vk}의 정점들 만을 통해서 vi에서 vj로 가는 최단경로가 vk를 거치지 않는 경우.
보기 : D5[1][3]=D4[1][3]=3
경우 2:{v1,v2,...,vk}의 점점들 만을 통해서 vi에서 vj로 가는 최단경로가 vk를 거치는 경우
보기 : D5[5][3]=D1[5][2]+D1[2][3]=4+3=7
보기 : D2[5][4]
- 상향식으로 k=1부터 n까지 다음과 같이 이 과정을 반복하여 해를 구한다.
D0,D1,D2,...,Dn
플로이드 알고리즘 1
- 문제
- 가중치 포함 그래프의 각 정점에서 다른 모든 정점까지의 최단거리를 계산하라
- 입력
- 가중치 포함, 방향성 그래프 W와 그 그래프에서의 정점의 수 n
- 출력
- 알고리즘
void floyd ( int n, const number W[][], number D[][]) {
int i, j, k;
D = W;
for (k=1; k<=n; k++)
for (i=1; i<=n; i++)
for (j=1; j<=n; j++)
D[i][j] = minimum(D[i][j], D[i][k]+D[k][j]); // k루프
}
- 모든 경우를 고려한 분석
- 단위연산 : for-j 루프안의 지정문
- 입력크기 : 그래프에서의 정점의 수 n
T(n)= n x n x n = n3 ∈ ⊝(n3)
플로이드 알고리즘 2
void floyd2(int n, const number W[][], number D[][], index P[][]) {
index i, j, k;
for(i=1; i<=n; i++)
for(j=1; j<=n; j++)
P[i][j] = 0;
D = W;
for (k=1; k<=n; k++)
for(i=1; i<=n; i++)
for(j=1; j<=n; j++)
if ( D[i][k] + D[k][j] < D[i][j] ) {
P[i][j] = k;
D[i][j] = D[i][k] + D[k][j];
}
}