알고리즘 문제풀이

[프로그래머스] 섬 연결하기 (C++)

SiO2whocode 2024. 10. 29. 16:35
728x90

https://school.programmers.co.kr/learn/courses/30/lessons/42861?language=cpp

 

프로그래머스

SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr

그리디

N개의 섬과 그 섬을 잇는 다리 건설 정보가 2차원 배열로 주어진다.

costs배열은 (출발점, 도착점, 비용)을 원소로 갖는 배열임

이때, 모든 섬이 서로 연결되도록 다리를 설치하는 데 드는 최소비용을 반환하는 문제

 

접근방법

처음에는 DFS로 풀어보려고 했다가 안돼서 그리디 문제 답게 그리디하게 풀었다 (?)

목적은 1) 새로운 섬을, 2) 적은 비용으로 가는 것.

그래서 이미 방문한 섬으로부터 갈 수 있는 섬들 중, 가장 비용이 적은 다리를 먼저 pick하는 식으로 풀었다.

음..우선순위 큐를 쓰면 좋을 것 같긴 했지만, 그냥 내가 최소 다리를 구하는 식으로 풀었다.

 

1. 인접행렬 셋팅

각 섬을 출발점으로 하는 모든 다리 정보를 정리한다. ex. 0 : (1(도착점), 1(비용)) ... 이런식으로

2. 그리고 우선 0번 섬을 방문한다고 가정하고 방문한 섬을 담아두는 배열(visitedIslands)에 넣고, 방문했다고 표시한다(visit)

3. 이제 반복문을 도는데, 모든 섬을 방문할 때까지 반복한다. (visitedIslands에 모든 섬이 담길 때 까지) - (못가는 섬은 없다했으니까..)

3.1. 반복문 안에서는, 이미 방문한 섬들로부터 만들 수 있는 다리 중 가장 비용이 싸면서, 아직 방문하지 않은 섬에 도착하는 다리를 고른다. 

4. 반복문이 끝난 후 찾은 다리를 만든다. = 해당 다리로 갈 수 있는 섬을 방문한다. + 비용에 그 다리를 건설하는 비용을 더한다.

 

오답노트

놀랍게도 adj 셋팅할 때 costs.size()가 아니라 n으로 둬서 틀렸었음 레전드..

 

인접행렬에 다리 양방향을 모두 넣어둬서 좀 비효율적이긴 한데 (그래도 크루스칼보다 시간 더 적던데요)

 

근데 크루스칼로 푸는게 너무 로직이 깔끔해 보여서 그걸로도 풀어봅시다.

 

소스코드

#include <string>
#include <vector>
#include <iostream>

using namespace std;


int solution(int n, vector<vector<int>> costs) {
    int answer = 0;
    
    vector<int> visitedIslands;
    vector<bool> visit(n, false);
    
    // adj 셋팅
    int maxCost = 0;
    vector<vector<pair<int,int>>> adj(n);
    for(int i = 0 ; i < costs.size() ; i++){
        adj[costs[i][0]].push_back({costs[i][1], costs[i][2]});
        adj[costs[i][1]].push_back({costs[i][0], costs[i][2]});
        if(maxCost < costs[i][2])
            maxCost = costs[i][2];
    }
    
    // 0번 섬 담기
    visitedIslands.push_back(0);
    visit[0] = true;
    
    while(visitedIslands.size() < n) {
        int min = maxCost;
        int minIsland = 0;
        
        for(int i = 0 ; i < visitedIslands.size() ; i++){
            // 섬 하나에 대해서 연결되는 섬 확인
            int now = visitedIslands[i];
            for(int j = 0 ; j < adj[now].size() ; j++){
                if(!visit[adj[now][j].first] && min >= adj[now][j].second){
                    min = adj[now][j].second;
                    minIsland = adj[now][j].first;
                }
            }
        }
        // 최소 거리 섬 방문
        visit[minIsland] = true;
        visitedIslands.push_back(minIsland);
        answer += min;
    }
    
    return answer;
}

 

크루스칼 풀이

각 섬의 연결정보를 저장하기 위해, 부모자식 관계를 이용하는 알고리즘이다.

모든 섬에 대해서 우선 본인의 부모노드는 본인이라고 초기화해두고, 섬을 다리로 연결할 때마다

출발점을 도착점의 부모노드로 설정해두면, 다리를 탐색할 때 두 섬이 이미 연결되어 있는지 아닌지 빠르게 확인할 수 있다.

그 전에, 최소비용을 구하는 것을 보장하기 위해서 costs배열을 cost에 대해 오름차순으로 정렬해둔다.

그리고나서 가장 적은 비용이 드는 다리부터 확인하면서 아직 연결되지 않는 섬들을 연결해가면

최소비용을 만족시키면서 모든 섬을 연결할 수 있다.

 

1. (costs배열 정렬 후) 첫번째 다리 정보부터 보면서, 출발점과 도착점 각각의 부모 노드를 찾는다.

2. 두 섬의 부모노드가 다르면, 이 두 섬은 아직 연결되지 않았다는 뜻이므로 (이전 풀이와 연결해 설명하면, 두 점 중 하나는 아직 방문하지 않은 섬이므로), 다리를 건설한다. = 도착점의 부모노드에 출발점(의 부모노드)를 넣고, 결과값에 해당 다리 비용을 더한다.

2-1. 두 섬의 부모노드가 같으면 이미 연결되어있는 섬이므로 패스

이렇게 반복문이 끝나면 결과값이 최소비용이 된다. 끝~

 

 

소스코드

#include <string>
#include <vector>
#include <algorithm>

using namespace std;

int parents[100];

int getParents(int node){
    if(parents[node] == node){
        return node;
    } else {
        return parents[node] = getParents(parents[node]);
    }
}

bool compare(vector<int> a, vector<int> b){
    return a[2] < b[2];
}

int solution(int n, vector<vector<int>> costs) {
    int answer = 0;
    
    for(int i = 0 ; i < n ; i++){
        parents[i] = i;
    }
    
    sort(costs.begin(), costs.end(), compare);
    
    for(int i = 0 ; i < costs.size(); i++){
        int start = getParents(costs[i][0]);
        int end = getParents(costs[i][1]);
        int cost = costs[i][2];
        
        if(start != end){
            parents[end] = start;
            answer += cost;
        }
    }
    
    return answer;
}
728x90