출처:
수학표기이지만 컴공에서는 알고리즘의 복잡도(시간 복잡도)를 단순화할때 사용
를 만족하는 충분히 큰 모든 에 대하여 가 성립하도록 하는 양의 실수 과 실수 가 존재하면 이다.
ex)
이라면 에 대해서
으로
사실 Big-O가 정확히 무엇을 뜻하는지는 모르겠고
여기서는 알고리즘의 시간 복잡도가 해당 차수 또는 그 이하의 차수라는 의미 (최악의 경우를 표현)
예를 들어 내가 가로가 n, 세로가 3n+5인 밭의 1x1 영역마다 씨앗을 하나씩 심는다면
번의 작업을 해야하는데 이를 Big-O로 표기하면
으로 표현할 수 있다. 밭의 가로 길이 n이 늘어나면 작업은 에 비례하는구나.
오름차순 정렬로 작성함
10000개의 무작위 정수 리스트로 작동 시간 측정
i번째 원소를 이미 정렬된 (i-1)개의 원소들 사이의 적절한 위치에 삽입하는 알고리즘
def insertion_sort(nums):
for i in range(1, len(nums)):
val = nums[i]
j = i
while j >= 1 and nums[j-1] > val:
# 원소를 삽입할 위치를 찾을때까지 원소들을 한칸씩 뒤로 밀기
nums[j] = nums[j-1]
j -= 1
nums[j] = val
return nums
j=i
를 j=i-1
로 변경하며 while 루프의 조건에서 j-1
연산을 제거j>=0
보다 nums[j]
를 앞에 위치시킴def insertion_sort(nums):
for i in range(1, len(nums)):
val = nums[i]
j = i-1
while nums[j] > val and j >= 0:
# 원소를 삽입할 위치를 찾을때까지 원소들을 한칸씩 뒤로 밀기
nums[j+1] = nums[j]
j -= 1
nums[j+1] = val
return nums
i번째 원소와 (i+1)~n번째 원소 중 최솟값인 원소와 교환. 나머지 원소들에 반복한다.
def selection_sort(nums):
for i in range(len(nums)-1):
min_idx = i
for j in range(i+1, len(nums)):
if nums[min_idx] > nums[j]:
min_idx = j
nums[i], nums[min_idx] = nums[min_idx], nums[i]
return nums
분할-정복 알고리즘으로 작동
데이터를 계속 반으로 나누며 길이 1인 배열들로 분할한다.
두개의 길이 1인 배열들의 원소를 비교하고 정렬하여 병합한다.
정렬된 두개의 길이 2인 배열들의 원소를 비교하고 정렬하여 병합한다.
... 반복
def merge_sort(nums):
# 길이가 1인 경우 바로 반환
if len(nums) <= 1:
return nums
n = len(nums)
# 반으로 분할
part_0, part_1 = nums[:n//2], nums[n//2:]
# 재귀로 분할된 파트를 정렬
part_0_sorted, part_1_sorted = merge_sort(part_0), merge_sort(part_1)
merged = []
# 정렬된 파트들의 원소들을 비교하고 정렬하여 하나로 병합함
while part_0_sorted and part_1_sorted:
if part_0_sorted[0] < part_1_sorted[0]:
merged.append(part_0_sorted.pop(0))
else:
merged.append(part_1_sorted.pop(0))
# 한 파트가 먼저 비워진다면 다른 파트 원소들을 순서를 유지한채로 병합 리스트에 추가
merged.extend(part_0_sorted+part_1_sorted)
return merged
배열의 처음부터 끝까지 나아가며 인접한 두 원소를 비교, 위치 교환을 반복하며 가장 큰 원소를 배열 끝으로 밀어올리는 작업을 반복한다.
def bubble_sort(nums):
for i in range(len(nums)-1):
for j in range(len(nums)-1-i):
if nums[j] > nums[j+1]:
nums[j], nums[j+1] = nums[j+1], nums[j]
return nums
배열의 한 원소를 피벗으로 삼고 원소간 위치 교환을 통해 피벗보다 작은 값은 피벗 좌측에, 피벗보다 큰 값은 피벗 우측에 위치시킨다. 이 작업을 피벗 좌측, 피벗 우측에 반복한다. 분할-점령 방식.
배열의 가장 마지막 원소를 피벗으로 설정한 경우
left는 피벗보다 크거나 같은 값을 선택하고, right는 피벗보다 작은 값을 선택하여 위치를 교환한다.
right가 피벗까지 도달하면 한 사이클 완료. 피벗을 제외한 피벗 좌우의 부분배열에 대해 작업을 반복.
def quick_sort(nums, l_bound=None, r_bound=None):
l_bound = 0 if l_bound is None else l_bound
r_bound = len(nums)-1 if r_bound is None else r_bound
l = r = l_bound
pivot = nums[r_bound]
while True:
while nums[l] < pivot and r < r_bound:
l += 1
r += 1
while nums[r] >= pivot and r < r_bound:
r += 1
if l != r:
nums[l], nums[r] = nums[r], nums[l]
if r == r_bound:
break
if (l-1) - l_bound >= 1:
quick_sort(nums, l_bound, l-1)
if r_bound - (l+1) >= 1:
quick_sort(nums, l+1, r_bound)
nums_ = nums.copy()
quick_sort(nums_)
힙heap 트리를 이용한 정렬. 힙 트리는 완전 이진 트리이면서 부모 노드가 자식 노드보다 항상 크거나(max heap) 작다(min heap).
힙 정렬은 힙 트리의 루트는 항상 최소값이거나 최대값임을 이용해 모든 데이터를 힙 트리에 넣고 루트부터 빼와 정렬된 배열을 만드는 방식이다.
def heap_insert(heap, elem):
if not heap:
heap.append(elem)
return
heap.append(elem)
idx = len(heap)-1
p_idx = (idx-1) // 2 # 부모노드 인덱스
while p_idx >= 0 and heap[p_idx] > heap[idx]:
# 부모노드각 자식노드보다 크면 위치 교환
heap[p_idx], heap[idx] = heap[idx], heap[p_idx]
idx = p_idx
p_idx = (idx-1) // 2
def heap_delete(heap):
if heap:
root_val = heap.pop(0)
else:
root_val = None
if heap:
heap.insert(0, heap.pop())
idx = 0
while True:
# 자식노드 인덱스
c_l_idx = 2 * idx + 1
c_r_idx = c_l_idx + 1
# 자식노드 중 유효하고, 더 작은 값을 가진 자식노드 인덱스 선택
if c_l_idx < len(heap) and c_r_idx < len(heap):
c_idx = c_l_idx if heap[c_l_idx] <= heap[c_r_idx] else c_r_idx
elif c_l_idx < len(heap) and c_r_idx >= len(heap):
c_idx = c_l_idx
else:
break
# 부모보다 자식이 더 작으면 위치 교환
if heap[idx] > heap[c_idx]:
heap[idx], heap[c_idx] = heap[c_idx], heap[idx]
idx = c_idx
else:
break
return root_val
heap = []
for n in nums:
heap_insert(heap, n)
sorted_heap = [heap_delete(heap) for _ in range(len(nums))]
최대힙으로 변환하기
def convert_to_max_heap(nums):
n = len(nums) - 1
if n % 2 == 0:
n -= 1
for c_idx in range(n, 0, -2): # 가장 끝에서부터 자식 노드 중 왼쪽 노드만 순회
idx = c_idx // 2 # 부모노드 인덱스
# 부모노드가 루트인 부분 트리가 최대힙 규칙을 준수하도록 변환
while True:
c_l_idx = 2 * idx + 1
c_r_idx = c_l_idx + 1
if c_l_idx < len(nums) and c_r_idx < len(nums):
c_idx = c_l_idx if nums[c_l_idx] >= nums[c_r_idx] else c_r_idx
elif c_l_idx < len(nums) and c_r_idx >= len(nums):
c_idx = c_l_idx
else:
break
if nums[idx] < nums[c_idx]:
nums[idx], nums[c_idx] = nums[c_idx], nums[idx]
idx = c_idx
else:
break
def heap_sort(nums):
# 최대힙으로 변경 - 루트가 최댓값
convert_to_max_heap(nums)
n = len(nums)
for i in range(n-1, 0, -1):
# 루트(최댓값)을 가장 뒤의 원소와 교환
nums[0], nums[i] = nums[i], nums[0]
idx = 0
# 루트로 올라온 원소를 최대힙 규칙을 준수하는 위치로 옮긴다.
while True:
c_l_idx = 2 * idx + 1
c_r_idx = c_l_idx + 1
if c_l_idx < i and c_r_idx < i: # i번째와 그 뒤의 원소들은 이미 정렬된 상태
c_idx = c_l_idx if nums[c_l_idx] >= nums[c_r_idx] else c_r_idx
elif c_l_idx < i and c_r_idx >= i:
c_idx = c_l_idx
else:
break
if nums[idx] < nums[c_idx]:
nums[idx], nums[c_idx] = nums[c_idx], nums[idx]
idx = c_idx
else:
break
nums_ = nums.copy()
heap_sort(nums_)
print(sorted_nums == nums_)