SlideShare a Scribd company logo
1 of 14
2011/01/18 - SKKU NPC Seminar




        Computational Complexity

 ●
     Why is it important?
 ●
     What is efficiency?
 ●
     Formal definitions
 ●
     A note on algorithm analysis
 ●
     Time complexity for recursive programs   1
Why is it important?
 ●
     Example 1.




어떤 회사의 자료를 처리한 결과가 위와 같은 경우,
표에 나타난 결과를 참고하여 두 방법 중 뛰어난 알고리즘이 무엇인지 구별할 수 있다.
하지만 알고리즘 구현에 앞서 결과를 예측할 수 있다면 보다 좋은 알고리즘만 구현하면
된다. 그리고 이와 같은 상황은 프로그래밍 대회에서도 일어난다.

입력자료의 크기가 주어졌고, 알고리즘을 생각해낸 경우 다음 물음을 떠올려봐야 한다.
“나의 알고리즘이 구현할 가치가 있는가” “가장 큰 입력자료의 경우, 제시간에 풀어낼 수
있는가” “여러 알고리즘이 존재할 때, 어떤 알고리즘을 구현해야 하는가”

이런 상황은 다음 물음을 갖게 만든다; “알고리즘들을 어떻게 비교해야 하는가?”      2
What is efficiency?
 ●
     Example 2.         for (int i=0; i<N; i++)
                          for (int j=i+1; j<N; j++)
                            if (A[i] > A[j])
                              swap( A[i], A[j] );


위 정렬 알고리즘에 대한 입력자료가 주어진 경우, (배열 A와 크기 N)
각 자료에 대한 연산 횟수, 심지어 프로세서의 명령 수마저 정확히 셈할 수 있다.
하지만 가능한 입력의 경우가 너무 많기 때문에 이런 접근은 실용적이지 못하다.

우리가 관심을 갖고 있는 건, 가능한 최악의 경우이기 때문에 이 경우의 입력자료를 확
인하고 최악의 경우 얼마나 오랜 시간이 걸릴지 그 상한 upper bound 을 구해야 한다.

이때 무엇이 최악의 경우인가, 단순히 많은 수의 입력자료를 사용함으로써 프로그램의
수행시간을 길게 만들 수 있다. 따라서 보다 중요한 질문들은 다음과 같은 것이다.

“700개의 자료를 처리하는 경우, 어떤 구성의 입력자료가 최악의 입력인가?”
“입력자료의 수를 늘리는 경우, 최대 수행시간이 얼마나 급증하는가?”                 3
How to measure efficiency?
 ●
     Example 2.           for (int i=0; i<N; i++)
                            for (int j=i+1; j<N; j++)
                              if (A[i] > A[j])
                                swap( A[i], A[j] );


“f(N) = 수행횟수” 라 정의하고, 최악의 경우를 생각해보자.
- 첫번째 줄은 N번 수행된다.
- 두번째 줄은 N(N-1)/2 번 수행된다. - 세번째 줄은 최대 N(N-1)/2 번 수행된다.

주어진 배열 A가 내림차순으로 정렬되어 있는 경우, 네번째 줄이 매번 실행되고 이것이
최악의 경우라 할 수 있다. 이 경우 위 알고리즘은 3N(N-1)/2+N = 1.5N²–0.5N 단계를
수행한다. 따라서 알고리즘은 f(N) = 1.5N²–0.5N 을 가진다고 할 수 있다.

위 과정에서 알 수 있듯이 f(N)을 정확히 구하는 것은 복잡하다. 그리고 이렇게 정확히
구하는 게 반드시 필요한 것도 아니다. 예를 들어 N²과 0.0001N³ 의 두 알고리즘을 보면
1000 이상의 입력에서는 N²이 더 빠르다. 상수에 관계없이 대부분의 입력에서, N² 차수
를 가진 알고리즘이 N³ 차수를 가진 알고리즘보다 빠르다.                      4
Formal definitions
 Let f, g be positive non-decreasing functions defined on positive integers.
 We say that f (N) is O(g(N)) if for some c and N0 the following condition holds:
                                  ∀N > N0; f (N) < c.g(N)

시각적으로 생각해보면 어떤 c에 대해 f의 그래프 전체가 c.g의 그래프 아래에 위치한다.
곧, f의 증가속도가 c.g의 증가속도를 능가하지 못한다.

f(N) = O(g(n)) 이라고 표기하는데, 이런 표현이 익숙치 않다면 O(g(n))을 함수들의 집합
이라고 상상해보라. 그리고 f(N)이 그 집합에 속한다고 생각하라. (곧, f(N) ∈ O(g(n)) )

