정렬
정렬의 종류, 언어에서 제공하는 정렬 등
- 선택(selection) 정렬
- 삽입(insertion) 정렬
- 퀵(quick) 정렬
- 병합(merge) 정렬
- 힙(heap) 정렬
- 계수 (count) 정렬
- 프로그래밍 언어에서 제공하는 정렬
- ToDo
- 각주
선택(selection) 정렬
원소가 들어갈 인덱스를 먼저 설정한 후, 어떤 원소가 들어갈 지 선택하는 정렬 방법이다.
def selection_sort(arr):
for i in range(0, len(arr)):
min_idx = i
for j in range(i, len(arr)):
# 1. 주어진 배열 중에 최소값을 찾는다.
if arr[j] < arr[min_idx]:
min_idx = j
# 2. 그 값을 맨 앞에 위치한 값과 교체한다.
# 3. 맨 처음 위치를 뺀 배열을 같은 방법으로 교체
arr[i], arr[min_idx] = arr[min_idx], arr[i]
return arr
시간복잡도
Best | Avg | Worst |
---|---|---|
O(N^2) | O(N^2) | O(N^2) |
- 모든 원소를 탐색하고 선택할지 결정해야 하므로 시간복잡도는 O(N^2)이 걸린다.
- 공간복잡도의 경우 해당 배열 내의 공간만큼만 차지하므로 O(N)을 차지한다.
삽입(insertion) 정렬
정렬된 원소에서 새로 추가될 원소를 특정 위치에 삽입하는 정렬 방법이다.
def insertion_sort(arr):
for cur_idx in range(1, len(arr)):
cur = arr[cur_idx]
prev_idx = cur_idx - 1
while prev_idx >= 0 and arr[prev_idx] > cur:
arr[prev_idx + 1] = arr[prev_idx]
prev_idx -= 1
arr[prev_idx + 1] = cur
return arr
시간복잡도
Best | Avg | Worst |
---|---|---|
O(N) | O(N^2) | O(N^2) |
- 단, 데이터가 조금 정렬되어있을 경우 효율적으로 동작한다.
- 공간복잡도는 배열의 크기만큼 차지하므로, O(N)
퀵(quick) 정렬
pivot
이라는 기준 데이터를 설정하고, 그보다 작은 값과 큰 값을 각각 왼쪽/오른쪽에 분리 후 분할정복 방식으로 정렬하는 방법이다.
def quick_sort(arr, start, end):
# 원소가 1개일 경우 바로 종료
if start >= end:
return arr
pivot = start
left = start + 1
right = end
# 왼쪽에는 피벗보다 작은 수, 오른쪽은 피벗보다 큰 수
while left <= right:
while left <= end and arr[left] <= arr[pivot]:
left += 1
while right > start and arr[right] >= arr[pivot]:
right -= 1
if left > right:
arr[pivot], arr[right] = arr[right], arr[pivot]
else:
arr[left], arr[right] = arr[right], arr[left]
quick_sort(arr, left, right - 1)
quick_sort(arr, right + 1, end)
return arr
시간복잡도
Best | Avg | Worst |
---|---|---|
O(N log(N)) | O(N log(N)) | O(N^2) |
병합(merge) 정렬
중간 지점으로 좌/우로 나눈 후 서로 정렬한 뒤 다시 합치는 정렬 방법이다. 재귀함수로 간결하게 구현할 수 있다.
def merge(left_half, right_half):
result = []
i = j = 0
while i < len(left_half) and j < len(right_half):
if left_half[i] < right_half[j]:
result.append(left_half[i])
i += 1
else:
result.append(right_half[j])
j += 1
result.extend(left_half[i:])
result.extend(right_half[j:])
return result
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
# left, right 반으로 나눈다.
left_half = arr[:mid]
right_half = arr[mid:]
left_half = merge_sort(left_half)
right_half = merge_sort(right_half)
# 왼쪽과 오른쪽을 합친다.
return merge(left_half, right_half)
시간복잡도
Best | Avg | Worst |
---|---|---|
O(N log(N)) | O(N log(N)) | O(N log(N)) |
힙(heap) 정렬
트리 자료구조에서 힙을 이용해 사용하는 정렬이다. 최대힙, 최소힙 등을 통해 최대값과 최솟값을 구하기 좋은 정렬 방법이다.
계수 (count) 정렬
각 원소가 몇개씩 등장하였는지 별도의 리스트에 횟수를 세고 이를 출력하는 방법이다.
다음과 같은 [2,2,0,0,1,3,3,3]
같은 리스트가 있다고 가정할 때
count/number | 0 | 1 | 2 | 3 |
---|---|---|---|---|
count | 2 | 1 | 2 | 3 |
다음과 같은 형식으로 만든 후 그 숫자를 차례대로 가져오는 방식이다.
count_list = [0] * (max(arr) + 1)
# 이후 모든 원소를 돌면서 count_list에 추가
[0,0,1,2,2,3,3,3] # count list에 있는 값들을 차례대로 출력
같은 값들이 많거나 한정될 경우에 O(N + K)의 빠른 시간복잡도를 가진다. (N = 데이터 개수, K = 최대값)
프로그래밍 언어에서 제공하는 정렬
프로그래밍 언에어 내장된 정렬들은 어떤 알고리즘을 사용할까? 궁금해서 한번 찾아보았다.
Java
Java의 Arrays.sort()
메서드는 내부적으로 퀵 정렬과 삽입 정렬을 번갈아가며 사용하여, 어떤 자료형에도 관계 없이 O(n log(n)) 시간복잡도를 만족한다. 번역한 내용은 아래와 같다. 1
이 클래스는 Vladimir Yaroslavskiy, Jon Bentley 및 Josh Bloch의 Dual-Pivot Quicksort 알고리즘의 강력하고 완전히 최적화된 순차 및 병렬 버전을 구현합니다.
이 알고리즘은 모든 데이터 세트에 대해 O(n log(n)) 성능을 제공하며 일반적으로 기존(1-피벗) Quicksort 구현보다 빠릅니다. 혼합 삽입 정렬, 실행 병합 및 힙 정렬, 계산 정렬, 병렬 병합 정렬과 같이 Dual-Pivot Quicksort에서 호출되는 추가 알고리즘도 있습니다.
/**
* Sorts the specified array into ascending numerical order.
*
* @implNote The sorting algorithm is a Dual-Pivot Quicksort
* by Vladimir Yaroslavskiy, Jon Bentley, and Joshua Bloch. This algorithm
* offers O(n log(n)) performance on all data sets, and is typically
* faster than traditional (one-pivot) Quicksort implementations.
*
* @param a the array to be sorted
*/
public static void sort(long[] a) {
DualPivotQuicksort.sort(a, 0, 0, a.length);
}
Python
Python의 경우 병합 정렬과 삽입 정렬을 사용한다.
ToDo
- 버블 정렬
- 힙 정렬
- 계수 정렬
- 기수 정렬