Time Complexity 시간복잡도
Time Complexity (시간 복잡도)
효율적인 알고리즘 구성을 위해 항상 신경써야하는 시간 복잡도.
그리고 시간 복잡도를 표기하는 방법인 Big-O(빅-오) 표기법에 대해 정리합니다.
시간 복잡도
- 위키백과에 따르면 시간복잡도는 계산복잡도 이론에서 문제를 해결하는데 걸리는 시간과 입력의 함수 관계를 가리킨다. 또
- 컴퓨터과학에서 알고리즘의 시간복잡도는 입력을 나타내는 문자열 길이의 함수로서 작동하는 알고리즘을 취해 시간을 정량화하는 것인데,
- 간단히 input 후 연산이 진행되고 output을 반환하는데 시간이 얼마나 걸리는가?를 계산한 값입니다.
- 조금 더 간단하게는 알고리즘의 수행시간입니다.
위에서 계산한 값이라고 적었는데 이 계산한 값의 표기법 중 하나가 Big-O(빅-오) 표기법입니다.
Big-O 표기법
시간복잡도 표기법에는 3가지 방법이 있습니다.
- Big-O(빅-오) ▶️ 알고리즘이 수행되는데 최악의 시간으로 결과값이 반환되는 경우 (상한 *점근)
- Big-Ω(빅-오메가) ▶️ 알고리즘이 수행되는데 최선의 시간으로 결과값이 반환되는 경우 (하한 점근)
- Big-θ(빅-세타) ▶️ 그 둘의 평균
위 세가지 경우 중 주로 Big-O(빅-오) 표기법으로 수행 시간을 고려하는 것이 좋습니다.
최악을 대비하는 것이 더 바람직하기 때문입니다.
점근: 점점 가까워 지는 모양 ? 점근 표기법: 어떤 함수의 증가 양상을 다른 함수와의 비교로 표현하는 수론과 해석학의 방법.
Big-O 표기법의 특징
-
상수항 무시
빅오 표기법은 데이터 입력값(n)이 충분히 크다고 가정하고 있고, 알고리즘의 효율성 또한 데이터 입력값(n)의 크기에 따라 영향 받기 때문에 상수항 같은 사소한 부분은 무시한다.
ex) O(2N) ▶ O(N)
-
영향력 없는 항 무시
빅오 표기법은 데이터 입력값(n)의 크기에 따라 영향을 받기 때문에 가장 영향력이 큰 항에 이외에 영향력이 없는 항들은 무시한다.
ex) O(N^2 + 2N + 1) ▶ O(N^2)
Big-O 표기법의 종류
-
O(1)
-
O(log n)
-
O(n)
-
O(n * log n)
-
O(n^2)
-
O(n^n)
그래프로 보면 성능의 비교가 가능한데
- FAST : O(1) — O(log n) — O(n) — O(n * log n) — O(n^2) — O(n^n) SLOW
- FAST : 상수함수 - 로그함수 - 선형함수 - 로그 함수와 선형 함수의 곱 - 다항함수 - 지수함수 : SLOW
- (오른쪽으로 갈 수록 속도가 느려진다.)
O(1)
O(1)는 일정한 복잡도(constant complexity)라고 하며, 입력값이 증가하더라도 시간이 늘어나지 않는다.
입력값의 크기와 관계없이, 즉시 출력값을 반환하는 알고리즘을 의미한다.
|
|
O(log n)
O(log n)은 로그 복잡도(logarithmic complexity)라고 부르며, Big-O표기법중 O(1) 다음으로 빠른 시간 복잡도를 가진다.
log n은 보통 log의 밑이 2인 경우를 상정합니다. == $\log_{2}{n}$
이는 *이진탐색의 로직과 거의 유사합니다. 예를 들어서 이야기 해보죠.
-
8개의 값이 주어졌을 때 이진 탐색으로 내가 원하는 값을 찾는 방법입니다.
-
8 > 4 > 2 > 1 총 3번의 탐색이 필요합니다. (운이 좋으면 1번에 찾을 수도 있지만, 최악의 상황을 가정)
-
$8 = 1 \times2^3$
-
-
만약 주어진 데이터가 N개고, X번의 탐색으로 통해 1이 되는 경우를 공식화하면
- $N = 1 \times2^X$
- 몇 번의 과정을 거쳐야 하는지 즉, 효율을 계산하려면 $X$가 필요하고 이를 구하기 위해 양변에 $\log_{2}$를 취하면
-
$X = \log_{2}{N}$
자연스럽게 log n의 식이 도출되고 이것이 O(log n) 복잡도입니다.
💡여기서 잠깐,
- 배열의 크기가 3일 때 이진 검색은 2단계
- 배열의 크기가 7일 때 이진 검색은 3단계
- 배열의 크기가 15일 때 이진 검색은 4단계
- 배열의 크기가 100일때 이진 검색은 7단계
- 배열의 크기가 10,000일때 이진 검색은 13단계
- 배열의 크기가 1,000,000일때 이진 검색은 20단계
👉 데이터가 커질수록 단계 수가 늘어나므로 이진 검색은 O(1)이라 표현할 수 없습니다.
👉 검색하고 있는 배열의 원소 수보다 단계 수가 훨씬 적으므로 O(N)이라 표현할 수도 없습니다.
O(log n)은 O(1)과 O(N) 사이 어디쯤엔가 있다.
저는 간단히 1/2 연산을 몇번 해야 되는지 알려주는 값이라고 외우고 있습니다.
또는 데이터가 두 배로 증가하면 한 단계씩 늘어나는 알고리즘
이진탐색: 배열의 중간에 있는 임의의 값을 선택하여 찾고자 하는 값 X와 비교한다. X가 중간 값보다 작으면 중간 값을 기준으로 좌측의 데이터들을 대상으로, X가 중간값보다 크면 배열의 우측을 대상으로 다시 탐색한다. 동일한 방법으로 다시 중간의 값을 임의로 선택하고 비교한다. 해당 값을 찾을 때까지 이 과정을 반복
O(n)
O(n)은 선형 복잡도(linear complexity)라고 부르며, 입력값이 증가함에 따라 시간 또한
같은 비율
로 증가하는 것을 의미한다.
데이터가 늘어날 때 정확히 그 데이터에 비례해 단계 수가 증가하는 알고리즘이다.
|
|
O(n * log n)
일반적으로 최적화된 알고리즘의 경우 해당 시간복잡도를 가진다.
linear한 단일 컬렉션에 logarithmic한 수행을 반영한 알고리즘이다.
|
|
O(n^2)
O(n^2)은 2차 복잡도(quadratic complexity)라고 부르며, 입력값이 증가함에 따라 시간이 n의 제곱수의 비율로 증가하는 것을 의미한다.
입력값이 1일 경우 1초가 걸리던 알고리즘에 5라는 값을 주었더니 25초가 걸리게 된다면, 이 알고리즘의 시간 복잡도는 O(n^2)라고 표현한다.
|
|
간단히 2중 중복문의 경우에 해당합니다.
3중, 10중 중복문의 경우에도 O(n^2)라고 표현합니다.
O(2^n)
O(2^n)은 기하급수적 복잡도(exponential complexity)라고 부르며, Big-O 표기법 중 가장 느린 시간 복잡도를 가진다.
대표적인 예가 피노나치의 수열인데,
피보나치 수는 첫째 및 둘째 항이 1이며 그 뒤의 모든 항은 바로 앞 두 항의 합인 수열입니다.
- [1, 1, 2, 3, 5, 8, ….]
|
|
많은 기술 블로그들이 이렇게 코드 짤꺼면 그냥 하지마라. 고 말하곤 합니다.ㅎ
복잡도 정량화 비교
Complexity | 1 | 10 | 100 |
---|---|---|---|
O(1) | 1 | 1 | 1 |
O(log n) | 0 | 2 | 5 |
O(n) | 1 | 10 | 100 |
O(n log n) | 0 | 20 | 461 |
O(n^2) | 1 | 100 | 10000 |
O(2^n) | 1 | 1024 | 1267650600228229401496703205376 |
시간 복잡도를 구하는 요령
- 하나의 루프를 사용하여 단일 *컬렉션을 반복하는 경우: O(n)
- 두 개의 다른 루프를 사용하여(중첩 X) 두 개의 다른 컬렉션을 반복하는 경우: O(n) + O(m) => O(n)
- 두 개의 중첩 루프를 사용하여 단일 컬렌션을 반복하는 경우: O(n^2)
- 두 개의 중첩 루프를 사용하여 두 개의 다른 컬렉션을 반복하는 경우: O(n) * O(m) => O(n^2)
컬렉션 자료형: 여러가지 요소를 하나로 묶어 사용하는 데이터 타입. python의 경우 시퀀스(리스트, 튜플, 딕셔너리, 레인지), 매핑(딕셔너리), 집합 등이 있다.
👀 마치면서..
사실 이론은 복잡하지 않습니다.
이 정리를 통해 어느 정도 이해할 수 있었다면, 실제 개발에서 효율을 고려하며 개발한다.를 적용할 수 있도록
코드마다 시간복잡도를 생각하면서 개발하는 노력이 필요할 것 같습니다.