정렬 세계관 최강자, Quick Sort

H43RO·2021년 10월 11일
31

CS 뿌셔먹기

목록 보기
10/17
post-thumbnail

퀵 소트는 이름에서 풍기는 자신감이 돋보인다(?). 웬만한 상황에선 가장 빠른 속도를 자랑하는 정렬 알고리즘이다. 이번 포스팅에선 이 퀵 정렬 알고리즘이 어떻게 동작하게 되는지, 최선의 경우와 최악의 경우 시간 복잡도는 어떻게 되는지에 대해 알아보자.

개요

퀵 소트는 분할 정복 기법을 통해 데이터를 정렬하는 녀석이다. 분할 정복(Divide and Conquer) 는 말 그대로, 어떤 문제를 반+반으로 쪼개어 각각을 해결 한 뒤에 다시 하나로 모아서 원래 문제를 해결하는 기법이다. 분할을 하고, 그 분할된 문제들 각각을 정복하여 원래 해결하고자 했던 문제를 해결하는 것이다.

퀵 소트는 원소 간의 비교만으로 정렬을 수행하는 알고리즘이기 때문에 비교 정렬의 한 종류이고, 불안정 정렬에 속한 다. 또 한 가지 특징이라면, 분할정복을 활용하는 다른 정렬 알고리즘인 Merge Sort 와 다르게 분할되는 배열이 불균등하다. (즉, 분할해놓고 보니 두 배열의 크기가 일치하지 않는다는 뜻)

🤔 불안정한 정렬 알고리즘이란?

정렬 이후 데이터의 순서가 정렬 이전 원래 순서와 같음을 보장하지 못하는 정렬 알고리즘
아래 사진을 통해 불안정 정렬과 안정 정렬의 차이를 이해해볼 수 있다.

로직

  1. 주어진 배열에서 임의의 한 원소를 고르고, 이를 피벗(Pivot) 이라 칭한다. (배열 한 가운데, 맨 앞 원소 등)

  2. 피벗을 기준으로 피벗보다 값이 작은 원소들을 싸그리 모아놓은 배열 하나와, 피벗보다 값이 큰 원소들을 싸그리 모아놓은 배열 하나 → 이렇게 배열을 둘로 나눈다. 분할을 마친 뒤 피벗은 그 자리에서 더이상 움직이지 않는다.

  3. 분할된 두 개의 배열에 대해 위 1, 2 과정을 재귀적으로 반복한다.

이렇게 하면, 1-2 과정을 한 번 수행할 때마다 피벗 원소의 자리가 고정되기 때문에 반드시 정렬이 언젠가 완수된다는 것을 보장할 수 있다.

위 로직에 따라, 코드를 작성해보면 아래와 같이 해볼 수 있을 것이다. (파이썬 기준)

array = [7, 5, 9, 0, 3, 1, 6, 2, 4, 8]

def quick_sort(array):
    if len(array) <= 1:  # 길이가 1까지 도달한 경우 고대로 리턴
        return array

    pivot = array[0]  # 이 경우, 첫 원소를 피벗으로 삼음
    tail = array[1:]  # 피벗 제외 배열 슬라이싱

    left_side = [x for x in tail if x <= pivot]  # 피벗보다 작거나 같은 원소만 모은 배열 
    right_side = [x for x in tail if x > pivot]  # 피벗보다 큰 원소만 모은 배열

    # 피벗의 자리는 결정되었고, 피벗 기준 왼쪽 배열과 오른쪽 배열에 대해 재귀 수행
    return quick_sort(left_side) + [pivot] + quick_sort(right_side)

print(quick_sort(array))


상황별 시간 복잡도 따져보기

최선의 경우

최선의 경우에 퀵 소트는 O(nlogn) 속도로 동작하게 된다. 이유는 다음과 같다.

1️⃣ 비교 횟수 : O(nlogn)

원소 개수 N 이 2의 거듭제곱이라고 가정했을 때 (n = 2^k), 예를 들어 n = 8 인 경우 (n = 2 ^ 3)

2^3 → 2^2 → 2^1 → 2^0 순으로 줄어들어, Recursive 깊이가 3임을 알 수 있다.

이것을 일반화 하게 되면, n = 2^k 라고 가정했을 때 k = log₂n 임을 알 수 있다.

2️⃣ 각 Recursive 호출 단계의 비교 연산 : O(n)

순환 호출 각각에서는 피벗전체 원소들 각각을 비교해야 하기 때문에, 평균적으로 n 번 비교가 발생한다.

따라서, 이러한 연산 복잡도를 종합해보았을 때 O(nlogn) 이 나오게 되는 것이다.


최악의 경우

퀵 소트에 있어서, 정렬하고자 하는 배열이 오름차순 혹은 내림차순으로 정렬되어 있을 때가 최악의 경우이다. 이 경우 시간 복잡도가 O(n^2) 까지 치솟게 된다. (닉값 못한다)

1️⃣ 비교 횟수 : O(n)

배열이 정렬되어 있기 때문에, 계속하여 아래와 같은 형태로 분할되고 말 것이다.

원소 개수가 n 이라고 하면, 순환 호출 깊이 역시 n 이 된다.

2️⃣ 각 Recursive 호출 단계의 비교 연산 : O(n)

아까 위에서 다룬 최적의 상황과 같은 맥락으로, O(n) 만큼 소요된다.

따라서, 최악의 경우 O(n^2) 이 소요된다.

(사실 정렬된 배열을 정렬하는 상황이 왜 있겠나 싶다)


공간 복잡도

뭐, 딱히 다른 테이블을 선언하고 그런 것이 아니고 정해진 배열 내에서 원소가 왔다갔다 하는 것이기 때문에 원소 개수 n 만큼, 즉 O(n) 을 차지한다.


장단점 살펴보기

장점

  • 필요한 자리 교환 연산만 수행하기 때문에 웬만하면 O(nlogn) 이 나오는 것 자체가 아주 이상적임
  • 다른 메모리 공간을 차지하지 않기 때문에 공간 복잡도 또한 우수함
    • 단, 위에서 소개한 코드는 퀵 소트 개념을 소개하는 코드일 뿐 공간 복잡도는 고려 안 됨

단점

  • 불안정한 정렬 알고리즘
  • 이미 정렬된 상태에서 수행할 시 불균형 분할에 의해 O(n^2) 까지 성능이 안 좋아짐

profile
어려울수록 기본에 미치고 열광하라

5개의 댓글

comment-user-thumbnail
2021년 10월 14일

고맙습니다 잘보고갑니다

1개의 답글
comment-user-thumbnail
2021년 10월 19일

감사합니다. 잘 보고 갑니다!

답글 달기
comment-user-thumbnail
2022년 8월 7일

덕분에 좋은 내용 잘 보고 갑니다
감사합니다.

답글 달기
comment-user-thumbnail
약 3시간 전

One of the brilliant advantages of GB WhatsApp Download is extra sharing. Large media files take much time to send, and many people cannot share because of internet issues. Moreover, common WhatsApp has a specific limit for images or videos. So, you cannot share some files on your device.

https://gbplusz.org.pk/

답글 달기