정렬 알고리즘

조현태·2023년 12월 21일
0

정렬이란?

데이터를 특정한 기준에 따라 순서대로 나열하는 것

1. 선택 정렬

처리되지 않은 데이터 중에서 가장 작은 데이터를 선택해
맨 앞에 있는 데이터와 바꾸는 것을 반복하는 정렬

array = list(map(int, input().split()))

for i in range(len(array)):
  min_index = i # 가장 작은 원소의 인덱스
  # 탐색하지 않은 원소들을 탐색
  for j in range(i + 1, len(array)):
    # 맨 앞의 원소보다 더 작은 원소가 있을 때
    if array[min_index] > array[j]:
      min_index = j # 그 원소를 가장 작은 원소로 입력
  # 가장 작은 원소와 맨 앞의 원소를 교환
  array[i], array[min_index] = array[min_index], array[i]

print(array)

9 6 7 3 5
[3, 5, 6, 7, 9]

선택 정렬은 N번 만큼 가장 작은 수를 찾아서 맨 앞으로 보내야 하므로
N + (N - 1) + (N - 2) + ... + 2 = (N^2 + N -2) / 2
시간 복잡도는 O(N^2)이다.

2. 삽입 정렬

처리되지 않은 데이터를 하나씩 골라 적절한 위치에 삽입한다.

array = list(map(int, input().split()))

for i in range(len(array)):
  # i부터 0까지 감소하며 반복된다.
  for j in range(i, 0, -1):
    # 왼 쪽의 값이 오른 쪽의 값보다 크면
    if array[j] < array[j - 1]:
      # 두 값을 교체한다.
      array[j], array[j - 1] = array[j - 1], array[j]
    # 왼쪽의 값이 오른 쪽의 값과 작거나 같으면 = 올바른 위치이므로 탈출
    else:
      break

print(array)

9 6 7 3 5
[3, 5, 6, 7, 9]

선택 정렬과 마찬가지로 반복문이 두 번 중첩되어 사용되므로 O(N^2)이다.
하지만 거의 정렬되어 있는 상태에서는 매우 빠르게 동작한다.
최선의 경우, O(N)이 될 수 있다.

3. 퀵 정렬

기준 데이터(Pivot)를 설정하고 그 기준보다 큰 데이터와 작은 데이터의 위치를 바꾸는 방법으로 첫 번째 데이터를 기준 데이터(Pivot)로 설정한다.

array = list(map(int, input().split()))

def quick_sort(array, start, end):
  if start >= end: # 원소가 1개인 경우 종료
    return 
  pivot = start # 피벗은 첫 번째 원소
  left = start + 1 # 피벗을 제외한 가장 왼쪽
  right = end # 가장 오른쪽

  # left가 right보다 커질 경우 종료 => 엇갈릴 때까지
  while(left <= right):
    # left가 끝까지 가면서 피벗보다 큰 데이터를 찾을 때까지 반복
    while(left <= end and array[left] <= array[pivot]):
      left += 1
    # right가 맨 앞으로 가면서 피벗보다 작은 데이터를 찾을 때까지 반복
    while(right > start and array[right] >= array[pivot]):
      right -= 1 
    if left > right:
      # 엇갈렸다면 작은 데이터와 피벗을 교체
      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)

9 6 7 3 5
[3, 5, 6, 7, 9]

평균 연산 횟수는 O(NlogN)의 시간 복잡도를 가질 수 있지만
최악의 경우, O(N^2)의 시간 복잡도를 가질 수 있다.

첫 번째 원소를 피벗으로 삼을 때, 이미 정렬된 배열에 대해서 수행한다면
분할이 한 쪽으로만 이루어지므로 O(N^2)이다.

파이썬의 퀵 정렬

리스트 슬라이싱과 리스트 컴프레싱을 통해서 간결하게 작성할 수 있다.

array = list(map(int, input().split()))

def quick_sort(array):
  # 리스트가 하나 이하의 원소를 담고 있다면 종료
  if len(array) <= 1:
    return array
  
  pivot = array[0] # 피벗은 첫 번째 원소
  tail = array[1:] # 피벗을 제외한 나머지 리스트

  # 분할된 왼쪽 부분 (피벗보다 작거나 같은 원소들)
  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))

4. 계수 정렬

데이터의 크기 범위가 제한되어 정수 형태로 표현할 수 있을 때 사용 가능하며
데이터 개수 N, 최댓값 K일 때, 최악의 경우에도 O(N + K)를 보장한다.

array = list(map(int, input().split()))

# 모든 원소의 범위를 포함하는 리스트 선언
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 = ' ')

7 5 9 0 3 1 6 2 9 1 4 8 0 5 2
0 0 1 1 2 2 3 4 5 5 6 7 8 9 9

계수 정렬의 시간 복잡도와 공간 복잡도는 모두 O(N + K)이다.
만약 0과 999,999로 2개만 존재하는 경우, 심각한 비효율성을 초래할 수 있다.

따라서 계수 정렬은 데이터의 분포를 미리 파악해야 하고
특히 동일한 값을 가지는 데이터가 여러 개 등장할 때 효과적으로 사용할 수 있다.

정렬 알고리즘 비교하기

하지만 표준 정렬 라이브러리는 최악의 경우에도 O(NlogN)을 보장하므로
문제에서 정렬 구현을 요구하지 않는다면 sort()를 사용하자!

정렬 알고리즘 문제

두 배열의 원소 교체

두 배열 A, B는 N개의 원소로 구성되어 있으며 배열의 원소는 모두 자연수이다.
최대 K번의 바꿔치기 연산을 수행할 수 있는데 바꿔치기 연산이란 배열 A의 원소 하나와 배열 B의 원소 하나를 골라서 서로 바꾸는 것을 말한다.
배열 A의 모든 원소의 합이 최대가 되도록 프로그램을 작성하라.
(1 <= N <= 100,000 / 0 <= K <= N)

풀이)
N = 100,000이므로 표준 정렬 라이브러리[O(NlogN)]을 사용해도 된다.
a는 오름차 정렬, b는 내림차 정렬을 하고 비교하면 된다.

n, k = map(int, input().split())

# 배열 입력
array_a = list(map(int, input().split()))
array_b = list(map(int, input().split()))

array_a.sort() # 오름차 순 정렬
array_b.sort(reverse = True) # 내림차 순 정렬

for i in range(k):
  # a의 원소보다 b의 원소가 클 때
  if (array_a[i] < array_b[i]):
    # 두 원소를 교체 (바꿔치기 연산)
    array_a[i], array_b[i] = array_b[i], array_a[i]
  # a의 원소가 b의 원소보다 크거나 같을 때 
  else:
    # a는 오름차 정렬, b는 내림차 정렬으로 더 비교할 필요가 없다.
    break

print(sum(array_a))

5 3
1 2 5 4 3
5 5 6 6 5
26

참고 자료

이코테 2021 강의 4편
이것이 코딩 테스트다 교재

0개의 댓글