0% found this document useful (0 votes)
35 views25 pages

Lec7 Divide and Conquer

Uploaded by

lsw9020
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
35 views25 pages

Lec7 Divide and Conquer

Uploaded by

lsw9020
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 25

Lecture 7:

Divide-and Conquer (분할 정복)

1
This Lecture will be covered …(이번 수업에서 배울 점)
 분할 정복 알고리즘
 분할 정복의 응용: 거듭 제곱(Exponentiation)
 분할 정복의 응용: 피보나치(Fibonacci) 수열
 분할 정복의 응용: 행렬 곱셈(Matrix Multiplication) 연산

2
분할 정복
 분할 정복의 개념:
- 주어진 문제를 재귀 반복(recursion)을 활용하여 해결하려는 전략 방법
- 만약 문제의 크기가 상대적으로 작은 경우(base case) 반복 없이 직접적으로 해결
- 반대의 경우(recursive case), 3 단계로 통해 수행됨
- 분할(Divide) : 해결하기 쉽도록 문제를 여러 개의 하위 문제로 나눔.
- 정복(Conquer) : 나눈 하위 문제를 각각 해결함
- 합병(Combine) : 하위 문제의 결과들을 결합하여 원래 문제에 대한 솔루션을 구성함

3
분할 정복의 응용: 거듭 제곱(Exponentiation)
 거듭 제곱(Exponentiation)
- 지수(Exponent)의 크기만큼 곱셈을 수행하는 연산
- 예) C8 = C∙ C∙ C∙ C∙ C∙ C∙ C∙ C 이므로 O(n)의 수행 시간을 소요함
- 분할 정복을 적용하면 C8 = (C4)∙(C4) = (C4)2 과 같이 표현 할 수 있음
- 더욱 분할을 수행하면 C8 = {(C4)2}2 C2를 계산한 뒤 제곱을 두 번 더 반복하여 같은 결과를 얻을 수
있음, 즉 3번의 제곱 재귀(recursion)

- 하지만 지수가 홀수인 경우, 지수가 2로 나누어지지 않기 때문에 이러한 연산을 바로 적용 할 수


없음

4
분할 정복의 응용: 거듭 제곱(Exponentiation)
 거듭 제곱(Exponentiation)
- 지수가 홀수(odd)인 경우

- = × × = ×

- 예) C5 = C2∙ C2∙C = {(C)2}2 C


- 최종적으로 지수가 짝수/홀수 인 경우에 대해 재귀 방정식을 만들면 다음과 같음

- = × × -> n이 홀수

- = × -> n이 짝수
- 거듭 제곱을 분할 정복을 이용하여 수행한 시간 복잡도는 O(logn)

5
분할 정복의 응용: 거듭 제곱(Exponentiation)
 분할 정복을 이용한 거듭 제곱(Exponentiation)의 파이썬 구현
def exponentiation(c, n):
if n == 0:
return 1

x = exponentiation(c, n//2)

if n % 2 == 0:
return x * x

else:
return x * x * c

print(exponentiation(2,256))
#115792089237316195423570985008687907853269984665640564039457584007913129639936

6
분할 정복의 응용: 피보나치(Fibonacci) 수
 피보나치 수
- 예)토끼 번식 : 첫번째 달에 토끼 한상이 태어났다고 가정
- 이 토끼는 두 달이 되면 다 자라서 번식이 가능하고, 매달 새끼 한 쌍을 낳음
- 8달 후에는 몇 마리의 토끼가 있을까? => 답: 21쌍 (42마리)
달수 토끼의 수(단위: 쌍) - 토끼가 다 자라기까지는 두 달이 걸리므로 두 달까지는
1 1
토끼 수의 변화가 없음
2 1
- 세 달 째가 되면 새끼 한 쌍을 낳음
3 2
4 3 - 네 달 째에는 최초의 토끼가 또 한 쌍의 새끼를 낳지만
5 5 세 달 째에 태어난 새끼들은 아직 다 자라지
6 8 않았으므로 토끼 수의 총합은 3쌍
7 13
8 21

7
분할 정복의 응용: 피보나치(Fibonacci) 수
 피보나치 수
- 토끼 번식 이야기는 이탈리아의 수학자 레오나르도 피보나치의 저서 “계산의 책(Liber Abbaci)”에
소개된 문제
- 이 문제의 답에 해당하는 1, 1, 2, 3, 5, 8, 13, … 의 수열을 일컬어 ‘피보나치 수열‘이라 칭함
- 피보나치 수열 정의

