자바스크립트 코딩테스트 강좌, 버블 정렬

오늘은 자바스크립트에서 가장 기초적인 정렬 알고리즘 중 하나인 버블 정렬(Bubble Sort)에 대해 알아보겠습니다. 버블 정렬은 간단하지만 비효율적인 정렬 알고리즘으로, 주로 교육적인 목적이나 알고리즘의 기초를 배우기 위해 사용됩니다.

버블 정렬 개요

버블 정렬은 주어진 리스트를 반복적으로 탐색하면서, 인접한 두 요소를 비교하고 그 순서를 바꾸는 방식으로 작동합니다. 이 과정은 리스트가 정렬되었다고 판단될 때까지 계속 진행됩니다. 최악의 경우 시간 복잡도는 O(n^2)입니다.

작동 원리

버블 정렬의 기본적인 작동 방식은 다음과 같습니다:

  1. 리스트의 첫 번째 요소부터 시작하여 두 개의 인접한 요소를 비교합니다.
  2. 왼쪽 요소가 오른쪽 요소보다 크다면, 두 요소의 위치를 바꿉니다.
  3. 리스트의 끝까지 이 과정을 반복합니다. 이 과정을 한 번 수행하면 가장 큰 요소가 마지막으로 이동하게 됩니다.
  4. 위 과정(1-3)을 남은 요소에 대해 반복합니다.

문제 정의

문제 설명

배열 arr가 주어집니다. 이 배열을 버블 정렬 알고리즘을 사용하여 오름차순으로 정렬한 후, 정렬된 배열을 반환하는 함수를 작성하세요.

입력 예시

arr = [64, 34, 25, 12, 22, 11, 90]

출력 예시

[11, 12, 22, 25, 34, 64, 90]

문제 풀이 과정

1단계: 함수 정의

먼저 버블 정렬을 수행할 함수를 정의합니다. 함수 이름은 bubbleSort로 하겠습니다. 입력으로는 배열을 받도록 합니다.

2단계: 이중 반복문 사용

버블 정렬은 이중 반복문을 사용하여 구현합니다. 외부 반복문은 배열의 마지막 요소까지 진행하고, 내부 반복문은 두 인접한 요소를 비교하여 정렬합니다.

3단계: 요소 비교 및 교환 로직 구현

내부 반복문에서 인접한 두 요소를 비교하고, 그 순서를 바꿉니다. 이를 위해 간단한 조건문을 사용합니다.

