정렬이란 데이터를 특정한 기준에 따라서 순서대로 나열 하는 것을 말한다.
선택 정렬 (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 |
댓글