이렇게 정의한 것을 Big-O 표기법이라 부르고, 함수 증가차수의 상한을 표시하는데
사용된다. 앞의 예를 생각하면, f(N) = 1.5N²–0.5N = O(N²) 이라 표시할 수 있다.
(c = 2, N0 = 0 으로 두는 경우 가능하다. f(N)은 N²보다 (점근적으로) 빠르게 증가하지
않는다고 볼 수 있다. ) Big-O 표기법과 비슷한 표기법으로 다음과 같은 것들이 있다.

f(N) = Ω(g(N)) if g(N) = O(f (N)), in other words if f grows at least as fast as g.
f(N) = Θ(g(N)) if f (N) = O(g(N)) and g(N) = O(f (N)),                                5
  in other words if both functions have approximately the same rate of growth.
Some examples of using the notation
정확한 f(N)을 알고 있더라도, “문제를 푸는데 얼만큼의 시간이 걸리는가?” 라는 물음에
답할 수는 없다. 하지만 f(N) = O(N²) 이라는 사실에서 f(N)이 이차의 차수를 가진다는
걸 알 수 있다. 입력값을 두배로 할 경우, 실행시간이 현재 실행시간의 대략 네배로 늘어
난다고 예측할 수 있다. 그리고 이런 비율은 컴퓨터의 속도와 무관하다.

다음은 표기법을 사용해 표시한 예들이다.

• 47N log N = O(N²)                       • N log N + 1000047N = Θ(N log N)
• All polynomials of order k are O(Nk).   • If an algorithm is O(N²), it is also O(N⁴).
• Each comparision-based sorting algorithm is Ω(N log N).

f   (N)   =   Θ(N): linear
f   (N)   =   Θ(log N): logarithmic
f   (N)   =   Θ(N2): quadratic
f   (N)   =   Θ(N3): cubic
f   (N)   =   O(Nk) for some k: polynomial
f   (N)   =   Ω(2ⁿ): exponential
                                                                                          6
A note on algorithm analysis
 ●
     Example 3.       int j=0;
                      for (int i=0; i<N; i++) {
                        while ( (j<N-1) && (A[i]-A[j] > D) )
                          j++;
                        if (A[i]-A[j] == D) return 1;
                      }


D만큼 차이가 나는 두 원소가 있는지 확인하는 알고리즘을 생각해보자.

여기서 수행횟수의 상한을 쉽게 O(N²) 이라 말할 수 있다.
내부 while 문은 N번 실행되고 내부의 j는 최대 N번까지 실행될 수 있다.

하지만 조금 더 유심히 살펴보면, O(N) 인 것을 알 수 있다.
j++이 실행되는 횟수를 생각해보면 N번 이상 실행되지 않는다.

우리가 “이 알고리즘은 O(N²)이다” 라고 말해도 틀린 것은 아니지만,
“이 알고리즘은 O(N)이다” 라고 말하는 것이, 보다 정확한 정보를 알려준다.

                                                               7
How to estimate the time complexity of a given algorithm:

                        Nested Loops
1. estimate the maximum number of times each loop can be executed,
2. add these bounds for cycles following each other.
3. multiply these bounds for nested cycles/parts of code,
                 int result=0;                           // 1
                 for (int i=0; i<N; i++)                 // 2
                   for (int j=i; j<N; j++) {             // 3
                     for (int k=0; k<M; k++) {           // 4
                       int x=0;                          // 5
                       while (x<N) { result++; x+=3; }   // 6
                     }                                   // 7
                     for (int k=0; k<2*M; k++)           // 8
                       if (k%7 == 4) result++;           // 9
                   }                                     // 10

6번 줄:       While문의 상한은 O(N), (N/3+1 보다 많이 실행되지 않는다)
4-7번 줄:     k 변수에 따라 O(M), 그리고 각 반복에서 while문 실행하므로 O(MN)
8-9번 줄:     2M번 보다 많이 실행되지 않으므로 O(M),
            4-9번 줄의 수행횟수 상한은 O(MN + M) = O(MN)
2-3번 줄:     중첩 for 루프들은 N(N+1)/2번 수행하므로 O(N²)

2-3번 루프의 내부 수행횟수를 고려하여 전체 상한을 구하면, O(N² * MN) = O(MN3)               8
How to estimate the time complexity of a given algorithm:
   Using recursion to generate combinatorial objects

0부터 N-1까지의 모든 순열을 구하는            vector<int> permutation(N);
알고리즘.                            vector<int> used(N,0);

                                 void try(int which, int what) {
재귀의 가장 빈번한 이용은,                    permutation[which] = what;
                                   used[what] = 1;
가능한 모든 경우를 시도해 보는 백트래
킹 구현이다.                              if (which == N-1)
                                       outputPermutation();
                                     else
이 때, 하한은 가능한 경우의 갯수이다.                 for (int next=0; next<N; next++)
                                          if (!used[next])
보통 백트래킹 알고리즘은 보다 효율적                        try(which+1, next);
인 알고리즘을 알 수 없을 때 사용한다.
                                     used[what] = 0;
이 때 탐색해야 할 해공간이 무척 크다.           }

                                 int main() {
알고리즘의 하한은 이론적인 하한에 가               for (int first=0; first<N; first++)
깝고, 상한을 구하기 위해선 추가적인 수               try(0,first);
                                 }
