알고리즘 문제를 풀다보면 해답을 찾는 것도 중요하지만좀 더 효율적으로 문제를 해결하는 방법에 대해 고민하게 된다.
이럴 때 필요한 것이 시간 복잡도를 고려하는 것이다.
시간 복잡도를 고려한다는 것은 '입력값의 변화에 따라 연산을 실행할 때, 연산 횟수에 비해 시간이 얼마나 걸리는가?'라는 뜻이다.
그리고 이 시간 복잡도는 주로 빅-오 표기법을 사용해 나타낸다.
O(1)은 일정한 복잡도(constant complexity)라고 하며, 입력값이 증가하더라고 시간이 늘어나지 않는다.
O(1)의 시간 복잡도를 가진 알고리즘
public int O_1_algorithm(int[] arr, int index) {
return arr[index];
}
int[] arr = new int[]{1,2,3,4,5};
int index = 1;
int results = O_1_algorithm(arr, index);
System.out.println(results); // 2
O(n)은 선형 복잡도(linear complexity)라고 부르며, 입력값이 증가함에 따라 시간 또한 같은 비율로 증가하는 것을 의미한다.
O(n)의 시간 복잡도를 가진 알고리즘
public void O_n_algorithm(int n) {
for (int i = 0; i < n; i++) {
// do something for 1 second
}
}
public void another_O_n_algorithm(int n) {
for (int i = 0; i < n * 2; i++) {
// do something for 1 second
}
}
another_O_n_algorithm
에서 입력 값이 1 증가할 때마다 코드의 실행 시간은 2초씩 증가한다. 하지만 표기법은 O(2n)이 아닌 O(n)으로 동일하다. 그 이유는 입력 값이 커질 수록 계수의 의미가 점점 퇴색되기 때문에, 같은 비율로 증가하고 있다면 O(n)으로 표기한다.
O(log n)은 로그 복잡도(logarithmic complexity)라고 부르며, Big-O 표기법 중 O(1) 다음으로 빠른 시간 복잡도를 가진다.
자료구조의 BST(Binary Search Tree)가 탐색 하면 할수록 경우의 수가 계속 절반으로 줄어들기 때문에 O(log n)의 시간 복잡도를 가진 알고리즘이라고 할 수 있다.
O(n^2)은 2차 복잡도(quadratic complexity)라고 부르며, 입려값이 증가함에 따라 시간이 n의 제곱수의 비율로 증가하는 것을 의미한다.
O(n^2)의 시간 복잡도를 가진 알고리즘
public void O_quadratic_algorithm(int n) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
// do something for 1 second
}
}
}
public void another_O_quadratic_algorithm(int n) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++) {
// do something for 1 second
}
}
}
}
O(n)과 마찬가지로 O(n^2)도 모두 O(n^2)로 표기한다.
O(2^n)은 기하급수적 복잡도(exponential complexity)라고 부르며, Big-O 표기법 중 가장 느린 시간 복잡도를 가진다.
O(2^n)의 시간 복잡도를 가진 알고리즘
public int fibonacci(int n) {
if (n <= 1) {
return 1;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
재귀로 구현하는 피보나치 수열은 O(2^n)의 시간 복잡도를 가진 대표적인 알고리즘이다.