선택 정렬

  • 가장 작은 것을 선택해서 앞으로 보내는 과정을 더 이상 반복할 수 없을 때까지 반복하는 정렬
  • 가장 작은 데이터를 앞으로 보내는 과정을 N - 1번 반복하면 정렬이 완료
  • 시간 복잡도 : (N**2 + N ) / 2 => O(N**2)
데이터의 개수 (N) 선택 정렬 퀵 정렬 기본 정렬 라이브러리
N=100 0.0123초 0.00156초 0.00000753초
N=1,000 0.354초 0.00343초 0.0000365초
N=10,000 15.475초 0.0312초 0.000248초
  • 위 표에서 알 수 있듯이 다른 정렬 알고리즘에 비해서 매우 비효율적
  • 하지만 특정 리스트에서 가장 작은 데이터를 찾는 경우 활용하기 좋다
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]:
            min_index=j
    array[i], array[min_index] = array[min_index], array[i] # 스와프
    
print(array)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]


스와프 코드

  • 두 변수의 위치를 변경하는 코드
  • 파이썬에선 간단하게 구현 가능
# 파이썬
array = [3, 5]
array[0], array[1] = array[1], array[0]

# 다른 언어
a = array[0]  # 5
b = array[1]  # 3
print(a, b)

temp = a
a = b
b = temp

print(a, b)
5 3
3 5


삽입 정렬

  • 특정한 데이터를 적절한 위치에 삽입하여 정렬
  • 적절한 위치에 들어가기 이전에, 그 앞까지의 데이터는 이미 정렬되어 있다고 가정
  • 단계별로 정렬이 이루어진 원소는 항상 오름차순을 유지하고 있다
    • 즉, 특정한 데이터보다 왼쪽에 있는 데이터들은 이미 정렬된 상태
      => 자기보다 작은 데이터를 만났다면 더 이상 데이터를 살펴볼 필요가 없이 그 자리에 삽입
  • 데이터가 거의 정렬되어 있을 때 매우 효율적 => 이 경우, 퀵 정렬보다 더 강력함
  • 시간 복잡도 : O(N**2) (단, 최선의 경우 O(N))
array = [7, 5, 9, 0, 3, 1, 6, 2, 4, 8]

for i in range(1, len(array)):
    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)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]


퀵 정렬

  • 가장 많이 사용되는 알고리즘
  • 병합 정렬만큼 빠름
  • 대부분의 프로그래밍 언어 정렬 라이브러리의 근간이 되는 알고리즘
  • 기준을 선정하고 큰 수와 작은 수를 교환한 후 리스트를 반으로 나누는 방식으로 동작
  • 이때 기준을 ‘피벗‘이라고 하며, 피벗을 설정하는 방식에 따라 퀵 정렬을 구분
  • 여기선 리스트에서 첫 번째 데이터를 피벗으로 설정하는 호어 분할 방식을 사용함
  • 1차적으로 분할이 완료되면 피벗의 왼쪽엔 피벗보다 작은 데이터가, 오른쪽엔 큰 데이터가 위치한다 => 파티션
  • 피벗을 기준으로 왼쪽 리스트와 오른쪽 리스트 각각에 피벗을 설정하여 동일한 방식으로 정렬을 수행한다
  • 재귀 함수 형태로 작성하면 구현이 매우 간결해짐
    • 종료 조건 : 현재 리스트의 데이터 개수가 1개인 경우
  • 시간 복잡도 : O(NlongN)
    • 선택 정렬, 삽입 정렬에 비해 매우 빠른 알고리즘
    • 단, 최악의 경우 시간 복잡도는 O(N*2) => 이미 정렬되어 있는 경우

가장 널리 사용되고 있는 직관적인 형태의 퀵 정렬 소스코드

array = [5, 7, 9, 0, 3, 1, 6, 2, 4, 8]

def quick_sort(array, start, end):
    # 원소가 1개인 경우 종료
    if start >= end:  
        return
    
    #피벗은 첫 번째 원소로 설정 (호어 분할 방식)
    pivot = start
    
    # 피벗을 제외하고 맨 왼쪽 요소와 오른쪽 끝 요소를 선택
    left = start + 1
    right = end
    
    while left <= right:
        # 피벗보다 큰 데이터를 찾을 때까지 반복
        while left <= end and array[left] <= array[pivot]:
            left += 1
            
        # 피벗보다 작은 데이터를 찾을 때까지 반복
        while right > start and array[right] >= array[pivot]:
            right -= 1
        
        # 두 값이 엇갈리면 작은 데이터와 피벗을 교체
        if left > right:
            
            # right가 계속 작은 값을 찾아가므로 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)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

파이썬의 장점을 살린 퀵 정렬 소스코드

