자료구조 & 알고리즘

퀵정렬과 병합 정렬

kyoulho 2024. 3. 1. 14:05

차이점


퀵정렬과 병합 정렬은 비슷한 듯 다른 알고리즘을 가지고 있다.

  퀵 정렬 병합 정렬
동작 방식 분할 정복(Divide and Conquer) 방식을 사용.
배열을 피벗(pivot)을 기준으로 두 개의 부분 배열로 분할하고, 각 부분 배열을 재귀적으로 정렬
분할 정복(Divide and Conquer) 방식을 사용.
배열을 나눠 정렬하고 병합하는 방식
평균 및 최악 시간 복잡도 평균 O(n logn)
최악의 경우(피벗이 최솟값이거나 최대값) O(n^2)
항상 O(n log n)
안정성 동일한 값에 대해 상대적인 순서가 변할 수 있다. 동일한 값에 대해 상대적인 순서가 유지된다.
공간 복잡도 재귀 호출에 따른 스택 공간을 고려해야 한다. 입력 배열 크기에 비례하는 공간이 필요.
따라서 메모리 사용량이 많을 수 있다.
적용 가능성 데이터의 분포를 고려하여 최악을 대비해야 한다. 입력 크기나 데이터의 분포를 알 수 없는 경우에도 일관된 성능을 제공하므로 안정적이다.

 

퀵 정렬


 

public class QuickSort {
    
    // 퀵 정렬 알고리즘
    public static void quickSort(int[] arr, int low, int high) {
        if (low < high) {
            int pivotIndex = partition(arr, low, high);

            // 피벗을 기준으로 분할하여 재귀적으로 정렬
            quickSort(arr, low, pivotIndex - 1);
            quickSort(arr, pivotIndex + 1, high);
        }
    }

    // 배열을 피벗을 기준으로 분할하는 메서드
    public static 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++;
                swap(arr, i, j); // swap 메서드 호출
            }
        }

        swap(arr, i + 1, high); // 피벗과 위치를 교환

        return i + 1;
    }
    
    // 배열의 두 요소를 교환하는 메서드
    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

 

 

병합 정렬


시간 복잡도가 항상 같은 이유

  • 분할 단계(Divide): 배열을 반으로 나누는 데 O(log N)의 시간이 소요된다.
  • 정복 단계(Conquer): 각각의 하위 배열을 정렬하고 합치는데 O(N)의 시간이 소요된다.

위 세 단계를 고려하면, 전체 병합 정렬의 시간 복잡도는 O(log N) * O(N) = O(N log N)이 된다.

예를 들어 8개의 요소를 분할하고 병합하는 과정은 3번 (log8 = 3) 이루어진다. 1번의 분할 병합 과정에서 요소에 1번씩 접근하게 된다.

 

public class MergeSort {

    // 배열을 병합 정렬하는 메서드
    void mergeSort(int[] arr) {
        mergeSort(arr, 0, arr.length - 1);
    }

    // 배열의 특정 부분을 병합 정렬하는 메서드
    void mergeSort(int[] arr, int start, int end) {
        // 시작 인덱스가 끝 인덱스보다 크거나 같으면 종료
        if (start >= end) {
            return;
        }

        // 중간 지점 계산
        int mid = (start + end) / 2;

        // 왼쪽 부분 배열 병합 정렬
        mergeSort(arr, start, mid);

        // 오른쪽 부분 배열 병합 정렬
        mergeSort(arr, mid + 1, end);

        // 이미 정렬된 상태라면 병합하지 않고 종료
        if (arr[mid] <= arr[mid + 1]) {
            return;
        }

        // 부분 배열들 병합
        merge(arr, start, mid, end);
    }

    // 두 부분 배열을 병합하는 메서드
    void merge(int[] arr, int start, int mid, int end) {
        // 임시 배열 생성
        int[] tmp = new int[end - start + 1];
        int lp = start;
        int rp = mid + 1;
        int currentIndex = 0;

        // 두 부분 배열을 비교하며 병합
        while (lp <= mid && rp <= end) {
            if (arr[lp] < arr[rp]) {
                tmp[currentIndex++] = arr[lp++];
            } else {
                tmp[currentIndex++] = arr[rp++];
            }
        }

        // 남은 요소들을 임시 배열에 추가
        while (lp <= mid) {
            tmp[currentIndex++] = arr[lp++];
        }

        while (rp <= end) {
            tmp[currentIndex++] = arr[rp++];
        }

        // 임시 배열의 내용을 원래 배열로 복사
        System.arraycopy(tmp, 0, arr, start, tmp.length);
    }
}

 

 

728x90

'자료구조 & 알고리즘' 카테고리의 다른 글

오일러 피  (0) 2024.03.03
기수 정렬  (0) 2024.03.01
티켓 전략 게임  (0) 2024.02.23
Trie 자료구조  (0) 2024.02.19
카탈란 수  (0) 2024.02.19