Java/알고리즘

퀵 정렬(Quick Sort)

heoheos 2023. 3. 27. 23:05

동빈나 실전 알고리즘 강좌(Algorithm Programming Tutorial) 참고

https://www.youtube.com/playlist?list=PLRx0vPvlEmdDHxCvAQS1_6XV4deOwfVrz

 

실전 알고리즘 강좌 (Algorithm Programming Tutorial)

 

www.youtube.com

퀵 정렬(Quick Sort)

특정한 값(피벗)을 기준으로 큰 숫자와 작은 숫자를 나누자

피벗(Pivot) : 퀵 정렬에서의 기준 값, 보통 첫 번째 원소를 피벗값으로 지정

하나의 큰 문제를 두 개의 작은 문제로 분할하는 방식으로 빠르게 정렬 ▶ 분할 정복 알고리즘의 대표적 예

평균 시간 복잡도 : O(N*logN)

최악의 시간 복잡도 : O(N^2)

분할 정복의 이점을 사용하지 못하면 최악의 시간복잡도가 나옴. 피벗값이 배열의 중간쯤 있어야 왼쪽 오른쪽 나누어 각각 퀵 정렬을 수행할 텐데 피벗값이 너무 치우쳐서 왼쪽에 값이 없거나 정렬된 수들만 있을 경우 최악의 시간 복잡도가 나옴

=> 이미 정렬이 되어있거나 거의 정렬이 되어있을 때

ex) 1 2 3 4 5 6 7 8 9 10

원리

3 7 8 1 5 9 6 10 2 4

 

위와 같이 정렬되지 않은 수들이 주어졌다고 했을 때 먼저 피벗값 = 3

배열의 가장 왼쪽에서 →으로 가면서 피벗값(3) 보다 큰 값을 찾고(7), 배열의 가장 오른쪽에서 ←으로 가면서 피벗값(3)보다 작은 값을 찾음(2)

3 7 8 1 5 9 6 10 2 4

큰 값(7)작은 값(2) 자리바꾸기

3 2 8 1 5 9 6 10 7 4

 

다시 배열의 가장 왼쪽에서 →으로 가면서 피벗값(3) 보다 큰을 찾고(8), 배열의 가장 오른쪽에서 ←으로 가면서 피벗값(3) 보다작은 값을 찾음(1)

3 2 8 1 5 9 6 10 7 4

큰 값(8) 작은 값(1) 자리 바꾸기

3 2 1 8 5 9 6 10 7 4

 

다시 배열의 가장 왼쪽에서 →으로 가면서 피벗값(3) 보다 큰을 찾고(8), 배열의 가장 오른쪽에서 ←으로 가면서 피벗값(3) 보다작은 값을 찾음(1)

3 2 1 8 5 9 6 10 7 4

작은 값의 인덱스 < 큰 값의 인덱스 ▶ 큰 값과 작은 값의 자리가 바뀜(원래라면 큰 값이 좌측에 있어서 서로 자리를 바꿈으로써 작은 값이 좌측에, 큰 값이 우측에 위치해야 함) ▶ 엇갈림

엇갈린 경우 작은 값(1)과 피벗값(3) 자리 바꾸기

1 2 3 8 5 9 6 10 7 4

피벗값(3)이 자리를 옮김으로써 이 값(3)은 정렬됨

∴  3보다 왼쪽은 3보다 작은 값 / 3보다 오른쪽은 3보다 큰 값 ▶ 1번의 분할이 일어난 것(작은 값 | 큰 값)

 

정렬된 3을 기준으로 왼쪽 집합 : 1 2 / 오른쪽 집합 : 8 5 9 6 10 7 4에서 각각 피벗값 선택

왼쪽 집합 피벗값 = 1

오른쪽 집합 피벗값 = 8

 

먼저 왼쪽 집합부터 퀵 정렬 수행

1 2

배열의 가장 왼쪽에서 →으로 가면서 피벗값(1) 보다 큰을 찾고(2), 배열의 가장 오른쪽에서 ←으로 가면서 피벗값(1) 보다작은 값을 찾음(없음 ▶ 없으면 피벗값 자기 자신 선택 = 1)

1 2

작은 값의 인덱스 < 큰 값의 인덱스 ▶ 큰 값과 작은 값의 자리가 바뀜(원래라면 큰 값이 좌측에 있어서 서로 자리를 바꿈으로써 작은 값이 좌측에, 큰 값이 우측에 위치해야 함) ▶ 엇갈림

엇갈린 경우 작은 값(1)과 피벗값(1) 자리 바꾸기(그냥 그대로) ▶ 1 정렬

1 2 3 8 5 9 6 10 7 4