행과정을 따져보아야 한다.

이런 알고리즘의 복잡도는 대개 지수적
                                                                          9
이거나, 그보다 성능이 나쁘다.
How to estimate the time complexity of a given algorithm:
       Divide&conquer using recursion
        MergeSort(sequence S) {
          if (size of S <= 1) return S;
          split S into S_1 and S_2 of roughly the same size;
          MergeSort(S_1);
          MergeSort(S_2);
          combine sorted S_1 and sorted S_2 to obtain sorted S;
          return sorted S;
        }

앞서 살펴본 재귀적인 알고리즘은 느렸지만, 또한 재귀는 효율적인 알고리즘을 설계
하는데 사용되기도 한다. 효율적인 재귀 알고리즘을 구현하는데 사용되는 개념으로
분할-정복 Divide&Conquer 이 있다. 문제를 보다 작은 소문제들로 나누어 해결한
다음, 부분해를 종합하여 전체 해를 구하는 방법이다.

위의 예는 합병정렬로, 알고리즘의 수행횟수를 생각해보면 다음과 같다.
배열을 분할하는 연산의 경우 O(N), 그리고 정렬된 두 배열을 합하는 연산 Θ(N)

함수 호출과정을 포함하여 전체 연산횟수를 나타내 보면,

                                     , 이 식은 앞서 살펴본 식들과는 다르다!      10
How to estimate the time complexity of a given algorithm:

         The substitution method
Substitution Method를 요약하자면,
함수 f의 상한을 추측한 다음, 귀납법을 이용하여 증명하는 방법.

합병정렬을 예로 들어, f (N) = O(N log N).임을 증명해본다.
앞에서 구한 식을 다음과 같이 표현할 수 있다.




f의 상한을 O(NlogN)이라 가정하면, f (N/2) ≤ d (N/2)lg(N/2) 이라 표현할 수 있고,




d > c 인 경우 성립하고, 이런 d를 선택할 수 있다.
                                                                 11
How to estimate the time complexity of a given algorithm:

                The recursion tree
Substitution Method를 사용하기 위해선 O(f(n))을 추측해야 한다. 상한을 추측하기
위해선 경험 혹은 직관이 필요한데, 이때 이용할 수 있는 것이 Recursion Tree 이다.

재귀함수의 입력에 대한 결과를 가시화시켜 본다.

오른쪽 그림은 5개 원소에 대한
합병정렬의 수행 과정을 나타낸다.
그리고 아래의 그림은 Worst Case 경우
합병정렬의 수행 과정을 표현한 것이다.


                                   재귀 과정을 표현한 트리를 통해,
                                   해당 알고리즘의 수행과정을 이해할 수 있
                                   다. 합병정렬의 경우, 말단 노드는 N개 존
                                   재할 것이며, 각 단계에서 배열의 크기가
                                   반으로 줄어드므로 트리의 높이는 lg N 일
                                   것이다. 이로써 알고리즘의 비용이,
                                                             12
                                   CN * lg N 이라고 추측해 볼 수 있다.
How to estimate the time complexity of a given algorithm:

                            Master Theorem
Let a ≥ 1 and b > 1 be integer constants. Let p be a non-negative non-decreasing function.
Let f be any solution of the recurrence equation


Then,




위 정리에 나타난 모양의 재귀 점화식이 존재하고 조건에 부합한다면,
정리에 따라 손쉽게 알고리즘의 비용을 구할 수 있다.

한가지 예를 살펴보면,

                            → p(N)=O(N(lg7)-ε), 1번에 해당하므로, f(N) = Θ(Nlg7) ≒ Θ(N2.807)

                                                                                             13
Conclusion / QnA
●
    시간/공간 복잡도를 통해 알고리즘 사이 성능 비교를 할 수 있다.
●
    알고리즘의 비용을 계산할 수 있어야 불필요한 시도를 줄일 수 있다.

    특히 빠른 시간내에 정확하게 문제를 해결해야하는
    Programming Contest 에서는 문제에 제시된 입력 크기와 알고리즘을 함
    께 고려할 수 있어야 한다. 시간/메모리 제한이 있으므로 시간 내에 결과
    를 산출하지 못하는 알고리즘이라면 소용이 없다.
●
    참고자료:
    http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=complexity1
    http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=complexity2




                                                                           14

More Related Content

What's hot

이산치3보고서
이산치3보고서이산치3보고서
이산치3보고서
KimChangHoen
 
이산치4보고서
이산치4보고서이산치4보고서
이산치4보고서
KimChangHoen
 

What's hot (20)

