코딩을 좀 해본 사람이라면 이 아저씨가 뉘신지 알 것이다.

분할 정복

Computer Science Apr 14, 2025

분할 정복이란?

프로그래밍을 하다 보면 분할 정복이라는 말을 종종 듣는다. 분할 정복... 무언가 멋있지 않은가?

분할 정복은 무엇일까? 간단하다. 큰 문제를 쪼개어 구한 뒤 합치는 것이다. 분할 정복을 이용한 알고리즘은 대표적으로 분할 정복을 이용한 거듭제곱이나 피보나치 수열, 퀵정렬 등이 있다. 어디선가 한 번쯤 들어본 것들 일 것이다. (아닌가...)

이번 글에서는 분할 정복의 원리와 구현, 그리고 다양한 응용 알고리즘을 배우자.


분할 정복의 원리

분할 정복의 원리는 아주 간단하다.

여러 번 계산해야 하는 것을 한 번 계산 후 저장해서 '재활용'하기

대표적인 분할 정복 알고리즘인 거듭제곱을 예시로 들어보자.

\(2^{100} \) 을 계산하기 위해서는 크게 두 가지 방법이 떠오를 것이다.

  1. 2를 100 번 곱하기: 단순하지만 직관적으로 쉽게 떠오르는 방법이다. 그러나 이 경우 지수가 1억, 100억...이 되면 시간 초과가 날 것 이다.
  2. \(2^{50}\)을 구하고 제곱하기: 100 번 해야 할 계산을 쪼개어 재활용하였다.

2번 방법이 분할 정복의 원리이다. 여러 번 계산(100번)을 쪼개어 작게 만든다. 이를 한 번 계산하고(\(2^{50}\)계산) 재활용하여 더 빠르게 답을 구한다.

수열을 절반 씩 나누어 정렬하는 것이 퀵정렬의 기본 원리다. 피보나치 수열도 행렬 계산을 쪼개어 재활용한다.


분할 정복의 구현

파이썬으로 분할 정복을 구현해보​자.

def cal(x,y) :
  return x*y
def f(n,x):
  if n==1:
    return x
  else:
    tmp=f (n//2,x)
    if n%2==0:
      return cal (tmp, tmp)
    else:
      return cal (tmp, cal (tmp, x))
print(f(10,2))

분할 정복을 이용한 거듭 제곱의 계산

위 코드는 아주 전형적인 분할 정복 알고리즘이다. tmp에 \(x^{n \over 2}\)를 저장, 재활용해서 O(log n)의 시간복잡도를 가지게 했다. 만약 이를 일일이 계산하려고 했다면 O(n)이었으므로 아주 효율적인 알고리즘임을 알 수 있다.

추가적인 정보: 사실 파이썬에 기본으로 제공하는 거듭 제곱 계산 (n**m)도 분할 정복을 이용한다. 그래도... 다른 문제를 푸려면 알아두는 게....


피보나치 수열

이 분할 정복을 이용한 대표적인 알고리즘, 피보나치 수열을 구하는 것이다. 일반적인 알고리즘을 먼저 보자.

n=int(input())
ans=[1,1]
for i in range(3,n+1):
  ans.append(ans[-1]+ans[- 2])
print(ans[-1])

기본적인 피보나치 수열 구하기

위 코드는 비효율적이다. DP를 이용한 알고리즘으로(DP는 다이나믹 알고리즘이다. 사실 이 녀석도 분할 정복과 유사한 면이 있다.) O(n)의 시간복잡도를 가진다. 따라서 만약 1억 번째 피보나치 수를 구하라고 한다면 C언어 기준 대략 1초가 걸리고 파이썬은 그보다 더 걸린다.

더 효율적인 코드가 있다. 분할 정복을 이용할 것이다. 그러나 이를 이해하기 위해서는 우선 행렬을 알아야 한다. 잠시 알아보고 돌아오자.


행렬

이 글에서는 행렬에 대한 자세한 내용을 다루지 않겠다. 딱 피보나치 수열에 필요한 정도만 설명한다.

다음 행렬을 생각하자.

\[\begin{pmatrix} F_{n} \\ F_{n-1} \end{pmatrix}\]

여기서 \(F_{n}\)은 n번째 피보나치 수이다. 그런 다음과 같은 식을 얻는다. 점화식이다.

\[\begin{pmatrix} F_{n} \\ F_{n-1} \end{pmatrix}=\begin{pmatrix} 1&1 \\ 1&0\end{pmatrix}\begin{pmatrix} F_{n-1} \\ F_{n-2} \end{pmatrix}\]

행렬의 곱셉을 생각하면 이주 당연한 결과다. 이를 이용하면 다음과 같이 나타낼 수 있다.

\[\begin{pmatrix} F_{n} \\ F_{n-1} \end{pmatrix}={\begin{pmatrix} 1&1 \\ 1&0\end{pmatrix}}^{n-1}\begin{pmatrix} F_{1} \\ F_{0} \end{pmatrix}\]

단, \(F_{1}=1,\ F_{0}=0\)이라고 하자.

이 식을 이용할 것이다.


분할 정복을 이용한 피보나치 수열 구하기

\[\begin{pmatrix} F_{n} \\ F_{n-1} \end{pmatrix}={\begin{pmatrix} 1&1 \\ 1&0\end{pmatrix}}^{n-1}\begin{pmatrix} F_{1} \\ F_{0} \end{pmatrix}\]

이 식에서 분할 정복을 이용할 부분이 보이는 가? (안보임 말고...) 그렇다. \({\begin{pmatrix} 1&1 \\ 1&0\end{pmatrix}}^{n-1}\)을 분할 정복을 이용해 계산한다.

n=int(input())
def cal(a,b) :
  c=[[0,0],[0,0]]
  for i in range(2): for j in range(2):
    s=0
    for k in range(2):
      s+=a[i][k]*b[k][j]
    c[i][j]=s
  return c
def f(x):
  if x==1:
    return [[1,1], [1,0]]
  else:
    tmp=f(x//2)
    if x%2==0:
      return cal(tmp, tmp)
    else:
      return cal(tmp, cal(tmp, [[1,1], [1,0]]))
if n==1:
  print(1)
  exit()
if n==2:
  print(1)
  exit()
m=f(n-2)
ans=m[0][0]+m[0][1]
print(ans)

분할 정복을 이용한 피보나치 수열 구하기

짠!!!

여기서 n이 1, 2 일 땐 예외 처리 해주는 것을 잊지 말자.

(사실 위에서 말한 2의 거듭 제곱 계산과 달라진 것은 cal 함수 밖에 없다.)


추가 연습 문제

분할 정복을 이용한 다양한 연습 문제를 첨부한다.

11444번: 피보나치 수 6

피보나치 수열 문제다. 수가 커서 DP로는 안 풀린다. 분할 정복을 이용하자.

17401번: 일하는 세포

행렬과 분할 정복을 연습할 수 있다.

12728번: n제곱 계산

기본적인 아이디어는 수학을 알아야 한다. 유명한 수학 문제이니 한 번 풀어보자. 아이디어를 찾았으면 계산은 분할 정복이다

Tags

Lim Hangyeol

25 KSA, 25 KSARANG. Guitarist. Love biology and other science subjects. Computer science and math are also (*✧×✧*)! Music and drawing? Wonderful!