N
: 정수의 개수 (1 ≤ N
≤ 10,000,000)
nums
: N개의 정수 (1 ≤ num
≤ 10,000)
✅ 입력 조건
1. N 입력
2. N개의 정수를 N번 반복해 입력
✅ 출력 조건
1. 정렬된 정수를 하나씩 출력
sort() 함수로 정렬하거나 for문 내에서 append로 리스트에 값을 추가하는 식의 코드는 메모리 초과
를 발생시킨다.
대신, 미리 10001 크기의 리스트를 정의한다.
N개의 정수를 돌면서 해당 정수를 그 리스트의 인덱스로 접근하여 값을 올려준다.
이 값은 중복된 숫자의 개수를 의미하게 된다.
그 후, 10001 크기의 리스트를 돌면서 해당 값만큼 인덱스 값을 반복해 출력해주면 원하는 방식으로 구현할 수 있다.
→ 위와 같은 방식은 카운팅 정렬(계수 정렬) 알고리즘을 기반으로 하였다.
N개의 정수를 리스트의 인덱스로 접근해 개수 세기 →
리스트 접근해 그 개수만큼 인덱스 출력 →
최종 시간복잡도
으로 제한 시간 내에 연산이 가능하다.
정수를 인덱스로 접근해 개수 세고 그 리스트를 돌면서 인덱스 출력하기.
import sys
input = sys.stdin.readline
# N 입력
N = int(input())
# N개의 수 입력
nums = [int(input()) for _ in range(N)]
# 오름차순 정렬
nums.sort()
# 원하는 형식으로 출력
for num in nums:
print(num)
sort()
로 정렬하는 방식으로 풀었더니 메모리 초과가 발생했다.N
≤ 10,000,000이라는 범위 때문에 sort()
를 이용한 정렬 방식은 적합하지 않았다.import sys
import heapq
input = sys.stdin.readline
# N 입력
N = int(input())
# N개의 수 입력
nums = [int(input()) for _ in range(N)]
# 최소 힙 사용
heapq.heapify(nums)
# 오름차순 정렬해 저장할 리스트 정의
result = []
# heapq.heappop을 반복하여 최소값 추출하며 정렬
for _ in range(N):
result.append(str(heapq.heappop(nums)))
# 원하는 형식으로 출력
for i in result:
print(i)
import sys
input = sys.stdin.readline
# 1. N 입력
N = int(input())
# 2. N개의 자연수 입력
nums = [int(input()) for _ in range(N)]
# 3. 10001 크기의 리스트 정의
count = [0] * 10001
# 4. N개의 정수를 리스트의 인덱스로 접근하여 개수 세기
for num in nums:
count[num] += 1
# 5. 리스트에 접근해 그 개수만큼 인덱스 출력]
for i in range(10001):
# count[i]이 1 이상이라면
if count[i] != 0:
# 그 값만큼 출력
for j in range(count[i]):
print(i)
import sys
input = sys.stdin.readline
# 1. N 입력
N = int(input())
# 2. 10001 크기의 리스트 정의
count = [0] * 10001
# 3. N개의 정수를 리스트의 인덱스로 접근하여 개수 세기
for _ in range(N):
num = int(input())
count[num] += 1
# 4. 리스트에 접근해 그 개수만큼 인덱스 출력
for i in range(10001):
# count[i]이 1 이상이라면
if count[i] != 0:
# 그 값만큼 출력
for j in range(count[i]):
print(i)
💡 기본 원리
💡 작동 원리
💡 특징
In-place sorting
)💡 구현
# 버블 정렬
def bubble_sort(arr):
n = len(arr)
for i in range(n):
for j in range(0, n-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
return arr
# 정렬 조기 종료 최적화한 버블 정렬
def optimized_bubble_sort(arr):
n = len(arr)
for i in range(n):
swapped = False
for j in range(0, n-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
swapped = True
if not swapped:
break
💡 기본 원리
💡 작동 원리
swap
) : 최소값을 배열 현재 위치와 교체, 처음엔 배열 첫번째 위치에서 시작💡 특징
swap
) 횟수 적음 → 최악 n-1
번n
일 때, 약 n(n-1)/2
번 비교 → In-place sorting
)💡 구현
def selection_sort(arr):
n = len(arr)
# 각 반복마다 가장 작은 요소 찾아 현재 위치 요소와 교환
for i in range(n):
min_idx = i
for j in range(i+1, n):
if arr[j] < arr[min_idx]:
min_idx = j
arr[i], arr[min_idx] = arr[min_idx], arr[i]
return arr
💡 기본 원리
💡 작동 원리
💡 특징
💡 구현
def insertion_sort(arr):
for i in range(i, len(arr)):
key = arr[i]
j = i - 1
while j >= 0 and key < arr[j]:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
💡 기본 원리
pivot
) 선택💡 작동 원리
💡 특징
장점 | 단점 |
---|---|
평균 으로 매우 빠른 실행 시간 | 최악 까지 증가 가능(피벗 선택 불균형일 경우) |
in-place 정렬 → 추가적 메모리 거의 사용 ❌ | 불안정 정렬 |
대규모 데이터셋에 효율적 작동, 실 상황에서 자주 사용 |
💡 구현
def quichSort(arr):
if len(arr) <= 1:
return arr
# pivot 선택
pivot = arr[len(arr) // 2]
# pivot보다 작은 것 왼쪽
left = [x for x in arr if x < pivot]
# 중간값
middle = [x for x in arr if x == pivot]
# pivot보다 큰 것 오른쪽
right = [x for x in arr if x > pivot]
# 재귀적 접근
return quichSort(left) + middle + quickSort(right)
💡 기본 원리
💡 작동 원리
💡 특징
💡 구현
def mergeSort(arr):
if len(arr) > 1:
mid = len(arr) // 2
# 분할
L = arr[:mid]
R = arr[mid:]
# 재귀 호출로 계속 분할
mergeSort(L)
mergeSort(R)
i = j = k = 0
while i < len(L) and j < len(R):
if L[i] < R[j]:
arr[k] = L[i]
i += 1
else:
arr[k] = R[j]
j += 1
k += 1
while i < len(L):
arr[k] = L[i]
i += 1
k += 1
while j < len(R):
arr[k] = R[i]
j += 1
k += 1
return arr
💡 기본 원리
Max Heap
)이나 최소 힙(Min Heap
) 트리 구조 활용해 배열 정렬💡 작동 원리
💡 특징
장점 | 단점 |
---|---|
모든 경우에 보장해 데이터 많아도 성능 급격 저하 ❌ | 구현 복잡 |
in-place 정렬 → 추가적 메모리 거의 사용 ❌ | 불안정 정렬 |
최악에도 안정적인 성능 | 다른 정렬 알고리즘 비해 상대적으로 느림 (퀵 정렬이 더 빠름) |
💡 구현
def heapify(arr, n, i):
largest = i # 루트를 최댓값으로 가정
left = 2 * i + 1 # 왼쪽 자식
right = 2 * i + 2 # 오른쪽 자식
# 왼쪽 자식이 루트보다 크다면
if left < n and arr[left] > arr[largest]:
largest = left
# 오른쪽 자식이 현재 최댓값보다 크다면
if right < n and arr[right] > arr[largest]:
largest = right
# 최댓값이 루트가 아니라면 교환
if largest != i:
arr[i], arr[largest] = arr[largest], arr[i]
# 교환된 루트에 대해 다시 힙 구성
heapify(arr, n , largest)
def heapSort(arr):
n = len(arr)
# 초기 최대 힙 구성
for i in range(n // 2 - 1, -1, -1):
heapify(arr, n, i)
# 하나씩 원소 꺼내서 다시 최대 힙 구성
for i in range(n-1, 0, -1):
arr[i], arr[0] = arr[0], arr[i]
# 루트와 마지막 요소 교환
heapify(arr, i, 0)
💡 기본 원리
💡 작동 원리
💡 특징
n
배열 크기, k
입력 데이터 범위장점 | 단점 |
---|---|
데이터 범위 한정적 & 데이터 크기 클 경우 → 빠른 정렬 속도 | 주로 정수 등 제한된 데이터 유형에서만 사용 가능 |
안정 정렬(Stable Sort) | 문자열/복잡한 객체같은 데이터 유형에 직접 적용 어려움 |
계수 정렬 과정에서 데이터에 대한 추가적 정보(데이터 분포 등) 얻기 가능 → 다른 알고리즘에서 유용 활용 | 데이터 최댓값 매우 클 경우, 상응하는 카운트 배열 필요 → 메모리 사용량 많아짐 |
💡 구현
def counting_sort(arr):
# 최대값 찾기
max_val = max(arr)
# 카운트 배열 초기화
count_arr = [0] * (max_val + 1)
# 입력 배열의 요소 세기
for num in arr:
count_arr[num] += 1
# 누적 카운트 배열 계산
for i in range(1, len(count_arr)):
count_arr[i] += count_arr[i-1]
# 결과 배열 초기화
output_arr = [0] * len(arr)
# 결과 배열 만들기
i = len(arr) - 1
while i >= 0:
output_arr[count_arr[arr[i]] - 1] = arr[i]
count_arr[arr[i]] -= 1
i -= 1
# 결과 복사
for i in range(len(arr)):
arr[i] = output_arr[i]
💡 기본 원리
실제 데이터는 대부분 이미 정렬돼 있을 것이다
라는 가정에 기반한 알고리즘💡 작동 원리
run
)로 잘라 각각의 덩어리를 삽입 정렬로 정렬💡 특징
💡 사용
# sort 함수 이용
arr.sort()
# sorted 함수 이용
sorted_arr = sorted(arr)
💡 기본 원리
💡 작동 원리
💡 특징
k
)에 크게 의존n
: 정렬할 원소의 수장점 | 단점 |
---|---|
데이터의 자릿수/문자의 위치 정보 활용해 정렬 수행 → 특정 조건에서 매우 빠름 | 숫자/문자열 같은 특정 유형의 데이터에만 적용 가능 |
안정 정렬 | 추가적 메모리 필요 |
💡 구현
# 카운팅 정렬
def counting_sort(arr, exp1):
n = len(arr)
output = [0] * n
count = [0] * (10)
for i in range(0, n):
index = arr[i] // exp1
count[index % 10] += 1
for i in range(1, 10):
count[i] += count[i - 1]
i = n - 1
while i >= 0:
index = arr[i] // exp1
output[count[index % 10] - 1] = arr[i]
count[index % 10] -= 1
i -= 1
for i in range(0, len(arr)):
arr[i] = output[i]
# 내부적으로 카운팅 정렬하는 기수 정렬
def radix_sort(arr):
max1 = max(arr)
exp = 1
while max1 // exp > 0:
counting_sort(arr, exp)
exp *= 10
📖 In-place sorting (제자리 정렬)
정렬 과정에서 추가적인 공간이 필요하지 않은 정렬
📖 Stable sorting (안정적인 정렬)
동일한 값을 가진 원소의 상대적인 순서가 정렬 후에도 유지
= 동일한 값 요소의 상대적 순서 변경 ❌
📖 Unstable sorting (불안정적인 정렬)
값 같은 레코드가 입력에 있을 때, 순서 정렬 후에도 유지되지 않을 수 있음