강의자료 2
강의자료 2강의자료 2
강의자료 2
 
2012 Dm 07
2012 Dm 072012 Dm 07
2012 Dm 07
 
HI-ARC PS 102 Brute Force
HI-ARC PS 102 Brute ForceHI-ARC PS 102 Brute Force
HI-ARC PS 102 Brute Force
 
딥러닝기본-신경망기초
딥러닝기본-신경망기초딥러닝기본-신경망기초
딥러닝기본-신경망기초
 
Backtracking [ICPC Sinchon]
Backtracking [ICPC Sinchon]Backtracking [ICPC Sinchon]
Backtracking [ICPC Sinchon]
 
알고리즘 스터디(정렬) Seungdols
알고리즘 스터디(정렬) Seungdols알고리즘 스터디(정렬) Seungdols
알고리즘 스터디(정렬) Seungdols
 
Hanoi2
Hanoi2Hanoi2
Hanoi2
 
파이썬 Numpy 선형대수 이해하기
파이썬 Numpy 선형대수 이해하기파이썬 Numpy 선형대수 이해하기
파이썬 Numpy 선형대수 이해하기
 
[신경망기초] 선형회귀분석
[신경망기초] 선형회귀분석[신경망기초] 선형회귀분석
[신경망기초] 선형회귀분석
 
이산치3보고서
이산치3보고서이산치3보고서
이산치3보고서
 
이산치4보고서
이산치4보고서이산치4보고서
이산치4보고서
 
[D2 CAMPUS] 부산대 Alcall 프로그래밍 경시대회 문제 풀이
[D2 CAMPUS] 부산대 Alcall 프로그래밍 경시대회 문제 풀이[D2 CAMPUS] 부산대 Alcall 프로그래밍 경시대회 문제 풀이
[D2 CAMPUS] 부산대 Alcall 프로그래밍 경시대회 문제 풀이
 
확통 회귀분석
확통 회귀분석확통 회귀분석
확통 회귀분석
 
HI-ARC Number Theory
HI-ARC Number TheoryHI-ARC Number Theory
HI-ARC Number Theory
 
NDC11_슈퍼클래스
NDC11_슈퍼클래스NDC11_슈퍼클래스
NDC11_슈퍼클래스
 
알고리즘과 자료구조
알고리즘과 자료구조알고리즘과 자료구조
알고리즘과 자료구조
 
2021 2학기 정기 세미나 5주차
2021 2학기 정기 세미나 5주차2021 2학기 정기 세미나 5주차
2021 2학기 정기 세미나 5주차
 
2021 2학기 정기 세미나 4주차
2021 2학기 정기 세미나 4주차2021 2학기 정기 세미나 4주차
2021 2학기 정기 세미나 4주차
 
[D2 CAMPUS] 2016 한양대학교 프로그래밍 경시대회 문제풀이
[D2 CAMPUS] 2016 한양대학교 프로그래밍 경시대회 문제풀이[D2 CAMPUS] 2016 한양대학교 프로그래밍 경시대회 문제풀이
[D2 CAMPUS] 2016 한양대학교 프로그래밍 경시대회 문제풀이
 
[신경망기초] 신경망의시작-퍼셉트론
[신경망기초] 신경망의시작-퍼셉트론[신경망기초] 신경망의시작-퍼셉트론
[신경망기초] 신경망의시작-퍼셉트론
 

Similar to Computational Complexity

1.자료구조와 알고리즘(강의자료)
1.자료구조와 알고리즘(강의자료)1.자료구조와 알고리즘(강의자료)
1.자료구조와 알고리즘(강의자료)
fmbvbfhs
 
2012 Dm A0 02 Pdf
2012 Dm A0 02 Pdf2012 Dm A0 02 Pdf
2012 Dm A0 02 Pdf
jinwookhong
 
2012 Dm A0 02 Pdf
2012 Dm A0 02 Pdf2012 Dm A0 02 Pdf
2012 Dm A0 02 Pdf
kd19h
 
자료구조 Project2
자료구조 Project2자료구조 Project2
자료구조 Project2
KoChungWook
 
2012 Ds B2 02 Pdf
2012 Ds B2 02 Pdf2012 Ds B2 02 Pdf
2012 Ds B2 02 Pdf
kd19h
 
2012 Ds B2 02
2012 Ds B2 022012 Ds B2 02
2012 Ds B2 02
chl132435
 

Similar to Computational Complexity (20)

자료구조01
자료구조01자료구조01
자료구조01
 
1.자료구조와 알고리즘(강의자료)
1.자료구조와 알고리즘(강의자료)1.자료구조와 알고리즘(강의자료)
1.자료구조와 알고리즘(강의자료)
 
2012 Ds 01
2012 Ds 012012 Ds 01
2012 Ds 01
 
