From de1228c7d5fa340c91b6e597af33b00f429e8f79 Mon Sep 17 00:00:00 2001 From: Jinwoo Sung <123284056+jwsung91@users.noreply.github.com> Date: Sun, 16 Feb 2025 22:39:56 +0900 Subject: [PATCH] Docs/pq dijkstra (#31) * docs: add dijkstra algorithm based on priority queue * docs: add dijkstra example code --- _posts/2025-02-16-dijkstra-priority-queue.md | 253 +++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 _posts/2025-02-16-dijkstra-priority-queue.md diff --git a/_posts/2025-02-16-dijkstra-priority-queue.md b/_posts/2025-02-16-dijkstra-priority-queue.md new file mode 100644 index 0000000..260a4cf --- /dev/null +++ b/_posts/2025-02-16-dijkstra-priority-queue.md @@ -0,0 +1,253 @@ +--- +title: Priroity queue를 이용한 Dijkstra +author: jinwoo +date: 2025-02-16 17:00:00 +0900 +categories: [Software engineering, Algorithm] +tags: [Algorithm, STL, C++] +render_with_liquid: false +mermaid: true +description: Priroity queue를 이용한 Dijkstra 알고리즘 구현 +--- + +## Dijkstra alogrithm + +### 개요 + +- 가중 그래프에서 한 정점에서 다른 모든 정점까지의 최단 경로를 찾는 알고리즘 +- 음수 가중치를 허용하지 않음 +- 방문하지 않은 정점 중 가장 거리가 짧은 정점을 선택하여 인접한 정점들의 거리를 갱신 + +### 동작 원리 + +1. 시작 정점을 최단 거리 0 으로 설정하고, 나머지 정점과의 최단 거리를 INF(무한대)로 설정 +2. 현재 방문할 정점을 선택 +3. 해당 정점과 인접한 정점들의 거리를 갱신 +4. 모든 정점에 대해 위 과정을 반복 + +### 핵심 연산 + +- 최소 거리 정점 선택 → 현재 방문하지 않은 정점 중 가장 최단 거리가 작은 정점을 선택 +- 거리 갱신 → 선택된 정점을 기준으로 인접한 정점들의 거리를 업데이트 + +### 자료구조와 연산 + +- `array`, `list` 등을 사용하여 찾으면 **O(V)**의 시간이 소요 +- `priority_queue`를 활활용하면 **O(log V)**로 줄일 수 있음 + +## 시간 복잡도 + +정점(vertex)의 수 `V`, 간선(edge)의 수 `E`로 할 때 각 자료구조 별 시간 복잡도는 아래와 같음 + +### 시간 복잡도 (배열) + +배열을 사용할 경우, 총 시간복잡도가 `O(V²)`가 되어 그래프가 dense할 수록 비효율성이 높아짐 + +1. 최소 거리 정점 선택 + - 최단 거리 노드를 찾는 작업에 모든 정점 탐색 필요 → `O(V)` + - 시간 복잡도: 모든 vertex에 대하여 계산 → `O(V²)` +2. 거리 갱신 + - 선택된 정점에서 인접한 간선들을 모두 확인 + - 시간 복잡도: 모든 edge에 대하여 계산 → `O(E)` +3. 전체 시간 복잡도 + - `O(V² + E)` ≈ `O(V²)` + +### 시간 복잡도 (우선순위 큐) + +priority_queue(최소 힙)를 사용하면 오름차순(작은 값 → 큰 값)으로 정렬 + +1. 최소 거리 정점 선택 + - `top()` 으로 최소 거리를 가진 정점을 `O(1)` 에 가져올 수 있음 + - `pop()` 의 시간 복잡도는 `O(log V)` 이므로 빠르게 삭제 가능 + - 시간 복잡도: priority queue에 삽입되는 원소의 수는 edge에 비례할 수 있음 → `O(E log V)` +2. 거리 갱신 + - 거리 값이 갱신되면 `push()` 를 이용하여 새로운 값 삽입 → `O(log V)` + - 시간 복잡도: 모든 edge에 대하여 발생할 경우 `O(E log V)` +3. 전체 시간 복잡도 + - `O(E log V)` + `O(E log V)` = `O(E log V)` + +## 알고리즘 구현 + +예제 코드는 [grade-e/dijkstra-pq-container](https://github.com/grade-e/dijkstra-pq-container/tree/main) +를 참고 + +### 소스 코드 + +동작 원리는 주석 참고 + +```c++ +#include +#include +#include +#include +#include + +using namespace std; +constexpr int INF = numeric_limits::max(); + +struct Node { + int vertex; + int cost; + bool operator>(const Node& other) const { // for min-heap + return this->cost > other.cost; + } +}; + +vector dijkstra(int start, int V, vector>& graph) { + // pq를 사용하여 (거리, 정점) 쌍을 관리 + // min heap pq(오름차순)를 설정 + priority_queue, greater> pq; + + // 거리 테이블 dist를 무한으로 초기화 + vector dist(V, INF); + + // 초기 위치 거리 값을 0으로 설정, pq에 삽입 + dist[start] = 0; + pq.push({start, 0}); + while (!pq.empty()) { + // priority_queue에서 최소 비용인 node를 꺼냄 + Node current = pq.top(); + pq.pop(); + + // 현재 node의 cost가 표 값보다 높으면 skip + int u = current.vertex; + int currentCost = current.cost; + if (currentCost > dist[u]) continue; + + // 현재 노드의 이웃 노드에 대하여 + for (const Node& neighbor : graph[u]) { + int v = neighbor.vertex; + int c = neighbor.cost; + // 현재 노드까지의 cost + 이웃 노드까지의 cost + int newDist = dist[u] + c; + // 표 값보다 높으면 update (pq에도 반영) + if (newDist < dist[v]) { + dist[v] = newDist; + pq.push({v, newDist}); + } + } + } + return dist; +} +``` + +## 동작 예시 + +```c++ +#include +#include +#include +#include + +#include "dijkstra.hpp" + +using namespace std; + +int main() { + int n = 5; // number of vertices + int start = 0; // starting vertex + + vector> graph(n); // adjacency list + graph[0].push_back({1, 10}); // 0 → 1, 10 + graph[0].push_back({3, 30}); // 0 → 3, 30 + graph[0].push_back({4, 100}); // 0 → 4, 100 + graph[1].push_back({2, 50}); // 1 → 2, 50 + graph[2].push_back({4, 10}); // 2 → 4, 10 + graph[3].push_back({2, 20}); // 3 → 2, 20 + graph[3].push_back({4, 60}); // 3 → 4, 60 + vector shortest_paths = dijkstra(start, n, graph); + + cout << "Shortest distances from vertex " << start << ":\n"; + for (int i = 0; i < n; ++i) { + if (shortest_paths[i] == std::numeric_limits::max()) { + cout << "Vertex " << i << ": INF\n"; + } else { + cout << "Vertex " << i << ": " << shortest_paths[i] << "\n"; + } + } + return 0; +} +``` + +### 그래프 + +예시를 그림으로 표현하면 아래와 같음 + +```mermaid +graph TD; + A((0)) -->|10| B((1)); + A((0)) -->|30| D((3)); + A((0)) -->|100| E((4)); + B((1)) -->|50| C((2)); + D((3)) -->|20| C((2)); + D((3)) -->|60| E((4)); + C((2)) -->|10| E((4)); +``` + +### 탐색 과정 + +#### Step 0. 초기 상태 + +```text +정점: 0 1 2 3 4 +거리: 0 INF INF INF INF +큐: [(0, 0)] +``` + +#### Step 1. 정점 0번 처리 + +`0` → `1, 3, 4` 거리 갱신 + +```text +정점: 0 1 2 3 4 +거리: 0 10 INF 30 100 +큐: [(1, 10), (3, 30), (4, 100)] +``` + +#### Step 2. 정점 1번 처리 + +`1` → `2` 거리 갱신 + +```text +정점: 0 1 2 3 4 +거리: 0 10 60 30 100 +큐: [(3, 30), (4, 100), (2, 60)] +``` + +#### Step 3. 정점 3번 처리 + +`3` → `2, 4` 거리 갱신 + +```text +정점: 0 1 2 3 4 +거리: 0 10 50 30 90 +큐: [(2, 50), (4, 100), (4, 90)] +``` + +#### Step 4. 정점 2번 처리 + +`2` → `4` 거리 갱신 + +```text +정점: 0 1 2 3 4 +거리: 0 10 50 30 60 +큐: [(4, 60), (4, 100), (4, 90)] +``` + +#### Step 5. 정점 4번 처리 + +최단거리 확정 + +```text +정점: 0 1 2 3 4 +거리: 0 10 50 30 60 +``` + +#### 프로그램 실행 결과 + +```text +Vertex 0: 0 +Vertex 1: 10 +Vertex 2: 50 +Vertex 3: 30 +Vertex 4: 60 +```