본문 바로가기
Algorithm

Sort (Selection, Insertion, Quick, Count)

by graygreat 2021. 1. 28.
728x90
반응형

정렬이란 데이터를 특정한 기준에 따라서 순서대로 나열 하는 것을 말한다.

선택 정렬 (Selection Sort)

데이터가 무작위로 여러 개 있을 때, 이 중에서 가장 작은 데이터를 선택해 맨 앞에 있는 데이터와 바꾸고, 그 다음 작은 데이터를 선택해 앞에서 두 번째 데이터와 바꾸는 과정이다.

1. [7] 5 9 (0) 3 1 6 2 4 8 #가장 작은 0을 선택해 맨 앞에 있는 7과 바꾼다.
2. 0 [5] 9 7 3 (1) 6 2 4 8 #정렬된 첫 번째는 제외하고 이후 데이터 중에서 가장 작은 데이터인 1을 선택해서 처리되지 않은 데이터 중 가장 앞에 있는 데이터 5와 바꾼다.
3. 0 1 [9] 7 3 5 6 (2) 4 8 #정렬된 첫 번째는 제외하고 이후 데이터 중에서 가장 작은 데이터인 2을 선택해서 처리되지 않은 데이터 중 가장 앞에 있는 데이터 9와 바꾼다.
...
10. 0 1 2 3 4 5 6 7 8 (9) #가장 작은 데이터를 앞으로 보내는 과정을 9번 반복한 상태는 다음과 같으며 마지막 데이터는 가만히 두어도 이미 정렬된 상태이다. 
                       #따라서 이 단계에서 정렬을 마칠 수 있다.
array = [7, 5, 9, 0, 3, 1, 6, 2, 4, 8]

for i in range(len(array)):
    min_index = i
    for j in range(i + 1, len(array)):
        if array[min_index] > array[j]:
            min_index = j
    array[i], array[min_index] = array[min_index], array[i]

print(array)

''' 결과 값
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
'''

선택 정렬의 시간 복잡도

선택 정렬은 N - 1번 만큼 가장 작은 수를 찾아서 맨 앞으로 보내야 한다. 또한 매번 가장 작은 수를 착기 위해서 비교 연산이 필요하다.

O(N^2)

선택 정렬은 기본 정렬 라이브러리를 포함해 다른 알고리즘과 비교했을 때 매우 비효율적이다.

삽입 정렬 (Insertion Sort)

특정한 데이터를 적절한 위치에 '삽입'한다는 의미이다. 더불어 삽입 정렬은 특정한 데이터가 적절한 위치에 들어가기 이전에, 그 앞까지의 데이터는 이미 정렬되어 있다고 가정한다. 정렬되어 있는 데이터 리스트에서 적절한 위치를 찾은 뒤에, 그 위치에 삽입된다는 점이 특징이다.

1. 7 (5) 9 0 3 1 6 2 4 8 #첫 번째 데이터 7은 그 자체로 정렬되어 있다고 판단하고, 두 번째 데이터인 5가 어떤 위치로 들어갈지 판단한다. 
                         #7의 왼쪽으로 들어가거나 혹은 오른쪽으로 들어가는 두 경우만 존재한다. 오름차순으로 정렬하고자 하므로 7의 왼쪽에 삽입한다.
2. 5 7 (9) 0 3 1 6 2 4 8 #9가 어떤 위치에 들어갈지 판단한다. 삽입될 수 있는 위치는 총 3가지이며 현재 9는 5와 7보다 크기 때문에 원래 자리 그대로 둔다.
3. 5 7 9 (0) 3 1 6 2 4 8 #0이 어떤 위치에 들어갈지 판단한다. 0은 5, 7, 9와 비교했을 때 가장 작기 때문에 첫 번째 위치에 삽입한다.
...
8. 0 1 2 3 5 6 7 9 (4) 8
9. 0 1 2 3 4 5 6 7 9 (8)
10. 0 1 2 3 4 5 6 7 8 9 #적절한 위치에 삽입하는 과정을 N - 1번 반복하게 되면 다음과 같이 모든 데이터가 정렬된 것을 확인할 수 있다.
array = [7, 5, 9, 0, 3, 1, 6, 2, 4, 8]

for i in range(1, len(array)):
    for j in range(i, 0, -1):
        if array[j] < array[j - 1]:
            array[j], array[j - 1] = array[j - 1], array[j]
        else:
            break

print(array)

''' 결과 값
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
'''

삽입 정렬의 시간 복잡도

삽입 정렬의 시간 복잡도는 O(N^2)이다.
단, 삽입 정렬은 현재 리스트의 데이터가 거의 정렬되어 있는 상태라면 매우 빠르게 동작한다.
최선의 경우 O(N)의 시간 복잡도를 가진다.
보통 삽입 정렬은 비효율적이나 정렬이 거의 되어 있는 상황에서는 퀵 정렬 알고리즘보다 더 강력하다.

퀵 정렬 (Quick Sort)

퀵 정렬은 기준을 설정한 다음 큰 수와 작은 수를 교환한 후 리스트를 반으로 나누는 방식으로 동작한다.
퀵 정렬에는 Pivot이 사용되는데 큰 숫자와 작은 숫자를 교환할 때 기준을 피벗이라고 한다.

