백준 1865 웜홀 - JAVA

2025. 2. 26. 18:51·알고리즘/백준

1865 문제 링크


https://www.acmicpc.net/problem/1865

 

문제 설명


 

입출력


 

코드


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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import java.io.*;
import java.util.*;
 
public class Main {
    static List<int []> list;
    static int N;
    static int dist[];
    private static final int INF = 500 * 10_000;
    
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        
        int TC = Integer.parseInt(br.readLine());
        for (int tc = 0; tc < TC; tc++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            N = Integer.parseInt(st.nextToken());
            int M = Integer.parseInt(st.nextToken());
            int W = Integer.parseInt(st.nextToken());
            list = new ArrayList<>();
            for (int i = 0; i < M + W; i++) {
                st = new StringTokenizer(br.readLine());
                int s = Integer.parseInt(st.nextToken());
                int e = Integer.parseInt(st.nextToken());
                int t = Integer.parseInt(st.nextToken());
                if (i < M) {
                    list.add(new int[] {s,e,t});
                    list.add(new int[] {e,s,t});
                } else {
                    list.add(new int[] {s,e,-t});
                }
            }
            if (bellmanFord()) {
                bw.write(String.valueOf("YES\n"));
            } else {
                bw.write(String.valueOf("NO\n"));
            }
        } 
        bw.flush();
    }
    private static boolean bellmanFord() {
        dist = new int[N+1];
        Arrays.fill(dist, INF);
        dist[1] = 0;
        boolean check = false;
        for (int i = 1; i < N; i++) {//모든 최단경로 계산
            for (int[] node : list) {
                int start = node[0];
                int end = node[1];
                int time = node[2];
                if (dist[end] > dist[start] + time) {
                    dist[end] = dist[start] + time;
                    check = true;
                }
            } 
            if(!check) {
                break;
            }
        } 
        if(check) {
            for (int[] node : list) { //한번 더 계산했을 때, 최단경로가 변경된다면 음수사이클 존재
                int start = node[0];
                int end = node[1];
                int time = node[2];
                if (dist[end] > dist[start] + time) {
                    return true;
                }
            }
        }
        return false;
    }
}
Colored by Color Scripter
cs

 

알고리즘 설명


벨만-포드 알고리즘은 음의 가중치를 포함한 방향 그래프에서 최단 거리를 구할 수 있는 알고리즘입니다. 또한 이 알고리즘은 음수 사이클이 존재하는지도 판별할 수 있습니다. 최단 거리를 구하기 위해서는 N-1번 반복해야 하는데, 그 이유는 음수 사이클이 없다면, N개의 노드에 대해 최단 거리가 될 수 있는 간선의 최대 개수가 N-1개이기 때문입니다. 쉽게 말해, 최단 거리를 구할 때, 아무리 많은 시간이 걸려도 최대 N-1개의 간선을 넘길 수 없다는 뜻입니다. 이후, 최단 거리 값은 dist 배열에 저장되고, 반복문을 통해 dist 배열을 계속해서 업데이트합니다. 시간과 메모리를 절약하기 위해 check라는 boolean 변수를 사용하고, 반복문을 한 번 돌았을 때 dist 배열의 값이 더 이상 갱신되지 않으면 최단 거리 구하는 반복문을 종료합니다. 이는 간선완화가 더 이상 되지않으면, 음수 사이클이 없는것으로 판단되기 때문입니다. 만약 반복문이 check가 true인 채로 종료되면, list 내의 간선들을 한 번 더 돌며 dist 배열 값이 줄어드는지 확인합니다. 만약 더 이상 값이 줄어들지 않으면 음수 사이클이 존재하지 않는다고 판단할 수 있습니다.

노드번호 1만 시작점으로 설정하고 반복적으로 순회하지 않는 이유는, list에 저장된, 모든 간선을 순회하면서, 모든 노드간의 음수사이클 유무를 판별할 수 있기 때문입니다.

 

생각보다는 간단한 알고리즘이지만, 저는 문제에서 노드가 끊어져 있을 수 있다는 점을 고려하지 못하고, if(dist[end] > dist[start] + time) 조건을 if(dist[start] != INF && dist[end] > dist[start] + time)로 설정하여 출발노드 1과 끊어져 있는 노드에 대해서는 음수 사이클을 판별하지 않도록 하는 실수를 저질렀습니다. dist[start] != INF라는 조건을 달게되면, 시작노드인 노드 1과 끊어져있는 노드의 경우에는 dist[]의 값이 초기값인 INF에서 변하지 않아, 음수사이클 판별에서 제외되기때문입니다.

 

