← Back to Blog

[Algorithm] Optimality Analysis

computer science > algorithm

2026-07-045 min read

#computer-science #algorithm

참고: GeeksforGeeks - Analysis of Algorithms | Big-O Analysis

수학 배경

i=1ni=n(n+1)2i=1ni2=2n3+3n2+n6n33i=1niknk+1k+1i=0k2i=2k+11i=1ki2i=(k1)2k+1+2\begin{align} &\sum^n_{i=1} i = \frac{n(n+1)}{2}\\ &\sum^n_{i=1} i^2 = \frac{2n^3 + 3n^2 + n}{6} \approx \frac{n^3}{3}\\ &\sum^n_{i=1} i^k \approx \frac{n^{k+1}}{k+1}\\ &\sum^k_{i=0} 2^i = 2^{k+1} - 1\\ &\sum^k_{i=1} i 2^i = (k-1)2^{k+1} + 2 \end{align}

학습할 내용

함수 증가율 분류

complexity image

이미지 출처: GeeksforGeeks

비교하는 방법

보통 최악의 경우를 위해 계산하기 때문에 무한대 상황을 가정한다.

limnf(n)g(n)\lim_{n\rightarrow\infty}\frac{f(n)}{g(n)}

결과값이 무한대로 갈 경우,
fω(g)f\in\omega(g) 또는 최소한 fΩ(g)f\in\Omega(g) 이다.

complexity image2

이미지 출처: GeeksforGeeks

O(g), Θ(g), ΩO(g),\ \Theta(g),\ \Omega 의 성질

fO(g)gΩ(f)f \in O(g) \leftrightarrow g \in \Omega(f) 이면
O(f+g)=O(max(f,g))O(f + g) = O(\max(f, g))Ω(f+g)=Ω(max(f,g))\Omega(f + g) = \Omega(\max(f, g)) 를 만족한다.

알고리즘과 문제 분석

조건1: Correctness가 증명되어야 한다

조건2: Amount of Work Done

Worst-Case Complexity

Average Complexity

예시

int seqSearch(int E[], int n, int K) {
    int ans = -1; // Assume failure

    for (int index = 0; index < n; index++) {
        if (K == E[index]) {
            ans = index; // Success
            break; // Done
        }
    }

    return ans;
}

Worst Case

Average Behavior Analysis

A(n)=Pr(succ)Asucc(n)+Pr(fail)Afail(n)A(n) = Pr(\text{succ})A_{\text{succ}}(n) + Pr(\text{fail})A_{\text{fail}}(n)

가정

성공하는 경우에는 KK 가 배열의 어느 위치에든 같은 확률로 존재한다고 가정한다. IiI_iKKii 번째 위치에서 발견되는 입력을 의미한다.

성공 시 평균 비교 횟수는 다음과 같다.

Asucc(n)=i=1nPr(Iisucc)t(Ii)=i=1n1ni=1ni=1ni=n+12\begin{align} A_{\text{succ}}(n) &= \sum_{i=1}^{n} Pr(I_i \mid \text{succ})t(I_i) \\ &= \sum_{i=1}^{n} \frac{1}{n}i \\ &= \frac{1}{n}\sum_{i=1}^{n}i \\ &= \frac{n+1}{2} \end{align}

실패하는 경우에는 배열 전체를 끝까지 확인해야 한다.

Afail(n)=nA_{\text{fail}}(n) = n

따라서 전체 평균 수행시간은 다음과 같다.

A(n)=pAsucc(n)+(1p)Afail(n)=pn+12+(1p)n\begin{align} A(n) &= pA_{\text{succ}}(n) + (1-p)A_{\text{fail}}(n) \\ &= p\frac{n+1}{2} + (1-p)n \end{align}

만약 성공 확률과 실패 확률이 각각 12\frac{1}{2} 이라면 다음과 같이 계산된다.

A(n)=12n+12+12n=3n+14\begin{align} A(n) &= \frac{1}{2}\frac{n+1}{2} + \frac{1}{2}n \\ &= \frac{3n+1}{4} \end{align}

결국 순차 탐색의 평균 수행시간도 nn 에 비례한다.

A(n)Θ(n)A(n) \in \Theta(n)

Best, Worst, Average Case 비교

알고리즘을 분석할 때는 같은 알고리즘이라도 어떤 입력을 받는지에 따라 수행시간이 달라진다. 따라서 보통 다음 세 가지 관점으로 나누어 생각한다.

분석 기준의미Sequential Search 예시
Best Case가장 빨리 끝나는 입력첫 번째 원소가 KK 인 경우
Worst Case가장 늦게 끝나는 입력KK 가 없거나 마지막에 있는 경우
Average Case입력 분포를 고려한 평균KK 의 위치와 실패 확률을 고려한 경우