1. [5] (7) 9 0 3 1 6 2 (4) 8 #리스트의 첫 번째 데이터를 피벗으로 설정하므로 피벗은 5이다.
                             #이후에 왼쪽에서부터 5보다 큰 데이터를 선택하므로 7이 선택되고,
                             #오른쪽에서부터 5보다 작은 데이터를 선택하므로 4가 선택된다.
                             #그리고 이 두 데이터의 위치를 서로 변경한다.
2. [5] 4 (9) 0 3 1 6 (2) 7 8 #그 다음 피벗보다 큰 데이터와 작은 데이터를 각각 찾는다.
                             #찾은 뒤에는 두 값의 위치를 서로 변경하는데, 
                             #현재 9와 2가 선택되었으므로 이 두 데이터의 위치를 서로 변경한다.
3. [5] 4 2 0 3 (1) (6) 9 7 8 #현재 왼쪽에서부터 찾는 값과 오른쪽에서부터 찾는 값의 위치가 
                             #서로 엇갈린 것을 알 수 있다.
                             #이렇게 두 값이 엇갈린 경우에는 작은 데이터와 피벗의 위치를 
                             #서로 변경한다.
4. 1 4 2 0 3 [5] 6 9 7 8     #분할 완료

위와 같은 작업을 동일하게 반복해준다.
왼쪽 리스트와 오른쪽 리스트에서도 각각 피벗을 설정하여 동일한 방식으로 정렬을 수행하면 전체 리스트에 대하여 모두 정렬이 이루어질 것이다.

# 전통 퀵 정렬 
array = [5, 7, 9, 0, 3, 1, 6, 2, 4, 8]

def quick_sort(array, start, end):
    if start >= end:
        return
    pivot = start
    left = start + 1
    right = end
    while left <= right:
        while left <= end and array[left] <= array[pivot]:
            left += 1
        while right > start and array[right] >= array[pivot]:
            right -= 1
        if left > right:
            array[right], array[pivot] = array[pivot], array[right]
        else:
            array[left], array[right] = array[right], array[left]

    quick_sort(array, start, right - 1)
    quick_sort(array, right + 1, end)

quick_sort(array, 0, len(array) - 1)
print(array)
# 파이썬스러운 퀵 정렬
array = [5, 7, 9, 0, 3, 1, 6, 2, 4, 8]

def quick_sort(array):
    if len(array) <= 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)이다. 단, 최악의 경우 시간 복잡도는 O(N^2)이다.
데이터가 무작위로 입력되는 경우 퀵 정렬은 빠르게 동작할 확률이 높다.

계수 정렬 (Count Sort)

계수 정렬 알고리즘은 특정한 조건이 부합할 때만 사용할 수 있지만 매우 빠른 정렬 알고리즘이다.
계수 정렬은 데이터의 크기 범위가 제한되어 정수 형태로 푠현할 수 있을 때만 사용할 수 있다.
일반적으로 가장 큰 데이터와 가장 작은 데이터의 차이가 1,000,000을 넘지 않을 때 효과적으로 사용할 수 있다.
계수 정렬은 일반적으로 별도의 리스트를 선언하고 그 안에 정렬에 대한 정보를 담는다는 특징이 있다.

초기 단계: 7 5 9 0 3 1 6 2 9 1 4 8 0 5 2
최대값 9 + 1의 크기를 가진 리스트를 선언한다.

1. 7을 선택하여 별도의 리스트 7번째 인덱스 값을 +1 해준다.
2. 5을 선택하여 별도의 리스트 5번째 인덱스 값을 +1 해준다.
...

결과적으로 위와 같이 리스트에는 각 데이터가 몇 번 등장했는지 그 횟수가 기록된다.

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

count = [0] * (max(array) + 1)

for arr in array:
    count[arr] += 1

for i in range(len(count)):
    for _ in range(count[i]):
        print(i, end = ' ')

계수 정렬의 시간 복잡도

모든 데이터가 양의 정수인 상황에서 데이터의 개수를 N, 데이터 중 최대 값의 크기를 K라고 할 때, 계수 정렬의 시간 복잡도는 O(N + K)이다.

계수 정렬의 공간 복잡도

계수 정렬은 때에 따라서 심각한 비효율성을 초래할 수 있다. 예를 들어 데이터가 0과 999,999, 단 2개만 존재한다고 하면, 리스트 크기가 100만 개가 되도록 선언해야한다. 따라서 항상 사용할 수 있는 정렬 알고리즘은 아니며, 동일한 값을 가지는 데이터가 여러 개 등장할 때 적합하다.
계수 정렬의 공간 복잡도는 O(N + K)이다.

Reference

  • 이것이 코딩 테스트다 with 파이썬
반응형

'Algorithm' 카테고리의 다른 글

[이코테] 만들 수 없는 금액  (0) 2021.04.02
Binary Search  (0) 2021.02.18
Dynamic Programming  (0) 2021.01.18
Brute Force & Back Tracking  (0) 2021.01.13
[Algorithm] BFS  (0) 2021.01.08

댓글