[JAVA/프로그래머스] 그래프_순위

👀 문제

https://programmers.co.kr/learn/courses/30/lessons/49191

👊 도전

1. 설계

  1. 플로이드 와샬 알고리즘을 사용한다.
  2. 알고리즘으로 계산한 최단 경로가 있는 갯수를 리턴한다.

2. 구현 (성공 코드)

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
import java.util.*;
/**
 *
 * @author HEESOO
 *
 */
 class Solution {
     int INF=987654321;//방문불가를 뜻함
     public int solution(int n, int[][] results) {
         int answer = 0;
         int[][] scores=new int[n+1][n+1];
         int win, lose;
         //배열 초기화
         for(int[] score:scores){
             Arrays.fill(score, INF);
         }
         //대각선을 0
         for(int i=0;i<scores.length;i++){
             for(int j=0;j<scores.length;j++){
                 if(i==j) scores[i][j]=0;
             }
         }
         //한방향 그래프 win->lose
         for(int[] result:results){
             win=result[0];
             lose=result[1];
             scores[win][lose]=1;
         }
         //scores[i][j]로 가는 최단경로 저장
         for(int k=1;k<=n;k++){
             for(int i=1;i<=n;i++){
                 for(int j=1;j<=n;j++){
                     if(scores[i][j]>scores[i][k]+scores[k][j]){
                         scores[i][j]=scores[i][k]+scores[k][j];
                     }
                 }
             }
         }
         // for(int[] score:scores){
         //     System.out.println(Arrays.toString(score));
         // }
         //선수들이 게임을 한 적이 있는지 확인
         boolean[] flag=new boolean[n+1];
         Arrays.fill(flag, true);
         for(int i=1;i<=n;i++){//사람 i 기준
             for(int j=1;j<=n;j++){//나머지 j선수들과 게임한적 있는지 체크
                 if(i==j) continue;//나자신과 게임을 뜻하므로 패스
                 if(scores[i][j]==INF&&scores[j][i]==INF){//경로가 존재하지 않으면(i와 j가 게임하지 않았다면)
                     flag[i]=false;
                     break;//모두와 게임을 해야하므로
                 }
             }
         }
         // System.out.println(Arrays.toString(flag));
         for(int i=1;i<flag.length;i++){
             if(flag[i]) answer++;
         }
         return answer;
     }
 }
 

3. 결과

실행결과 🤟 성공 🤟

4. 설명

  1. 플로이드 와샬 알고리즘을 적용한다. 실행결과
    • 그래프에서 모든 꼭짓점 사이의 최단 경로의 거리를 구하는 알고리즘이다.
    • 이 알고리즘을 이용하면 최단 경로를 알 수 있을 뿐만 아니라 연결되지 않은 경로(갈 수 없는 경로)도 알 수 있다.
    • 연결되지 않았다는 뜻은 선수 i, j의 승패를 알 수 없다는 뜻이다.
    • 직접 연결되지 않았지만 선수 k로 거쳐서 i, j가 연결된다면 선수의 승패를 알 수 있다(i, j가 게임하진 않았지만, i가 k한테 이겼고, k가 j한테 이겼다면 i는 j한테 이김을 알 수 있다).
    • 따라서 i, j 사이에 k를 거쳐가는 방법을 모두 찾아 최단경로를 결정하는 플로이드 와샬 알고리즘을 이용하면 선수들의 승패를 확인할 수 있다.
  2. 2차원 배열에 플로이드 와샬 알고리즘을 결과값을 저장한다.
    • int[][] scores로 n+1길이만큼 선언한다.
    • 선수는 1부터 시작하므로 배열을 n+1만큼 생성하여 0은 사용하지 않겠다.
    • 내가 나와 게임하는 경우는 없으므로 대각선은 0으로 초기화한다.
    • 나머지 값들은 INF에 적당히 큰 수를 넣어 이 값으로 저장한다.
    • 이때 큰 수를 넣는다고 Integer.MAX_VALUE를 넣으면 안된다. 실행결과 최단경로를 계산하기 위해 값을 서로 더하는데, int의 최댓값을 둘 다 더할 경우 오버플로우로 음수가 발생한다.
    • win->lose 방향으로 한 방향 그래프를 만든다.
    • 3개 for문을 이용해 최단 경로를 찾는다. 이때 k는 거쳐가는 꼭짓점, i는 출발하는 꼭짓점, j는 도착하는 꼭짓점이다.
    • 즉, 선수 i, j의 승패를 알고자 할 때, i, j가 선수 k와 게임한 적이 있다면 해당 거리를 저장한다(INF에서 최단경로 거리값으로 저장되었으므로 선수 i, j간의 승패여부를 알 수 있다는 뜻).
  3. 승패를 알 수 없는 선수들을 찾는다.
    • boolean[] flag로 선수의 순위를 알 수 있는지에 대한 여부를 저장한다.
    • 일단 모두가 알 수 있다고 가정한 후, scores를 확인하면서 알 수 없는 선수에게 false를 준다. 실행결과 (0은 사용하지 않고, 대각선은 나자신이므로 빗금처리 했다.)
    • 선수 i를 기준으로, 선수 j와 승패를 알 수 있는지 확인한다. scores[i][j]와 scores[j][i]가 모두 INF면 선수 i, j간에 경로가 없다는 뜻이다. 즉, 게임을 한 적이 없고, 다른 선수를 통해서도 승패를 알 수 없으므로 flag[i]를 false로 바꾼다.
    • 선수 i는 나를 제외한 n-1명과 방문할 수 있어야 승패를 알 수 있다. 따라서 하나라도 경로가 존재하지 않는다면 i의 선수 승패는 알 수 없으므로 break로 i에 대한 탐색을 종료한다.
    • 마지막으로 flage의 true 갯수를 리턴하면 그것이 정답이다.

👏 해결 완료!

선수의 승패를 알기 위해 플로이드 와샬 알고리즘을 적용하겠다는 생각을 하는 사람들이 대단하다. 알고리즘을 이해하는 것은 어렵지 않았지만, 왜 이 문제를 플로이드 와샬 알고리즘으로 푸는지 이해하는 시간이 오래 걸렸다.

참고