Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 데비안
- 자료구조와 함께 배우는 알고리즘 입문
- 선형대수
- 알파회계
- Kernighan의 C언어 프로그래밍
- 페이징
- ㅒ
- 네트워크 설정
- 자료구조와함께배우는알고리즘입문
- 처음 만나는 AI 수학 with Python
- 티스토리 쿠키 삭제
- 목록처리
- resttemplate
- 리눅스
- 스프링부트핵심가이드
- 구멍가게코딩단
- 코드로배우는스프링부트웹프로젝트
- /etc/network/interfaces
- 처음 만나는 AI수학 with Python
- iterator
- 스프링 시큐리티
- 자바편
- 코드로배우는스프링웹프로젝트
- 친절한SQL튜닝
- GIT
- 이터레이터
- network configuration
- d
- baeldung
- 서버설정
Archives
- Today
- Total
bright jazz music
다익스트라(Dijkstra) 알고리즘 본문
그래프와 이진 힙을 사용한 우선순위 큐를 알아야 한다.
다익스트라 알고리즘의 목적은 그래프의 두 정점(vertex) 사이에 존재하는 최단 경로를 찾는 것이다.
- 서울에서 부산으로 가는 최단경로(GPS)
- 인간 사이의 바이러스의 감염 경로
- 목적지까지의 가장 저렴한 경로
이를 위해 선행되어야 하는 작업은 두 정점 사이에 가중치(예를 들면 거리) 가 존재하도록 해야 한다는 것이다.
이전 포스팅에서는 양방향 무가중치 그래프만을 다뤘다.
여기서는 가중치 그래프를 다룬다. 큰 차이점은 정점이 가지고 있는 리스트에 단순히 다른 정점이 추가되는 것이 아니라, 정점과 가중치가 추가된다는 것이다. 따라서 여기서는 정점과 가중치를 객체의 속성으로 하여 저장하는 방식을 사용한다.
class WeightedGraph {
constructor() {
// 인접리스트. 지만 객체로 관리
this.adjacencyList = {};
}
addVertex(vertex) {
// 인접리스트 객체에 없으면 프로퍼티로 추가하고 빈 배열을 값으로 준다. 존재하면 아무 작업도 수행하지 않는다.
if(!this.adjacencyList[vertex]) this.adjacencyList[vertex] = [];
}
addEdge(vertex1, vertex2, weight) {
this.adjacencyList[vertex1].push({node: vertex2, weight});
this.adjacencyList[vertex2].push({node: vertex1, weight});
}
}
가중치 그래프의 기본적인 구조는 위와 같다.
아래는 가중치 그래프와 우선순위 큐를 사용하여 구현한 다익스트라 알고리즘이다.
// 다익스트라 알고리즘은 가중치 그래프로 구성된 자료구조에서 메서드로서 작동될 수 있다.
//
class WeightedGraph {
constructor() {
this.adjacencyList = {};
}
// 정점 추가
addVertex(vertex) {
if(!this.adjacencyList[vertex]) this.adjacencyList[vertex] = [];
// 이로써 여러 정점들이 인접리스트 객체 내에 키(문자열):밸류(배열) 형태로 들어가게 된다.
}
// 간선 추가
addEdge(vertex1, vertex2, weight) {
if(!this.adjacencyList[vertex1] || !this.adjacencyList[vertex2]) return null;
this.adjacencyList[vertex1].push({node: vertex2, weight});
this.adjacencyList[vertex2].push({node: vertex1, weight});
/* 만약 방향 그래프라면 하나만 한쪽에만 넣어주면 된다. */
}
/* 다익스트라 메서드 : 여기서는 최단경로를 찾는다.*/
Dijkstra(start, finish) {
// 우선순위 큐 생성 (최단 거리가 가장 짧은 노드를 먼저 처리하기 위함)
const priorityQ = new PriorityQueue();
// 시작점으로부터 각 정점까지의 최단 거리를 저장할 객체
// 예: { "A": 0, "B": 4, "C": 2, ... }
const distances = {};
// previous는 최단 경로를 추적하기 위한 객체이다. (각 정점의 이전 정점을 저장)
// 예: { "B": "A", "C": "A", ... } -> B까지 가는 최단 경로의 이전 정점은 A
const previous = {};
// 최종 경로를 저장할 배열
let path = [];
let smallest;
// 초기화: 모든 정점의 거리를 Infinity로, 시작점은 0으로 설정
for(let vertex in this.adjacencyList) {
if(vertex === start) {
distances[vertex] = 0;
priorityQ.enqueue(vertex, 0);
} else {
distances[vertex] = Infinity;
priorityQ.enqueue(vertex, Infinity);
}
previous[vertex] = null;
}
// 우선순위 큐가 빌 때까지 반복
while(priorityQ.values.length) {
// 현재 가장 가까운 정점을 가져옴
smallest = priorityQ.dequeue().val;
// 목적지에 도달했다면
if(smallest === finish) {
// 경로 재구성
while(previous[smallest]) {
path.push(smallest);
smallest = previous[smallest];
}
break;
}
// 현재 정점이 유효하고 아직 방문하지 않은 경우
if(smallest || distances[smallest] !== Infinity) {
// 현재 정점의 이웃들을 순회
for(let nextNode of this.adjacencyList[smallest]) {
// 현재까지의 거리 + 현재 간선의 가중치 계산
let candidate = distances[smallest] + nextNode.weight;
let nextNeighbor = nextNode.node;
// 더 짧은 경로를 찾은 경우
if(candidate < distances[nextNeighbor]) {
// 최단 거리 업데이트
distances[nextNeighbor] = candidate;
// 경로 추적을 위해 이전 정점 정보 업데이트
previous[nextNeighbor] = smallest;
// 우선순위 큐에 새로운 정보 추가
priorityQ.enqueue(nextNeighbor, candidate);
}
}
}
}
// 시작점을 포함하여 경로를 완성하고 올바른 순서로 반환
return path.concat(smallest).reverse();
}
}
class PriorityQueue {
constructor() {
this.values = [];
}
enqueue(val, priority) { // 값과 우선순위
let newNode = new Node(val, priority);
this.values.push(newNode);
this.bubbleUp();
}
bubbleUp() {
let idx = this.values.length - 1;
const element = this.values[idx];
while(idx > 0) {
// 부모에서 자식을 찾을 때 -> 2n+1(좌), 2n+2(우) 이므로
// 자식에서 부모를 찾기 위해 역으로 계산한다. Math.floor((자식인덱스-1)/2)
let parentIdx = Math.floor((idx - 1) / 2);
let parent = this.values[parentIdx];
// 최소 힙이기 때문에 우선순위가 작은 것이 부모 위치에 있게 된다. 따라서 탈출
if(element.priority >= parent.priority) break;
this.values[idx] = this.values[parentIdx];
this.values[parentIdx] = element;
idx = parentIdx; // 부모 위치로 이동
}
}
dequeue() {
const min = this.values[0]; //왜냐하면 최소힙이기 때문에 우선순위 큐의 맨 앞은 최소값이다.
const end = this.values.pop();
if(this.values.length > 0) {
this.values[0] = end;
this.sinkDown();
}
return min; // 조건문 이전에 리턴해도 된다.
}
/* 가장 낮은 우선순위를 가진 노드가 맨 앞(루트 노드)에 왔을 때 자식 노드들과 비교해가며 적합한 위치를 찾도록 하는 목적*/
sinkDown() {
let idx = 0;
const length = this.values.length;
let element = this.values[0]; //루트노드에 최소 우선순위가 와 있으므로 0부터 시작
while(true) {
let leftChildIdx = 2 * idx + 1;
let rightChildIdx = 2 * idx + 2;
let swap = null; // 바꿀 인덱스를 저장하는 변수
let leftChild;
let rightChild;
// 루트 노드에 올라온 최솟값을 왼쪽-오른쪽 자식의 우선순위와 번갈아가며 확인한다.
// 먼저 대상 노드의 우선순위를 왼쪽과 비교하여 swap(임시값)을 정하고,
// 그 이후 오른쪽 노드의 우선순위와 비교하여 swap의 값을 확정한 뒤 노드의 위치를 교체한다.
// 우선 왼쪽부터
if(leftChildIdx < length) {
leftChild = this.values[leftChildIdx];
// 왼쪽자식의 우선순위가 높다면(숫자값은 낮음), 스왑에 자식의 인덱스를 담는다
if(leftChild.priority < element.priority) {
swap = leftChildIdx
}
}
// 이제 오른쪽: swap이 null이 아닌 경우, 왼쪽자식과의 우선순위를 비교해야 한다는 점에 주의.
if(rightChildIdx < length) {
rightChild = this.values[rightChildIdx];
if(
swap === null && rightChild.priority < element.priority ||
swap !== null && rightChild.priority < leftChild.priority
) {
swap = rightChildIdx;
}
}
// 스왑이 널이라는 의미는 자식들의 우선순위보다 대상 노드의 우선순위가 높다는 것이므로 탈출
if(swap === null) break;
this.values[idx] = this.values[swap];
this.values[swap] = element;
idx = swap;
}
}
}
class Node {
constructor(val, priority) {
this.val = val;
this.priority = priority;
}
}
예시 코드
// 그래프 생성
const graph = new WeightedGraph();
// 정점 추가
graph.addVertex("A");
graph.addVertex("B");
graph.addVertex("C");
graph.addVertex("D");
// 간선 추가 (무방향 그래프)
graph.addEdge("A", "B", 3); // A-B 거리 3
graph.addEdge("A", "C", 2); // A-C 거리 2
graph.addEdge("B", "D", 4); // B-D 거리 4
graph.addEdge("C", "D", 1); // C-D 거리 1
/*
3
A ----- B
| |
2 | | 4
| |
C ----- D
1
*/
// 최단 경로 찾기
console.log("A에서 D까지의 최단 경로:", graph.Dijkstra("A", "D"));
// 출력: ["A", "C", "D"]
// 그래프의 구조 확인
console.log("그래프의 인접 리스트:", graph.adjacencyList);
/* 출력:
{
"A": [
{ "node": "B", "weight": 3 },
{ "node": "C", "weight": 2 }
],
"B": [
{ "node": "A", "weight": 3 },
{ "node": "D", "weight": 4 }
],
"C": [
{ "node": "A", "weight": 2 },
{ "node": "D", "weight": 1 }
],
"D": [
{ "node": "B", "weight": 4 },
{ "node": "C", "weight": 1 }
]
}
*/
'Algorithm&Data structure > JS alg.' 카테고리의 다른 글
Brute force 알고리즘 +NP문제 (0) | 2024.11.11 |
---|---|
백트래킹 알고리즘(backtracking algorithms) (0) | 2024.11.11 |
탐욕법 (greedy algorithm) (1) | 2024.11.09 |
동적 프로그래밍(Dynamic Programming) (0) | 2024.11.08 |
최소신장트리(Minimum Spanning Tree, MST) + (프림, 크루스칼 알고리즘) (0) | 2024.11.05 |
Comments