안정적으로 빠른 속도를 보장하는 병합 정렬과 힙 정렬, 제한된 범위의 중복 값을 정렬할 때 효과적인 도수 정렬을 정리해 보았다.
| 정렬 | 설명 | 시간복잡도 | 공간복잡도 | 안정성 |
|---|---|---|---|---|
| 병합 정렬 | 배열을 두 그룹으로 나누어 각각 재귀적으로 정렬 후 병합 | O | ||
| 힙 정렬 | 최대 힙 자료구조를 사용한 정렬 방법 | X | ||
| 도수 정렬 | 값을 비교하는 대신, 값의 개수를 셈 | (는 데이터값의 범위) | O |
A, B의 각각 맨 앞에서 시작하는 포인터 i, j를 둠A[i], B[j] 중 작은 값을 새로운 배열 C에 추가def merge_sorted_list(a, b, c):
i, j, k = 0, 0, 0
na, nb, nc = len(a), len(b), len(c)
# 두 배열의 원소 비교
while i < na and j < nb:
if a[i] <= b[j]:
c[k] = a[i]
i += 1
else:
c[k] = b[j]
j += 1
k += 1
# 한 쪽이 끝나면, 나머지 배열의 값을 모두 복사
while i < na:
c[k] = a[i]
i += 1
k += 1
while j < nb:
c[k] = b[j]
j += 1
k += 1
a = [2, 4, 6, 8, 11, 13]
b = [1, 2, 3, 4, 9, 16, 21]
c = [None] * (len(a) + len(b))
merge_sorted_list(a, b, c)
print(c) # [1, 2, 2, 3, 4, 4, 6, 8, 9, 11, 13, 16, 21]

