본문으로 건너뛰기
← 블로그로 돌아가기
CS 기초

알고리즘 입문: 정렬과 탐색

3분 읽기
#C++#sorting

코딩 테스트 준비든 실무 개발이든, 정렬과 탐색은 피할 수 없는 기본기입니다. 어떤 언어를 쓰든 결국 이 두 가지로 귀결되는 문제가 대부분입니다.

정렬 알고리즘

Bubble Sort — 가장 단순한 정렬

인접한 두 원소를 비교해서 순서가 틀리면 교환합니다. 직관적이지만 느립니다.

void bubbleSort(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                std::swap(arr[j], arr[j + 1]);
            }
        }
    }
}

시간복잡도는 O(n²)입니다. 데이터가 1만 개만 넘어도 체감될 정도로 느려집니다. 교육용으로는 좋지만 실무에서 쓸 일은 거의 없습니다.

Selection Sort — 최솟값 찾기 반복

전체 배열에서 최솟값을 찾아 맨 앞으로 보내는 방식입니다.

void selectionSort(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        int minIdx = i;
        for (int j = i + 1; j < n; j++) {
            if (arr[j] < arr[minIdx]) {
                minIdx = j;
            }
        }
        std::swap(arr[i], arr[minIdx]);
    }
}

이것도 O(n²)입니다. Bubble Sort보다 swap 횟수는 적지만 비교 횟수는 동일합니다.

Quick Sort — 실전에서 가장 많이 쓰이는 정렬

피벗을 기준으로 작은 값과 큰 값을 나누고, 재귀적으로 정렬합니다.

int partition(int arr[], int low, int high) {
    int pivot = arr[high];
    int i = low - 1;
    for (int j = low; j < high; j++) {
        if (arr[j] < pivot) {
            i++;
            std::swap(arr[i], arr[j]);
        }
    }
    std::swap(arr[i + 1], arr[high]);
    return i + 1;
}

void quickSort(int arr[], int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);
        quickSort(arr, low, pi - 1);
        quickSort(arr, pi + 1, high);
    }
}

평균 시간복잡도 O(n log n). 최악의 경우 O(n²)이지만, 랜덤 피벗 선택으로 거의 발생하지 않습니다. C++ STL의 std::sort도 내부적으로 Introsort(Quick Sort 변형)를 사용합니다.

시간복잡도 비교

알고리즘최선평균최악안정성
Bubble SortO(n)O(n²)O(n²)안정
Selection SortO(n²)O(n²)O(n²)불안정
Quick SortO(n log n)O(n log n)O(n²)불안정
Merge SortO(n log n)O(n log n)O(n log n)안정

"안정 정렬"이란 같은 값의 원소들이 원래 순서를 유지하는 것입니다. 데이터에 부가 정보가 있을 때 중요해집니다.

탐색 알고리즘

Linear Search — 하나씩 확인

배열을 처음부터 끝까지 순회하면서 목표 값을 찾습니다. 정렬이 필요 없다는 장점이 있습니다.

int linearSearch(int arr[], int n, int target) {
    for (int i = 0; i < n; i++) {
        if (arr[i] == target) return i;
    }
    return -1;
}

O(n)입니다. 데이터가 적거나 정렬되지 않은 경우에 씁니다.

Binary Search — 반씩 잘라내기

정렬된 배열에서만 동작합니다. 중간값과 비교해서 절반을 버리는 방식이라 매우 빠릅니다.

int binarySearch(int arr[], int n, int target) {
    int left = 0, right = n - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (arr[mid] == target) return mid;
        if (arr[mid] < target) left = mid + 1;
        else right = mid - 1;
    }
    return -1;
}

O(log n)입니다. 100만 개의 데이터에서 최대 20번의 비교로 답을 찾습니다.

mid = (left + right) / 2 대신 mid = left + (right - left) / 2를 쓰는 이유는 정수 오버플로우 방지입니다. 사소해 보이지만 실제 코딩 테스트에서 이 차이로 틀리는 경우가 있습니다.

어떤 상황에 어떤 알고리즘을 쓸까

정렬이 필요하면 대부분의 경우 std::sort를 쓰면 됩니다. 직접 구현할 일은 코딩 테스트 외에는 드뭅니다. 탐색은 데이터가 정렬되어 있으면 Binary Search, 아니면 Linear Search가 기본입니다.

직접 각 알고리즘을 구현해보고, 데이터 크기를 바꿔가며 실행 시간을 측정해 보는 것을 권합니다. 숫자로 체감해야 시간복잡도가 몸에 익습니다.

Share
JJY
JJYAuthor

AI, 웹 보안, 개발 환경에 관심이 많습니다.

새 글 알림 받기

스팸 없이 새 포스트만 전달합니다.

관련 포스트