Skip to content

Commit

Permalink
Docs/pq dijkstra (#31)
Browse files Browse the repository at this point in the history
* docs: add dijkstra algorithm based on priority queue

* docs: add dijkstra example code
  • Loading branch information
jwsung91 authored Feb 16, 2025
1 parent da9b7db commit de1228c
Showing 1 changed file with 253 additions and 0 deletions.
253 changes: 253 additions & 0 deletions _posts/2025-02-16-dijkstra-priority-queue.md
Original file line number Diff line number Diff line change
@@ -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 <iostream>
#include <limits>
#include <queue>
#include <utility>
#include <vector>

using namespace std;
constexpr int INF = numeric_limits<int>::max();

struct Node {
int vertex;
int cost;
bool operator>(const Node& other) const { // for min-heap
return this->cost > other.cost;
}
};

vector<int> dijkstra(int start, int V, vector<vector<Node>>& graph) {
// pq를 사용하여 (거리, 정점) 쌍을 관리
// min heap pq(오름차순)를 설정
priority_queue<Node, vector<Node>, greater<Node>> pq;

// 거리 테이블 dist를 무한으로 초기화
vector<int> 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 <iostream>
#include <limits>
#include <queue>
#include <vector>

#include "dijkstra.hpp"

using namespace std;

int main() {
int n = 5; // number of vertices
int start = 0; // starting vertex

vector<vector<Node>> 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<int> 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<int>::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
```

0 comments on commit de1228c

Please sign in to comment.