그래프에서 최단거리 구하는 방법
BFS/너비 우선 탐색
과정
코드
class Graph {
private int V; // 총 노드 개수
private LinkedList<Integer> adj[]; // 인접리스트
/** 생성자 **/
Graph(int v) {
V = v;
adj = new LinkedList[v];
for (int i = 0; i < v; ++i) {
adj[i] = new LinkedList();
}
}
/** 단방향 노드 연결 **/
void addEdge(int v, int w) {
adj[v].add(w);
}
/** s노드부터 시작하는 BFS. 탐색한 노드 출력 **/
void BFS(int s) {
// 노드 방문 여부 (초기값: false)
boolean visited[] = new boolean[V];
// 큐 생성
LinkedList<Integer> queue = new LinkedList<Integer>();
// 현재 노드 방문 체크. 큐에 삽입
visited[s] = true;
queue.add(s);
// 큐가 빌 때까지 시작 노드와 인접한 노드 방문
while (queue.size() != 0) {
s = queue.poll();
System.out.print(s + " ");
}
}
}
다이나믹 프로그래밍을 활용한 최단경로 탐색 알고리즘
우선순위 큐(힙)을 사용하지 않고 구현하면 O(V^2)
우선순위 큐를 사용하면
public class Dijkstra {
static class Node {
int idx;
int cost
Node(int idx, int cost) {
this.idx = idx;
this.cost = cost;
}
}
static ArrayList<Node>[] graph;
static boolean[] visited;
static int[] distance;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int v = Integer.parseInt(st.nextToken());
int e = Integer.parseInt(st.nextToken());
int k = Integer.parseInt(br.readLine());
graph = new ArrayList[v+1];
visited = new boolean[v+1];
distance = new int[v+1];
for (int i = 1; i <= v; i++) {
graph[i] = new ArrayList<>();
distance[i] = Integer.MAX_VALUE; // 최단거리를 찾기 위해 최대값으로 초기화
}
for (int i = 0; i < e; i++) {
// u -> v로 가는 가중치 w가 주어짐
st = new StringTokenizer(br.readLine());
int inputU = Integer.parseInt(st.nextToken());
int inputV = Integer.parseInt(st.nextToken());
int inputW = Integer.parseInt(st.nextToken());
graph[inputU].add(new Node(inputV, inputW));
}
dijkstra(k);
for (int i = 1; int <= v; i++) {
System.out.println(dist[i] == Integer.MAX_VALUE ? "INF" : dist[i]);
}
}
static void dijkstra(int start) {
// 우선순위 큐 사용, 가중치를 기준으로 오름차순
PriorityQueue<Node> q = new PriorityQueue<>((o1, o2) -> o1.cost - o2.cost);
// 시작노드에 대해서 초기화
q.add(new Node(start, 0));
distance[start] = 0;
while (!q.isEmpty()) {
// 현재 최단 거리가 가장 짧은 노드를 꺼내서 방문 처리
Node now = q.poll();
if (!viist[now.v]) {
visit[now.v] = true;
}
for (Node next : graph[now.v]) {
// 방문하지 않은 노드 중 현재 노드를 거쳐서 다른 노드로 이동하는 거리가 더 짧은 노드
if (!visit[next.v] && distance[next.v] > now.cost + next.cost) {
distance[next.v] = now.cost + next.cost;
q.add(new Node(next.v, distance[next.v]));
}
}
}
}
}
벨만-포드 알고리즘: 한 노드에서 다른 노드까지의 최단 거리 구하기
V번 반복에 대해서 해당 정점과 연결되어 있는 모든 간선(E)을 탐색해서 시간복잡도는 O(VE)
코드
import sys
input = sys.stdin.readline
INF = int(1e9)
# 노드의 개수, 간선의 개수를 입력
v, e = map(int, input().split())
# 모든 간선에 대한 정보 담는 리스트
edges = []
# 최단 거리 테이블 초기화 (무한)
distance = [INF] * (v + 1)
# 모든 간선의 정보 입력
for _ in range(e):
a, b, c = map(int, input().split())
# a번 노드에서 b번 노드로 가는 비용이 c임 (a -> b의 비용이 c)
edges.append((a, b, c))
def bellman_ford(start):
# 시작 노드 초기화
distance[start] = 0
# 전체 (v-1)번 반복
for i in range(v):
# 반복마다 모든 간선 확인
for j in range(e):
current_node = edges[j][0]
next_node = edges[j][1]
edge_cost = edges[j][2]
# 현재 간선을 거쳐서 다른 노드로 이동하는 거리가 더 짧을 때
if distance[current_node] != INF and distance[next_node] > distance[current_node] + edge_cost:
distance[next_node] = distance[current_node] + edge_cost
# v번째 라운드에서도 값이 갱신된다면 음수 순환이 존재
if i == v - 1:
retun True
return False
시간복잡도
public void floydWarshall() {
int distance[n][n];
// 결과 그래프를 기존 그래프로 초기화
for (int i = 0; i < num; i++) {
for (int j = 0; j < num; j++) {
d[i][j] = a[i][j];
}
}
// k: 거쳐가는 노드
for (int k = 0; k < num; k++) {
// i: 출발 노드
for (int i = 0; i < num; i++) {
// j: 도착 노드
for (int j = 0; j < number; j++) {
if (d[i][k] + d[k][j] < d[i][j]) {
d[i][j] = d[i][k] + d[k][j];
}
}
}
}
// 결과 출력
for (int i = 0; i < num; i++) {
for (int j = 0; j < num; j ++) {
System.out.print(d[i][j]));
}
System.out.println();
}
}
다익스탈: O(Elog V) = O(N^3log N)
벨만 포드: O(VE) = O(N^4)
플로이드-워셜: O(N^3) = O(N^3)
플로이드-워셜 알고리즘이 가장 효율적임
A* 알고리즘:
개념
F = G + H
A* 알고리즘은 음의 가중치도 계산 가능
음수 간선/사이클이 존재하면 다익스트라 알고리즘은 부적합
플로이드-워셜 알고리즘은 자기 자신에게 가는 간선 거리를 검사해서 음수 사이클을 검출함
참고: