C++ 코딩테스트 강좌, 다익스트라

다익스트라 알고리즘(Dijkstra’s Algorithm)은 그래프에서 한 정점에서 다른 정점까지의 최단 경로를 찾는 데 매우 유용한 알고리즘입니다. 특히 가중치가 있는 그래프에서 최단 경로를 구하는 데 사용됩니다. 본 강좌에서는 다익스트라 알고리즘의 원리를 이해하고, C++을 사용하여 그 구현 방법을 배워보겠습니다.

문제 설명

주어진 N개의 도시와 M개의 도로가 있을 때, 한 도시에서 다른 도시로 가는 최소 비용을 구하시오. 각 도시는 정점으로 표현되고 도로는 그래프에서 간선으로 표현됩니다.

도시와 도로에 대한 정보는 다음과 같습니다:

  • 도시의 수: N (1 ≤ N ≤ 100,000)
  • 도로의 수: M (1 ≤ M ≤ 200,000)
  • 도로 정보: A, B, C (도시 A와 도시 B를 연결하는 도로의 가중치가 C)

예를 들어, 5개의 도시와 6개의 도로가 있는 그래프가 주어진다면, 다음과 같은 형태로 입력됩니다:

5 6
1 2 4
1 3 2
2 3 5
1 4 1
4 3 3
2 5 7
        

입력 형식

1번째 줄: N M
2번째 줄부터 M번째 줄: A B C (각 도로에 대한 정보)
        

출력 형식

각 도시(1~N)에 대한 최소 비용을 출력한다. 
불가능한 경우 -1을 출력한다.
        

예제 입력

5 7
1 2 2
1 3 3
2 3 1
2 4 4
3 4 2
3 5 5
4 5 1
        

예제 출력

1
2
3
3
4
        

알고리즘 설명

다익스트라 알고리즘은 한 정점에서 출발하여 다른 정점까지의 최단 경로를 찾기 위해 그리디 방식으로 작동합니다. 기본적인 구상은 다음과 같습니다:

  1. 모든 정점까지의 초기 거리를 무한대로 설정하고, 시작 정점의 거리는 0으로 설정합니다.
  2. 가장 짧은 거리를 가진 정점을 선택하고, 그 정점에 인접한 모든 정점의 거리를 업데이트합니다.
  3. 모든 정점에 대한 거리가 업데이트될 때까지 이 과정을 반복합니다.

구현 단계

  1. 그래프 생성: 인접 리스트를 사용하여 그래프를 표현합니다.
  2. 우선순위 큐 활용: 가장 짧은 경로를 찾기 위해 우선순위 큐(힙)를 사용합니다.
  3. 거리 배열 초기화: 각 정점에 대한 거리를 초기화합니다.
  4. 최단 경로 계산: 최소 거리 갱신 과정을 반복합니다.

C++ 코드 구현

#include <iostream>
#include <vector>
#include <queue>
#include <utility>
#include <limits>

using namespace std;

vector<pair<int, int>> graph[100001];
vector<int> dist;

void dijkstra(int start) {
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
    dist[start] = 0;
    pq.push(make_pair(0, start));

    while (!pq.empty()) {
        int currentDist = pq.top().first;
        int currentNode = pq.top().second;
        pq.pop();

        if (currentDist > dist[currentNode]) continue;

        for (auto edge : graph[currentNode]) {
            int next = edge.first;
            int weight = edge.second;

            if (dist[currentNode] + weight < dist[next]) {
                dist[next] = dist[currentNode] + weight;
                pq.push(make_pair(dist[next], next));
            }
        }
    }
}

int main() {
    int N, M;
    cin >> N >> M;
    dist.resize(N + 1, numeric_limits<int>::max());

    for (int i = 0; i < M; i++) {
        int A, B, C;
        cin >> A >> B >> C;
        graph[A].push_back(make_pair(B, C));
        graph[B].push_back(make_pair(A, C)); // 무향 그래프일 경우
    }

    dijkstra(1); // 1번 도시에서 시작

    for (int i = 1; i <= N; i++) {
        if (dist[i] == numeric_limits<int>::max()) {
            cout << -1 << &endl
        } else {
            cout << dist[i] << &endl
        }
    }

    return 0;
}
        

