본문 바로가기
필기

분할 정복을 이용한 수열의 빠른 합

by 킴제리 2021. 12. 14.

아래 내용은 종만북을 공부한 후 나중에 복습용으로 정리한 것입니다.

 

1. 수열의 빠른 합.

보통 수열의 합은 단순히 

$$ \sum_{k = 1}^N k = \frac{\left(n\right)\left(n+1\right)}{2} $$

이런식으로 공식을 이용하면 \( O \left( 1 \right) \)의 시간복잡도로 바로 계산이 가능합니다. 

하지만 분할 정복을 공부하기 위해 이번에는 \(  \sum_{k=1}^n k \) 을 분할 정복을 이용해 구현해 보겠습니다.

 

재귀 호출

우선 먼저 일반적인 재귀 호출을 이용한 코드입니다.

 

int recursiveSum(int n) {
    // 기저 조건.
    if(n == 1) return 1;
    return n + recursiveSum(n - 1);
}

 

가장 쉽게 떠올릴 수 있고, 구현이 가능한 코드입니다. 시간복잡도는 \( O \left( N \right) \)이 됩니다.

 

분할 정복

다음은 분할 정복을 이용해서 같은 일을 하는 \( fastSum \left( n \right) \)함수를 작성하겠습니다. 그 전에 분할정복을 어떻게 적용할지 아이디어를 먼저 떠올려 봅시다.

 

$$  fastSum\left(\right)=1+2+\cdots+n=\left(1+2+\cdots+\frac{n}{2}\right)+\left(\left(\frac{n}{2}+1\right)+\cdots+n\right) $$

 

반으로 잘라서 2개의 부분문제를 생성했습니다. 첫번째 조각을 잘 살펴보면 1 부터 \( \frac{n}{2} \) 까지의 합을 계산합니다. \(fastSum\left(n\right) \) 함수는 1부터 n 까지의 합을 계산하는 함수임을 기억해본다면, 첫번째 조각은 \( fastSum\left(\frac{n}{2}\right) \) 임을 알 수 있게 됩니다.

 

하지만 두번째 조각은 \( fastSum \left( \right) \) 함수로 표현할 수 없습니다. 1부터 n까지의 합 형태만 함수로 나타낼 수 있기 때문입니다. 그러면 어떻게 해야할까요? \( fastSum \left( \right) \)함수로 나타낼 수 있도록 한번 변형해 보겠습니다.

$$ \left( \left( \frac{n}{2} + 1 \right) + \cdots +n \right) = \left( \frac{n}{2} + 1 \right) + \left( \frac{n}{2} + 2 \right) + \cdots + \left( \frac{n}{2} + \frac{n}{2} \right) $$

 

변형한 식을 자세히 살펴보면 \( \frac{n}{2} \) 가 계속 반복해서 등장하는 것을 발견할 수 있습니다. 걔네들만 따로 빼서 식을 정리하면,

 

$$ \left( \frac{n}{2} \times \frac{n}{2} \right) + \left( 1 + 2 + \cdots + \frac{n}{2} \right) $$

 

이렇게 정리됩니다. 이 때 1부터 \( \frac{n}{2} \) 까지 더하는 항을 찾을 수 있는데, 이는 \( fastSum \left( \frac{n}{2} \right) \) 로 변형이 가능합니다.

즉, \( fastSum \left( n \right) \) 함수를 재귀적으로 표현하게 되면,

 

$$ fastSum \left( n \right) = 2 \times fastSum \left( \frac{n}{2} \right) + \frac{n^2}{4}  $$

 

이렇게 표현할 수 있겠네요. 참고로 n 이 짝수 라고 가정했을 때만 성립합니다.

 

이제 코드로도 한번 구현해 보겠습니다.

 

int fastSum(int n) {
    // 기저 조건.
    if(n == 1) return 1;
    // n 이 홀수인 경우.
    if(n % 2 == 1) return fastSum(n - 1) + n;
    return 2 * fastSum(n/2) + (n/2)*(n/2);
}

 

 

n이 홀수 인 경우도 문제 없죠. 단순히 n-1까지의 합에 n 만 더해주면 되니까요.

분할정복이 일반적으로 계산하는 것보다 훨씬 빠르다고 알려져 있습니다. 그렇기 때문에 이 고생을 해가며 점화식을 구해서 분할정복을 적용해서 코드를 구현했습니다. 진짜로 더 빠른지 시간복잡도를 간단하게 구해봅시다.

 

만약  \( fastSum \left( 32 \right) \) 를 구한다고 가정해봅시다. 그러면 \( 2 \times fastSum \left( 16 \right) + \frac{32^2}{4}\) 이 유도됩니다. 음... 뒤에 있는 분수항은 상수이므로 그냥 무시하고 함수의 호출만 살펴보겠습니다.

 

$$ fastSum \left( 32 \right) \Rightarrow fastSum \left( 16 \right) \Rightarrow fastSum \left( 8 \right) \Rightarrow fastSum \left( 4 \right) \Rightarrow fastSum \left( 2 \right) \Rightarrow fastSum \left( 1 \right)$$

 

이런식으로 함수가 호출되겠죠. 이런식으로 단순히 절반씩 줄어드는 경우에는 시간복잡도는 \( O \left( logN \right) \) 이 됩니다.

\( O \left( logN \right) \) 은 거의 상수라고 생각해도 무방할 정도로 작은 수이죠. 단순 재귀로 구현했을 때 보다 훨씬 빠릅니다.

'필기' 카테고리의 다른 글

[SQL] With, 스칼라 서브쿼리, DB 수정.  (0) 2022.03.31
SQL 서브쿼리. Oracle 기준.  (0) 2022.03.31
SQL 명령어 정리. Oracle 기준.  (0) 2022.03.30
비트마스크(2)  (0) 2021.11.25
비트마스크(1)  (0) 2021.11.25

댓글