Dijkstra Algorithm

Dijkstra Algorithm

개념

  • 다익스트라 알고리즘은 하나의 정점에서 다른 모든 정점까지의 최단 경로를 구한다.
  • 간선들은 모두 양의 간선들을 가져야 한다.
  • 다익스트라 알고리즘의 기본 로직은, 첫 정점을 기준으로 연결되어 있는 정점들을 추가해가며, 최단 거리를 갱신하는 것이다.
  • 정점을 잇기 전까지는 시작점을 제외한 정점들은 모두 무한대의 값을 가진다.
  • 정점 A에서 정점 B로 이어지면, 정점 B가 가지는 최단 거리는 (시작점부터 정점 A까지의 최단거리 + 점 A와 점B 간선의 가중치)와 (기존에 가지고 있던 정점 B의 최단거리) 중 작은 값이 B의 최단 거리가 된다.

우선순위 큐(힙구조)를 이용한 다익스트라 알고리즘

  • 먼저 모든 정점들을 힙(우선순위 큐)에 넣는다.

  • 위 표에서 i는 정점 인덱스, d는 최단거리이고 p는 이전 정점이다. d를 기준으로 최소 힙으로 구성한다.

  • 힙에서 가장 위에 있는 값을 꺼낸다. 인덱스가 5이고 최소 거리가 0인 값이다.

  • 기존에 있던 dist[5]와 d를 비교한다. dist[5]가 더 작으면 연산하지 않고, 같거나 크면 연산한다.

  • 둘 다 0으로 같으므로, 5 주변의 인접 정점을 계산하고 기존의 dist[i]보다 더 작아지는 정점들을 큐에 넣는다.

    dist[2] = min(dist[2], dist[5] + adj[5][2])
    dist[4] = min(dist[4], dist[5] + adj[5][4])
    

  • 힙에서 가장 위에 있는 값을 꺼낸다. 인덱스가 4이고 최소거리가 2인 값이다.

  • 기존에 있던 dist[4]와 d를 비교한다. dist[4]가 더 작으면 연산하지 않고, 같거나 크면 연산한다.

  • dist[4]는 2이고 d는 2로 같으므로, 4 주변의 인접 정점을 계산해서 기존의 dist[i]보다 더 작아지는 정점들을 큐에 넣는다.

    dist[2] = min(dist[2], d + adj[4][2])
    dist[3] = min(dist[3], d + adj[4][3])
    

  • 힙에서 가장 위에 있는 값을 꺼낸다. 인덱스가 2이고 최소거리가 3인 값이다.

  • 기존에 있던 dist[2]와 d를 비교한다. dist[2]가 더 작으면 연산하지 않고, 같거나 크면 연산한다.

  • dist[2]는 3이고, d는 3으로 같으므로, 2 주변의 인접 정점을 계산해서 기존의 dist[i]보다 더 작아지는 정점들을 큐에 넣는다.

    dist[1] = min(dist[1], dist[2] + adj[2][1])
    

  • 힙에서 가장 위에 있는 값을 꺼낸다. 인덱스가 3이고 최소 거리가 3인 값이다.

  • 기존에 있던 dist[3]과 d를 비교한다. dist[3]이 더 작으면 연산하지 않고, 같거나 크면 연산한다.

  • dist[3]은 3이고, d는 3으로 같으므로, 3 주변의 인접 정점을 계산해서 기존의 dist[i]보다 더 작아지는 정점들을 큐에 넣는다.

    dist[4] = min(dist[4], dist[3] + adj[3][4])
    
  • 여기서 dist[4]는 2로 계산값보다 더 작으므로 큐에 넣지 않는다.

  • 힙에서 가장 위에 있는 값을 꺼낸다. 인덱스가 2이고 최소 거리가 4인 값이다.

  • dist[2] = 3보다 d = 4가 더 크므로 계산하지 않는다.

  • 힙에서 가장 위에 있는 값을 꺼낸다. 인덱스가 1이고 최소거리가 6인 값이다.

  • 기존에 있던 dist[1]과 d를 비교한다. dist[1]은 6이고 d는 6으로 같으므로, 1주변의 인접 정점을 계산해서 기존의 dist[i]보다 더 작아지는 정점들을 큐에 넣는다.

    dist[4] = min(dist[4], dist[1] + adj[1][4])
    dist[3] = min(dist[3], dist[1] + adj[1][3])
    
  • 둘다 기존의 dist[i]보다 크므로 큐에 넣지 않는다.

  • 큐에 남아있는 정점들은 전부 d가 INF로 dist[i]보다 크므로 계산되지 않는다.

코드

    /**
     * 배달 - 다익스트라 알고리즘
     * 하나의 정점에서 다른 모든 정점까지의 최단 경로를 구하는 문제 해결법
     * 단, 다익스트라 알고리즘에서는 음의 가중치를 가진 간선은 쓸 수 없다!!!
     * https://hsp1116.tistory.com/42 참고
     * @param N  마을의 갯수
     * @param road  도로의 정보
     * @param K  음식 배달이 가능한 시간
     * @return
     */
    public static int solution(int N, int[][] road, int K) {
        int answer = 0;
        int[] dist = new int[N + 1];
        Arrays.fill(dist, Integer.MAX_VALUE);  // 처음에 dist 배열을 INF값으로 채워준다.

        // 그래프 제작
        List<Node>[] nodes = new ArrayList[N + 1];
        for (int i = 0; i < nodes.length; ++i) {
            nodes[i] = new ArrayList<>();
        }

        for (int i = 0; i < road.length; ++i) {
            nodes[road[i][0]].add(new Node(road[i][1], road[i][2]));
            nodes[road[i][1]].add(new Node(road[i][0], road[i][2]));
        }

        Queue<Node> pq = new PriorityQueue<>((a, b) -> a.weight - b.weight);
        dist[1] = 0;  // 시작정점의 최단 경로는 0이다.
        pq.add(new Node(1, 0));

        while (!pq.isEmpty()) {
            Node c = pq.poll();

            // 현재 정점까지의 비용이 이미 구한 비용보다 크다면 무시
            if(c.weight > dist[c.index])
                continue;
        
            // 현재 정점과 연결된 정점들을 탐색한다.
            for (Node n : nodes[c.index]) {
                // (현재 정점까지 온 비용 + 다음 정점까지 가는 비용) < 이미 구한 다음 정점까지의 비용이면,
                if (dist[c.index] + n.weight < dist[n.index]) {
                    dist[n.index] = dist[c.index] + n.weight;   // 다음 정점까지의 비용을 갱신하고
                    pq.add(new Node(n.index, dist[c.index] + n.weight));  // 큐에 삽입

                }
            }   
        }

        // 1부터 다른 마을까지의 비용 중 K보다 작게 걸린다면 배달 가능!
        for (int i = 1; i < dist.length; ++i) {
            if (dist[i] <= K)
                answer++;
        }
        
        return answer;
    }