정렬 알고리즘이란 원소들을 번호순이나 사전 순서와 같이 일정한 순서대로 열거하는 알고리즘이다. 효율적인 정렬은 탐색이나 병합 알고리즘처럼 다른 알고리즘을 최적화하는 데 중요하다. 또 정렬 알고리즘은 데이터의 정규화나 의미있는 결과물을 생성하는 데 유용히 쓰인다.
정렬 알고리즘은 데이터를 교환, 선택, 삽입하면서 정렬을 완료한다.
복수의 원소로 주어진 데이터를 정해진 기준에 따라 새로 늘어놓는 작업
sorted().sort()오름차순(기본, reverse=False), 내림차순(reverse=True)key=lambda 함수# key에 lambda 함수를 이용하여 원하는 기준으로 정렬하기
# 1. 문자열 길이에 따라 지정하기
L = ['abcd', 'efg', 'a']
sorted(L, key=lambda x: len(x))
>>> ['a', 'efg', 'abcd']
# 2. 딕셔너리에서 이용하기
L = [{'name': 'jain', 'score': 80}, {'name': 'alex', 'score': 70}]
L.sort(key=lambda x: x['score'], reverse=True)
>>> 점수 높은 순으로 정렬

👉 다양한 상황에서의 정렬 알고리즘의 퍼포먼스 확인하기