- 참고 -

간선완화(Edge Relaxation) : 두 노드 간의 최단 거리가 더 줄어들 수 있다면 그 거리를 갱신하는 것 또는 과정.

 

고민했던 부분


간선 저장을 객체로 저장해야 할지, 배열에 저장할지에 대한 고민을 했었습니다. 다만, 객체 저장방식은 각 노드마다 연결된 노드를 찾기위해 탐색해야하는 과정이 있지만, 배열의 경우에는 배열에 담긴 간선을 차례대로 연속적으로 탐색하기 때문에, 캐시가 접근하는 공간 지역성 부분에서 더 우위를 차지합니다. 또한, 배열 저장방식은 연속된 메모리에 저장되므로 코드 실행 시, 메모리 접근 속도가 객체에 비해 빠릅니다. 하지만, 객체 저장 방식은 각 노드의 간선 정보를 유연하게 다룰 수 있기 때문에 복잡한 구조나 동적 추가/삭제가 필요한 경우에 유리할 수 있습니다. 저장방식에 따른 차이를 간단하게 표로 정리해드리겠습니다.

 

객체 지향적인 코드가 당연히 더 유연한 설계는 가능합니다... ㅎㅎ 역시 CS지식은 시간이 지날수록 더 중요해지는 것 같습니다.

 

코드 해석


배열 리스트를 선언하고, 도로의 경우는 s와 e를 번갈아 배열에 저장하고, 웜홀의 경우에는 가중치를 마이너스로 설정하고, 단방향 저장을 실행합니다. 출발 노드인 1번 노드를 기준으로 최단 경로를 계산. N-1번 반복을 통해 각 간선에 대해 최단 경로를 계산합니다. 이때, dist[end] > dist[start] + time이면 dist[end]를 갱신합니다. 한 번의 반복에서 경로가 갱신되지 않으면 더 이상 경로가 변경되지 않는다고 판단하여 종료합니다.

N-1번 반복 이후, 한 번 더 모든 간선을 확인하여 최단 경로가 갱신된다면, 이는 음수 사이클이 존재한다는 의미이므로 true를 반환하고, 음수 사이클이 없다면 false를 반환합니다. 각 테스트케이스에 대하여 실행한 bellmanFord()함수의 결과값을 순서대로 출력합니다.

'알고리즘 > 백준' 카테고리의 다른 글

백준 14502 연구소 - JAVA  (0) 2025.03.03
백준 2206 벽 부수고 이동하기 - JAVA  (1) 2025.03.01
백준 1043 거짓말 - JAVA  (1) 2025.02.25
백준 11404 플로이드 - JAVA  (0) 2025.02.24
백준 1504 특정한 최단 경로 - JAVA  (1) 2025.02.23
'알고리즘/백준' 카테고리의 다른 글
  • 백준 14502 연구소 - JAVA
  • 백준 2206 벽 부수고 이동하기 - JAVA
  • 백준 1043 거짓말 - JAVA
  • 백준 11404 플로이드 - JAVA
BIS's tech Blog
BIS's tech Blog
Welcome to Kanghyun's tech blog :)
  • BIS's tech Blog
    벼익숙의 기술 블로그
    BIS's tech Blog
  • 전체
    오늘
    어제
    • 분류 전체보기 (63) N
      • 알고리즘 (49) N
        • 백준 (40) N
        • 프로그래머스 (9)
      • CS 스터디 (14)
        • CS - JAVA (11)
        • CS - DB (3)
      • Spring (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • GitHub
  • 공지사항

  • 인기 글

  • 태그

    bottom-up
    백준
    Top-Down
    기술질문
    완전탐색
    프로그래머스
    java
    동적 계획
    브루트포스
    재귀
    CS
    너비 우선 탐색
    DFS
    자료구조
    깊이 우선 탐색
    Baekjoon
    Lv2
    알고리즘 고득점 kit
    dp
    BFS
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
BIS's tech Blog
백준 1865 웜홀 - JAVA
상단으로

티스토리툴바