코드 설명

위의 코드에서는 다음과 같은 방식으로 다익스트라 알고리즘을 구현합니다:

  • 인접 리스트 생성: 주어진 도시와 도로 정보를 읽고, 그래프를 인접 리스트 형태로 표현합니다.
  • 우선순위 큐: 최소 거리를 찾기 위해 우선순위 큐를 사용하여 가장 짧은 거리를 계속해서 추출합니다.
  • 거리 갱신: 현재 정점에서 인접한 정점으로 이동할 때 새로운 거리가 더 짧으면 거리를 갱신합니다.
  • 최종 결과 출력: 각 도시까지의 최단 거리를 출력합니다. 도달할 수 없는 도시의 경우 -1을 출력합니다.

복습 및 마무리

이번 강좌를 통해 다익스트라 알고리즘의 작동 원리를 이해하고, C++로 구현하는 방법을 배웠습니다. 알고리즘과 자료구조는 코딩 테스트에서 매우 중요한 주제이며, 다익스트라 알고리즘은 특히 그래프 관련 문제에서 많이 활용됩니다. 추가적으로 BFS, DFS와 같은 그래프 탐색 알고리즘도 연습해보시기 바랍니다.

다익스트라 알고리즘을 심화 학습하기 위해서는 다양한 문제를 풀어보고, 다른 알고리즘과 비교해보는 것이 도움이 됩니다. 다음 번에는 벨만-포드 알고리즘이나 플로이드-워셜 알고리즘에 대해서도 알아보도록 하겠습니다. 감사합니다!

C++ 코딩테스트 강좌, 다리 놓기

문제 정의

다리 놓기 문제는 N개의 다리와 M개의 기둥이 주어질 때, 기둥 사이에 놓일 수 있는 다리의 개수를 구하는 문제입니다. 각 기둥 사이에 놓인 다리들은 서로 연결될 수 있어야 하며, 한 기둥에 최대 한 개의 다리만을 놓을 수 있습니다. 이 문제는 조합론과 동적 프로그래밍을 활용하여 풀 수 있습니다.

문제 예시

예를 들어 N = 2, M = 4인 경우를 생각해 봅시다. 다음과 같이 기둥 A, B, C, D가 있을 때, 다리를 놓는 방법은 다음과 같습니다.

  • A – B
  • A – C
  • A – D
  • B – C
  • B – D
  • C – D

위와 같이 최대 6개의 조합이 가능합니다.

문제 이해 및 분석

문제를 해결하기 위해서는 조합(combination) 개념을 적용해야 합니다. 이 문제는 기둥 M개에서 다리 N개를 선택하는 것으로, 조합의 수는 다음과 같은 수식으로 표현될 수 있습니다.

            C(M, N) = M! / (N! * (M - N)!)
            

여기서 C는 조합을 의미하며, M은 기둥의 총 개수, N은 놓을 다리의 개수입니다. 이를 C++로 구현할 때 조합을 계산할 수 있는 함수를 만들어 주어야 합니다.

알고리즘 설계

주어진 N과 M값에 따라 다리 놓기 문제의 경우는 조합을 활용하여 다리의 배치를 결정하는 방식으로 해결할 수 있습니다. 다리 놓기 문제의 점화식은 다음과 같습니다:

            dp[n][m] = dp[n-1][m-1] + dp[n][m-1]
            

여기서 dp[n][m]은 n개의 다리를 m개의 기둥에 놓을 수 있는 경우의 수를 의미합니다. 기둥 하나를 선택해 다리를 놓고 남은 다리를 놓는 경우의 수와 다리를 놓지 않고 넘어가는 경우의 수를 더하여 구할 수 있습니다.

알고리즘 구현

다음은 이 알고리즘을 C++로 구현한 예제 코드입니다:

#include <iostream>
#include <vector>

using namespace std;