1. 버블 정렬 (Bubble Sort):
O(n^2)2. 선택 정렬 (Selection Sort):
O(n^2)3. 삽입 정렬 (Insertion Sort):
O(n) - 이미 정렬된 데이터에 대해O(n^2)4. 병합 정렬 (Merge Sort):
O(n log n)5. 퀵 정렬 (Quick Sort):
O(n log n)O(n^2)6. 힙 정렬 (Heap Sort):
O(n log n)7. 빠른 정렬 (Tim Sort):
O(n log n)O(n^2)이웃한 두 원소의 대소 관계를 비교하여 필요에 따라 교환을 반복하는 알고리즘 단순 교환 정렬
패스 pass (비교/교환하는 과정)를 하면 가장 작은 원소가 맨 앞으로 이동한다.O(n²)# 버블 정렬 - 기본형
def bubble_sort(L):
n = len(L)
for i in range(n-1):
for j in range(n-1, i, -1):
if L[j-1] > L[j] :
L[j-1], L[j] = L[j], L[j-1]
# 버블 정렬 - 교환 횟수에 따른 중단
def bubble_sort(L):
n = len(L)
for i in range(n-1):
exchange = 0
for j in range(n-1, i, -1):
if L[j-1] > L[j] :
L[j-1], L[j] = L[j], L[j-1]
exchange += 1
if exchange == 0:
break
# 버블 정렬 - 스캔 범위 제한
def bubble_sort(L):
n = len(L)
k = 0
while k < n-1 :
last = n-1
for j in range(n-1, k, -1):
if L[j-1] > L[j] :
L[j-1], L[j] = L[j], L[j-1]
last = j
k = last
# 효율 : 기본형 < 교환 횟수에 따른 중단 < 스캔 범위 제한
버블 정렬을 개선한 알고리즘
양방향 버블 정렬 bidirectional bubble sort = 칵테일 정렬 cocktail sort = 칵테일 셰이커 정렬 cocktail shaker sort# 셰이커 정렬
def shaker_sort(L) :
left = 0
right = len(L) - 1
last = right
while left < right:
for i in range(right, left, -1):
if L[i-1] > L[i]:
L[i-1], L[i] = L[i], L[i-1]
last = i
left = last
for j in range(left, right):
if L[j] > L[j+1]:
L[j], L[j+1] = L[j+1], L[j]
last = j
right = last
가장 작은 원소부터 선택해 알맞은 위치로 옮기는 작업을 반복하며 정렬하는 알고리즘 (선형 탐색을 응용한 알고리즘으로, 각 위치에 어떤 값이 들어갈지 찾는다)
단순 선택 정렬 straight selection sort(n-1) + (n-2) + ... + 1 번O(n²) - 하지만, 버블 정렬보다 2배 빠르다# 단순 선택 정렬
def selection_sort(L) :
n = len(L)
for i in range(n-1):
min = i
for j in range(i+1, n):
if L[j] < L[min]:
min = j
L[i], L[min] = L[min], L[i]
O(n), 평균 O(n²), 최악 O(n²)# 단순 삽입 정렬
def insertion_sort(L):
n = len(L)
for i in range(1, n):
j = i
tmp = L[i]
while j > 0 and L[j-1] > temp:
L[j] = L[j-1]
j -= 1
L[j] = tmp
# 이진 삽입 정렬 - 효율성 개선
def binary_insertion_sort(L):
n = len(L)
for i in range(1, n):
key = L[i]
l = 0
r = i-1
while True:
m = (l+r) // 2
if L[m] == key:
break
elif L[m] < key:
l = m+1
else :
r = m-1
if l > r:
break
x = m + 1 if l <= r else r + 1
for j in range(i, x, -1):
L[j] = L[j-1]
L[x] = key
# 이진 삽입 정렬 - 모듈 이용
import bisect
def binary_insertion_sort(L):
for i in range(1, len(L)):
bisect.insort(L, L.pop(i), 0, i)
O(n), 평균 O(n^1.5), 최악 O(n²)# 셀 정렬
def shell_sort(L):
n = len(L)
h = n // 2
while h > 0:
for i in range(h, n):
j = i - h
tmp = L[i]
while j >= 0 and L[j] > tmp:
L[j + h] = L[j]
j -= h
L[j + h] = tmp
h //= 2
# 셀 정렬 - h*3+1 수열 사용
def shell_sort(L) :
n = len(L)
h = 1
while h < n // 9 :
h = h * 3 + 1
while h > 0 :
for i in range(h, n):
j = i - h
tmp = L[i]
while j >= 0 and L[j] > tmp :
L[j + h] = L[j]
j -= h
L[j + h] = tmp
h //= 3
분할 정복 Divide and conquer : 데이터를 반으로 나누어 정렬을 수행하는 알고리즘O(nlogn)# 병합 정렬
분할 정복 방식 이용O(nlogn)파티션 Partition 수행 : 피벗 pivot을 기준점으로 작은 수는 왼쪽, 큰 수는 오른쪽에 정렬하는 과정피벗 pivot으로 설정왼쪽 마커 left market, 오른쪽 마커 right marker로 설정왼쪽 마커 L는 오른쪽으로 이동하며 피벗 P보다 크거나 같은 첫 번째 숫자를 선택오른쪽 마커 R는 왼쪽으로 이동하며 피벗 P보다 작은 첫 번째 숫자를 선택L, R 두 마커의 숫자를 바꿈 (반복)L과 R이 서로 교차하는 상태가 되면 R과 P의 숫자를 바꿈P보다 작은 숫자는 배열의 왼쪽에, 큰 숫자는 오른쪽에 위치하게 됨P의 왼쪽과 오른쪽에 있는 배열의 맨 왼쪽 요소를 새로운 피벗으로 삼아 처음부터 다시 반복# 파티셔닝 -> 이것을 발전시키면 퀵 정렬이 됨
def partition(L) :
n = len(L)
l = 0
r = n - 1
x = L[n // 2]
while l <= r:
while L[l] < x :
l += 1
while L[r] > x :
r -= 1
if l <= r :
L[l], L[r] = L[r], L[l]
l += 1
r -= 1
# 퀵 정렬 (재귀적)
def qsort(L, left, right) :
l = left
r = rignt
x = L[(left + right) // 2]
while l <= r:
while L[l] < x : l += 1
while L[r] > x : r -= 1
if l <= r :
L[l], L[r] = L[r], L[l]
l += 1
r -= 1
if left < r : qsort(L, left, r)
if l < right : qsort(L, l, right)
def quick_sort(L):
qsort(L, 0, len(L)-1)
# 퀵 정렬 (비재귀적)
def qsort(L, left, right) :
range = Stack(right - left + 1)
range.push((left, right))
while not range.is_empty() :
l, r = left, right = range.pop()
x =.L[(left, right) // 2]
while l <= r:
while L[l] < x : l += 1
while L[r] > x : r -= 1
if l <= r :
L[l], L[r] = L[r], L[l]
l += 1
r -= 1
if left < r : range.push((left, r))
if l < right : range.push((l, right))
def quick_sort(L):
qsort(L, 0, len(L)-1)
O(nlogn) : 바로 옆에 있는 부모 및 자식 노드를 상대 비교하기 때문에# 힙 정렬 - 모듈 이용
import heapq
def heap_sort(L) :
heap = []
for i in L :
heapq.heappuch(heap, i)
for i in range(len(L)) :
L[i] = heapq.heappop(heap)
# 힙 정렬
def heap_sort(L) :
def down_heap(L, left, right) :
temp = L[left]
parent = left
while parent < (right + 1) // 2 :
l = parent * 2 + 1
r = l + 1
child = r if r <= right and L[r] > L[l] else l
if temp >= L[child] :
break
L[parent] = L[child]
parent = child
L[parent] = temp
n = len(L)
for i in range((n-1) // 2, -1, -1):
down_heap(L, i, n-1)
for i in range(n-1, 0, -1):
L[0], L[i] = L[i], L[0]
down_heap(L, 0, i-1)
버킷 : 여러 데이터를 저장하는 장소O(n²), 평균 O(n+n²/m+m) (m:버킷 수)O(mn) (m:요소의 자릿수)분포수 세기 정렬 distribution counting sort# 도수 정렬
def fsort(L, max) :
n = len(L)
f = [0] * (max + 1) # 누적 도수 분포표 배열
b = [0] * n # 작업용 배열
for i in range(n) :
f[L[i]] += 1
for i in range(1, max+1) :
f[i] += f[i-1]
for i in range(n-1, -1, -1) :
f[L[i]] -= 1
b[f[L[i]]] = L[i]
for i in range(n) :
L[i] = b[i]
def counting_sort(L):
fsort(L, max(L))
정렬 알고리즘에 대한 자세한 내용은 여기서!
출처
programmerse
코드 없는 자료구조와 알고리즘