[Algorithm 이론] Sort 알고리즘 정리 | (ft. Bubble / Counting Sort)
Bubble/Counting Sort 방식에 대한 정리;
정렬(Sort)이란?
“2개 이상의 자료를 특정 기준에 의해 작은 값부터 큰 값(오름차순: ascending), 또는 그 반대의 순서대로(내림차순: descending) 재배열하는 것”
[관련용어] Key: 자료를 정렬할 때 기준이 되는 특정 값
(ex1) 서류 번호대로 정렬하기 ⇒ key := 서류 번호
(ex2) 카드 번호대로 정렬하기 ⇒ key := 카드 번호
(대표적인) 정렬 방식의 정류
- 버블 정렬 (Bubble Sort)
- 카운팅 정렬 (Counting Sort)
- 선택 정렬 (Selection Sort)
- 퀵 정렬 (Quick Sort)
- 삽입 정렬 (Insertion Sort)
- 병합 정렬 (Merge Sort)
1. Bubble Sort
“인접한 두 개의 원소를 비교하며 자리를 계속 교환하는 방식”
[Process]
- 첫 번째 원소부터 인접한 원소끼리 계속 자리를 교환하면서 맨 마지막 자리까지 이동
- 한 단계가 끝나면 가장 큰 원소 또는 가장 작은 원소가 마지막 자리로 정렬됨
※ 교환하며 자리를 이동하는 모습이 물 위에 올라오는 거품모양(bubble) 같아서 버블 정렬이라고 함
※ 시간복잡도 =
EXAMPLE: {55, 7, 78, 12, 42}를 버블 정렬하는 과정
# 첫 번째 패스 (첫 번째 원소인 55부터)
| 55 | 7 | 78 | 12 | 42 | ; 55와 인접한 7를 비교했을 때 7 < 55 이므로, 자리 바꿈
| 7 | 55 | 78 | 12 | 42 | ; 55와 인접한 78을 비교했을 때 55 < 78 이므로, 자리 안 바꿈
| 7 | 55 | 78 | 12 | 42 | ; 77과 인접한 12를 비교했을 때 12 < 78 이므로, 자리 바꿈
| 7 | 55 | 12 | 78 | 42 | ; 78과 인접한 42를 비교했을 때 42 > 78 이므로, 자리 바꿈
# output: 첫 번째 패스 결과
| 7 | 55 | 12 | 42 | 78 |
- 정렬된 원소 = 78
- 미정렬된 원소 = { 7, 55, 12, 42}
# 두 번째 패스 (첫 번째 원소인 7부터)
| 7 | 55 | 12 | 42 | 78 | ; 7과 인접한 55를 비교했을 때 7 < 55 이므로, 자리 안 바꿈
| 7 | 55 | 12 | 42 | 78 | ; 55와 인접한 12를 비교했을 때 12 < 55 이므로, 자리 바꿈
| 7 | 12 | 55 | 42 | 78 | ; 55와 인접한 42를 비교했을 때 42 < 55 이므로, 자리 바꿈
| 7 | 12 | 42 | 55 | 78 | ; 55와 인접한 78을 비교했을 때 55 < 78 이므로, 자리 안 바꿈
# output: 두 번째 패스 결과
| 7 | 12 | 42 | 55 | 78 |
- 정렬된 원소 = {55, 78}
- 미정렬 원소 = {7, 12, 42}
# 세 번째 패스 (첫 번째 원소인 7부터)
| 7 | 12 | 42 | 55 | 78 | ; 7과 인접한 12를 비교했을 때 7 < 12 이므로, 자리 안 바꿈
| 7 | 12 | 42 | 55 | 78 | ; 12와 인접한 42를 비교했을 때 12 < 42 이므로, 자리 안 바꿈
# output: 세 번째 패스 결과
| 7 | 12 | 42 | 55 | 78 |
- 정렬된 원소 = {42, 55, 78}
- 미정렬 원소 = {7, 12}
# 네 번째 패스 (첫 번째 원소인 7부터)
| 7 | 12 | 42 | 55 | 78 | ; 7과 인접한 12를 비교했을 때 7 < 12 이므로, 자리 안 바꿈
# output: 네 번재 패스 결과
| 7 | 12 | 42 | 55 | 78 |
- 정렬된 원소 = {12, 42, 55, 78}
- 미정렬 원소 = 7
# 다섯 번째 패스
미정렬 원소가 한 개 이므로 정렬 끝
# output
| 7 | 12 | 42 | 55 | 78 |
Pseudo Code
# === 정의 === #
def BubbleSort(input:list):
for step, i in enumerate(range(len(input)-1, 0, -1)):
for j in range(0, i):
if input[j] > input[j+1]: # 인접한 원소끼리 비교
input[j], input[j+1] = input[j+1], input[j] # swap
print(f"Step{step+1} results={input}")
# === 실행 === #
input = [55, 7, 78, 12, 42]
BubbleSort(input)
# === 출력 === #
Step1 results=[7, 55, 12, 42, 78]
Step2 results=[7, 12, 42, 55, 78]
Step3 results=[7, 12, 42, 55, 78]
Step4 results=[7, 12, 42, 55, 78]
BubbleSort() 의 명령어 개수 =
Big-O 표기법으로 표현하면:
2. Counting Sort
“항목들의 순서를 결정하기 위해 집합에 각 항목이 몇 개씩 있는지 세는 작업을 하여, 선형 시간에 정렬하는 효율적인 알고리즘”
[Process]
- 정수(
int
)나 정수로 표현할 수 있는 자료에 대해서만 적용 가능함 (각 항목의 발생 회수를 기록하기 위해, 정수 항목으로 인덱스되는 카운트들의 리스트를 사용하기 때문) - 카운트들을 위한 충분한 공간을 할당하려면 집합 내의 가장 큰 정수를 알아야 함
※ 시간복잡도 = : 은 리스트의 개수, 는 정수의 최대값
EXAMPLE: {0, 4, 1, 3, 1, 2, 4, 1} 을 카운팅 정렬하는 과정
[step1] Data에서 각 항목들의 발생 회수를 세고, 정수 항목들로 직접 인덱스 되는 카운트 리스트 COUNTS
에 저장
DATA = [0, 4, 1, 3, 1, 2, 4, 1]
COUNTS = [0] * 5 # 발생 횟수를 저장하는 리스트
# ==== 발생 횟수 카운트 === #
| 0 | 1 | 2 | 3 | 4 | # 숫자 항목
COUNTS | 1 | 3 | 1 | 1 | 2 | # 발생 횟수
[step2] 정렬된 집합에서 각 항목의 앞에 위치할 항목의 개수를 반영하기 위해 COUNTS
의 원소를 조정
DATA = [0, 4, 1, 3, 1, 2, 4, 1]
COUNTS = [1, 3, 1, 1, 2]
0 앞에 오는 항목의 개수 0개
1 앞에 오는 항목의 개수 1개
2 앞에 오는 항목의 개수 4개
3 앞에 오는 항목의 개수 5개
4 앞에 오는 항목의 개수 6개
# COUNTS 리스트 조정
COUNTS = [1+0, 3+1, 1+4, 1+5, 2+6]
[step3] DATA
의 마지막 항목부터 데이터 정렬하기
DATA | 0 | 4 | 1 | 3 | 1 | 2 | 4 | 1 |
COUNTS | 1 | 4 | 5 | 6 | 8 |
# COUNTS[1]을 1 감소시키고 TEMP에 1을 삽입
COUNTS | 1 | 3 | 5 | 6 | 8 |
TEMP | | | | 1 | | | | |
DATA[-1]
의 원소는 1 이다 ⇒COUNTS[1]
로 이동COUNTS[1]
의 원소가 4임을 확인 ⇒ 해당 원소를 1 감소 시키고 (-1)TEMP
리스트의 4번째 칸에 1 을 삽입
DATA | 0 | 4 | 1 | 3 | 1 | 2 | 4 | 1 |
COUNTS | 1 | 3 | 5 | 6 | 8 |
# COUNTS[4]를 감소시키고 TEMP에 4를 삽입
COUNTS | 1 | 3 | 5 | 6 | 7 |
TEMP | | | | 1 | | | | 4 |
DATA[-2]
의 원소가 4 이다 ⇒COUNTS[4]
로 이동COUNT[4]
의 원소가 8임을 확인 ⇒ 해당 원소를 1 감소 시키고TEMP
리스트의 8번째 칸에 4를 삽입
DATA | 0 | 4 | 1 | 3 | 1 | 2 | 4 | 1 |
COUNTS | 1 | 3 | 5 | 6 | 7 |
# COUNTS[2]를 감소시키고 TEMP에 2를 삽입
COUNTS | 1 | 3 | 4 | 6 | 7 |
TEMP | | | | 1 | 2 | | | 4 |
DATA[-3]
의 원소가 2 이다 ⇒COUNTS[2]
로 이동COUNTS[2]
의 원소가 5임을 확인 ⇒ 해당 원소를 1 감소 시키고TEMP
리스트의 5번째 칸에 2를 삽입
DATA | 0 | 4 | 1 | 3 | 1 | 2 | 4 | 1 |
COUNTS | 1 | 3 | 4 | 6 | 7 |
# COUNTS[1]을 감소시키고 TEMP에 1을 삽입
COUNTS | 1 | 2 | 4 | 6 | 7 |
TEMP | | | 1 | 1 | 2 | | | 4 |
DATA[-4]
의 원소가 1 이다 ⇒COUNTS[1]
로 이동COUNTS[1]
의 원소가 3임을 확인 ⇒ 해당 원소를 1 감소 시키고TEMP
리스트의 3번째 칸에 1을 삽입
DATA | 0 | 4 | 1 | 3 | 1 | 2 | 4 | 1 |
COUNTS | 1 | 2 | 4 | 6 | 7 |
# COUNTS[3]을 감소시키고 TEMP에 3을 삽입
COUNTS | 1 | 2 | 4 | 5 | 7 |
TEMP | | | 1 | 1 | 2 | 3 | | 4 |
DATA[-5]
의 원소가 3 이다 ⇒COUNTS[3]
로 이동COUNTS[3]
의 원소가 6임을 확인 ⇒ 해당 원소를 1 감소 시키고TEMP
리스트의 6번째 칸에 3을 삽입
DATA | 0 | 4 | 1 | 3 | 1 | 2 | 4 | 1 |
COUNTS | 1 | 2 | 4 | 5 | 7 |
# COUNTS[1]을 감소시키고 TEMP에 1을 삽입
COUNTS | 1 | 1 | 4 | 5 | 7 |
TEMP | | 1 | 1 | 1 | 2 | 3 | | 4 |
DATA[-6]
의 원소가 1 이다 ⇒COUNTS[1]
로 이동COUNTS[1]
의 원소가 2임을 확인 ⇒ 해당 원소를 1 감소 시키고TEMP
리스트의 2번째 칸에 1을 삽입
DATA | 0 | 4 | 1 | 3 | 1 | 2 | 4 | 1 |
COUNTS | 1 | 1 | 4 | 5 | 7 |
# COUNTS[0]를 감소시키고 TEMP에 0을 삽입
COUNTS | 1 | 1 | 4 | 5 | 6 |
TEMP | | 1 | 1 | 1 | 2 | 3 | 4 | 4 |
DATA[-7]
의 원소가 4 이다 ⇒COUNTS[4]
로 이동COUNTS[4]
의 원소가 7임을 확인 ⇒ 해당 원소를 1 감소 시키고TEMP
리스트의 7번째 칸에 4을 삽입
DATA | 0 | 4 | 1 | 3 | 1 | 2 | 4 | 1 |
COUNTS | 1 | 1 | 4 | 5 | 6 |
# COUNTS[0]를 감소시키고 TEMP에 0을 삽입
COUNTS | 0 | 1 | 4 | 5 | 6 |
TEMP | 0 | 1 | 1 | 1 | 2 | 3 | 4 | 4 |
DATA[-8]
의 원소가 0 이다 ⇒COUNTS[0]
로 이동COUNTS[0]
의 원소가 1임을 확인 ⇒ 해당 원소를 1 감소 시키고TEMP
리스트의 1번째 칸에 0을 삽입
TEMP
업데이트 완료하고 정렬 작업을 종료
Pseudo Code
def CountingSort(DATA:list, TEMP:list, k:int):
""" DATA : 입력된 데이터 리스트
TEMP : 정렬해서 저장할 리스트
k : 숫자 항목 개수
"""
# === Step1. 항목 숫자 발생 횟수 === #
COUNTS = [0] * k # 1회
for i in range(0, len(TEMP)): # 2n회
COUNTS[DATA[i]] += 1
print(f"COUNTS= {COUNTS}") # 1회
# === Step2. COUNTS의 원소를 조정 === #
for i in range(1, len(COUNTS)): # 2(k-1)회
COUNTS[i] += COUNTS[i-1]
print(f"re-COUNTS= {COUNTS}") # 1회
# === Step3. DATA 의 마지막 항목부터 데이터 정렬 === #
for i in range(len(TEMP)-1, -1, -1): # 4n 회
TEMP[ COUNTS[DATA[i]]-1] = DATA[i]
COUNTS[DATA[i]] -= 1
print(f"TEMP = {TEMP}")
DATA = [0, 4, 1, 3, 1, 2, 4, 1]
TEMP = [0]*len(DATA)
CountingSort(DATA, TEMP, len(set(DATA)))
# === output === #
COUNTS= [1, 3, 1, 1, 2]
re-COUNTS= [1, 4, 5, 6, 8]
TEMP = [0, 0, 0, 1, 0, 0, 0, 0]
TEMP = [0, 0, 0, 1, 0, 0, 0, 4]
TEMP = [0, 0, 0, 1, 2, 0, 0, 4]
TEMP = [0, 0, 1, 1, 2, 0, 0, 4]
TEMP = [0, 0, 1, 1, 2, 3, 0, 4]
TEMP = [0, 1, 1, 1, 2, 3, 0, 4]
TEMP = [0, 1, 1, 1, 2, 3, 4, 4]
TEMP = [0, 1, 1, 1, 2, 3, 4, 4]
CountingSort() 의 명령어 개수 =
Big-O 표기법으로 표현하면:
특징
알고리즘 | 평균 수행시간 | 최악 수행시간 | 알고리즘 기법 | 비고 |
---|---|---|---|---|
Bubble sort | 비교와 교환 | 코딩이 가장 쉬움 | ||
Counting sort | 교환 없음 | 이 비교적 작을 때만 가능 |
Summary
- Sort의 정의 - 2개 이상의 자료를 특정 기준에 의해 오름차순/내림차순으로 재배열 하는 것
- 대표적인 Sort 방식의 종류
- Bubble / Counting Sort 알고리즘 정리
Reference
Counting sort 방식이 좀 헷갈린다.
답글삭제핵심은
- 입력된 시퀀스의 각 항목의 발생 빈도를 먼저 센다 (COUNTS 리스트 생성)
- 각 항목이 차지할 영역중 가장 마지막 칸의 위치를 알기 위해 각 항목별로 앞의 항목들의 개수를 더한다 (COUNTS 리스트 조정)
- 본격적으로 소팅 시작. DATA의 맨 마지막 인덱스부터 역방향으로 각 항목이 들어갈 위치를 찾는다.
DATA에서 선택된 숫자가 위치해야할 영역의 맨 마지막 칸을 알기 위해 COUNTS 리스트에서 확인한다.
위치가 확인되면 TEMP 리스트에 해당 위치로 숫자를 입력한다.
입력이 끝나면 해당 영역에 자리가 찾다는 의미로 COUNTS 리스트에서 해당 숫자의 발생 수를 -1 해준다.