Chapter 15

계산량과 로그

반으로 나누면 단계가 줄어드는 이유
핵심 질문

N2N^2 계산과 NlogNN\log N 계산은 얼마나 다를까?

계산량과 로그

핵심 질문

계산을 반씩 나누면 왜 훨씬 빨라질까?

교과서 설명

어떤 알고리즘이 입력 크기 에 따라 얼마나 많은 계산을 하는지 대략 나타낼 때 표기를 쓴다.

DFT를 그대로 계산하면 모든 출력 마다 모든 입력 을 확인한다. 그래서 대략 다음만큼 계산한다.

이것을 이라고 쓴다.

FFT는 문제를 반씩 나누어 다시 합친다. 을 계속 반으로 나누면 몇 번 만에 이 되는지 나타내는 수가 이다.

위 과정은 번 나누었으므로

이다. FFT의 계산량은 대략 다음처럼 줄어든다.

여기서 은 보통 처럼 “몇 번 반으로 나눌 수 있는가”라는 뜻으로 생각해도 충분하다.

Big-O와 점화식으로 보는 차이

표기는 정확한 실행 시간을 말하는 것이 아니라, 입력 크기가 커질 때 지배적으로 커지는 항을 나타내는 표기다. 예를 들어

이 커질수록 항이 가장 중요하므로 이라고 쓴다.

DFT를 직접 계산하면 출력 개 있고, 각 출력마다 입력 개 모두 더한다.

반면 FFT는 길이 문제를 길이 문제 두 개로 나누고, 마지막에 개 정도의 값을 합친다. 이를 점화식으로 쓰면

재귀 단계별로 보면 더 분명하다. 번째 단계에는 작은 문제가 개 있고, 각각의 크기는 이다. 그 단계에서 합치는 총량은 대략

으로 일정하다.

한 단계의 총 합치는 비용은 , 단계 수는 이므로 전체는

이 된다. 여기서 중요한 것은 “반으로 나누기”만으로 충분하지 않다는 점이다. 작은 문제의 결과를 다시 쓸 수 있는 단위원근의 대칭이 있어야 이 점화식이 성립한다.

직관 비유

토너먼트 대진표를 생각하자. 16명이 참가하면 한 라운드가 끝날 때마다 남은 사람 수가 16명, 8명, 4명, 2명, 1명으로 반씩 줄어든다. 우승자가 나오기까지 필요한 라운드 수가 이고, 로그는 이렇게 “몇 번 반으로 줄이면 하나가 되는가”를 세는 말이다.

예제

이면 반으로 나누는 과정은 다음과 같다.

따라서

그냥 DFT 계산량과 FFT식 계산량을 거칠게 비교하면

이고,

이다. 실제 알고리즘에는 숨은 상수가 있지만, 입력이 커질수록 차이가 빠르게 벌어진다.

손풀이 체크

  1. 를 계속 반으로 나누면 몇 번 만에 이 될까?

    답 보기

    55

  2. 은?

    답 보기

    33

  3. 일 때 은 각각 얼마일까?

    답 보기

    64642424

다음으로 이어지는 생각

FFT가 빠른 이유는 단순히 반으로 나누기 때문만은 아니다. 단위원 위 회전값들이 서로 짝을 이루는 성질도 함께 사용한다.

이번 장에서 기억할 3문장

  1. OO 표기는 입력이 커질 때 계산량이 어떤 항에 지배되는지 나타낸다.
  2. log2N\log_2 NNN을 1이 될 때까지 몇 번 반으로 나눌 수 있는지를 센다.
  3. FFT의 속도 이득은 단계마다 전체 O(N)O(N) 일을 하고 그런 단계가 logN\log N개인 구조에서 나온다.

C++ Practice

C++로 확인하기

O(N2)O(N^2)O(NlogN)O(N\log N)의 증가 속도 비교하기

#include <cmath>
#include <iomanip>
#include <iostream>

using namespace std;

int main() {
    double N = 1024;
    double slow = N * N;
    double fast = N * log2(N);

    cout << fixed << setprecision(0);
    cout << "N^2 = " << slow << "\n";
    cout << "N log2 N = " << fast << "\n";
    cout << "ratio = " << slow / fast << "\n";
}

연습: N=32,1024,1000000N=32,1024,1000000으로 바꾸고 차이가 얼마나 커지는지 보자.