long long combinations(int n, int r) {
    if (r > n) return 0;
    if (r == 0 || r == n) return 1;
    vector<long long> dp(n + 1);
    dp[0] = 1;

    for (int i = 1; i <= n; ++i) {
        for (int j = i; j >= 1; --j) {
            dp[j] += dp[j - 1];
        }
    }
    return dp[r];
}

int main() {
    int n, m;
    cout << "다리의 수와 기둥의 수를 입력하세요: ";
    cin >> n >> m;

    long long result = combinations(m, n);
    cout << "최대 다리 놓기 가능 조합 수: " << result << endl;
    return 0;
}
            

코드 설명

위의 코드에서 우리는 다리의 수와 기둥의 수를 입력받고, 조합을 계산하여 출력합니다. combinations 함수에서는 다이나믹 프로그래밍을 사용해 조합을 계산하는 방식을 이용했습니다. 이를 통해 효율적인 계산이 가능해졌습니다.

복잡도 분석

이 알고리즘의 시간 복잡도는 O(n * r)으로, r은 기둥에서 선택할 다리의 수를 의미합니다. 공간 복잡도 또한 O(n)입니다. 이러한 효율적인 구조 덕분에 상당히 큰 값에서도 빠르게 결과를 도출할 수 있습니다.

결론

다리 놓기 문제는 조합론의 기초를 잘 이해하고, 다이나믹 프로그래밍을 활용하여 효율적으로 풀 수 있는 좋은 연습문제입니다. 이러한 방식은 코딩 테스트 뿐만 아니라 임의의 조합 문제를 해결하는 데에도 유용합니다. 여러분은 이 문제를 통해 기본적인 프로그래밍 능력 뿐만 아니라 문제 해결 능력을 키울 수 있을 것입니다.

이 글은 C++ 코딩테스트에 대한 블로그 시리즈의 일부로, 더 많은 문제 풀이 강좌가 계속해서 올라올 예정입니다.

C++ 코딩테스트 강좌, 다리 만들기

문제 설명

오늘의 문제는 ‘다리 만들기’입니다. 이 문제는 주어진 두 섬을 연결하는 다리를 만드는 것으로, 각 섬에는 자연수를 가진 깊이가 주어집니다.
다리를 만들기 위해서는 두 섬의 깊이를 합산해야 하며, 모든 다리의 높이는 동일해야 합니다. 다리를 만들기 위해 두 섬의 높이에 따라 여러 방법이 존재하며,
가장 높은 다리를 만들 수 있는 방법을 찾는 것이 목표입니다.

문제 입력

첫 번째 줄에는 섬의 개수 N과 다리의 개수 M이 주어집니다. (1 ≤ N, M ≤ 105)

다음 줄에는 각 섬의 깊이에 대한 배열 heights가 주어지며, heights[i]는 i번째 섬의 깊이를 나타냅니다.
각 깊이는 1부터 1000까지의 자연수입니다.

출력

가장 높은 다리를 만들기 위해 필요한 다리의 높이 최댓값을 출력합니다.

문제 풀이

이 문제를 해결하기 위해서는 우선 간단하게 두 섬의 깊이를 파악하고, 그에 따라 다리의 높이를 최적화할 필요가 있습니다.
가장 높은 다리는 두 섬에서 가질 수 있는 최대 깊이의 합이라고 방정식을 세울 수 있습니다.
즉, 두 섬의 깊이의 평균값 이상일 때 두 섬을 직접 잇는 다리를 만들 수 있습니다.

1. 문제 분석

주어진 섬의 깊이에서 다리를 만들기 위해 우선 직관적으로 각 섬의 깊이의 평균을 계산해 보겠습니다.
다음은 다리의 높이를 결정하기 위해 할 수 있는 중요한 분석입니다.

2. 알고리즘 설계

1. 두 섬의 깊이를 평균하여 두 섬의 높이 합을 구합니다.
2. 전체 섬의 깊이를 조합하여 가능한 모든 다리 만들기를 시도합니다.
3. 다리를 놓을 수 있는 최대 높이를 기록합니다.
4. 이러한 과정은 이분 탐색을 통해 최적화할 수 있습니다.