- 20번까지의 피보나치 수열

8
분할 정복의 응용: 피보나치(Fibonacci) 수
 간단한 피보나치 수의 파이썬 구현
def fibonacci(n):
if n == 0:
return 0
elif n == 1 or n == 2:
return 1
else:
return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(8))
#21

 재귀 호출
- fibonacci(n)을 호출하면 두개의 재귀 호출 fibonacci(n-1) 그리고 fibonacci(n-2) 발생됨

9
분할 정복의 응용: 피보나치(Fibonacci) 수
재귀 호출 계속
- 두 번째 단계에서는 fibonacci() 호출이 네 개로 늘어남
- 그 다음 단계를 전개하면 모두 8개의 fibonacci() 함수 호출이 생기며, 이 반복은 n이 0이 될 때까지
계속됨
- 이 알고리즘이 n번째 피보나치 수를 구하기 위한 작업량, 즉 수행 시간은 O(2n)
- 분할 정복 기법을 이용하면 피보나치 수를 찾는 데에 드는 소요 시간을 O(logn) 수준으로 줄일 수
있음

10
분할 정복의 응용: 피보나치(Fibonacci) 수
분할 정복을 활용한 피보나치 수열
- 1, 그리고 2 번째 피보나치 수를 각각 F1, F2이라고 하면, 2차원 정 사각 행렬로 나타내면 다음과
같음

- 이 정 사각 행렬을 n번 제곱하면 다음과 같은 점화식을 유도 할 수 있음

- 참고로, 이 행렬만 가지고 O(n)의 피보나치 수 알고리즘을 구현 가능함


- 분할 정복을 이용하면, n 번째 피보나치 수를 구할 때 n/2번째 피보나치 수를 찾아서 제곱하면 됨

11
분할 정복의 응용: 피보나치(Fibonacci) 수
분할 정복을 활용한 피보나치 수열
- 분할 정복을 이용하면, n/2 번째 피보나치 수를 구할 때 (n/2)/2번째 피보나치 수를 찾아서
제곱하면 됨
- 즉 n 번째 피보나치 수를 구하기 위해서는 최초의 행렬을 logn회 제곱하면 됨.
- 예) 10번째 피보나치 수를 찾기

1 1 1 1 1 1
- 다음, 10/2 5인데, 5는 홀수 이므로 = 을 활용하여 다음과 같은 식을
1 1 1 1 1 1
이끌어냄

12
분할 정복의 응용: 피보나치(Fibonacci) 수
분할 정복을 활용한 피보나치 수열
- 4 번째 피보나치 수를 구하기 위해 제곱수를 2로 나눔

- 2 번째 피보나치 수를 구하기 위해 제곱수를 1로 나눔

- 분할을 마쳤으니 정복하고 병합하기

최종적으로 F10은 55

13
분할 정복의 응용: 피보나치(Fibonacci) 수
 분할 정복을 활용한 피보나치 수열의 파이썬 구현
