Java로 구현하는 핵심 정렬 알고리즘의 이해와 활용

1. 삽입 정렬 (Insertion Sort)

삽입 정렬은 현재 위치의 요소를 이미 정렬된 앞부분의 적절한 위치에 찾아 넣는 방식입니다. 구현이 간단하며 데이터가 거의 정렬된 상태에서는 매우 효율적입니다. 시간 복잡도는 평균 $O(n^2)$이며, 안정 정렬(Stable Sort)에 속합니다.

public void insertionSort(int[] data) {
    int len = data.length;
    for (int i = 1; i < len; i++) {
        int key = data[i];
        int j = i - 1;
        
        while (j >= 0 && data[j] > key) {
            data[j + 1] = data[j];
            j--;
        }
        data[j + 1] = key;
    }
}

2. 쉘 정렬 (Shell Sort)

삽입 정렬의 단점인 '먼 거리 이동'을 보완한 알고리즘입니다. 일정한 간격(Gap)을 두고 그룹을 나누어 각 그룹별로 삽입 정렬을 수행한 뒤, 점진적으로 간격을 줄여가며 최종적으로 전체를 정렬합니다. 삽입 정렬보다 빠르지만 불안정 정렬(Unstable Sort)입니다.

public void shellSort(int[] data) {
    int n = data.length;
    for (int gap = n / 2; gap > 0; gap /= 2) {
        for (int i = gap; i < n; i++) {
            int target = data[i];
            int j = i;
            while (j >= gap && data[j - gap] > target) {
                data[j] = data[j - gap];
                j -= gap;
            }
            data[j] = target;
        }
    }
}

3. 힙 정렬 (Heap Sort)

이진 힙(Binary Heap) 자료구조를 활용하는 정렬 방식입니다. 오름차순 정렬을 위해 최대 힙(Max Heap)을 구성한 후, 루트 노드(최대값)를 마지막 요소와 교체하고 힙 크기를 줄여가며 다시 힙 구조를 유지(Heapify)하는 과정을 반복합니다.

public void heapSort(int[] data) {
    int n = data.length;
    for (int i = n / 2 - 1; i >= 0; i--) {
        rebuildHeap(data, n, i);
    }
    for (int i = n - 1; i > 0; i--) {
        swap(data, 0, i);
        rebuildHeap(data, i, 0);
    }
}

private void rebuildHeap(int[] data, int size, int root) {
    int largest = root;
    int left = 2 * root + 1;
    int right = 2 * root + 2;

    if (left < size && data[left] > data[largest]) largest = left;
    if (right < size && data[right] > data[largest]) largest = right;

    if (largest != root) {
        swap(data, root, largest);
        rebuildHeap(data, size, largest);
    }
}

private void swap(int[] data, int i, int j) {
    int temp = data[i];
    data[i] = data[j];
    data[j] = temp;
}

4. 선택 정렬 (Selection Sort)

전체 배열에서 최소값(혹은 최대값)을 찾아 가장 앞에 위치한 데이터와 교체하는 방식입니다. 로직이 직관적이지만 성능 면에서는 $O(n^2)$으로 낮으며 불안정 정렬에 해당합니다.

public void selectionSort(int[] data) {
    int n = data.length;
    for (int i = 0; i < n - 1; i++) {
        int minIdx = i;
        for (int j = i + 1; j < n; j++) {
            if (data[j] < data[minIdx]) {
                minIdx = j;
            }
        }
        swap(data, i, minIdx);
    }
}

5. 버블 정렬 (Bubble Sort)

인접한 두 요소를 비교하여 조건에 맞지 않으면 자리를 바꾸는 과정을 반복합니다. 한 회전이 끝날 때마다 가장 큰 요소가 배열의 마지막으로 이동합니다. 교체가 발생하지 않을 경우 조기 종료하는 최적화가 가능합니다.

public void bubbleSort(int[] data) {
    int n = data.length;
    for (int i = 0; i < n - 1; i++) {
        boolean swapped = false;
        for (int j = 0; j < n - 1 - i; j++) {
            if (data[j] > data[j + 1]) {
                swap(data, j, j + 1);
                swapped = true;
            }
        }
        if (!swapped) break;
    }
}

6. 퀵 정렬 (Quick Sort)

피벗(Pivot)을 설정하고 이를 기준으로 작은 데이터는 왼쪽, 큰 데이터는 오른쪽으로 분할한 뒤 재귀적으로 정렬하는 분할 정복(Divide and Conquer) 알고리즘입니다. 평균적으로 $O(n \log n)$의 뛰어난 속도를 보여줍니다.

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

private int partition(int[] data, int low, int high) {
    int pivot = data[low];
    int i = low;
    int j = high;
    
    while (i < j) {
        while (i < j && data[j] >= pivot) j--;
        while (i < j && data[i] <= pivot) i++;
        swap(data, i, j);
    }
    swap(data, low, i);
    return i;
}

7. 병합 정렬 (Merge Sort)

배열을 최소 단위까지 쪼갠 뒤, 다시 합치면서 정렬을 수행하는 방식입니다. 항상 $O(n \log n)$의 시간 복잡도를 보장하며 안정 정렬이라는 장점이 있지만, 병합 과정에서 추가적인 메모리 공간이 필요합니다.

public void mergeSort(int[] data, int left, int right) {
    if (left < right) {
        int mid = (left + right) / 2;
        mergeSort(data, left, mid);
        mergeSort(data, mid + 1, right);
        merge(data, left, mid, right);
    }
}

private void merge(int[] data, int left, int mid, int right) {
    int[] temp = new int[right - left + 1];
    int i = left, j = mid + 1, k = 0;

    while (i <= mid && j <= right) {
        temp[k++] = (data[i] <= data[j]) ? data[i++] : data[j++];
    }
    while (i <= mid) temp[k++] = data[i++];
    while (j <= right) temp[k++] = data[j++];

    for (int l = 0; l < temp.length; l++) {
        data[left + l] = temp[l];
    }
}

태그: java algorithm sorting DataStructure

8월 16일 16:41에 게시됨