Coursera Machine Learning (by Andrew Ng)_강의정리
Coursera Machine Learning (by Andrew Ng)_강의정리Coursera Machine Learning (by Andrew Ng)_강의정리
Coursera Machine Learning (by Andrew Ng)_강의정리
 
2012 Dm A0 02 Pdf
2012 Dm A0 02 Pdf2012 Dm A0 02 Pdf
2012 Dm A0 02 Pdf
 
이산치2번
이산치2번이산치2번
이산치2번
 
2012 Dm A0 02 Pdf
2012 Dm A0 02 Pdf2012 Dm A0 02 Pdf
2012 Dm A0 02 Pdf
 
Neural network (perceptron)
Neural network (perceptron)Neural network (perceptron)
Neural network (perceptron)
 
Mlp logical input pattern classfication report doc
Mlp logical input pattern classfication report docMlp logical input pattern classfication report doc
Mlp logical input pattern classfication report doc
 
정수론적 알고리즘 - Sogang ICPC Team, 2020 Winter
정수론적 알고리즘 - Sogang ICPC Team, 2020 Winter정수론적 알고리즘 - Sogang ICPC Team, 2020 Winter
정수론적 알고리즘 - Sogang ICPC Team, 2020 Winter
 
Amugona study 1회 jjw
Amugona study 1회 jjwAmugona study 1회 jjw
Amugona study 1회 jjw
 
Amugona study 1회 jjw
Amugona study 1회 jjwAmugona study 1회 jjw
Amugona study 1회 jjw
 
자료구조 Project2
자료구조 Project2자료구조 Project2
자료구조 Project2
 
자료구조 프로젝트
자료구조 프로젝트자료구조 프로젝트
자료구조 프로젝트
 
자구2번
자구2번자구2번
자구2번
 
Number theory
Number theoryNumber theory
Number theory
 
Adversarial Attack in Neural Machine Translation
Adversarial Attack in Neural Machine TranslationAdversarial Attack in Neural Machine Translation
Adversarial Attack in Neural Machine Translation
 
분할정복
분할정복분할정복
분할정복
 
2012 Ds B2 02 Pdf
2012 Ds B2 02 Pdf2012 Ds B2 02 Pdf
2012 Ds B2 02 Pdf
 
2012 Ds B2 02
2012 Ds B2 022012 Ds B2 02
2012 Ds B2 02
 

More from skku_npc (13)

Maximum Flow
Maximum FlowMaximum Flow
Maximum Flow
 
String Searching Algorithms
String Searching AlgorithmsString Searching Algorithms
String Searching Algorithms
 
disjoint-set data structures
disjoint-set data structuresdisjoint-set data structures
disjoint-set data structures
 
Line sweep algorithms
Line sweep algorithmsLine sweep algorithms
Line sweep algorithms
 
Data Structures
Data StructuresData Structures
Data Structures
 
Prime numbers, factorization
Prime numbers, factorizationPrime numbers, factorization
Prime numbers, factorization
 
Greedy is Good
Greedy is GoodGreedy is Good
Greedy is Good
 
Binary Search
Binary SearchBinary Search
Binary Search
 
How to find a solution
How to find a solutionHow to find a solution
How to find a solution
 
Dynamic programming
Dynamic programmingDynamic programming
Dynamic programming
 
An introduction to recursion
An introduction to recursionAn introduction to recursion
An introduction to recursion
 
Algorithm Games
Algorithm GamesAlgorithm Games
Algorithm Games
 
Introduction to Graphs
Introduction to GraphsIntroduction to Graphs
Introduction to Graphs
 

Recently uploaded

Console API (Kitworks Team Study 백혜인 발표자료)
Console API (Kitworks Team Study 백혜인 발표자료)Console API (Kitworks Team Study 백혜인 발표자료)
Console API (Kitworks Team Study 백혜인 발표자료)
Wonjun Hwang
 
Merge (Kitworks Team Study 이성수 발표자료 240426)
Merge (Kitworks Team Study 이성수 발표자료 240426)Merge (Kitworks Team Study 이성수 발표자료 240426)
Merge (Kitworks Team Study 이성수 발표자료 240426)
Wonjun Hwang
 

Recently uploaded (6)

Console API (Kitworks Team Study 백혜인 발표자료)
Console API (Kitworks Team Study 백혜인 발표자료)Console API (Kitworks Team Study 백혜인 발표자료)
Console API (Kitworks Team Study 백혜인 발표자료)
 
Continual Active Learning for Efficient Adaptation of Machine LearningModels ...
Continual Active Learning for Efficient Adaptation of Machine LearningModels ...Continual Active Learning for Efficient Adaptation of Machine LearningModels ...
Continual Active Learning for Efficient Adaptation of Machine LearningModels ...
 
캐드앤그래픽스 2024년 5월호 목차
캐드앤그래픽스 2024년 5월호 목차캐드앤그래픽스 2024년 5월호 목차
캐드앤그래픽스 2024년 5월호 목차
 
