이 글은 포스텍 오은진 교수님의 알고리즘(CSED331) 강의를 기반으로 재구성한 것입니다.
세 알고리즘 모두 '최단 거리'를 알아내는 알고리즘이다. 다만 어디서부터 어디까지 알아내는가가 다를 뿐이다.
1. dijkstra : O((|V| + |E|) * log|V|)
한 vertex에서 다른 모든 vertex까지의 최단거리를 알아내는 알고리즘이다. BFS와 유사한 알고리즘으로, BFS는 queue를 사용하지만 dijkstra는 priority queue(heap)을 사용한다. 다만 dijkstra는 weight가 음수인 경우에는 풀리지 않는다!
procedure dijkstra(G, s) for all vertex u in V do dist(u) = INF prev(u) = null end for dist(s) = 0 H = makeHeap(V) while H is not empty do u = deleteMin(H) for all edges (u, v) in E do if dist(v) > dist(u) + weight(u, v) then dist(v) = dist(u) + weight(u, v) prev(v) = u decreaeKey(H, v) end if end for end while // makeHeap(V) : 모든 vertex로 priority queue를 생성 // deleteMin(H) : priority queue의 제일 작은 key를 가지는 element를 pop // decreaseKey(H, v) : priority queue의 v에 해당하는 key를 갱신
- 핵심은 edge (u, v)에 대해 dist(v) > dist(u) + weight(u, v)라면 dist(v)를 dist(u) + weight(u, v)로 설정하는 것이다.
Time Complexity of dijkstra
위 pseudo code에도 작성되어 있지만 제일 처음 dist(u)를 설정하는 과정에서 O(n), queue의 deletemin은 n time, decreasekey가 |E| time. 보통 priority queue는 binary tree를 이용하기 때문에 deletemin, insert, decreasekey은 O(log|V|)이기 때문에 O((|V| + |E|)log|V|)라고 기억하면 된다.
코드 : O((|V| + |E|) * log|V|)
struct mycomp{ bool operator()(pii&a, pii&b){ return a.second > b.second; // weight가 큰 것을 앞으로 } }; // edges[i] : i가 edge의 시작인 edge list // edges[i][0].first : edge list의 edge end // edges[i][0].second : edge list의 edge weight vector<int> dijkstra(int start, vector<vector<pii>> edges){ // dist initialize vector<int> dist(V+1, INF); dist[start] = 0; // heap initialize priority_queue<pii, vector<pii>, mycomp> pq; pq.push({start, 0}); while(!pq.empty()){ int cur = pq.top().first; int cur_weight = pq.top().second; pq.pop(); // 같은 경로지만 cost가 다른 경우를 대비해 아래 로직 넣음 if(dist[cur] < cur_weight) continue; // pq.top()에 연결된 모든 edge에 대해 dijkstra 수행 for(pii &adjs : edges[cur]){ int adj = adjs.first, adj_weight = adjs.second; // 만약 갱신 가능하다면 dist 갱신 이후 pq에 다시 넣음 if(dist[adj] > dist[cur] + adj_weight){ dist[adj] = dist[cur] + adj_weight; pq.push({adj, dist[adj]}); } } } return dist; }
2. Bellman-Ford : O(|V| * |E|)
dijkstra는 negative weight edge가 존재하면 풀리지 않는다. 이런 경우를 해결하기 위해 bellman-ford 알고리즘이 나왔으며, 음수 사이클이 존재하는 경우에는 풀 수 없다.(계속 값이 갱신되기 때문) dijkstra의 더 큰 버전이니만큼 한 vertex부터 다른 모든 vertex까지의 최단거리를 구한다.
음수 사이클이 존재하는지의 여부는 V번째 for문에서 값이 갱신되는지 여부로 알 수 있다. bellman-ford 알고리즘의 특성상 V-1번째 for문에서 모든 vertex까지의 최단거리가 나오는데, V번째 cycle에서 또 갱신된다면 음수 cycle이 있기 때문이다.
bellman-ford 알고리즘이 V-1번째 for문에서 모든 vertex까지의 최단거리가 결정되나?
proof)
vertex가 V개일 때, path π의 최장 길이는 V일 것이다. 한편 시작 vertex가 s일 때 dist[s]는 0인 것을 이미 알고 있다. 그러면 path π에서 s 다음에 있는 것을 w, w', w'', ...라고 두었을 때 dist[w]는 1번의 순회로 알 수 있고 dist[w']는 2번의 순회로 최단거리를 알 수 있다. 귀납적으로, path π의 제일 마지막 vertex x는 V-1번의 순회로 최단거리를 알 수 있다.
만약 이 과정에서 같은 vertex v를 2번 이상 반복한다면 그 cycle은 negative weight를 가지게 된다. 그렇지 않다면 weight가 더 증가하기 때문에 v를 2번 이상 반복할 이유가 없기 때문이다. 다르게 표현하면 v를 2번 이상 반복하는 것이 없는 경우 V-1번의 순회로 모든 vertex까지 최단거리를 알 수 있다.
이렇게 V-1번의 순회로 모든 vertex까지 최단거리를 구했다. 만약 V번째 순회로 dist[x]가 갱신된다면 그 중간에 negative weight cycle이 있다는 것이다.
- 핵심은 dist[from]이 INF가 아닐 때만 검사해야 한다.
코드 : O(|V| * |E|)
// edges[i] : i가 edge의 시작인 edge list // edges[i][0].first : edge list의 edge end // edges[i][0].second : edge list의 edge weight vector<int> bellman_ford(int start, vector<vector<pii>> edges){ vector<long long> dist(V.size(), INF); dist[start] = 0; // V-1번 순회 for(int i = 0; i< V.size() - 1; i++){ // 모든 vertex에 대해 for(int v = 0; v < V.size(); v++){ // v의 모든 edge에 대해 for(pii edge : edges[v]){ int from = v, to = edge.first, weight = edge.second; // dist[from]이 INF이면 갱신이 이상하게 될 수도 있음 if(dist[from] == INF) continue; dist[to] = min(dist[to], dist[from] + edge.second); } } } bool hasNegCycle = false; for(int v = 0; v < V.size(); v++){ for(pii edge : edges[v]){ int from = v, to = edge.first, weight = edge.second; if(dist[from] == INF) continue; if(dist[to] > dist[from] + weight){ hasNegCycle = true; break; } } } if(hasNegCycle) return {-1}; else return dist; }
3. Floyd-Warshall : O(|V|3)
floyd-warshall은 모든 점에서 모든 점까지의 최단거리를 구한다.
아래 코드 중 dist는 2차원 배열이며, dist[i][j]는 vertex i에서 vertex j까지 거리이다.
vector<int> V; // V는 vertex vector vector<vector<int>> dist(V.size(), vector<int>(V.size(), INF)); // dist[i][j] : distance of vertex i to vertex j for(int i = 0; i<V.size(); i++){ dist[i][i] = 0; } for(int temp = 0; temp < V.size(); temp++){ for(int from = 0; from < V.size(); from++){ for(int to = 0; to < V.size(); to++){ dist[from][to] = min(dist[from][temp] + dist[temp][to], dist[from][to]); } } }
4. Dijkstra vs Bellman-Ford vs Floyd-Warshall
dijkstra | bellman-ford | floyd-warshall |
한 점부터 다른 모든 점까지 | 한 점부터 다른 모든 점까지 | 모든 점부터 다른 모든 점까지 |
negative weight edge가 있을 시 사용 불가 | negative weight edge가 있어도 사용 가능 | negative weight cycle이 있으면 사용 불가 |
'PS > Algorithm' 카테고리의 다른 글
그래프 알고리즘 - (5) Minimum Spanning Tree(Kruskal, Prim) (0) | 2022.06.22 |
---|---|
그래프 알고리즘 - (4) Union-find(disjoint set) (0) | 2022.06.22 |
그래프 알고리즘 - (2) 넓이 우선 탐색 & 깊이 우선 탐색 BFS & DFS (0) | 2022.06.22 |
그래프 알고리즘 - (1) Graph의 기본 (0) | 2022.06.22 |
PS에서 Master Theorem (0) | 2022.06.22 |