array = [5, 7, 9, 0, 3, 1, 6, 2, 4, 8]

def quick_sort_py(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_py(left_side) + [pivot] + quick_sort_py(right_side)

print(quick_sort_py(array))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]


계수 정렬

  • 특정 조건이 부합할 때만 사용할 수 있으나 매우 빠른 정렬 알고리즘
    • 데이터의 크기 범위가 제한되어 정수 형태로 표현할 수 있을 때만 사용 가능
    • 가장 큰 데이터 - 가장 작은 데이터 < 1000000 일 때 효과적
    • 모든 범위를 담을 수 있는 크기의 리스트를 선언해야 하기 때문
  • 시간 복잡도 : O(N + K)
    • N : 데이터의 개수, K : 최댓값의 크기
  • 동일한 값을 가지는 데이터가 여러개 등장할 때 적합 => 데이터의 특성을 파악하기 쉬워야함
  • 공간 복잡도 : O(N + K)
# 모든 원소의 값이 0보다 크거나 같다고 가정
array = [7, 5, 9, 0, 3, 1, 6, 2, 9, 1, 4, 8, 0, 5, 2]
# 모든 범위를 포함하는 리스트 선언(모든 값은 0으로 초기화)
count = [0] * (max(array) + 1)  # max값보다 1만큼 커야 max값에 해당하는 인덱스까지 담을 수 있음

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= ' ')
0 0 1 1 2 2 3 4 5 5 6 7 8 9 9 


파이썬 기본 정렬 라이브러리

  • 병합 정렬 + 삽입 정렬 => 하이브리드 방식
  • 퀵 정렬보다 느리지만 최악의 경우에도 O(NlogN)을 보장
  • sorted()
    • 리스트, 딕셔너리 자료형을 입력받아서 정렬된 결과를 출력 => return 값이 존재
  • sort()
    • 리스트 객체의 내장 함수
    • 정렬된 리스트가 반환되는 것이 아니라 리스트 자체 내부 원소가 바로 정렬됨
  • 위 두 함수 모두 reverse, key 매개변수를 입력받을 수 있음
    • key : 하나의 함수가 들어가야 하며, 이는 정렬의 기준이 됨
    • reverse : 내림차순 정렬 (default : False)

sorted()

array = [7, 5, 9, 0, 3, 1, 6, 2, 4, 8]

result = sorted(array)
print(result)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

sort()

array = [7, 5, 9, 0, 3, 1, 6, 2, 4, 8]

array.sort()
print(array)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

key parameter 활용

array = [('바나나', 2), ('사과', 5), ('당근', 3)]

def setting(data):
    return data[1]

# 여기서 data = array, tuple[1] => 2, 5, 3 이므로 이 숫자를 기준으로 정렬한 값을 출력
result = sorted(array, key=setting)
print(result)
[('바나나', 2), ('당근', 3), ('사과', 5)]


문제

1. 위에서 아래로

  • 수열을 내림차순으로 정렬하는 프로그램을 작성
n = int(input())
list_ = []

for i in range(n):
    list_.append(int(input()))
    
list_.sort(reverse=True)

for i in list_:
    print(i, end= ' ')
3
15
27
12
27 15 12 

2. 성적이 낮은 순서로 학생 출력하기

  • 학생의 이름과 학생의 성적을 입력 받고 성적이 낮은 순서대로 이름을 출력하는 프로그램 작성
n = int(input())
student_list = []

for i in range(n):
    st = input().split()
    
    student_list.append([st[0], int(st[1])])

# key를 이용하여 2번째 인덱스인 점수를 기준으로 정렬
student_list.sort(key = lambda s : (s[1], s[0]))

for s in student_list:
    print(s[0], end = ' ')
3
박박박 92
김김김 93
크크크 23
크크크 박박박 김김김 

3. 두 배열의 원소 교체

  • n개의 요소로 구성된 두 배열 A, B를 K번 원소들을 바꿔치기 연산해서 A의 원소 합의 최댓값을 출력
n, k = map(int, input().split())
a = list(map(int, input().split()))
b = list(map(int, input().split()))

# a의 원소가 최대가 되게 바꿔치기를 하려면 a는 오름차순, b는 내림차순 정렬한 후
# k번 만큼 앞에서부터 원소를 바꿔주면 된다
a.sort()
b.sort(reverse=True)

for i in range(k):
    if a[i] < b[i]:
        a[i], b[i] = b[i], a[i]
    else:
        # a, b 모두 정렬을 했기 때문에 a[i] > b[i]이면 a[i+1] > b[i+1]이기 때문에 이후의 반복을 하지 않는다
        break
        
print(sum(a))
5 3
1 2 5 4 3
5 5 6 6 5
26

댓글남기기