3. C++ 구현

이제 위에서 설명한 알고리즘을 C++ 코드로 구현해 보겠습니다. 아래 코드는 heights 배열에서 다리를 만들 수 있는 최대 높이를 계산합니다.


#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main() {
    int N, M;
    cin >> N >> M;
    vector<int> heights(N);

    for(int i = 0; i < N; i++) {
        cin >> heights[i];
    }

    int max_height = 0;
    
    // 다리를 만들기 위한 최대 높이를 구하는 로직
    for (int i = 0; i < heights.size(); i++) {
        for (int j = i + 1; j < heights.size(); j++) {
            int bridge_height = heights[i] + heights[j];
            max_height = max(max_height, bridge_height);
        }
    }

    cout << max_height << endl;

    return 0;
}

4. 복잡도 분석

위의 코드에서 가장 큰 문제는 이중 루프를 사용함으로써 O(N^2)의 시간 복잡도를 가지게 됩니다.
이는 입력의 크기가 클수록 성능에 심각한 영향을 미칠 수 있으므로, 최적화를 위해 정렬 알고리즘을 사용할 수 있습니다.

5. 최적화된 구현

이제 두 성에서 가장 높은 다리의 최적화를 위해 해당 깊이를 정렬하고, 이분 탐색 기법을 활용하여
어려운 구간을 찾아보는 방법이 있습니다. 아래는 이를 고려하여 수정된 C++ 코드입니다.


#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main() {
    int N, M;
    cin >> N >> M;
    vector<int> heights(N);

    for(int i = 0; i < N; i++) {
        cin >> heights[i];
    }

    sort(heights.begin(), heights.end());
    int max_height = 0;

    for (int i = 0; i < N; i++) {
        for (int j = i + 1; j < N; j++) {
            max_height = max(max_height, heights[i] + heights[j]);
        }
    }

    cout << max_height << endl;

    return 0;
}

결론

오늘은 ‘다리 만들기’라는 주제를 다루어봤습니다. 이 문제를 통해 데이터를 처리하며
알고리즘을 최적화하는 과정을 배웠습니다.
앞으로 더 많은 알고리즘 문제를 실습하면서 이러한 기술을 더욱 발전시켜 나가길 바랍니다.

C++ 코딩테스트 강좌, 다각형의 면적 구하기

이번 강좌에서는 C++을 사용하여 다각형의 면적을 구하는 방법에 대해 설명하겠습니다. 알고리즘 문제는 다음과 같이 주어집니다.

문제 설명

주어진 점들이 이루는 다각형의 면적을 구하시오. 다각형의 꼭짓점은 시계 또는 반시계 방향으로 주어지며, 점의 좌표는 정수입니다. 다각형의 면적은 다음의 공식을 통해 구할 수 있습니다:

면적 = (1/2) * | Σ (xi * yi+1 – xi+1 * yi) |

여기서 (xi, yi)는 다각형의 i번째 꼭짓점의 좌표이며, (xn, yn)는 다각형의 마지막 꼭짓점입니다. 다각형의 꼭짓점 개수는 3 이상입니다.

입력

  • 첫 번째 줄에는 다각형의 꼭짓점 개수 N (3 ≤ N ≤ 105)이 주어진다.
  • 다음 N줄에는 각 꼭짓점의 x, y 좌표가 정수로 주어진다. (-109 ≤ x, y ≤ 109)

출력

다각형의 면적을 소수점 둘째 자리까지 출력한다.

예시 입력

4
0 0
4 0
4 3
0 4

예시 출력

12.00

문제 풀이 방법

다각형의 면적을 구하는 알고리즘은 다음과 같은 절차로 진행됩니다.

  1. 입력을 받고 좌표를 저장합니다.
  2. 면적 공식을 사용하여 다각형의 면적을 계산합니다.
  3. 계산된 면적을 소수점 둘째 자리까지 반올림하여 출력합니다.

1. 입력 받기