def power(F, n): def multiply(F, M): def fibonacci(n):
병합
if( n == 0 or n == 1): x = (F[0][0] * M[0][0] + F = [[1, 1],
return F[0][1] * M[1][0]) [1, 0]]
M = [[1, 1], y = (F[0][0] * M[0][1] + if (n == 0):
[1, 0]] F[0][1] * M[1][1]) return 0
z = (F[1][0] * M[0][0] + power(F, n - 1)
power(F, n // 2) F[1][1] * M[1][0])
multiply(F, F) w = (F[1][0] * M[0][1] + return F[0][0]
F[1][1] * M[1][1])
if (n % 2 != 0): print(fibonacci(8))
multiply(F, M) F[0][0] = x #21
F[0][1] = y
F[1][0] = z
분할 F[1][1] = w

정복

14
분할 정복의 응용: Multiplying Square Matrices (행렬의 곱셈)
 단순한 행렬의 곱셈 알고리즘의 pseudocode:

- 시간 복잡도: 3중으로 중첩된 for 루프는 각각 정확히 N번 반복 실행되고 수도코드의 4행을 실행할
때는 일정한 시간(곱셈)이 걸리기 때문에 행렬 곱셈은 O(n3) 시간에 작동함.

15
Multiplying Square Matrices (행렬의 곱셈)
 단순한 행렬의 곱셈 알고리즘의 파이썬 구현:
def Simple_Matrix_Multiplication(A,B):
C = [[0 for i in range(len(B[0]))] for j in range(len(A))]
for i in range(len(A)):
for j in range(len(B[0])):
for k in range(len(A[0])):
C[i][j] = C[i][j] + A[i][k]*B[k][j]
return C
A=[[1,2,3,4],[3,4,5,6],[5,6,7,8],[7,8,9,10]]
B=[[4,1,1,0],[1,0,9,8],[9,8,7,6],[7,6,5,4]]
print(Simple_Matrix_Multiplication(A,B))
#[[61, 49, 60, 50], [103, 79, 104, 86], [145, 109, 148, 122], [187, 139, 192, 158]]

16
Multiplying Square Matrices (행렬의 곱셈)
 분할 정복을 이용한 두 N x N 행렬의 곱셈 :
- 분할 정복 적용: 두 행렬 A,B을 4개의 (N/2) x (N/2)의 행렬 A11, A12, A21, A22 그리고 B11, B12,
B21, B22으로 나눔
A11 A12 B11 B12 C11 C12
A= B= C=
A21 A22 B21 B22 C21 C22
- 다음, 서브 (N/2) x (N/2) 행렬인 C11 = A11∙B11 + A12∙B21, C12 = A11∙B12 + A12∙B22,
C21=A21∙B11 + A22∙B21, C22=A21∙B12 + A22∙B22을 계산하여 하나의 N x N 행렬 C로 결합하는
과정을 재귀적으로 반복한다.
C11 C12 A11 A12 B11 B12 A11 B11 + A12 B21 A11 B12 + A12 B22
= =
C21 C22 A21 A22 B21 B22 A21 B11 + A22 B21 A21 B12 + A22 B22

C11= A11 B11 + A12 B21


C12= A11 B12 + A12 B22 (N/2) x (N/2) 행렬에 대해 8개의 곱셈 연산 그리고 4개의
C21= A21 B11 + A22 B21 덧셈 연산을 수행함
C22= A21 B12 + A12 B22

17
Multiplying Square Matrices (행렬의 곱셈)
 분할 정복을 이용한 두 N x N 행렬의 곱셈의 pseudocode:

인덱스를 활용하면 Θ(1)의 시간이 소비됨

18
Multiplying Square Matrices (행렬의 곱셈)
 분할 정복을 이용한 두 N x N 행렬의 곱셈의 pseudocode:

MATRIX-MULTIPLY-
RECURSIVE 함수를 8차례
호출

19
Multiplying Square Matrices (행렬의 곱셈)
 분할 정복 기반 행렬의 곱셈 알고리즘의 파이썬 구현:
def merge_matrix(A, B):

return [rowa + rowb for rowa, rowb in zip(A,B)]

def MatAdd(A,B):

result = [[0 for i in range(len(A))] for j in range(len(A))]


for i in range(len(A)):
for j in range(len(A)):
result[i][j] = A[i][j] + B[i][j]
return result

def createSubmatrices(A,starting_index,rows,columns):

result = [[0 for i in range(rows)] for j in range(columns)]


for i in range(rows):
for j in range(columns):
result[i][j] = A[starting_index[0] + i][starting_index[1] + j]
return result

20
Multiplying Square Matrices (행렬의 곱셈)
 분할 정복 기반 행렬의 곱셈 알고리즘의 파이썬 구현:
def MMRecursive(A,B,n):
if(n==1):
return [[A[0][0]*B[0][0]]]
else:
A11,B11 = createSubmatrices(A, (0,0), n//2, n//2), createSubmatrices(B, (0,0), n//2, n//2)
A12,B12 = createSubmatrices(A, (0,n//2), n//2, n//2), createSubmatrices(B, (0,n//2), n//2, n//2)
분할
A21,B21 = createSubmatrices(A, (n//2,0), n//2, n//2), createSubmatrices(B, (n//2,0), n//2, n//2)
A22,B22 = createSubmatrices(A, (n//2,n//2), n//2, n//2), createSubmatrices(B, (n//2,n//2), n//2, n//2)

C11 = list(MatAdd(MMRecursive(A11, B11, n//2) , MMRecursive(A12, B21, n//2)))


정복
C12 = list(MatAdd(MMRecursive(A11, B12, n//2) , MMRecursive(A12, B22, n//2)))
C21 = list(MatAdd(MMRecursive(A21, B11, n//2) , MMRecursive(A22, B21, n//2)))
C22 = list(MatAdd(MMRecursive(A21, B12, n//2) , MMRecursive(A22, B22, n//2)))

return merge_matrix(C11, C12) + merge_matrix(C21, C22) 병합


A=[[1,2,3,4],[3,4,5,6],[5,6,7,8],[7,8,9,10]]
B=[[4,1,1,0],[1,0,9,8],[9,8,7,6],[7,6,5,4]]

C = MMRecursive(A, B, 4)
print(C)
#[[61, 49, 60, 50], [103, 79, 104, 86], [145, 109, 148, 122], [187, 139, 192, 158]]

21
행렬 곱셈을 위한 Strassen’s 알고리즘
 분할 정복을 이용한 Strassen 알고리즘 기반 두 N x N 행렬의 곱셈 :
- 분할 정복으로 서브 행렬로 나눈 7번의 곱셈으로 행렬 곱을 구하는 방법
- 분할 정복 적용: 두 행렬 A,B을 4개의 (N/2) x (N/2)의 행렬 A11, A12, A21, A22 그리고 B11, B12,
B21, B22으로 나눔
A11 A12 B11 B12 C11 C12
A= B= C=
A21 A22 B21 B22 C21 C22
- 다음, 서브 (N/2) x (N/2) 행렬인 C11 = m1+m4-m5+m7, C12 = m3+m5, C21=m2+m4,
C22=m1+m3-m2+m6을 계산하여 하나의 N x N 행렬 C로 결합하는 과정을 재귀적으로 반복한다.
C11 C12 m1 + m4 – m5 + m7 m3 + m5
=
C21 C22 m2 + m4 m1 + m3 – m2 + m6
(N/2) x (N/2) 행렬에 대해 7개의 곱셈 연산
m1= (A11 + A22)(B11 + B22) m5= (A11 + A12)∙B22
그리고 18개의 덧셈/뺄셈 연산을 수행함
m2= (A21 + A22)∙B11 m6= (A21 – A11)∙(B11 + B12)
행렬의 길이가 기준치(threshold) 이상일 때는
m3= A11∙(B12 - B22) m7= (A12 – A22)∙(B21 + B22)
Strassen 알고리즘이 효과적임 -> O(n2.81)
m4= A21(B21 - B11) log27=2.8073

22
Multiplying Square Matrices (행렬의 곱셈)
 Strassen 알고리즘 기반 두 N x N 행렬 곱셈의 파이썬 구현:
import numpy as np

def strassen(A,B):
n = len(A)
if n == 1:
return A * B
else:
a11, b11 = A[:len(A)//2,:len(A)//2], B[:len(B)//2,:len(B)//2]
a12, b12 = A[:len(A)//2,len(A)//2:], B[:len(B)//2,len(B)//2:]
a21, b21 = A[len(A)//2:,:len(A)//2], B[len(B)//2:,:len(B)//2] 분할
a22, b22 = A[len(A)//2:,len(A)//2:], B[len(B)//2:,len(B)//2:]

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)

23
Multiplying Square Matrices (행렬의 곱셈)
 Strassen 알고리즘 기반 두 N x N 행렬 곱셈의 파이썬 구현:
c11 = m1 + m4 - m5 + m7
c12 = m3 + m5
c21 = m2 + m4
c22 = m1 + m3 - m2 + m6

result = np.zeros((n,n))
result[:int(len(result)/2),:int(len(result)/2)] = c11
result[:int(len(result)/2),int(len(result)/2):] = c12 병합
result[int(len(result)/2):,:int(len(result)/2)] = c21
result[int(len(result)/2):,int(len(result)/2):] = c22
return result

A=np.array([[1,2,3,4],[3,4,5,6],[5,6,7,8],[7,8,9,10]])
B=np.array([[4,1,1,0],[1,0,9,8],[9,8,7,6],[7,6,5,4]])

C = strassen(A, B)
print(C)
#[[ 61. 49. 60. 50.]
[103. 79. 104. 86.]
[145. 109. 148. 122.]
[187. 139. 192. 158.]]

24
Multiplying Square Matrices (행렬의 곱셈)
 분할 정복 기반 행렬의 곱셈 알고리즘과 Strassen 알고리즘의 비교: 랜덤 입력
O(n3)

A = np.random.randint(low=-100,high=100,size=(size,size),dtype=int)
B = np.random.randint(low=-100,high=100,size=(size,size),dtype=int)

O(n2.81)

Array_size Divide_Conquer Strassen


2 0.000998 0
4 0.000997 0.000998
8 0.001997 0.001001
이론적인
16 0.011958 0.009993
임계치 32 0.097735 0.076791
(threshold) 64 0.659297 0.554476
128 5.391873 3.415633
256 46.35474 25.26365
Matrix_Multiplication_TestBench.py 참조

25

You might also like