A future that integrates LLMs and LAMs (Symposium)
A future that integrates LLMs and LAMs (Symposium)A future that integrates LLMs and LAMs (Symposium)
A future that integrates LLMs and LAMs (Symposium)
 
Merge (Kitworks Team Study 이성수 발표자료 240426)
Merge (Kitworks Team Study 이성수 발표자료 240426)Merge (Kitworks Team Study 이성수 발표자료 240426)
Merge (Kitworks Team Study 이성수 발표자료 240426)
 
MOODv2 : Masked Image Modeling for Out-of-Distribution Detection
MOODv2 : Masked Image Modeling for Out-of-Distribution DetectionMOODv2 : Masked Image Modeling for Out-of-Distribution Detection
MOODv2 : Masked Image Modeling for Out-of-Distribution Detection
 

Computational Complexity

  • 1. 2011/01/18 - SKKU NPC Seminar Computational Complexity ● Why is it important? ● What is efficiency? ● Formal definitions ● A note on algorithm analysis ● Time complexity for recursive programs 1
  • 2. Why is it important? ● Example 1. 어떤 회사의 자료를 처리한 결과가 위와 같은 경우, 표에 나타난 결과를 참고하여 두 방법 중 뛰어난 알고리즘이 무엇인지 구별할 수 있다. 하지만 알고리즘 구현에 앞서 결과를 예측할 수 있다면 보다 좋은 알고리즘만 구현하면 된다. 그리고 이와 같은 상황은 프로그래밍 대회에서도 일어난다. 입력자료의 크기가 주어졌고, 알고리즘을 생각해낸 경우 다음 물음을 떠올려봐야 한다. “나의 알고리즘이 구현할 가치가 있는가” “가장 큰 입력자료의 경우, 제시간에 풀어낼 수 있는가” “여러 알고리즘이 존재할 때, 어떤 알고리즘을 구현해야 하는가” 이런 상황은 다음 물음을 갖게 만든다; “알고리즘들을 어떻게 비교해야 하는가?” 2
  • 3. What is efficiency? ● Example 2. for (int i=0; i<N; i++) for (int j=i+1; j<N; j++) if (A[i] > A[j]) swap( A[i], A[j] ); 위 정렬 알고리즘에 대한 입력자료가 주어진 경우, (배열 A와 크기 N) 각 자료에 대한 연산 횟수, 심지어 프로세서의 명령 수마저 정확히 셈할 수 있다. 하지만 가능한 입력의 경우가 너무 많기 때문에 이런 접근은 실용적이지 못하다. 우리가 관심을 갖고 있는 건, 가능한 최악의 경우이기 때문에 이 경우의 입력자료를 확 인하고 최악의 경우 얼마나 오랜 시간이 걸릴지 그 상한 upper bound 을 구해야 한다. 이때 무엇이 최악의 경우인가, 단순히 많은 수의 입력자료를 사용함으로써 프로그램의 수행시간을 길게 만들 수 있다. 따라서 보다 중요한 질문들은 다음과 같은 것이다. “700개의 자료를 처리하는 경우, 어떤 구성의 입력자료가 최악의 입력인가?” “입력자료의 수를 늘리는 경우, 최대 수행시간이 얼마나 급증하는가?” 3
  • 4. How to measure efficiency? ● Example 2. for (int i=0; i<N; i++) for (int j=i+1; j<N; j++) if (A[i] > A[j]) swap( A[i], A[j] ); “f(N) = 수행횟수” 라 정의하고, 최악의 경우를 생각해보자. - 첫번째 줄은 N번 수행된다. - 두번째 줄은 N(N-1)/2 번 수행된다. - 세번째 줄은 최대 N(N-1)/2 번 수행된다. 주어진 배열 A가 내림차순으로 정렬되어 있는 경우, 네번째 줄이 매번 실행되고 이것이 최악의 경우라 할 수 있다. 이 경우 위 알고리즘은 3N(N-1)/2+N = 1.5N²–0.5N 단계를 수행한다. 따라서 알고리즘은 f(N) = 1.5N²–0.5N 을 가진다고 할 수 있다. 위 과정에서 알 수 있듯이 f(N)을 정확히 구하는 것은 복잡하다. 그리고 이렇게 정확히 구하는 게 반드시 필요한 것도 아니다. 예를 들어 N²과 0.0001N³ 의 두 알고리즘을 보면 1000 이상의 입력에서는 N²이 더 빠르다. 상수에 관계없이 대부분의 입력에서, N² 차수 를 가진 알고리즘이 N³ 차수를 가진 알고리즘보다 빠르다. 4
  • 5. Formal definitions Let f, g be positive non-decreasing functions defined on positive integers. We say that f (N) is O(g(N)) if for some c and N0 the following condition holds: ∀N > N0; f (N) < c.g(N) 시각적으로 생각해보면 어떤 c에 대해 f의 그래프 전체가 c.g의 그래프 아래에 위치한다. 곧, f의 증가속도가 c.g의 증가속도를 능가하지 못한다. f(N) = O(g(n)) 이라고 표기하는데, 이런 표현이 익숙치 않다면 O(g(n))을 함수들의 집합 이라고 상상해보라. 그리고 f(N)이 그 집합에 속한다고 생각하라. (곧, f(N) ∈ O(g(n)) ) 이렇게 정의한 것을 Big-O 표기법이라 부르고, 함수 증가차수의 상한을 표시하는데 사용된다. 앞의 예를 생각하면, f(N) = 1.5N²–0.5N = O(N²) 이라 표시할 수 있다. (c = 2, N0 = 0 으로 두는 경우 가능하다. f(N)은 N²보다 (점근적으로) 빠르게 증가하지 않는다고 볼 수 있다. ) Big-O 표기법과 비슷한 표기법으로 다음과 같은 것들이 있다. f(N) = Ω(g(N)) if g(N) = O(f (N)), in other words if f grows at least as fast as g. f(N) = Θ(g(N)) if f (N) = O(g(N)) and g(N) = O(f (N)), 5 in other words if both functions have approximately the same rate of growth.
  • 6. Some examples of using the notation 정확한 f(N)을 알고 있더라도, “문제를 푸는데 얼만큼의 시간이 걸리는가?” 라는 물음에 답할 수는 없다. 하지만 f(N) = O(N²) 이라는 사실에서 f(N)이 이차의 차수를 가진다는 걸 알 수 있다. 입력값을 두배로 할 경우, 실행시간이 현재 실행시간의 대략 네배로 늘어 난다고 예측할 수 있다. 그리고 이런 비율은 컴퓨터의 속도와 무관하다. 다음은 표기법을 사용해 표시한 예들이다. • 47N log N = O(N²) • N log N + 1000047N = Θ(N log N) • All polynomials of order k are O(Nk). • If an algorithm is O(N²), it is also O(N⁴). • Each comparision-based sorting algorithm is Ω(N log N). f (N) = Θ(N): linear f (N) = Θ(log N): logarithmic f (N) = Θ(N2): quadratic f (N) = Θ(N3): cubic f (N) = O(Nk) for some k: polynomial f (N) = Ω(2ⁿ): exponential 6
  • 7. A note on algorithm analysis ● Example 3. int j=0; for (int i=0; i<N; i++) { while ( (j<N-1) && (A[i]-A[j] > D) ) j++; if (A[i]-A[j] == D) return 1; } D만큼 차이가 나는 두 원소가 있는지 확인하는 알고리즘을 생각해보자. 여기서 수행횟수의 상한을 쉽게 O(N²) 이라 말할 수 있다. 내부 while 문은 N번 실행되고 내부의 j는 최대 N번까지 실행될 수 있다. 하지만 조금 더 유심히 살펴보면, O(N) 인 것을 알 수 있다. j++이 실행되는 횟수를 생각해보면 N번 이상 실행되지 않는다. 우리가 “이 알고리즘은 O(N²)이다” 라고 말해도 틀린 것은 아니지만, “이 알고리즘은 O(N)이다” 라고 말하는 것이, 보다 정확한 정보를 알려준다. 7
  • 8. How to estimate the time complexity of a given algorithm: Nested Loops 1. estimate the maximum number of times each loop can be executed, 2. add these bounds for cycles following each other. 3. multiply these bounds for nested cycles/parts of code, int result=0; // 1 for (int i=0; i<N; i++) // 2 for (int j=i; j<N; j++) { // 3 for (int k=0; k<M; k++) { // 4 int x=0; // 5 while (x<N) { result++; x+=3; } // 6 } // 7 for (int k=0; k<2*M; k++) // 8 if (k%7 == 4) result++; // 9 } // 10 6번 줄: While문의 상한은 O(N), (N/3+1 보다 많이 실행되지 않는다) 4-7번 줄: k 변수에 따라 O(M), 그리고 각 반복에서 while문 실행하므로 O(MN) 8-9번 줄: 2M번 보다 많이 실행되지 않으므로 O(M), 4-9번 줄의 수행횟수 상한은 O(MN + M) = O(MN) 2-3번 줄: 중첩 for 루프들은 N(N+1)/2번 수행하므로 O(N²) 2-3번 루프의 내부 수행횟수를 고려하여 전체 상한을 구하면, O(N² * MN) = O(MN3) 8
  • 9. How to estimate the time complexity of a given algorithm: Using recursion to generate combinatorial objects 0부터 N-1까지의 모든 순열을 구하는 vector<int> permutation(N); 알고리즘. vector<int> used(N,0); void try(int which, int what) { 재귀의 가장 빈번한 이용은, permutation[which] = what; used[what] = 1; 가능한 모든 경우를 시도해 보는 백트래 킹 구현이다. if (which == N-1) outputPermutation(); else 이 때, 하한은 가능한 경우의 갯수이다. for (int next=0; next<N; next++) if (!used[next]) 보통 백트래킹 알고리즘은 보다 효율적 try(which+1, next); 인 알고리즘을 알 수 없을 때 사용한다. used[what] = 0; 이 때 탐색해야 할 해공간이 무척 크다. } int main() { 알고리즘의 하한은 이론적인 하한에 가 for (int first=0; first<N; first++) 깝고, 상한을 구하기 위해선 추가적인 수 try(0,first); } 행과정을 따져보아야 한다. 이런 알고리즘의 복잡도는 대개 지수적 9 이거나, 그보다 성능이 나쁘다.
  • 10. How to estimate the time complexity of a given algorithm: Divide&conquer using recursion MergeSort(sequence S) { if (size of S <= 1) return S; split S into S_1 and S_2 of roughly the same size; MergeSort(S_1); MergeSort(S_2); combine sorted S_1 and sorted S_2 to obtain sorted S; return sorted S; } 앞서 살펴본 재귀적인 알고리즘은 느렸지만, 또한 재귀는 효율적인 알고리즘을 설계 하는데 사용되기도 한다. 효율적인 재귀 알고리즘을 구현하는데 사용되는 개념으로 분할-정복 Divide&Conquer 이 있다. 문제를 보다 작은 소문제들로 나누어 해결한 다음, 부분해를 종합하여 전체 해를 구하는 방법이다. 위의 예는 합병정렬로, 알고리즘의 수행횟수를 생각해보면 다음과 같다. 배열을 분할하는 연산의 경우 O(N), 그리고 정렬된 두 배열을 합하는 연산 Θ(N) 함수 호출과정을 포함하여 전체 연산횟수를 나타내 보면, , 이 식은 앞서 살펴본 식들과는 다르다! 10
  • 11. How to estimate the time complexity of a given algorithm: The substitution method Substitution Method를 요약하자면, 함수 f의 상한을 추측한 다음, 귀납법을 이용하여 증명하는 방법. 합병정렬을 예로 들어, f (N) = O(N log N).임을 증명해본다. 앞에서 구한 식을 다음과 같이 표현할 수 있다. f의 상한을 O(NlogN)이라 가정하면, f (N/2) ≤ d (N/2)lg(N/2) 이라 표현할 수 있고, d > c 인 경우 성립하고, 이런 d를 선택할 수 있다. 11
  • 12. How to estimate the time complexity of a given algorithm: The recursion tree Substitution Method를 사용하기 위해선 O(f(n))을 추측해야 한다. 상한을 추측하기 위해선 경험 혹은 직관이 필요한데, 이때 이용할 수 있는 것이 Recursion Tree 이다. 재귀함수의 입력에 대한 결과를 가시화시켜 본다. 오른쪽 그림은 5개 원소에 대한 합병정렬의 수행 과정을 나타낸다. 그리고 아래의 그림은 Worst Case 경우 합병정렬의 수행 과정을 표현한 것이다. 재귀 과정을 표현한 트리를 통해, 해당 알고리즘의 수행과정을 이해할 수 있 다. 합병정렬의 경우, 말단 노드는 N개 존 재할 것이며, 각 단계에서 배열의 크기가 반으로 줄어드므로 트리의 높이는 lg N 일 것이다. 이로써 알고리즘의 비용이, 12 CN * lg N 이라고 추측해 볼 수 있다.
  • 13. How to estimate the time complexity of a given algorithm: Master Theorem Let a ≥ 1 and b > 1 be integer constants. Let p be a non-negative non-decreasing function. Let f be any solution of the recurrence equation Then, 위 정리에 나타난 모양의 재귀 점화식이 존재하고 조건에 부합한다면, 정리에 따라 손쉽게 알고리즘의 비용을 구할 수 있다. 한가지 예를 살펴보면, → p(N)=O(N(lg7)-ε), 1번에 해당하므로, f(N) = Θ(Nlg7) ≒ Θ(N2.807) 13
  • 14. Conclusion / QnA ● 시간/공간 복잡도를 통해 알고리즘 사이 성능 비교를 할 수 있다. ● 알고리즘의 비용을 계산할 수 있어야 불필요한 시도를 줄일 수 있다. 특히 빠른 시간내에 정확하게 문제를 해결해야하는 Programming Contest 에서는 문제에 제시된 입력 크기와 알고리즘을 함 께 고려할 수 있어야 한다. 시간/메모리 제한이 있으므로 시간 내에 결과 를 산출하지 못하는 알고리즘이라면 소용이 없다. ● 참고자료: http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=complexity1 http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=complexity2 14