먼저, 사용자는 다각형의 꼭짓점 개수와 좌표를 입력해야 합니다. 이를 위해 벡터를 사용하여 좌표를 저장하겠습니다.

2. 면적 계산

주어진 공식에 따라 면적을 계산하기 위해, 주어진 점들을 반복하면서 계산을 진행합니다.

3. 출력 형식 맞추기

출력은 소수점 둘째 자리까지 표시해야 하므로, C++의 iomanip 헤더의 setprecisionfixed를 사용하여 형식을 맞출 수 있습니다.

C++ 코드 예제

#include 
#include 
#include 

using namespace std;

int main() {
    int N;
    cin >> N;

    vector> points(N);
    for (int i = 0; i < N; i++) {
        cin >> points[i].first >> points[i].second;
    }

    double area = 0;
    for (int i = 0; i < N; i++) {
        int j = (i + 1) % N;  // 다음 점, 마지막 점은 처음 점으로
        area += points[i].first * points[j].second;
        area -= points[j].first * points[i].second;
    }

    area = abs(area) / 2.0; // 절대값을 취하고 2로 나눈다.

    cout << fixed << setprecision(2) << area << endl;

    return 0;
}

코드 설명

위의 코드는 다각형의 면적을 구하기 위해 다음의 과정을 거칩니다:

  • 입력 받기: 먼저, 다각형의 꼭짓점 개수 N과 각 꼭짓점의 좌표를 입력받습니다. 이를 위해 vector>를 사용하여 좌표를 저장합니다.
  • 면적 계산: 주어진 면적 공식을 사용하여 면적을 계산합니다. 이때, 꼭짓점 인덱스를 ij로 관리하며, 마지막 점에서 처음 점으로 다시 이어주는 형태로 구현합니다.
  • 출력: 계산된 면적을 소수점 둘째 자리까지 출력하기 위해 fixedsetprecision을 사용합니다.

시간 복잡도

위의 알고리즘의 시간 복잡도는 O(N)입니다. 각 꼭짓점을 한 번씩 방문하여 면적을 계산하기 때문입니다. 따라서 N이 최대 105일 때도 충분히 빠르게 실행됩니다.

결론

이 강좌를 통해 C++을 사용하여 다각형의 면적을 효율적으로 계산하는 방법을 배웠습니다. 알고리즘 문제해결 능력을 기르는 데 큰 도움이 될 것입니다. 앞으로 더욱 다양한 문제를 통해 연습해보시기 바랍니다!

C++ 코딩테스트 강좌, 너비 우선 탐색

여러분 안녕하세요! 오늘은 코딩테스트에 자주 등장하는 너비 우선 탐색(Breadth-First Search, BFS) 알고리즘에 대해 설명하겠습니다. 이 강좌에서는 BFS의 기본 개념을 살펴보고, 이를 활용한 알고리즘 문제를 하나 풀어 보도록 하겠습니다.

BFS란 무엇인가?

너비 우선 탐색(BFS)은 그래프 이론에서 노드를 탐색하는 알고리즘 중 하나입니다. BFS는 한 노드에서 시작하여 인접한 모든 노드를 탐색한 후, 그 다음 단계에서 인접한 노드를 탐색하는 방식으로 진행됩니다. 이를 통해 최단 경로 문제를 풀거나, 최단 거리도 계산할 수 있습니다.

BFS의 특징

  • 큐(Queue) 자료구조 사용: BFS는 탐색할 노드를 저장하기 위해 큐 자료구조를 사용합니다.
  • 최단 경로 보장: BFS를 사용하면 가중치가 동일한 그래프의 경우 최단 경로를 보장합니다.
  • 레벨 순으로 탐색: BFS는 각 레벨을 순차적으로 탐색하기 때문에, 가까운 노드부터 먼 노드로 탐색이 이루어집니다.

문제 설명

이제 BFS를 활용한 간단한 문제를 살펴보겠습니다.

문제: 01-너비 우선 탐색을 통한 최단 거리 계산

