6. 그래프의 구현(인접 행렬)
- 행과 열의 인덱스로 노드의 값을 나타내고, 배열의 값은 간선의 가중치가 됨
- 노드 대비 간선이 적을 경우 메모리 공간효율이 좋지 않음
- 노드 “서울” 에서 노드 “부산” 방향으로 가는 간선의 가중치는 400
- 가중치가 없는 간선을 나타내는 경우 간선이 있으면 1, 없으면 0으로 표기
- 노드가 100개이고 간선이 1개인 경우에도 100x100 행렬이 필요함(메모리 낭비)
- 특정 노드 사이 간선존재 여부를 한번에 알수 있음
7. 그래프의 구현(인접 리스트)
- 특정 시작 노드를 기준으로 연결된 노드들을 리스트로 연결하는 방식
- 실제 그래프의 노드 갯수만큼만 추가하므로 메모리 낭비 없음
- 특정 노드에 모든노드가 연결된 경우, 탐색시 O(N)이 될수 있음(드문 케이스)
인접 리스트 표현 방식
1. 노드 개수 만큼 배열을 준비한다.(각 배열 인덱스는 시작 노드 의미)
2. 각 배열의 인덱스는 시작 노드를 나타냄, 해당 인덱스에 연결된 노드 추가
8. 그래프의 구현(인접 리스트)
인접 리스트를 표현하기 전에….
인접 리스트용 노드를 정의하자!
* 그림에서 시작 노드는 {1}
v, w 시작 노드에서 정점 V까지 가중치
시작노드에서 가중치 W로 연결된 정점
1
2
3
4
5
배열
16. 인접 행렬 VS 인접 리스트
공간 복잡도 특정 정점의 간선을
찾는 경우
그래프의 모든 간선
을 찾는 경우
인접 행렬 O(V^2) O(1) O(V^2)
인접 리스트 O(V+E) O(E), 대부분은
O(E/V)
O(V+E)
노드의 갯수가 많을때는 인접 리스트를 써야 함(인접행렬 사용시 메모리 초과 가능성)
특정 정점의 간선을 찾는 경우가 많으면 인접 행렬 사용 고려해보는 것도 좋음
뭘 써야 할지 모른다면 인접 리스트
17. 그래프의 탐색
그래프를 정해진 순서로 순회하는 방법
깊이 우선 탐색(DFS)
- 더이상 탐색할 노드가 없을 때까지 일단 간다.
- 더 이상 탐색할 노드가 없으면 최근에 방문했던 퇴각 후, 가지 않은 노드 방문
너비 우선 탐색(BFS)
- 현재 위치에서 가장 가까운 노드 부터 방문 하고 다음 노드로 넘어감
- 모든 노드를 방문할 때 까지 위 과정 반복
18. 그래프의 탐색(깊이 우선 탐색)
A
B
D
C
- 노드 {A}에서 노드 {B}와 노드 {D}를 순차적으로 방문
- 노드 {D}에서는 더 이상 깊이 들어가서 방문할 노드가 없음
E
19. 그래프의 탐색(깊이 우선 탐색)
A
B
D
C
- 노드 {D}에서는 더 이상 깊이 들어가서 방문할 노드가 없으므
로 노드{B}로 퇴각함(백트래킹)
- 노드 {B}에서 아직 방문하지 않은 노드가 있으므로 깊이 들어
가 봄, 노드 {E} 방문
E
20. 그래프의 탐색(깊이 우선 탐색)
A
B
D
C
- 노드 {E}에서는 더 이상 깊이 들어가서 방문할 노드가 없으므
로 노드{B}로 퇴각함(백트래킹)
- 노드 {B}에서는 더 이상 깊이 들어가서 방문할 노드가 없으므
로 노드{A}로 퇴각함(백트래킹)
E
21. 그래프의 탐색(깊이 우선 탐색)
A
B
D
C
- 노드 {A}에서 노드 {C}를 방문
모든 노드를 방문했으므로 탐색 종료
E
22. 깊이 우선 탐색 구현하기
구현해야 할 동작
1. 계속해서 깊이 탐색할 수 있어야 함
2. 더 이상 깊은 곳이 없는 경우, 가장 최근에 방문했던 노드로 퇴각해야 함
3. 이미 방문한 노드는 중복해서 방문하지 않아야 함
가장 최근에 방문했던 노드로 가는 효율적인 방법은 뭐가 있을까요?
=> LIFO(Last In First Out) 로 동작하는 스택을 활용
=> 방문할 노드는 푸시, 방문한 노드는 팝 하면 스택의 top은 가장 최근에 방문한 노드
=> 함수의 call stack도 stack과 같이 동작하므로 재귀함수로 구현하는 경우가 많음
=> visited 배열을 활용해서 방문 여부를 확인 후 방문함
23. 그래프의 탐색(깊이 우선 탐색, with 재귀)
A
B
D
C
E A
A B C D E
V
A
A B C D E
V V
B
A
A B C D E
V V V
B
D
- dfs(A) 호출, 노드 A 방문 처리
- dfs(A)에서 A와 연결된 노드 중 아직 방문하지 않은 노드 확인, dfs(B) 호출, 노드 B 방문 처리
* 아직 노드 A와 연결된 모든 노드중 방문하지 않은 노드가 있으므로 팝 하지 않음
- dfs(B)에서 B와 연결된 노드 중 아직 방문하지 않은 노드 확인, dfs(D) 호출, 노드 D 방문 처리
* 아직 노드 B와 연결된 모든 노드중 방문하지 않은 노드가 있으므로 팝 하지 않음
24. 그래프의 탐색(깊이 우선 탐색, with 재귀)
A
B
D
C
E A
A B C D E
V V V
B
D
A
A B C D E
V V V V
B
E
- 현재 dfs(D)가 호출된 상황이고 연결된 노드 중, 더 이상 방문할 노드가 없음 따라서 함수 종료
- call stack에 의해 dfs(B)의 남은 부분이 호출됨.
- B와 연결된 노드중 아직 방문하지 않은 노드 E확인 후 dfs(E) 호출, 노드 E 방문 처리
25. 그래프의 탐색(깊이 우선 탐색, with 재귀)
A
B
D
C
E
A
A B C D E
V V V V
B
E
A
A B C D E
V V V V
B
- 현재 dfs(E)가 호출된 상황이고 연결된 노드 중, 더 이상 방문할 노드가 없음 따라서 함수 종료
- call stack에 의해 dfs(B)의 남은 부분이 호출됨.
- 현재 dfs(B)가 호출된 상황이고 연결된 노드 중, 더 이상 방문할 노드가 없음 따라서 함수 종료
- call stack에 의해 dfs(A)의 남은 부분이 호출됨.
26. 그래프의 탐색(깊이 우선 탐색, with 재귀)
A
B
D
C
E A
A B C D E
V V V V
A
A B C D E
V V V V V
C
A
A B C D E
V V V V V
C
A
A B C D E
V V V V V
- 현재 dfs(A)가 호출된 상황이고 연결된 노드 중, 더 이상 방문할 노드가 없음 따라서 함수 종료
- call stack에 의해 dfs(A)의 남은 부분이 호출됨.
- A와 연결된 노드중 아직 방문하지 않은 노드 C확인 후 dfs(C) 호출, 노드 C 방문처리
- 현재 dfs(C)가 호출된 상황이고 연결된 노드 중, 더 이상 방문할 노드가 없음 따라서 함수 종료
- call stack에 의해 dfs(A)의 남은 부분이 호출됨.
- 더 이상 방문할 노드가 없으므로 함수 종료
28. 그래프 탐색(너비 우선 탐색)
A
B
D
C
E - 노드 {A}에서 가장 가까운 노드 {B,C}를 방문(간선 1개를 거쳐
갈수 있음)
29. 그래프 탐색(너비 우선 탐색)
A
B
D
C
E
- 노드 {D,E}를 방문(간선 2개를 거쳐 갈수 있음)
모든 노드를 방문했으므로 탐색 끝.
30. 너비 우선 탐색 구현하기
구현해야 할 동작
1. 루트노드부터 시작해서, 가장 가까운 노드들부터 방문할수 있어야 함
* 루트노드방문 -> 1개 간선으로 갈수있는 노드 방문 -> 2개 간선으로 갈수 있는
노드 방문 …..(모든 노드를 방문할때 까지 반복)
가장 가까운 노드부터 방문하는 효율적인 방법은 뭐가 있을까요?
=> FIFO(First In First Out) 로 동작하는 큐를 활용
1. 시작 노드 푸시
2. 팝 후에 방문 처리 이후, 현재 노드에서 연결된 노드중 방문하지 않은 노드 모두 푸시
=> 모든 노드를 방문할 때까지 2.를 반복
32. 그래프 탐색(너비 우선 탐색)
A
B
D
C
E
A
A B C D E
V
B
A B C D E
V V V
C
33. 그래프 탐색(너비 우선 탐색)
A
B
D
C
E
B
A B C D E
V V V
C
C
A B C D E
V V V V V
D
E
C
A B C D E
V V V V V
D
E
34. 그래프 탐색(너비 우선 탐색)
A
B
D
C
E
A B C D E
V V V V V
D
E
A B C D E
V V V V V
E
35. 깊이 우선 탐색 vs 너비 우선 탐색
- 백트래킹은 깊이우선 탐색에만 존재
* 스도쿠 문제, 1~5를 사용해서 합이 10이 되는 모든 경우
- 너비우선탐색으로 찾은 해만 최적해를 보장
* 시작점에서 끝지점 까지 가는데 최소 스텝수
- 너비우선탐색은 모든 다음노드를 큐에 푸시하므로 깊이우선탐색보다 메모리사용
량이 높음
- 물론 둘다 되는 경우가 존재… 애매하면 깊이 우선 탐색 먼저 시도
36. 최단경로 알고리즘
두 노드를 잇는 가중치의 합을 최소로 하는 경로를 찾는 알고리즘
아래 경우 노드1에서 노드 5 까지 최단경로는 1-> 4 -> 5이고 이때 가중치 합은 53
37. 최단 경로를 구하기 위한 아이디어 1
A F
B
C
20
15 900
50
노드 길이
A 0
B INF
15
C INF
20
F INF
- 시작 노드는 A
- A에서 바로 갈수 있는 노드는 노드 B와 노드 C
- 현재 기준 노드 모든 노드까지 가는 최소 비용중 B까지 가
는 최소비용이 15로 가장 적음
- 간선의 가중치가 모두 양수라면, 현재까지 구한 최소 비용
중 가작 적은 비용은 추후에 갱신 될 여지가 없음
* 예를 들면 A->B 경로는 현재까지 구한 최소 비용중 가장
적은 비용이고, 이후에 A->B의 경로가 갱신될 경우는 없음
38. 최단 경로를 구하기 위한 아이디어 2
A F
B
C
20
15 900
50
노드 길이
A 0
B INF
C INF
F INF
노드 길이
A 0
B INF
15
C INF
20
F INF
-노드 A에서 갈수있는 노드 B, C의 최소 비용 업데이트
* 현재기준 노드 B까지 최소 비용이 제일 적음(후보노드)
노드 길이
A 0
B 15
C 20
F INF
915
후보노드를 거쳐서 최소 비용을 갱신할
수 있는지 확인
- 후보노드 까지는 지금까지 구한 최소
경로의 값 사용
- 후보노드에서 각 간선의 가중치는 그
래프 데이터 참조
- 15 + 900 < INF 이므로 노드 F갱신
이 과정을 모든 정점에 대해 한번씩 수행하면 시작 노드에서 각 정점까지 최소 비용을 구할수 있게됨!
39. 다익스트라 알고리즘으로 실제 최단경로 구하기
시작 노드 A의 최소 비용을 0으로 하고, 직전 노드를 A로 변경함
40. 다익스트라 알고리즘으로 실제 최단경로 구하기
A를 후보노드로 정함(visited에 속하지 않은 노드 중 최소비용 임)
시작 노드 A에서 갈 수 있는 노드들 중, 최단 경로를 갱신할 수 있는 노드들을 확인함
* A까지 최소 비용 + (A -> B) < INF(현재 B까지 최소비용)
* A까지 최소 비용 + (A -> C) < INF(현재 C까지 최소비용)
* A까지 최소 비용 + (A -> E) < INF(현재 까지 최소비용)
41. 다익스트라 알고리즘으로 실제 최단경로 구하기
E를 후보 노드로 정함(visited에 속하지 않은 노드 중 최소 비용 임)
E까지 최소 비용으로 가고 E에서 갈 수 있는 노드들 중, 최단 경로를 갱신할 수 있는 노드들을 확인함
* E까지 최소 비용 + (E ->C) < 4(현재 C까지 최소 비용)
42. 다익스트라 알고리즘으로 실제 최단경로 구하기
C를 후보 노드로 정함(visited에 속하지 않은 노드 중 최소 비용 임)
C까지 최소 비용으로 가고 C에서 갈 수 있는 노드들 중, 최단 경로를 갱신할 수 있는 노드들을 확인함
* C까지 최소 비용 + (C -> D) < INF(현재 D까지 최소 비용)
43. 다익스트라 알고리즘으로 실제 최단경로 구하기
B를 후보 노드로 정함(visited에 속하지 않은 노드 중 최소 비용 임)
B까지 최소 비용으로 가고 B에서 갈 수 있는 노드들 중, 최단 경로를 갱신할 수 있는 노드들을 확인함
* 없음
44. 다익스트라 알고리즘으로 실제 최단경로 구하기
D를 후보 노드로 정함(visited에 속하지 않은 노드 중 최소 비용 임)
D까지 최소 비용으로 가고 B에서 갈 수 있는 노드들 중, 최단 경로를 갱신할 수 있는 노드들을 확인함
* 없음
* 최종 완성
45. 다익스트라 알고리즘의 결과 분석
아래를 종합하면 E까지 가는 경로는 A->E->C이고 최소비용은 1+2 = 3 임을 알 수 있습니다.
노드C의 직전 노드는 노드 E
노드 E의 직전 노드는 A
A의 직전 노드는 A(시작 노드)
46. 다익스트라 알고리즘의 구현
후보노드는 현재까지 후보가 되지 않았던 노드 중, 최소비용이 가장 적은 노드
=> visited 배열을 활용해 방문여부를 체크
=> 최소비용이 가장 적은 노드는 우선순위큐를 활용해서 확인
* 최소비용이 갱신된 노드는 우선순위큐에 푸시
* 우선순위큐에서 팝을 할때 visited를 활용해서 이미 방문여부를 확인,
@ 방문했다면 아무 동작도 하지 않음
@ 방문하지 않았다면 최소비용 갱신 진행
각 노드까지 최소비용과 최단 경로가 업데이트 되야함
=> 후보노드가 X인경우, 아래와 같은 상황일 것임
@ 직전노드는 X로 변경,
@최소비용은 X까지 최소비용 + X에서 Y까지 가중치
A X Y
….
X까지 최소비용
(단일 간선이 아닐수도 있음, 중간 생략)
X에서 Y까지 가중치
(단일 간선)
47. 음의 가중치가 있는 그래프에서 다익스트라 알고리즘은?
다익스트라 알고리즘이 찾은 최단경로
실제 최단경로
다익스트라에서 각 노드는 정확히 한번씩 후보노드가 됨(중복되서 후보노드가 되지 않음)
=> 위 그래프에서 노드 B가 첫번째 후보노드가 됨
@ 현재기준 가장 최소비용이 적은 노드는 추후 최소비용이 갱신될 가능성이 없다고봄(그리디적 선택)
@ 하지만 실제 A->B보다, A->C->B 경로의 비용이 더 적음
@ 이런 이유로 음의 가중치가 있는경우 다익스트라의 정상동작을 보장할 수 없음
# 음의 가중치가 있을 경우 밸만-포드 알고리즘을 사용해야 함(책 참조)