Radix sort의 In-Place가 Yes로 되어있지만 No가 맞다. Yes가 되는 정렬도 존재하지만 특수한 경우다. https://en.wikipedia.org/wiki/Radix_sort#In-place_MSD_radix_sort_implementations
<aside> 💡 알고리즘은 문제를 해결하기 위해 사용하는 일련의 단계다. 정해진 순서대로 문제를 해결하는 방법이며 줄여서 ‘절차’라고 할 수도 있다. 정렬 알고리즘의 평균 시간복잡도는 $O(nlogn)$ 이상으로 빠를 수 없다.
</aside>
시간 복잡도
공간 복잡도
점근적 분석(asymptotic analysis)
빅 오 표기법
빅 오메가, 리틀 오메가, 리틀 오, 세타 등 다양한 표기법이 존재한다.
빅 오 표기법
빅 오메가 표기법
빅 세타 표기법
이 모든 경우는 n이 무한대로 나아갈 경우를 가정하기 때문에, 앞에 3n이든 100n이든 무한대에서는 의미가 없으므로 더 작은 차항과 상수는 무시하고 최고 차항만 사용한다.
set x = 0
for i = 0 ... i < 10
x += 1
print(x)
function example(n)
set x = 0
for i = 0 ... i < n
x += 1
print(x)
set x = 0
for i = 0 ... i < n / 2
x += 1
print(x)
1번 예시
function example(n)
while 0 > n or n > 100
if n < 0
n++
else
n--
return n
2번 예시
set x = 0
for i = 0 ... i < n
for j = 5 ... j < n
x += 1
print(x)
for i = 0 ... i < n
x += 1
print(x)
$O(1)$ - 상수 시간
상수형 알고리즘으로 데이터 입력량과 무관하게 실행 시간이 일정하다.
# Array
nums = [1, 2, 3]
nums.append(4) # push to end
nums.pop() # pop from end
nums[0] # lookup
nums[1]
nums[2]
# HashMap / Set
hashMap = {}
hashMap["key"] = 10 # insert
print("key" in hashMap) # lookup
print(hashMap["key"]) # lookup
hashMap.pop("key") # remove
$O(n)$ - 선형 시간
nums = [1, 2, 3]
sum(nums) # sum of array
for n in nums: # looping
print(n)
nums.insert(1, 100) # insert middle
nums.remove(100) # remove middle
print(100 in nums) # search
import heapq
heapq.heapify(nums) # build heap
# sometimes even nested loops can be O(n)
# (e.g. monotonic stack or sliding window)
$O(logn)$ - 로그 시간
# Binary search
nums = [1, 2, 3, 4, 5]
target = 6
l, r = 0, len(nums) - 1
while l <= r:
m = (l + r) // 2
if target < nums[m]:
r = m - 1
elif target > nums[m]:
l = m + 1
else:
print(m)
break
# Binary Search on BST
def search(root, target):
if not root:
return False
if target < root.val:
return search(root.left, target)
elif target > root.val:
return search(root.right, target)
else:
return True
# Heap Push and Pop
import heapq
minHeap = []
heapq.heappush(minHeap, 5)
heapq.heappop(minHeap)
$O(\sqrt{n})$ - 제곱근 시간
# Get all factors of n
import math
n = 12
factors = set()
for i in range(1, int(math.sqrt(n)) + 1):
if n % i == 0:
factors.add(i)
factors.add(n // i)
$O(nlogn)$ - 선형 로그 시간
# HeapSort
import heapq
nums = [1, 2, 3, 4, 5]
heapq.heapify(nums) # O(n)
while nums:
heapq.heappop(nums) # O(logn)
# MergeSort (and most built-in sorting functions)
$O(n\times m)$ - 이중 선형 시간
# Get every pair of elements from two arrays
nums1, nums2 = [1, 2, 3], [4, 5]
for i in range(len(nums1)):
for j in range(len(nums2)):
print(nums1[i], nums2[j])
# Traverse rectangle grid
nums = [[1, 2, 3], [4, 5, 6]]
for i in range(len(nums)):
for j in range(len(nums[i])):
print(nums[i][j])
$O(n^2)$ - n차 시간
# Traverse a square grid
nums = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
for i in range(len(nums)):
for j in range(len(nums[i])):
print(nums[i][j])
# Get every pair of elements in array
nums = [1, 2, 3]
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
print(nums[i], nums[j])
# Insertion sort (insert in middle n times -> n^2)
# n^3
# Get every triplet of elements in array
nums = [1, 2, 3]
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
for k in range(j + 1, len(nums)):
print(nums[i], nums[j], nums[k])
$O(2^n)$ - 지수 시간
# Recursion, tree height n, two branches
def recursion(i, nums):
if i == len(nums):
return 0
branch1 = recursion(i + 1, nums)
branch2 = recursion(i + 2, nums)
# c^n
# c branches, where c is sometimes n.
def recursion(i, nums, c):
if i == len(nums):
return 0
for j in range(i, i + c):
branch = recursion(j + 1, nums)
$O(n!)$ - 팩토리얼 시간
# Permutations
def permute(nums):
def backtrack(path, nums):
if not nums:
res.append(path)
return
for i in range(len(nums)):
backtrack(path + [nums[i]], nums[:i] + nums[i + 1:])
res = []
backtrack([], nums)
return res
빅 오 표기법으로 실제 코드를 계산하는 방법
보통 재귀나 반복문이 코드에 들어가면 여기에 대한 절대적인 연산량을 계산하기란 매우 복잡하기 때문에 통상 빅 오 표기법으로 대략적으로 표기한다.
1회 계산하고 끝나는 것들은 $O(1)$ 이다
set a = 10
set a = 5
if a != 10
print('hello')
좀 더 직관적으로 파악하기 위해서 딱 1초 만큼의 제한이 있는 계산기가 있을 때 가능한 계산 범위를 상상해보면 좋다.
빅 오 노테이션 시간을 계산하는데 단순히 for 문만 따져서는 안된다.
function solution(n, m) {
let sum = 0;
let visited = Array.from(Array(n), () => Array(m).fill(0));
for (let i = 1; i <= n; i++) {
for (let j = 1; j <= m; j++) {
if (visited[i][j] === 0) {
for (let k = j; k <= m; k++) {
visited[i][k] = 1;
}
}
}
}
}
로그 계산법
function solution(n, count) {
if (n <= 1) {
return count;
}
return solution(n / 3, count * 3);
}
호출 횟수 | 첫 번째 인자 (n) | 두 번째 인자 (count) |
---|---|---|
1 | N | cnt |
2 | N / 3 | cnt * 3 |
3 | (N / 3) / 3 = N / 3^2 | (cnt * 3) * 3 = cnt * 3^2 |
안정(Stable)한 정렬
In-Place 한 정렬
Lower Bound와 Upper Bound