[프림 알고리즘]

시작 노드에서 출발해 인접 노드를 확인하면서 트리를 확장하는 노드 중심 알고리즘. 
인접 리스트를 사용하기 때문에 노드 간의 연결을 효율적으로 확인하기 위해 양방향 간선을 저장.

- **임의의 노드에서 시작**
- 연결된 노드들의 간선 중 **낮은 가중치를 갖는 간선 선택**
- **간선의 개수가 많을 때** 크루스칼 보다 유리
- O(ElogV)
- PriorityQueue 와 visited 배열 사용
- 간선 수가 노드 수 -1 되면 전체 계산이 된 것 착안해서 계산

cf) 크루스칼, 프림 모두무방향 그래프를 대상
-> 크루스칼은 간선 중심 알고리즘이기 때문에 간선 단방향 저장
/**
 * 기존 답안이나, 잘못 설명된 것이 많은 것 같아서 의아한 점은 주석으로 남김
 */
// 프림 알고리즘
public class Main2 {

    public static int prim(int[][] data, int v, int e) {
        int weightSum = 0;
        List<List<Edge>> graph = new ArrayList<>();

        for (int i = 0; i < v + 1; i++) {
            graph.add(new ArrayList<>());
        }

        /**
         * 이부분 틀림 주의 !! -> 간선 정보를 단방향으로 설정해서 틀림
         */
//        for (int i = 0; i < data.length; i++) {
//            List<Node> nodes = graph.get(data[i][0]);
//            Node node = new Node(data[i][1], data[i][2]);
//            nodes.add(node);
//        }
        for (int i = 0; i < e; i++) { //간선 정보 표기할 때 from <-> to 양방향으로 간선 정보 입력 주의!!!
            graph.get(data[i][0]).add(new Edge(data[i][1], data[i][2]));
            graph.get(data[i][1]).add(new Edge(data[i][0], data[i][2]));
        }

        boolean[] visited = new boolean[v + 1];
        PriorityQueue<Edge> priorityQueue = new PriorityQueue<>((x, y) -> x.weight - y.weight);
        //임의의 시작노드 // 첫번째 임의의 노드 방문시 가중치값 들지 않는다는 것 주의 !!
        priorityQueue.add(new Edge(1, 0));
        int cnt = 0; // 연결된 간선 수 -> 사실 연결된 간선 수 -1임! (첫 접근 노드에 대한 접근은 간선이 아니므로)

        while (!priorityQueue.isEmpty()) {
            Edge curEdge = priorityQueue.poll();
//            cnt += 1; // 왜 방문 체크하지 않고 먼저 더함..? <-- 이부분 수정 OK !!
            if (visited[curEdge.to]) {
                continue;
            }
            visited[curEdge.to] = true;
            weightSum += curEdge.weight; // 첫번째 임의의 노드 방문시 가중치값 들지 않는다는 것 주의 (처음 접근 시 가중치 0으로 생성함)
            cnt += 1; // 방문 후에 카운트 하는 것이 올바르다!

            /**
             * 이 부분 특히 주의 ->  엄밀히 말해서 cnt는 간선 수가 아니라 간선수 -1 !!
             */
            if (cnt == v) { // 첫 시작 노드는 간선으로 추가하지 말아야하므로 !!!
                break;
            }

            List<Edge> edges = graph.get(curEdge.to);
            for (Edge edge : edges) {
                // 어차피 위에서 방문 체크 하는데 여기서 하면 중복 하닌가..?
//                if (visited[node.to]) {   <-- 이부분 수정 OK !!
//                    continue;
//                }
                priorityQueue.add(edge);
            }
        }

        return weightSum;
    }

    public static void main(String[] args) {
        // Test code
        int v = 7;
        int e = 10;
        int[][] graph = {{1, 3, 1}, {1, 2, 9}, {1, 6, 8}, {2, 4, 13}, {2, 5, 2}, {2, 6, 7}, {3, 4, 12}, {4, 7, 17}, {5, 6, 5}, {5, 7, 20}};

        System.out.println(prim(graph, v, e));
    }

    /**
     * Node와 Edge 헷갈리지 말자
     * 아래는 둘 다 Edge
     * to, weight 두 요소
     * to, from, weight 세 요소
     */
//    public static class Node {
    public static class Edge {
        int to;
        //        int from;
        int weight;

        public Edge(int to, int weight) {
            this.to = to;
//            this.from = from;
            this.weight = weight;
        }
    }
}