function bubbleSort(arr) {
        let n = arr.length;
        for (let i = 0; i < n - 1; i++) {
            for (let j = 0; j < n - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    // 요소 교환
                    let temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
        return arr;
    }

4단계: 유틸리티 함수 추가 (선택 사항)

정렬된 결과를 확인하기 위해 배열을 출력하는 유틸리티 함수를 작성할 수 있습니다. 간단한 console.log를 활용하여 결과를 확인할 수 있습니다.

const arr = [64, 34, 25, 12, 22, 11, 90];
    console.log(bubbleSort(arr)); // [11, 12, 22, 25, 34, 64, 90]

버블 정렬의 시간복잡도

버블 정렬의 시간복잡도는 최악의 경우 O(n^2)입니다. 이는 전체 배열을 두 번 반복하기 때문입니다. 최선의 경우 (이미 정렬된 경우)에도 O(n)이지만, 일반적으로는 비효율적입니다.

공간 복잡도는 O(1)로, 추가로 사용하는 메모리가 상수이기 때문에 효율적입니다.

버블 정렬의 의미와 활용

버블 정렬은 그 단순한 구현 덕분에 알고리즘을 배우는 데에 적합합니다. 하지만 실제 산업 현장에서는 그 성능이 비교적 떨어져, 다른 정렬 알고리즘이 선호됩니다. 하지만 알고리즘 공부를 하면서 이해할 수 있는 중요한 기본 개념입니다.

결론

이번 글에서는 자바스크립트로 버블 정렬을 구현하는 방법에 대해 배웠습니다. 기초적인 정렬 알고리즘을 이해하고, 이를 통해 더 복잡한 알고리즘을 배우는 발판을 마련할 수 있기를 바랍니다. 질문이나 필요한 추가 정보가 있다면 댓글로 남겨주세요!

© 2023 코딩 테스트 강좌

자바스크립트 코딩테스트 강좌, 불우이웃돕기

문제 설명

불우이웃을 돕기 위해 기부금을 모으려 합니다. 각 기부자는 기부 가능한 금액을 입력하면 됩니다. 기부자가 입력한 금액은 리스트에 저장하고, 최종적으로 기부받은 모든 금액의 총합을 계산하는 함수를 구현하세요. 또한, 기부자가 기부할 수 있는 최대 금액은 100000원으로 제한합니다.

입력

기부자 수 n (1 <= n <= 1000)과 각 기부자의 기부 금액이 주어집니다.

출력

전체 기부금의 총합을 출력합니다.

예제 입력

    5
    10000
    25000
    50000
    120000
    30000
    

예제 출력

    95000
    

문제 풀이 접근법

이 문제는 주어진 입력 값을 처리하여 총합을 계산하는 단순한 알고리즘을 요구합니다. 입력으로 주어진 기부 금액을 배열에 저장하고, 배열의 모든 요소를 합산하여 결과를 도출하는 방식으로 접근할 수 있습니다. 다음은 이 문제를 푸는 단계입니다.

1단계: 입력 받기

입력을 받기 위해, 사용자로부터 기부자 수(n)와 각 기부자의 기부 금액을 입력받겠습니다. 이 입력 값들은 배열에 저장됩니다. 기부 금액이 100000원보다 큰 경우는 무시하고, 이를 체크하는 로직이 필요합니다.

2단계: 배열에 기부 금액 저장

올바른 기부 금액만 배열에 추가하고, 총 기부 금액을 계산할 변수를 설정합니다.

3단계: 총 합계 계산 및 출력

배열에 저장된 기부 금액의 합계를 계산하고 결과를 출력합니다.

자바스크립트 코드 구현

이제 전체 로직을 자바스크립트 코드로 구현해 보겠습니다. 다음은 위에서 설명한 로직을 기반으로 한 코드입니다:


function calculateDonation() {
    const n = parseInt(prompt("기부자의 수를 입력하세요: "));
    let donations = [];
    let totalDonation = 0;

    for (let i = 0; i < n; i++) {
        let donation = parseInt(prompt(`${i + 1}번째 기부 금액을 입력하세요: `));

        // 기부 금액이 100000원 이하일 경우에만 배열에 추가
        if (donation <= 100000) {
            donations.push(donation);
            totalDonation += donation;
        } else {
            console.log("기부 금액은 100000원 이하이어야 합니다.");
        }
    }

    console.log(`총 기부금: ${totalDonation}원`);
}

calculateDonation();
    

코드 설명

코드를 분석해보면, calculateDonation 함수가 정의되며, 먼저 사용자가 기부자 수를 입력받습니다. 그 다음 for문을 통해 각 기부 금액을 입력받고, 조건문으로 기부 금액이 100000원 이하인 경우에만 배열에 추가하며 총합을 계산합니다. 기부 금액이 초과할 경우 경고 메시지를 출력합니다. 마지막으로 총 기부금액을 출력합니다.

결론

이번 강좌에서는 자바스크립트를 사용하여 기부 데이터를 관리하는 간단한 프로그램을 구현해 보았습니다. 이 코드는 기부금 총합을 쉽게 계산할 수 있으며, 기부자 수와 기부 금액의 유효성을 체크하는 로직을 포함하고 있습니다. 이런 방식으로 간단한 문제를 해결해 나가는 경험을 통해 알고리즘 문제 해결에도 자신감을 가질 수 있습니다.

추가적인 연습 문제

문제 해결을 위한 연습을 위해 아래와 같은 문제를 추가로 해결해 보세요:

  1. 기부자의 기부 금액을 오름차순으로 정렬하여 출력하는 기능 추가하기
  2. 기부자의 평균 기부 금액을 계산하기
  3. 기부자 수가 10명 이상일 때, 10%의 추가 기부금을 자동으로 더하는 기능 구현하기

마무리

프로그래밍 연습은 단순히 문제를 푸는 것 이상으로 깊은 이해를 필요로 합니다. 문제를 해결해가는 과정에서 얻는 경험과 지식을 바탕으로, 더 나아가 자신만의 프로그램을 만들거나 더 복잡한 알고리즘 문제를 다룰 수 있는 자신감을 얻기 바랍니다.

자바스크립트 코딩테스트 강좌, 플로이드-워셜

작성자: 조광형

작성일: 2024년 11월 26일

목차

  1. 1. 소개
  2. 2. 문제 설명
  3. 3. 플로이드-워셜 알고리즘 이해
  4. 4. 자바스크립트 구현
  5. 5. 시간 복잡도
  6. 6. 결론

1. 소개

코딩테스트에서 알고리즘 문제는 빈번하게 출제되며, 다양한 알고리즘을 이해하고 구현할 수 있는 능력이 중요합니다. 이 강좌에서는 플로이드-워셜(Floyd-Warshall) 알고리즘에 대해 다루고, 이를 자바스크립트로 구현하는 방법을 배웁니다. 플로이드-워셜 알고리즘은 그래프에서 모든 정점 쌍 간의 최단 경로를 찾는 데 유용하며, 특히 대규모 그래프에 적합합니다.

2. 문제 설명

문제: 주어진 방향 그래프에서 모든 정점 사이의 최단 경로를 구하는 알고리즘을 작성하시오. 주어진 그래프는 가중치가 있는 간선으로 구성되어 있습니다. 만약 어떤 두 정점 사이에 경로가 없다면, 최단 경로의 값은 무한대로 처리합니다.


입력:
- 정점의 수 N (1 ≤ N ≤ 100)
- 간선의 수 M (1 ≤ M ≤ 10000)
- M개의 간선 정보 (A, B, C): A에서 B로 가는 가중치 C

출력:
- 목적지 정점 간의 최단 경로 길이를 나타내는 N x N 행렬을 출력하시오.
        

3. 플로이드-워셜 알고리즘 이해

플로이드-워셜 알고리즘은 동적 프로그래밍(dynamic programming) 기법을 활용하여 모든 쌍의 최단 경로를 계산합니다. 해당 알고리즘은 다음과 같은 단계를 포함합니다:

  1. 모든 정점 쌍 (i, j) 간의 초기 거리 값을 설정합니다. 직접 연결된 정점 간은 가중치를, 연결되지 않은 경우는 무한대(INFINITY)를 설정합니다.
  2. 각 정점을 중간 정점으로 설정하여, i에서 j로 가는 최단 경로를 체크합니다. 중간 정점을 k로 두고 i -> k -> j 경로가 i -> j 경로보다 짧은지 확인합니다.
  3. 이 과정을 모든 정점에 대해 반복합니다.

이렇게 함으로써 최종적으로 모든 정점 쌍 간의 최단 경로를 구할 수 있습니다.


function floydWarshall(graph) {
  const dist = JSON.parse(JSON.stringify(graph)); // 그래프를 복사하여 거리 행렬 초기화

  const n = graph.length; // 정점 수
  for (let k = 0; k < n; k++) {
    for (let i = 0; i < n; i++) {
      for (let j = 0; j < n; j++) {
        if (dist[i][j] > dist[i][k] + dist[k][j]) {
          dist[i][j] = dist[i][k] + dist[k][j];
        }
      }
    }
  }
  return dist;
}
        

4. 자바스크립트 구현

이제 플로이드-워셜 알고리즘을 자바스크립트로 구현해보겠습니다. 먼저 입력을 처리하고, 그래프를 초기화한 다음, 최단 경로를 계산하는 함수를 작성합니다:


function initializeGraph(N, edges) {
  const graph = Array.from({ length: N }, () => Array(N).fill(Infinity));
  for (let i = 0; i < N; i++) {
    graph[i][i] = 0; // 자기 자신으로 가는 경우는 0
  }

  edges.forEach(([A, B, C]) => {
    graph[A][B] = Math.min(graph[A][B], C); // 여러 간선이 있을 경우 최소 가중치로 업데이트
  });

  return graph;
}

// 문제를 해결하는 메인 함수
function solve(N, M, edges) {
  const graph = initializeGraph(N, edges);
  const shortestPaths = floydWarshall(graph);

  console.log("최단 경로 행렬:");
  shortestPaths.forEach(row => console.log(row.join(" ")));
}
        

5. 시간 복잡도

플로이드-워셜 알고리즘의 시간 복잡도는 O(N^3)입니다. 이는 N이 정점의 수일 때 각 정점 쌍을 확인하기 위해 3중 루프를 사용하기 때문입니다. 따라서 정점 수가 적당히 작은 경우 (100 이하) 효율적으로 사용될 수 있지만, 수백 이상의 정점이 있는 경우에는 다른 알고리즘을 고려해야 합니다.

6. 결론

본 강좌에서는 플로이드-워셜 알고리즘의 이론과 자바스크립트 구현 방법을 살펴보았습니다. 알고리즘의 이해 및 코드 구현 연습을 통해 코딩 테스트에서의 문제 해결 능력을 키울 수 있습니다. 다음 번에는 더욱 다양한 알고리즘을 다루어 보도록 하겠습니다.

자바스크립트 코딩테스트 강좌, 최소 신장 트리

코딩 테스트는 프로그래밍 능력을 평가하는 좋은 방법입니다. 오늘은 최소 신장 트리(MST) 문제를 해결하는 방법에 대해 알아보겠습니다.
이 문제는 다양한 분야에서 활용되며, 특히 컴퓨터 네트워크와 관련된 문제에서 자주 등장합니다.
최소 신장 트리를 구축하는 알고리즘은 여러 개가 있지만, 특히 크루스칼 알고리즘(Kruskal’s Algorithm)프림 알고리즘(Prim’s Algorithm)이 많이 사용됩니다.

문제 설명

아래 그래프를 나타내는 간선이 주어졌을 때, 최소 신장 트리를 구하는 문제를 해결해 보겠습니다.
각 간선의 가중치가 부여되어 있으며, 전체 가중치의 합이 최소가 되도록 연결된 그래프를 찾아야 합니다.

문제 정의

    입력:
        [(1, 2, 4), (1, 3, 1), (2, 3, 2), (2, 4, 5), (3, 4, 8), (1, 4, 3)]
    출력:
        최소 신장 트리의 간선 리스트: [(1, 3, 1), (2, 3, 2), (1, 4, 3), (2, 4, 5)]
        최소 가중치의 합: 11

문제 풀이 과정

1단계: 그래프 데이터 구조 이해하기

그래프는 정점(노드)와 간선으로 이루어져 있습니다.
여기서는 간선이 튜플의 형태로 `(시작 정점, 끝 정점, 가중치)`로 주어집니다.
우리는 이 데이터를 이용해 그래프를 구성해야 합니다.

2단계: 간선 정렬하기

크루스칼 알고리즘은 먼저 간선을 가중치를 기준으로 정렬합니다.
와 함께 주어진 간선 리스트를 오름차순으로 정렬하여 최소 가중치의 간선을 선택할 수 있도록 합니다.

3단계: Union-Find 구조로 사이클 검사하기

간선을 추가할 때 사이클이 발생하는지를 확인하기 위해 Union-Find 자료구조를 사용합니다.
이 자료구조는 두 가지 주요 기능을 가지고 있습니다:

  • Find: 특정 요소가 어떤 집합에 속하는지 찾기
  • Union: 두 요소가 속하는 집합을 합치기

사이클이 발생하지 않으면 간선을 선택하고, 그렇지 않으면 간선을 무시합니다.
알고리즘을 구현하는 데 필요한 기본적인 Union-Find 클래스와 그 메서드를 정의합니다.

4단계: MST 구현하기

이제 모든 단계의 구현을 결합하여 최종적으로 MST를 구해보겠습니다.
아래는 자바스크립트로 구현한 크루스칼 알고리즘의 예시입니다.

    class UnionFind {
        constructor(size) {
            this.parent = Array.from({length: size}, (_, index) => index);
            this.rank = Array(size).fill(1);
        }

        find(node) {
            if (this.parent[node] !== node) {
                this.parent[node] = this.find(this.parent[node]);
            }
            return this.parent[node];
        }

        union(node1, node2) {
            const root1 = this.find(node1);
            const root2 = this.find(node2);
            if (root1 !== root2) {
                if (this.rank[root1] > this.rank[root2]) {
                    this.parent[root2] = root1;
                } else if (this.rank[root1] < this.rank[root2]) {
                    this.parent[root1] = root2;
                } else {
                    this.parent[root2] = root1;
                    this.rank[root1]++;
                }
            }
        }

        connected(node1, node2) {
            return this.find(node1) === this.find(node2);
        }
    }

    function kruskal(edges, numVertices) {
        edges.sort((a, b) => a[2] - b[2]); // 간선들을 가중치 기준으로 정렬
        const uf = new UnionFind(numVertices);
        const mst = [];
        let totalWeight = 0;

        for (const [u, v, weight] of edges) {
            if (!uf.connected(u - 1, v - 1)) {
                uf.union(u - 1, v - 1);
                mst.push([u, v, weight]);
                totalWeight += weight;
            }
        }

        return { mst, totalWeight };
    }

    const edges = [
        [1, 2, 4],
        [1, 3, 1],
        [2, 3, 2],
        [2, 4, 5],
        [3, 4, 8],
        [1, 4, 3]
    ];
    const numVertices = 4;

    const { mst, totalWeight } = kruskal(edges, numVertices);
    console.log("최소 신장 트리의 간선 리스트:", mst);
    console.log("최소 가중치의 합:", totalWeight);

5단계: 결과 분석하기

구현된 알고리즘을 통해 얻어진 결과는 다음과 같습니다.
최소 신장 트리의 간선 리스트 및 전체 가중치를 확인하여 알고리즘이 제대로 작동하는지 분석합니다.
결과적으로, 주어진 간선을 기준으로 최소 신장 트리를形成하는 데 성공했습니다.

  • 최소 신장 트리의 간선 리스트: [(1, 3, 1), (2, 3, 2), (1, 4, 3), (2, 4, 5)]
  • 최소 가중치의 합: 11

결론

오늘은 자바스크립트를 사용하여 최소 신장 트리 문제를 해결하는 방법을 살펴보았습니다.
주어진 간선 리스트를 통해 여러 알고리즘을 적용할 수 있으며, 이 과정에서 그래프의 특성 또한 이해할 수 있습니다.
이러한 문제를 풀어 나가면서 알고리즘의 성능 분석 및 최적화에 대한 경험을 쌓을 수 있으며,
실제 코딩 테스트에서 출제될 가능성이 높은 주제이므로 충분히 연습해보시기 바랍니다.

자바스크립트 코딩테스트 강좌, 너비 우선 탐색

1. 문제 제기

너비 우선 탐색(Breadth-First Search, BFS)은 그래프나 트리 구조에서 유용하게 사용되는 탐색 알고리즘 중 하나입니다. 본 강좌에서는 BFS를 활용하여 “최단 경로 찾기” 문제를 해결해보겠습니다.

문제: 최단 경로 찾기

주어진 그래프에서 두 노드 사이의 최단 경로를 찾는 함수를 작성하시오. 그래프는 인접 리스트 형태로 주어지며, 노드는 문자열로 표현됩니다.

입력 형식

        graph: {
            "A": ["B", "C"],
            "B": ["A", "D", "E"],
            "C": ["A", "F"],
            "D": ["B"],
            "E": ["B", "F"],
            "F": ["C", "E"]
        }
        start: "A"
        end: "F"
    

출력 형식

        ["A", "C", "F"]
    

2. 문제 분석

최단 경로를 찾기 위해 BFS를 사용하는 이유는 BFS가 한 번에 모든 이웃 노드를 탐색하기 때문에, 가장 빠른 경로를 보장하기 때문입니다. DFS(깊이 우선 탐색)와 달리 BFS는 현재 노드에 연결된 모든 노드를 먼저 탐색한 후, 다음 레벨로 이동합니다. 이 특징으로 인해 BFS는 최단 경로 탐색에 적합합니다.

3. 알고리즘 설계

다음은 BFS를 이용한 최단 경로 찾기 알고리즘의 기본 흐름입니다:

  1. 시작 노드를 큐에 추가하고 방문 표시를 합니다.
  2. 큐에서 노드를 하나 꺼내고, 그 노드의 이웃 노드를 모두 확인합니다.
  3. 이웃 노드 중 방문하지 않은 노드를 큐에 추가하고, 해당 노드의 부모 노드를 현재 노드로 설정합니다.
  4. 종료 노드를 찾기 전까지 2-3 단계를 반복합니다.
  5. 종료 노드에 도달했을 때, 부모 노드를 거꾸로 탐색하여 최단 경로를 작성합니다.

4. 코드 구현

아래는 BFS를 이용한 최단 경로 찾기 함수의 구현 코드입니다:

        function findShortestPath(graph, start, end) {
            // 큐와 방문 표시를 위한 객체
            let queue = [start];
            let visited = {};
            let parent = {};
            
            visited[start] = true;
            parent[start] = null;

            while (queue.length > 0) {
                let currentNode = queue.shift(); // 큐에서 노드 제거
                
                // 종료 노드에 도달한 경우
                if (currentNode === end) {
                    return reconstructPath(parent, start, end);
                }

                // 현재 노드의 이웃들 탐색
                for (let neighbor of graph[currentNode]) {
                    if (!visited[neighbor]) {
                        visited[neighbor] = true;
                        parent[neighbor] = currentNode; // 이웃 노드의 부모를 현재 노드로 설정
                        queue.push(neighbor); // 큐에 이웃 노드 추가
                    }
                }
            }
            return []; // 경로가 없을 경우 빈 배열 리턴
        }

        function reconstructPath(parent, start, end) {
            let path = [];
            let current = end;
            while (current !== null) {
                path.push(current);
                current = parent[current];
            }
            return path.reverse(); // 역순으로 경로 리턴
        }
    

5. 알고리즘 분석

위 코드는 최단 경로를 찾기 위해 BFS를 사용하고 있습니다. 시간 복잡도는 O(V + E)로, V는 노드의 수, E는 간선의 수를 의미합니다. 이 알고리즘은 인접 리스트를 활용하기 때문에 메모리 효율적인 장점이 있습니다.

6. 시간 복잡도와 공간 복잡도 분석

시간 복잡도는 O(V + E)로, 모든 노드와 간선을 한 번씩 탐색하기 때문에 이와 같은 복잡도가 발생합니다. 공간 복잡도 또한 O(V)로 큐와 방문 표시 배열, 부모 저장 배열 등이 노드 수에 비례하기 때문입니다.

7. 테스트 케이스

위 코드를 확인하기 위해 몇 가지 테스트 케이스를 만들어 보겠습니다.

        const graph = {
            "A": ["B", "C"],
            "B": ["A", "D", "E"],
            "C": ["A", "F"],
            "D": ["B"],
            "E": ["B", "F"],
            "F": ["C", "E"]
        };

        console.log(findShortestPath(graph, "A", "F")); // Output: ["A", "C", "F"]
        console.log(findShortestPath(graph, "B", "F")); // Output: ["B", "E", "F"]
        console.log(findShortestPath(graph, "D", "A")); // Output: ["D", "B", "A"]
        console.log(findShortestPath(graph, "A", "A")); // Output: ["A"]
    

8. 마무리

이번 강좌에서는 자바스크립트로 너비 우선 탐색(BFS)을 이용하여 최단 경로를 찾는 문제를 해결해 보았습니다. BFS는 인접 리스트와 같은 구조를 활용해 그래프를 탐색하는 유용한 방법이며, 다양한 문제에 응용될 수 있습니다. 앞으로도 알고리즘을 연습하며 더 많은 문제를 해결해 나가길 바랍니다.

9. 추가 자료

더 많은 알고리즘 문제와 해법을 학습하고 싶다면 온라인 코딩 테스트 플랫폼을 이용해 보세요. 각종 문제와 힌트 등을 통해 실력을 더욱 향상시킬 수 있습니다.

© 2023 자바스크립트 코딩테스트 강좌