def div_and_merge(a, temp, start, end):
if start < end:
center = (start + end) // 2
div_and_merge(a, temp, start, center)
div_and_merge(a, temp, center + 1, end)
i = start
j = center + 1
k = start
while i <= center and j <= end:
if a[i] <= a[j]:
temp[k] = a[i]
i += 1
else:
temp[k] = a[j]
j += 1
k += 1
while i <= center:
temp[k] = a[i]
i += 1
k += 1
while j <= end:
temp[k] = a[j]
j += 1
k += 1
for x in range(start, end + 1):
a[x] = temp[x]
def merge_sort(a):
n = len(a)
temp = [0] * n
div_and_merge(a, temp, 0, n - 1)
a = [1, 3, 12, 6, 4, 11, 8, 7, 3, 2, 6, 5]
merge_sort(a)
print(a) # [1, 2, 3, 3, 4, 5, 6, 6, 7, 8, 11, 12]
시간 복잡도
공간 복잡도
temp 배열 생성 -> 안정성
a[i] <= b[j]로 조건문을 설정한 이유a[i]a[(i - 1) // 2]에 저장됨a[2 * i + 1](좌), a[2 * i + 2](우)

i를 n-1로 초기화a의 a[0] (루트)와 a[i]를 교환a[0]...a[i-1]을 힙으로 재구성a[0](루트)에서 시작하여, 자신보다 큰 값을 가진 자식과 자리를 바꾸며 아래로 내려가는 작업 반복i의 값을 1 감소한 뒤, (2)로 돌아가 반복i == 0일 때 정렬 완료
n // 2 - 1 부터 역순으로 순회하면 됨def heap_sort(a):
# a[start] ~ a[end] 원소를 힙으로 만듦
def down_heap(a, start, end):
# a[start] 외엔 힙 상태로 가정
# a[start] 를 알맞은 위치로 내려 힙 상태 만들기
value = a[start]
parent = start
while True:
cl = 2 * parent + 1 # 왼쪽 자식
cr = cl + 1 # 오른쪽 자식
# 자녀가 없는 경우 break
if cl > end:
break
# 더 큰 자식 선택
if cr <= end and a[cr] > a[cl]:
child = cr
else:
child = cl
# 부모가 두 자식보다 크거나 같으면 멈춤
if value >= a[child]:
break
# 자식 값을 부모 위치로 끌어올림
a[parent] = a[child]
parent = child
# value를 최종 위치에 저장
a[parent] = value
n = len(a)
# 배열 a를 힙으로 만들기
for i in range(n // 2 - 1, -1, -1):
down_heap(a, i, n - 1)
# 최댓값(루트)를 배열 끝으로 이동
# 나머지 원소들을 힙으로 재구성
# 위 과정을 반복
for i in range(n - 1, 0, -1):
a[0], a[i] = a[i], a[0]
down_heap(a, 0, i - 1)
a = [6, 4, 3, 7, 1, 9, 8]
heap_sort(a)
print(a)
시간 복잡도
down_heap로 노드를 힙 내 알맞은 위치로 내릴 때down_heap을 하면 같지만, 실제론 down_heap이 항상 루트부터 시작하므로, 이동 깊이는 down_heap을 번 반복 -> 공간 복잡도
a의 원소의 교환만 이루어지고, 추가 배열이 선언되지 않음안정성

freq을 만듦freq[x]는 원소 x가 몇 개인지 나타냄freq[x]를 값 x 이하인 원소 개수로 업데이트output을 만듦a를 역순으로 순회하며, 아래와 같이 output에 저장for i in range(n - 1, -1, -1):
# 현재 값의 인덱스 구하기
freq[a[i]] -= 1
# 해당 인덱스에 넣기
output[freq[a[i]]] = a[i]
def counting_sort(a):
max_elem = max(a)
n = len(a)
freq = [0] * (max_elem + 1) # 도수분포표 배열
output = [0] * n # 정렬 결과
# 원소별 개수 계산
for i in range(n):
freq[a[i]] += 1
# 누적도수분포 계산
for i in range(1, len(freq)):
freq[i] += freq[i - 1]
# 정렬
for i in range(n - 1, -1, -1):
freq[a[i]] -= 1
output[freq[a[i]]] = a[i]
for i in range(n):
a[i] = output[i]
a = [5, 7, 0, 2, 4, 10, 3, 1, 3] # [0, 1, 2, 3, 3, 4, 5, 7, 10]
counting_sort(a)
print(a)
.sort(), sorted()sys.stdin.readline 써야 함!!!import sys
input = sys.stdin.readline
def merge_sort(a, start, end):
if start < end:
mid = (start + end) // 2
merge_sort(a, start, mid)
merge_sort(a, mid + 1, end)
i = start
j = mid + 1
merged = []
while i <= mid and j <= end:
if a[i] <= a[j]:
merged.append(a[i])
i += 1
else:
merged.append(a[j])
j += 1
while i <= mid:
merged.append(a[i])
i += 1
while j <= end:
merged.append(a[j])
j += 1
for i in range(len(merged)):
a[start + i] = merged[i]
N = int(input())
nums = []
for _ in range(N):
nums.append(int(input()))
merge_sort(nums, 0, len(nums) - 1)
for n in nums:
print(n)
import sys
input = sys.stdin.readline
N = int(input())
count = [0] * 10001
# O(N)
for _ in range(N):
num = int(input())
count[num] += 1
# O(K)
for i in range(1, len(count)):
for _ in range(count[i]):
print(i)
.sort()나 sorted()의 key 매개변수에 정렬 기준을 반환하는 함수를 입력할 수 있음N = int(input())
words = set([]) # 집합은 중복을 허용하지 않음
for _ in range(N):
words.add(input())
words = list(words) # 정렬을 위해 리스트로 변경
# 순서: 길이 -> 사전순
words.sort(key=lambda x: (len(x), x))
for w in words:
print(w)
[백준 / 실버 3 / 20920. 영단어 암기는 괴로워]
key로 보내야 함from collections import Counter
N, M = map(int, input().split())
words = []
# 단어 리스트
for _ in range(N):
word = input()
if len(word) < M:
continue
words.append(word)
# 단어의 수 세기: 각 단어의 등장 수를 저장한 딕셔너리 반환
words_count = Counter(words)
# 중복 단어 제거
words = list(set(words))
# 정렬: 자주 나오는 순서 -> 단어의 길이 -> 알파벳 사전순
# 역순은 - 붙이기
words.sort(key=lambda x: (-words_count[x], -len(x), x))
for word in words:
print(word)
[2, 4, -10, 4, 9] -> {2, 4, -10, 9} -> [-10, 2, 4, 9]9보다 작은 좌표는 -10, 2, 4 3개9는 3번째 인덱스에 위치N = int(input())
array = list(map(int, input().split()))
# 집합으로 중복값 없애고, 다시 리스트로 변환 후 정렬
set_array = list(set(array))
set_array.sort()
# 각 좌표의 인덱스 저장
x_dict = dict()
for i in range(len(set_array)):
x_dict[set_array[i]] = i
# 압축된 좌표 반환
for a in array:
print(x_dict[a], end=" ")