Best Case는 알고리즘의 가장 좋은 상황을 보여주지만, 일반적인 성능을 대표하기 어렵다. Worst Case는 어떤 입력이 들어와도 보장할 수 있는 상한을 제공하기 때문에 알고리즘 분석에서 자주 사용된다. Average Case는 실제 사용 환경을 더 잘 반영할 수 있지만, 입력이 어떤 확률 분포를 따르는지 가정해야 한다.

예제: Nested Loop

다음 코드를 분석해보자.

for (int i = 1; i <= n; ++i) {
    for (int j = 1; j <= i; ++j) {
        count++;
    }
}

내부 반복문은 ii 값에 따라 실행 횟수가 달라진다. 전체 실행 횟수는 다음과 같다.

i=1ni=n(n+1)2\sum_{i=1}^{n} i = \frac{n(n+1)}{2}

상수와 낮은 차수 항은 nn 이 커질수록 영향이 작아진다.

n(n+1)2=n2+n2Θ(n2)\frac{n(n+1)}{2} = \frac{n^2+n}{2} \in \Theta(n^2)

따라서 이 코드는 O(n2)O(n^2) 이면서 동시에 Ω(n2)\Omega(n^2) 이므로 Θ(n2)\Theta(n^2) 이다.

예제: Binary Search

Binary Search는 정렬된 배열에서 중간 값을 기준으로 탐색 범위를 절반씩 줄인다.

int binarySearch(vector<int>& arr, int target) {
    int left = 0;
    int right = arr.size() - 1;

    while (left <= right) {
        int mid = (left + right) / 2;

        if (arr[mid] == target) {
            return mid;
        }

        if (arr[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }

    return -1;
}

입력 크기가 nn 일 때, 반복할 때마다 탐색 범위는 아래처럼 줄어든다.

n, n2, n4, n8, n,\ \frac{n}{2},\ \frac{n}{4},\ \frac{n}{8},\ \cdots

kk 번 반복한 뒤 탐색 범위가 1 이하가 된다고 하면 다음을 만족한다.

n2k1\frac{n}{2^k} \leq 1

따라서

n2kn \leq 2^k

양변에 로그를 취하면

klog2nk \geq \log_2 n

즉 Binary Search의 worst-case complexity는 다음과 같다.

W(n)Θ(logn)W(n) \in \Theta(\log n)

증가율 비교

알고리즘의 정확한 실행 시간은 하드웨어, 컴파일러, 구현 방식에 따라 달라질 수 있다. 하지만 입력 크기가 충분히 커지면 증가율이 더 중요한 기준이 된다.

대표적인 증가율은 다음 순서로 커진다.

O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(2n)<O(n!)O(1) < O(\log n) < O(n) < O(n\log n) < O(n^2) < O(2^n) < O(n!)
Complexity대표 예시
O(1)O(1)배열 index 접근
O(logn)O(\log n)Binary Search
O(n)O(n)Sequential Search
O(nlogn)O(n\log n)Merge Sort, Heap Sort
O(n2)O(n^2)단순 이중 반복문, Bubble Sort
O(2n)O(2^n)부분집합 전체 탐색
O(n!)O(n!)순열 전체 탐색

중요한 것은 작은 입력에서는 O(n2)O(n^2) 알고리즘이 더 빠를 수도 있다는 점이다. Big-O는 실제 시간을 직접 말하는 것이 아니라, 입력 크기가 커질 때의 증가 경향을 설명한다.

알고리즘 분석 절차

알고리즘을 분석할 때는 아래 순서로 접근하면 된다.

  1. 입력 크기 nn 을 정의한다.
  2. Basic Operation을 정한다.
  3. Basic Operation이 몇 번 실행되는지 계산한다.
  4. Best, Worst, Average 중 어떤 기준으로 분석하는지 정한다.
  5. 가장 지배적인 항만 남겨 asymptotic notation으로 표현한다.

예를 들어 이중 반복문이라도 항상 O(n2)O(n^2) 인 것은 아니다. 반복문의 범위가 어떻게 변하는지, 중간에 종료되는 조건이 있는지, 입력의 구조가 어떤지까지 함께 봐야 한다.

정리

Optimality Analysis는 단순히 코드를 보고 반복문 개수를 세는 것이 아니다. 알고리즘이 올바르게 동작한다는 correctness를 확인한 뒤, 입력 크기에 따라 필요한 work가 어떻게 증가하는지 분석하는 과정이다.

알고리즘 문제를 풀 때도 이 분석이 중요하다. 제한 조건에서 nn 의 크기를 먼저 보고, 허용 가능한 시간복잡도를 예상한 뒤 풀이 방법을 선택해야 한다.