선택 정렬
가장 작은 데이터를 선택해 맨 앞에 있는 데이터와 바꾸고, 그 다음 작은 데이터를 선택해 두번째 데이터와 바꾸는 과정을 반복해보면 어떨까? |
첫번째 for문의 idx가 array끼리의 자리 변경을 위한 앞 인덱스 저장.
두번째 for문이 앞에 지정된 인덱스를 제외한 모든 뒤 요소들끼리 크기 비교를 통해 최소값을 가지는 인덱스 저장.
두 인덱스를 활용하여 값 변경
array = [7, 5, 9, 0, 3, 1, 6, 2, 4, 8]
for i in range(len(array)):
min_index = i # 가장 작은 원소의 인덱스
for j in range(i+1, len(array)):
if array[min_index] > array[j]: # 가장 작은 원소를 찾기위해 맨 앞 element부터 search
min_index = j
array[i], array[min_index] = array[min_index], array[i] # 파이썬은 swap 기능을 통해 간단하게 swap
print(array)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
시간복잡도를 계산해보는데 for j in range(i+1, len(array))
부분으로 인해 \(N + (N - 1) + (N - 2) + ... + 2 = {N^2 + N \over 2}\) 가 되고 for n in range(len(array))
로 인해 \(O(N^3)\) 이 되는것이 아닌가 헷갈렸다.
\({N^2 + N \over 2}\)에 for n in range(len(array))
가 돌아가는 것도 포함되어 있는 것이다.
다음의 그림을 보면 이해가 쉬울 것이다.
삽입정렬
데이터를 하나씩 확인하며, 각 데이터를 적절한 위치에 삽입하고 바로 끝내면 어떨까? |
출처: https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=justant&logNo=20204025251
- 위 그림의 주황색 영역은 이미 정렬이 되어 있는것으로 간주.
= 빨간색 요소를 적절한 위치에 삽입하는것이 목표이며 주황색 요소의 맨 뒤부터 앞까지 순차적으로 비교를 진행함. - 중요한것은 한번이라도 빨간색 요소보다 작은 주황색 요소가 발견되었다면 그대로 종료함.
- 이 특성때문에 정렬이 어느정도 되어있는 array의 경우 퀵 정렬 알고리즘보다도 빠르다.
array = [7, 5, 9, 0, 3, 1, 6, 2, 4, 8]
for i in range(1, len(array)):
# 처음 시작할 때 맨 앞 노드는 정렬되어 있는것으로 가정.
# 생각해보면 당연한게 정렬이란 개념은 2개 이상 원소가 있을 때 부터 필요한것임
for j in range(i, 0, -1): # 인덱스 i부터 1까지 감소하며 반복하는 문법
if array[j] < array[j-1]: # 한 칸씩 왼쪽으로 이동
array[j], array[j-1] = array[j-1], array[j]
else: # 자기보다 작은 데이터를 만나면 그 위치에서 멈춤
break
print(array)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
\(O(N^2)\) 의 소요시간을 갖지만 최선의 경우 \(O(N)\)의 소요시간을 갖는다.
퀵 정렬
기준 데이터를 설정하고 그 기준보다 큰 데이터와 작은 데이터의 위치를 바꾸면 어떨까? |
- 이해하는데 가장 도움이 됐던 영상
- 영상은 pivot을 비교할 리스트의 가장 오른쪽에 두는 것으로 설명 했지만 아래 작성해놓은 코드는 pivot을 비교할 리스트의 가장 왼쪽에 두는것으로 구현되어 있다.
- 핵심적으로 기억해야 할 부분은 피벗이 이동한 경우 이동된 그 위치는 이제부터 고정된다. (앞으로 안바뀜)
- 그 위치에서의 왼쪽, 오른쪽이 정렬된지는 모르겠으나, 왼쪽은 자신보다 작고 오른쪽은 자신보다 큰 값들로 이루어져 있다.
array = [5, 7, 9, 0, 3, 1, 6, 2, 4, 8]
def quick_sort(array, start, end):
if start >= end: # 재귀함수 종료조건
# 비교할 array가 하나일 때 start, end가 같아진다.
return
pivot = start
left = start + 1
right = end
while(left <= right): # 이 조건으로 인해 left, right 인덱스가 교차되는 순간 탈출하게 됨
# 아래 if left > right 조건문을 같이 보면, 피벗 위치가 변경되는 순간 while문 탈출하고 재귀적 호출 진행됨
# This is art..
# 피벗보다 큰 데이터를 찾을 때까지 반복
while left <= end and array[left] <= array[pivot]:
left += 1
# 피벗보다 작은 데이터를 찾을 때가지 반복
while right > start and array[right] >= array[pivot]:
right -= 1
if left > right: # 엇갈렸다면 작은 데이터와 피벗을 교체
# 이렇게 교체 된 이후에는, while문의 조건이 false로 변경됨
# 여기 if문을 들어온 순간 while문도 탈출 예정. 즉 pivot, right swap하고 바로 변경된다.
array[right], array[pivot] = array[pivot], array[right]
else: # 엇갈리지 않았다면 작은 데이터와 큰 데이터 교체
array[left], array[right] = array[right], array[left]
# 분할 이후 왼쪽 부분과 오른쪽 부분에서 각각 정렬 수행
quick_sort(array, start, right-1)
quick_sort(array, right + 1, end)
quick_sort(array, 0, len(array) - 1)
print(array)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
array = [5, 7, 9, 0, 3, 1, 6, 2, 4, 8]
def quick_sort(array):
# 리스트가 하나 이하의 원소만 담고 있다면 종료
# 나 처음에 구현할 때 == 1 로 했었음..
if len(array) <= 1:
return array
pivot = array[0] # 리스트의 제일 앞을 pivot으로 지정
tail = array[1:] # pivot 제외한 부분에 대해 작업 예정
left_side = [x for x in tail if x < pivot]
right_side = [x for x in tail if x > pivot]
return quick_sort(left_side) + [pivot] + quick_sort(right_side)
print(quick_sort(array))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
위 그림처럼 N이 늘어날 때 높이는 \(logN\) 이라고 할 수 있다.
즉 데이터 N이 기하급수적으로 늘어나도 평균 시간 복잡도가 \(O(N log N)\)이다.
하지만 최악의 경우(가장 왼쪽 데이터를 피벗으로 잡는다고 할 때 이미 정렬되어 있는 경우, 높이가 매우 커짐) \(O(N^2)\) 이다.
계수정렬
정수형 데이터를 정렬할 때, 리스트의 정수형 인덱스를 counting에 활용하여 매우 빠르게 정렬해보자 |
로직은 쉽다.
array = [7, 5, 9, 0, 3, 1, 6, 2, 9, 1, 4, 8, 0, 5, 2]
count = [0] * (max(array) + 1)
for i in range(len(array)):
count[array[i]] += 1
for i in range(len(count)):
for j in range(count[i]):
print(i, end=' ')
수행시간이 최악의 경우에도 \(O(N + K)\) 이다. 난 처음에 2중 반복문이여서 왜 \(O(N^2)\)이 아니였는지 의아했지만 두번째 반복문 부분이 count 배열 크기에 의존하였으며 이 count 배열 크기는 data 갯수가 아닌 현재 데이터 중 최대값 K로 결정된 다는 것 때문에 \(O(N + K)\) 이다.
근데 \(O(N+K)\)면 그냥 \(O(N)\)으로 나타내도 될 듯 하다
문제
p.183 |
- 기존 풀이
n, k = map(int, input().split())
a = []
b = []
a = list(map(int, input().split()))
b = list(map(int, input().split()))
for _ in range(k):
a_min, b_max = min(a), max(b)
if a_min < b_max:
a[a.index(a_min)], b[b.index(b_max)] = b[b.index(b_max)], a[a.index(a_min)]
sum = 0
for _ in range(len(a)):
sum += a[_]
print(sum)
- 좀 더 효율적인 풀이
- 오름차순, 내림차순 정렬을 활용하여 break를 통해 더 빠른 알고리즘을 구현하였음