정렬된 1을 기준으로 1보다 왼쪽은 1보다 작은 값(없음) / 1보다 오른쪽은 1보다 큰 값

 

왼쪽 집합(1 2)에서 1 정렬했으니까 2 차례

2도 똑같이 수행 ▶ 2 앞 뒤로 정렬된 수가 있으므로 정렬되지 않은 데이터는 2 한 개 (그냥 내버려 둠) ▶ 2 정렬

1 2 3 8 5 9 6 10 7 4

 

오른쪽 집합 퀵 정렬 수행

8 5 9 6 10 7 4

왼쪽에서 →으로 가면서 피벗값(8) 보다 큰을 찾고(9), 배열의 가장 오른쪽에서 ←으로 가면서 피벗값(8) 보다작은 값을 찾음(4)

8 5 9 6 10 7 4

큰 값(9) 작은 값(4) 자리 바꾸기

8 5 4 6 10 7 9

 

다시 왼쪽에서 →으로 가면서 피벗값(8) 보다 큰을 찾고(10), 배열의 가장 오른쪽에서 ←으로 가면서 피벗값(8) 보다작은 값을 찾음(7)

8 5 4 6 10 7 9

큰 값(10) 작은 값(7) 자리 바꾸기

8 5 4 6 7 10 9

 

다시 왼쪽에서 →으로 가면서 피벗값(8) 보다 큰을 찾고(10), 배열의 가장 오른쪽에서 ←으로 가면서 피벗값(8) 보다작은 값을 찾음(7)

8 5 4 6 7 10 9

작은 값의 인덱스 < 큰 값의 인덱스 ▶ 큰 값과 작은 값의 자리가 바뀜(원래라면 큰 값이 좌측에 있어서 서로 자리를 바꿈으로써 작은 값이 좌측에, 큰 값이 우측에 위치해야 함) ▶ 엇갈림

엇갈린 경우 작은 값(7)과 피벗값(8) 자리 바꾸기

7 5 4 6 8 10 9

피벗값(8)이 자리를 옮김으로써 이 값(8)은 정렬됨

∴  8보다 왼쪽은 8보다 작은 값 / 8보다 오른쪽은 8보다 큰 값 ▶ 1번의 분할이 일어난 것(작은 값 | 큰 값)

 

이런 식으로 또 8을 기준으로 8보다 작은 왼쪽 집합(7 5 4 6), 8보다 큰 오른쪽 집합(10 9) 각각 퀵 정렬 수행

 

package algorithmProgramming;

public class QuickSort {

    private static void show(int[] data) {
        for (int i = 0; i < data.length; i++) {
            System.out.print(data[i] + " ");
        }
    }

    private static void quickSort(int[] data, int startIdx, int endIdx) {
        // 원소가 1개면 그대로 종료
        if (startIdx >= endIdx) return;

        int pivot = data[startIdx];
        int pivotIdx = startIdx;
        int leftIdx = startIdx + 1;
        int rightIdx = endIdx;


        while (leftIdx <= rightIdx) {
            // leftIdx가 endIdx를 넘어가지 않고, pivot보다 큰 값을 찾을 때까지 leftIdx++
            while (leftIdx <= endIdx && data[leftIdx] <= pivot) {
                leftIdx++;
            }
            // rightIdx가 startIdx보다 작아지지 않고, pivot보다 작은 값을 찾을 때까지 rightIdx--
            // but startIdx와 동일해지면 안됨 pivot의 인덱스가 startIdx이므로
            while (rightIdx > startIdx && data[rightIdx] >= pivot) {
                rightIdx--;
            }

            // 위 while문을 통해 각각 작은값과 큰값을 찾음
            if (leftIdx < rightIdx) { // leftIdx와 rightIdx가 만나지 않으면 서로 자리 바꿈
                int tmp = data[leftIdx];
                data[leftIdx] = data[rightIdx];
                data[rightIdx] = tmp;
            } else { // leftIdx와 rightIdx가 만났다면 pivot값과 rightIdx의 값 자리 바꿈
                int tmp = data[rightIdx];
                data[rightIdx] = data[pivotIdx];
                data[pivotIdx] = tmp;
            }
        }

        quickSort(data, startIdx, rightIdx - 1);
        quickSort(data, rightIdx + 1, endIdx);

    }

    public static void main(String[] args) {

        int[] data = { 1, 10, 5, 8, 7, 6, 4, 3, 2, 9 };

        System.out.print("정렬 전 : ");
        show(data);

        System.out.println();

        quickSort(data, 0, data.length - 1);
        System.out.print("정렬 후 : ");
        show(data);

    }

}

 

오름차순 정렬 완료