호어(Hoare)가 1962년에 개발
합병정렬과 비슷
분할 방식
기준원소
항상 가장 빠른 정렬 알고리즘은 아니지만 평균적으로 가장 빠름.
15 22 13 27 12 10 20 25
# 분할 알고리즘: 기준원소(pivot)를 사용하여 리스트 분할하기
# 기준원소: 리스트의 맨 왼편에 위치한 값
# 주의: 리스트의 항목을 직접 수정함. 따라서 제자리 분할임.
def partition(aList, low, high):
pivotitem = aList[low] # 기준원소(pivot)
pivotpoint = low # 분할 후 기준원소가 저장될 위치
# 기준원소 보다 작은 값을 리스트 왼편에 위치시키기
# i 는 기준원소를 제외한 구간 내 항목 전체를 대상으로 움직임
for i in range(low+1, high+1):
if aList[i] < pivotitem:
pivotpoint += 1
aList[i], aList[pivotpoint] = aList[pivotpoint], aList[i]
# 분할이 완료된 후 기준원소를 적절한 위치(pivotpoint)로 옮기기
aList[low], aList[pivotpoint] = aList[pivotpoint], aList[low]
return pivotpoint
aList = [15, 22, 13, 27, 12, 10, 20, 25]
n = len(aList)
partition(aList, 0, n-1)
print(aList)
[10, 13, 12, 15, 22, 27, 20, 25]
# 퀵정렬 재귀
# 주의: 리스트의 항목을 직접 수정함. 따라서 제자리 정렬임.
def quickSort(aList, low, high):
if low < high:
# 분할 후 기준원소 위치 확인
pivotpoint = partition(aList, low, high)
# 분할된 부분 정렬(재귀)
quickSort(aList, low, pivotpoint-1)
quickSort(aList, pivotpoint+1, high)
return aList
quickSort(aList, 0, n-1)
[10, 12, 13, 15, 20, 22, 25, 27]
partition
) 알고리즘¶첫째 원소(기준원소)를 제외한 모든 원소와 비교. 따라서 다음 성립:
$$T(n) = n-1$$
quicksort
) 알고리즘¶partition
함수 실행 과정에서 기준원소(pivot)와의 비교 횟수quicksort
) 알고리즘¶partition
함수 실행 과정에서 기준원소(pivot)와의 비교 횟수<출처: 위키피디아 - 행렬곱셈>
행렬 $A$와 $B$가 아래와 같이 주어졌을 때,
$$ A = \begin{bmatrix} a_{1 1} & a_{1 2}\\ a_{2 1} & a_{2 2} \end{bmatrix} \qquad B = \begin{bmatrix} b_{1 1} & b_{1 2}\\ b_{2 1} & b_{2 2} \end{bmatrix} $$두 행렬의 곱 $C$을 정의할 수 있다.
$$ C = A \times B = \begin{bmatrix} c_{1 1} & c_{1 2}\\ c_{2 1} & c_{2 2} \end{bmatrix} $$이때 다음이 성립한다.
\begin{align*} c_{i j} &= a_{i 1} \cdot b_{1 j} + a_{i 2} \cdot b_{2 j} \\ &= \sum_{k=1}^{2} a_{i k} \cdot b_{k j} \end{align*}예를 들어
\begin{align*} \begin{bmatrix} 2 & 3 \\ 4 & 1 \end{bmatrix} \times \begin{bmatrix} 5 & 7 \\ 6 & 8 \end{bmatrix} &= \begin{bmatrix} 2\times 5 + 3\times 6 & 2 \times 7 + 3 \times 8 \\ 4 \times 5 + 1 \times 6 & 4 \times 7 + 1 \times 8 \end{bmatrix} \\ &= \begin{bmatrix} 28 & 38 \\ 26 & 36 \end{bmatrix} \end{align*}A = [[2, 3],
[4, 1]]
B = [[5, 7],
[6, 8]]
C = [[0, 0],
[0, 0]]
for i in range(0,2):
for j in range(0, 2):
for k in range(0, 2):
C[i][j] += A[i][k] * B[k][j]
C
[[28, 38], [26, 36]]
def matrixmult(A, B):
n = len(A)
# C 행렬을 초기화 하기 위해 리스트 조건제시법 활용
C = [[0 for _ in range(n)] for _ in range(n)]
for i in range(0,2):
for j in range(0, 2):
for k in range(0, 2):
C[i][j] += A[i][k] * B[k][j]
return C
matrixmult(A, B)
[[28, 38], [26, 36]]
def matrixmult_com(A, B):
n = len(A)
C = [[sum([A[i][k] * B[k][j] for k in range(n)]) for j in range(n)] for i in range(n)]
return C
matrixmult_com(A, B)
[[28, 38], [26, 36]]
import numpy as np
def matrixmult_np(A, B):
n = len(A)
# (nxn) 크기의 0행렬 생성. 실수가 아닌 정수 행렬 생성.
C = np.zeros((n,n), dtype=int)
for i in range(0,2):
for j in range(0, 2):
for k in range(0, 2):
C[i, j] += A[i, k] * B[k, j]
return C
A1 = np.array(A)
B1 = np.array(B)
matrixmult_np(A1, B1)
array([[28, 38], [26, 36]])
행렬 $A$와 $B$가 아래와 같이 주어졌을 때,
$$ A = \begin{bmatrix} a_{1 1} & a_{1 2}\\ a_{2 1} & a_{2 2} \end{bmatrix} \qquad B = \begin{bmatrix} b_{1 1} & b_{1 2}\\ b_{2 1} & b_{2 2} \end{bmatrix} $$다음이 성립한다.
numpy
모듈의 array
활용import numpy as np
def partition(matrix):
"""
(n x n) 크기의 행렬을 (n/2 x n/2) 크기의 행렬 4개로 분할하기
"""
size = len(matrix)
size2 = size//2
return (matrix[:size2, :size2], matrix[:size2, size2:],
matrix[size2:, :size2], matrix[size2:, size2:])
# 분할정복을 활용한 슈트라센의 행렬곱셈 (재귀)
def strassen(A, B):
# n=2 일 경우: 일반 정의가 좀 더 빠름
if len(A) == 1:
return A * B
# 행렬 인자 4등분하기. 재귀적으로 (2x2) 행렬이 만들어질 때까지.
A11, A12, A21, A22 = partition(A)
B11, B12, B21, B22 = partition(B)
# 분할된 부분행렬에 대해 재귀 적용
M1 = strassen(A11 + A22, B11 + B22)
M2 = strassen(A21 + A22, B11)
M3 = strassen(A11, B12 - B22)
M4 = strassen(A22, B21 - B11)
M5 = strassen(A11 + A12, B22)
M6 = strassen(A21 - A11, B11 + B12)
M7 = strassen(A12 - A22, B21 + B22)
# 4개의 부분행렬 완성
C11 = M1 + M4 - M5 + M7
C12 = M3 + M5
C21 = M2 + M4
C22 = M1 + M3 - M2 + M6
# 4개의 부분행렬을 하나의 행렬로 합병
C = np.vstack((np.hstack((C11, C12)), np.hstack((C21, C22))))
return C
A2 = np.array([[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 1, 2, 3],
[4, 5, 6, 7]])
B2 = np.array([[8, 9, 1, 2],
[3, 4, 5, 6],
[7, 8, 9, 1],
[2, 3, 4, 5]])
strassen(A2, B2)
array([[ 43, 53, 54, 37], [123, 149, 130, 93], [ 95, 110, 44, 41], [103, 125, 111, 79]])
이 식을 전개하면:
이 식을 전개하면:
표준 알고리즘 | 슈트라쎈 알고리즘 | |
---|---|---|
곱셉 | $n^3$ | $n^{2.81}$ |
덧셈/뺄셈 | $n^3 - n^2$ | $6 n^{2.81} - 6 n^2$ |
예제: 1장에서 살펴본 피보나찌 수열 계산 함수(재귀)
fib(k) = fib(k-2) + fib(k-1)
예제:
\begin{align*} T(n) &= n\, T(n/c) \end{align*}
def pseudo_hanoi(begin, end, temp, n):
if n==1:
print(f"{begin}에서 디스크 1개를 {end}로 옮기세요")
return
pseudo_hanoi(begin, temp, end, n-1)
print(f"{begin}에서 디스크 {n}을 {end}로 옮기세요")
pseudo_hanoi(temp, end, begin, n-1)
# 디스크 1개
num_disks = 1
pseudo_hanoi('말뚝 A','말뚝 C','말뚝 B', num_disks)
말뚝 A에서 디스크 1개를 말뚝 C로 옮기세요
# 디스크 3개
num_disks = 3
pseudo_hanoi('말뚝 A','말뚝 C','말뚝 B', num_disks)
말뚝 A에서 디스크 1개를 말뚝 C로 옮기세요 말뚝 A에서 디스크 2을 말뚝 B로 옮기세요 말뚝 C에서 디스크 1개를 말뚝 B로 옮기세요 말뚝 A에서 디스크 3을 말뚝 C로 옮기세요 말뚝 B에서 디스크 1개를 말뚝 A로 옮기세요 말뚝 B에서 디스크 2을 말뚝 C로 옮기세요 말뚝 A에서 디스크 1개를 말뚝 C로 옮기세요
pseudo_hanoi
의 일정 시간복잡도¶def pseudo_hanoi_(begin, end, temp, n):
count = 0
if n==1:
print(f"{begin}에서 디스크 1개를 {end}로 옮기세요")
return count+1
count += pseudo_hanoi_(begin, temp, end, n-1)
print(f"{begin}에서 디스크 {n}을 {end}로 옮기세요")
count += 1
count += pseudo_hanoi_(temp, end, begin, n-1)
return count
# 디스크 4개
num_disks = 4
pseudo_hanoi_('말뚝 A','말뚝 C','말뚝 B', num_disks)
말뚝 A에서 디스크 1개를 말뚝 B로 옮기세요 말뚝 A에서 디스크 2을 말뚝 C로 옮기세요 말뚝 B에서 디스크 1개를 말뚝 C로 옮기세요 말뚝 A에서 디스크 3을 말뚝 B로 옮기세요 말뚝 C에서 디스크 1개를 말뚝 A로 옮기세요 말뚝 C에서 디스크 2을 말뚝 B로 옮기세요 말뚝 A에서 디스크 1개를 말뚝 B로 옮기세요 말뚝 A에서 디스크 4을 말뚝 C로 옮기세요 말뚝 B에서 디스크 1개를 말뚝 C로 옮기세요 말뚝 B에서 디스크 2을 말뚝 A로 옮기세요 말뚝 C에서 디스크 1개를 말뚝 A로 옮기세요 말뚝 B에서 디스크 3을 말뚝 C로 옮기세요 말뚝 A에서 디스크 1개를 말뚝 B로 옮기세요 말뚝 A에서 디스크 2을 말뚝 C로 옮기세요 말뚝 B에서 디스크 1개를 말뚝 C로 옮기세요
15
pop()
과 append()
메서드 활용# 말뚝 인자로 리스트 활용
def hanoi_list(begin, end, temp, n):
if n == 1:
end.append(begin.pop())
else:
hanoi_list(begin, temp, end, n - 1)
hanoi_list(begin, end, temp, 1)
hanoi_list(temp, end, begin, n - 1)
tower_a = []
tower_b = []
tower_c = []
# 디스크 수
num_discs = 3
for i in range(num_discs, 0, -1):
tower_a.append(i)
# 시작할 때
print("시작할 때:")
print(f"tower_a: {tower_a}")
print(f"tower_b: {tower_b}")
print(f"tower_c: {tower_c}")
hanoi_list(tower_a, tower_c, tower_b, num_discs)
# 이동 후
print("\n이동 후:")
print(f"tower_a: {tower_a}")
print(f"tower_b: {tower_b}")
print(f"tower_c: {tower_c}")
시작할 때: tower_a: [3, 2, 1] tower_b: [] tower_c: [] 이동 후: tower_a: [] tower_b: [] tower_c: [3, 2, 1]
push()
: 항목 추가를 위한 메서드pop()
: 항목 삭제를 위한 메서드class Stack():
def __init__(self):
self._container = []
def push(self, item):
self._container.append(item)
def pop(self):
return self._container.pop()
def __repr__(self):
return repr(self._container)
def hanoi(begin, end, temp, n):
if n == 1:
end.push(begin.pop())
else:
hanoi(begin, temp, end, n - 1)
hanoi(begin, end, temp, 1)
hanoi(temp, end, begin, n - 1)
tower_a = Stack()
tower_b = Stack()
tower_c = Stack()
# 디스크 수
num_discs = 5
for i in range(num_discs, 0, -1):
tower_a.push(i)
# 시작할 때
print("시작할 때:")
print(f"tower_a: {tower_a}")
print(f"tower_b: {tower_b}")
print(f"tower_c: {tower_c}")
hanoi(tower_a, tower_c, tower_b, num_discs)
# 이동 후
print("\n이동 후:")
print(f"tower_a: {tower_a}")
print(f"tower_b: {tower_b}")
print(f"tower_c: {tower_c}")
시작할 때: tower_a: [5, 4, 3, 2, 1] tower_b: [] tower_c: [] 이동 후: tower_a: [] tower_b: [] tower_c: [5, 4, 3, 2, 1]