주어진 무방향 그래프에서 특정 시작 노드로부터 모든 다른 노드까지의 최단 거리를 계산하는 프로그램을 작성하시오. 입력은 그래프의 노드 수와 간선 수, 그리고 각 간선의 양쪽 노드를 나타냅니다.

입력 형식

첫 번째 줄에는 그래프의 노드 수 N (1 ≤ N ≤ 10^5)와 간선 수 M (1 ≤ M ≤ 2 * 10^5)이 주어진다.
다음 M줄에 걸쳐 각 간선의 양쪽 노드 u, v (1 ≤ u, v ≤ N)를 나타낸다.
마지막 줄에는 시작 노드 S가 주어진다.

출력 형식

각 노드까지의 최단 거리를 공백으로 구분하여 출력한다.
연결되어 있지 않은 노드는 -1로 출력한다.

문제 해결 과정

BFS를 사용하여 문제를 해결하기 위한 과정을 단계별로 살펴보겠습니다.

1단계: 그래프 표현

그래프를 표현하기 위해 인접 리스트를 사용하겠습니다. 인접 리스트는 각 노드에 연결된 다른 노드들을 리스트 형태로 저장합니다. 이를 통해 메모리를 효율적으로 사용할 수 있습니다.

2단계: BFS 구현

BFS는 큐를 사용하여 인접한 노드를 탐색합니다. 탐색 순서는 다음과 같습니다:

  1. 시작 노드를 큐에 삽입하고, 거리를 0으로 초기화합니다.
  2. 큐에서 노드를 하나 꺼내고, 그 노드에 인접한 모든 노드를 확인합니다.
  3. 탐색한 노드가 방문하지 않은 노드라면, 거리를 현재 노드의 거리 + 1로 설정하고 큐에 삽입합니다.
  4. 큐가 비어있을 때까지 이 과정을 반복합니다.

3단계: 코드 작성

이제 위의 과정을 바탕으로 C++ 코드를 작성해 보겠습니다.


#include 
#include 
#include 
#include 

using namespace std;

const int MAX_N = 100000;
vector graph[MAX_N + 1];
int dist[MAX_N + 1];

void bfs(int start, int n) {
    queue q;
    fill(dist, dist + n + 1, -1);  // 거리 초기화
    dist[start] = 0;                // 시작 노드 거리 설정
    q.push(start);                   // 큐에 시작 노드 삽입

    while(!q.empty()) {
        int current = q.front();
        q.pop();
        
        for(int neighbor : graph[current]) {
            if(dist[neighbor] == -1) { // 방문하지 않은 노드
                dist[neighbor] = dist[current] + 1;
                q.push(neighbor);
            }
        }
    }
}

int main() {
    int N, M, S;
    cin >> N >> M;
    
    for(int i = 0; i < M; ++i) {
        int u, v;
        cin >> u >> v;
        graph[u].push_back(v);         // 무방향 간선 추가
        graph[v].push_back(u);
    }
    cin >> S;

    bfs(S, N); // BFS 호출

    for(int i = 1; i <= N; ++i) {
        cout << dist[i] << " ";       // 거리 출력
    }
    cout << endl;

    return 0;
}

4단계: 코드 실행 및 결과

위의 코드를 실행하면, 시작 노드 S로부터 각 노드까지의 최단 거리를 계산하여 출력합니다. 예를 들어, 아래와 같은 입력이 주어진다면:

입력 예제:

6 7
1 2
1 3
2 4
3 4
4 5
4 6
5 6
1

출력 예제:

0 1 1 2 3 2 

5단계: 결론

이번 강좌에서는 C++ 코딩테스트에서 자주 등장하는 너비 우선 탐색(BFS) 알고리즘에 대해 알아보았습니다. BFS는 최단 경로 문제를 해결하는 데 유용하며, 다양한 문제에 적용될 수 있습니다. 이 알고리즘을 잘 이해하고 활용한다면 코딩 테스트에서 좋은 성과를 거둘 수 있을 것입니다.

이번 강좌가 여러분의 취업 준비에 도움이 되었기를 바랍니다. 감사합니다!