기본적인 정렬 알고리즘에 대해 공부하고 코드를 작성해본다.
간단하게 표현하면 제일 작은 수부터 차례로 찾아가며 정렬하는 방법이다.
리스트 요소를 하나씩 서로 비교해가며 '최솟값, 두번째 최솟값, 세번째 최솟값 ... ' 이런식으로 제일 작은 값부터 채워나가며 정렬하는 방식이다. 이때 정렬된 리스트를 담을 새로운 리스트를 만들고 이곳에 '최솟값, 두번째 최솟값, 세번째 최솟값 ... '을 채워넣는 식이 아니라, 기존에 존재하는 리스트 자체에서 '첫번째 요소와 리스트 내 최솟값을 스왑, 두번째 요소와 남은(첫번째 요소를 제외한) 리스트 내 최솟값을 스왑, 세번째 요소와 남은(첫번째 요소와 두번째 요소를 제외한) 리스트 내 최솟값을 스왑, ...' 이런식으로 스왑을 이용해 기존 리스트를 정렬한다. 즉, 'n번째 요소와 n번째 최솟값 스왑'을 반복하는 것이다.
간단하게 표현하면 리스트에서 요소를 하나씩 빼서 출력값(정렬된 리스트)으로 제출할 리스트에 추가하고 정렬하는 행위를 한 세트로 묶어 반복하며 정렬하는 방법이다.
리스트에서 첫번째 요소는 이미 정렬된 것으로 여기며 시작한다. 이렇게 정렬된 첫번째 요소만을 포함한 리스트를 정렬 리스트라고 표현해본다.여기서 두번째 요소를 정렬 리스트에 추가하고 원래 정렬 리스트에 존재하던 요소들과 비교하며 정렬한다. 여기까지가 한 세트다. 이것을 계속 반복한다.
파이썬 구현
시간 복잡도
(리스트 길이가 n이라면)
두번째 요소와 첫번째 요소를 비교하고 스왑 결정 = 1번
세번째 요소와 두번째 요소를 비교하고 스왑 결정(만약 스왑한다면)
두번째 요소와 첫번째 요소를 비교하고 스왑 결정 = 2번
네번째 요소와 세번째 요소를 비교하고 스왑 결정(만약 스왑한다면)
세번째 요소와 두번째 요소를 비교하고 스왑 결정(만약 스왑한다면)
두번째 요소와 첫번째 요소를 비교하고 스왑 결정 = 3번
...
마지막 요소(위와 같이 계속 스왑한다면) = n-1번
총 1,2,3, ... , n-1번 => (1 + n-1) * (n-1)
즉, 빅오 표기법으로 O(n^2)
하지만 이 경우는 위처럼 계속 스왑을 해줘야 하는 최악의 경우를 가정한 것이고, 만약 이미 리스트가 어느 정도 정렬되어 있는 경우 위처럼 스왑을 생략하고 넘어가는 경우가 많을 것이고 그에 따라 시간 복잡도도 줄어들 것이다.
=> 특징 : 어느 정도 정렬된 리스트에서 굉장히 효율적으로 동작한다.
pivot을 이용한 정렬 방법이다. pivot은 기준점이라고 표현할 수 있다. 이 pivot은 정렬하고 싶은 리스트의 첫번째 요소를 의미한다. 정렬하고 싶은 리스트의 pivot(첫번째 요소)보다 작은 수들은 pivot 왼쪽에 정렬하고, pivot보다 큰 수들은 pivot 오른쪽에 정렬한다. 이 방법을 pivot을 기준으로 두개로 나뉜 리스트에 각각 적용하며 반복한다. 그렇게 리스트의 길이가 1이 되어 더 이상 두개의 리스트로 나뉘지 못하면 정렬이 완성된다.
파이썬 구현
코드를 통한 설명이 이해하기 쉽다.
시간 복잡도
피벗을 이용해 계속해서 거의 리스트를 이등분해나가며 정렬해주는 모습을 띈다. 길이가 n인 리스트를 길이가 1이 될 때까지 이등분하는 횟수는 log2(n)이다. 또한 각각의 이등분 단계에서 리스트의 길이만큼 연산이 들어가므로 n이 곱해진다.
즉, 빅오 표기법으로 O(n*log2(n))
간단하게 표현하면 리스트를 이등분해나가다 모두 길이가 1인 리스트들로 쪼개지면 다시 정렬하며 병합해나가 모든 리스트를 다시 정렬 병합하는 방법이다
파이썬 구현
시간 복잡도
모두 이등분하며 다시 2개씩 병합하며 1개의 리스트로 만드는 단계 수 : log2(n), 그리고 각 단계마다 연산하는 리스트 요소 갯수 : n개
즉, 빅오 표기법 : O(nlog2(n))
=> 특징1 : 언제나 동작방식은 동일하여 아무리 정렬이 안된 리스트를 정렬해도 O(nlog2(n))의 복잡도를 보장해준다.
=> 특징2 : 어느 정도 정렬된 리스트에서 오히려 굉장히 비효율적으로 동작한다.
###################### < 오름차순 정렬 방법들 > ######################
import random
arr=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
random.shuffle(arr)
print('랜덤배열 : ',arr)
#----------- 선택정렬 O( n^2 ) -----------#
def select_sort(arr):
for i in range(len(arr)):
min_idx = i # 기준인 i번째 인덱스값이 가장 작다고 가정
for j in range(i+1,len(arr)): # i번째 뒤쪽 인덱스를 모두 훑으면서 가장 작다고 가정된 i번째 값보다 작은 값을 찾으며 최솟값을 갱신한다.
if arr[min_idx] > arr[j]:
min_idx = j # 새로운 최솟값 갱신
arr[i],arr[min_idx]=arr[min_idx],arr[i] # 스왑
return arr
print('선택정렬 : ',select_sort(arr))
#----------- 삽입정렬 O( n^2 ) -----------#
def insert_sort(arr):
for i in range(1,len(arr)):
for j in range(1,i+1):
if arr[i-j] > arr[i+1-j]: # 새로운 기준값이 바로 왼쪽값보다 작으면 스왑
arr[i-j],arr[i+1-j]=arr[i+1-j],arr[i-j] # 스왑
else:
break
return arr
print('삽입정렬 : ',insert_sort(arr))
#----------- 퀵정렬 O( nlog(n) ) -----------#
def quick_sort(arr):
if len(arr) < 2:
return arr
pivot = 0 # pivot, i(끝에서부터왼쪽, pivot보다작은값), j(pivot에서부터오른쪽, pivot보다큰값)
i=len(arr)-1
j=pivot+1
while True:
while arr[pivot] < arr[i]:
i-=1
while arr[pivot] > arr[j]:
j+=1
if j == len(arr):
break
if i < j: # i와 j가 엇갈린 경우
arr[pivot],arr[i]=arr[i],arr[pivot] # 이제 arr[i]는 고정 / arr[i]을 pivot으로 왼쪽, 오른쪽 또 정렬해주면 된다.
break
else: # 아직 엇갈리지 않은 경우
arr[i],arr[j]=arr[j],arr[i]
return quick_sort(arr[:i])+[arr[i]]+quick_sort(arr[i+1:])
print(' 퀵 정렬 : ',quick_sort(arr))
#----------- 병합정렬 O( nlog(n) ) -----------#
def merge_sort(arr):
if len(arr) < 2:
return arr
mid = len(arr) // 2
left = arr[:mid] # 리스트를 반으로 나누기
right = arr[mid:]
left_merging = merge_sort(left) # 리스트길이가 1이 되어 return될때까지 계속 재귀
right_merging = merge_sort(right)
# 여기서부터 리스트길이가 1이되어 arr가 return되며, 다시 거슬러 올라가는 부분 (left_merging 과 right_merging 의 값이 구해진 시점 이후)
merging=[] # left_merging과 right_merging이 정렬되며 합쳐질 리스트 선언-----------------------------------------------------------
l_idx = 0
r_idx = 0
l_max = len(left_merging)-1
r_max = len(right_merging)-1
while (l_idx <= l_max) and (r_idx <= r_max):
if left_merging[l_idx] < right_merging[r_idx]:
merging.append(left_merging[l_idx])
l_idx += 1
else:
merging.append(right_merging[r_idx])
r_idx += 1
# left_merging 이나 right_merging 중 한 쪽이 모두 merging으로 들어가 while문 종료된 시점. 나머지 부분 붙여준다.
if l_idx > l_max: # left_merging이 모두 소비된 경우
merging.extend(right_merging[r_idx:])
else: # right_merging이 모두 소비된 경우
merging.extend(left_merging[l_idx:])
# ------------------------------------------------merging 완성---------------------------------------------------------------------
return merging
print('병합정렬 : ',merge_sort(arr))