✨ 알고리즘을 설계하고나면 알고리즘 성능을 분석할 필요가 있다. 이런 알고리즘 효율성을 판단하기 위해 공간 복잡도와 시간 복잡도의 두가지 방법을 이용한다.
시간복잡도
주어진 입력에 따라 알고리즘이 문제를 해결할 때 걸리는 시간. 가장 일반적으로 사용하는 알고리즘 성능 효율 분석 방법.
공간복잡도
알고리즘이 문제를 해결할 때 점유하는 메모리 공간을 뜻함. 자원이 제한된 시스템에서 동작하는 프로그램을 구현하는 것과 같이 특별한 경우에 사용하는 분석 방법.
알고리즘을 분석하거나 성능을 측정할 때는 시간 복잡도를 훨씬 더 자주 이용한다.
알고리즘의 효율성을 수학적으로 판단하는 방법은 점근적 분석이라고도 한다.
-> 이 방법의 본질은 수학적으로 알고리즘 성능의 한계를 증명하는 것이므로 실제적인 방법보다 많은 시간을 절약할 수 있기도 하다.
점근적 분석
입력 되는 데이터의 크기에 따라 수행 시간과 공간을 얼마나 차지하는지를 측정
빅 오메가, 리틀 오메가, 빅 오, 리틀 오, 세타 등 다양한 표기법이 존재
✨ 참고
Big-O(빅-오) ⇒ 상한 점근
Big-Ω(빅-오메가) ⇒ 하한 점근
Big-θ(빅-세타) ⇒ 그 둘의 평균
위 세 가지 표기법은 시간 복잡도를 각각 최악, 최선, 중간(평균)의 경우에 대하여 나타내는 방법이다.
점근적 표기법
- 어떤 함수의 증가 양상을 다른 함수와의 비교로 표현하는 수론과 해석학의 방법
가장 많이 사용되는 표기방법이 빅 오 표기법이다.
✨ Why big O?
✨ 시간 복잡도 최선의 경우를 고려한 경우
✨ 시간 복잡도 중간의 경우를 고려한 경우
✨ 시간 복잡도 최악의 경우를 고려한 경우
O(1) : 상수형 알고리즘, 데이터 입력량과 무관하게 실행시간이 일정하다
O(n) : 선형 알고리즘, 데이터 입력량에 비례하여 실행 시간이 증가
O(logn) : 로그형 알고리즘, 데이터 입력량이 늘어날수록 단위 입력당 실행 시간이 줄어든다
O(n logn) : 선형-로그형 알고리즘, 데이터 입력량이 n배 늘면 실행 시간이 n배 조금 넘게 늘어남
O(n²) : 2차 알고리즘, 데이터 입력량의 제곱에 비례하여 실행 시간이 늘어 난다.
O(2ⁿ) : 지수형 알고리즘, 데이터 입력량이 추가될 때마다 실행 시간이 두배로 늘어난다
O(n!) : 팩토리얼형 알고리즘, 데이터 입력이 추가될 때마다 실행 시간이 n배로 늘어난다
걸리는 시간
O(1) < O(logn) < O(n) < O(n logn) < O(n²) < O(2ⁿ) < O(n!)
왼쪽에서 오른쪽으로 갈수록 느려지고 효율이 떨어 진다.
일정한 복잡도(constant complexity)라고 하며, 입력값이 증가하더라도 시간이 늘어나지 않는다.
즉, 입력값의 크기와 관계없이, 즉시 출력값을 얻어낼 수 있다는 의미이다
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
int index = 1;
int result = O_1_algorithm(arr, index);
System.out.println(result); // 2
}
public static int O_1_algorithm(int[] arr, int index) {
return arr[index];
}
int BinarySearchRecursive(int arr[], int target, int low, int high) {
if (low > high)
return -1;
int mid = (low + high) / 2; //중간값 찾기
if (arr[mid] == target)
return mid;
else if (arr[mid] > target)
return BinarySearchRecursive(arr, target, low, mid-1);
else
return BinarySearchRecursive(arr, target, mid+1, high);
}
public static void main(String[] args) {
O_n_algorithm(n);
}
public static int O_n_algorithm(n) {
int answer = 0;
for(int i =0; i<n; i++) {
answer +=i;
}
return answer;
}
public static int another_O_n_algorithm(n) {
int answer = 0;
for(int i =0; i<2n; i++) {
answer +=i;
}
return answer;
}
public static void O_quadratic_algorithm(n) {
for (int i = 1; i < n; i++) {
for (int j = 1; j < n; j++) {
System.out.println(i * j);
}
}
}
📌 참고
계수는 입력 크기가 무한대로 증가함에 따라 알고리즘의 성장률에 영향을 미치지 않기 때문에 일반적으로 Big O 표기법에서 고려되지 않습니다. 그러나 n³ 및 n⁵와 같은 서로 다른 지수는 O(n²)와 동일하지 않은 별도의 시간 복잡도를 갖습니다 .
기하급수적 복잡도(exponential complexity)라고 부르며, 매우 느린 시간 복잡도를 가집니다.
예 : 피보나치 수열의 n번째 숫자를 가져오는 함수.
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int result = fibonacci(n);
System.out.println(result);
}
public static int fibonacci(int n) {
if (n <= 1) {
return n;
} else {
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
✨ 총 필요 저장 공간
✨ 공간 복잡도 표기
S(P) = c + Sp(n)
c: 고정 공간
𝑆𝑝(𝑛)Sp(n): 가변 공간
📌 고정 공간은 상수이므로 공간 복잡도는 가변 공간예 좌우된다. 공간 복잡도 계산은 실제 알고리즘 실행시 사용되는 저장공간을 계산하면 됨
public int get_factorial(int n) {
int i = 0;
int result = 1;
for(i = 1; i <= n; i++) {
result = result * i;
}
return result;
}
public int factorial(int n) {
if(n == 1) {
return n;
}
return n*factorial(n-1);
}