[Algorithm] 시간 복잡도
🍪 시간 복잡도 표기법
- 시간 복잡도: 문제를 해결하기 위한 연산 횟수
- 일반적으로 1억 번의 연산을 1초의 시간으로 간주
시간 복잡도 정의
- 빅-오메가: 최선일 때의 연산 횟수
- 빅-세타: 보통일 때의 연산 횟수
- 빅-오: 최악일 때의 연산 횟수
public class timeComplexityExample1 {
public static void main(String[] args) {
// 1~100 사이 값 랜덤 선택
int findNumber = (int)(Math.random() * 100);
for (int i = 100; i < 100; i++) {
if (i == findNumber) {
System.out.println(n(i));
break;
}
}
}
}
// 빅-오메가: 1
// 빅-세타: 2/N
// 빅-오: N
코딩테스트에서 사용하는 시간 복잡도 유형
- 코딩테스트에서 항상 빅-오를 기준으로 계산해야 함!
- 다양한 테스트 케이스를 수행해 모두 통과해야 하기 때문
- 빅-오 표기법의 수행 시간
따져봐야 할 것
- 알고리즘 유형에 따라
- 본인이 짠 코드가 얼마나 걸리는지
🍪 시간 복잡도 활용하기
ex) 수 정렬하기 (BOJ 2750)
- 제한시간 2초 -> 2억 번 이하의 연산 횟수로 해결
- N개의 수가 주어졌을 때 오름차순 정렬
- 입력
- 첫번째 줄에 N (1 ≤ N ≤ 1,000,000)
- 두번째 줄부터는 N개의 줄에 숫자가 주어짐
- 출력
- 1부터 N개의 줄에 오름차순 정렬한 결과 출력
연산 횟수 = 알고리즘 시간 복잡도 x 데이터의 크기
- 버블 정렬(N²) -> (1,000,000)² : 2억보다 큼 (시간초과!)
- 병합 정렬(nlogn) -> (1,000,000)log(1,000,000) : 약 2억
- 데이터의 크기의 최댓값으로 따짐!
시간 복잡도 바탕으로 코드 로직 개선
- 상수는 시간 복잡도 계산에서 제외
- 가장 많이 중첩된 반복문의 수행 횟수가 시간 복잡도의 기준이 됨
- ex) 연산 횟수가 N인 경우
public class complexity1 { public static void main(String[] args) { int N = 100000; int cnt = 0; for(int i = 0; i < N; i++) { System.out.println('연산횟수: ' + cnt++); } } }
- ex) 연산 횟수가 3N인 경우
public class complexity2 { public static void main(String[] args) { int N = 100000; int cnt = 0; for(int i = 0; i < N; i++) { System.out.println('연산횟수: ' + cnt++); } for(int i = 0; i < N; i++) { System.out.println('연산횟수: ' + cnt++); } for(int i = 0; i < N; i++) { System.out.println('연산횟수: ' + cnt++); } } }
- 두 코드의 연산 횟수는 3배 차이가 남
- 하지만, 코딩테스트에서는 일반적으로 상수를 무시하므로 두 코드 모두 시간 복잡도는 O(n)으로 같음!
- 상수는 프로그램에서 실질적으로 엄청 큰 영향을 끼치지는 않음
- ex) 연산 횟수가 N²인 경우
public class complexity3 { public static void main(String[] args) { int N = 100000; int cnt = 0; for(int i = 0; i < N; i++) { for(int j = 0; j < N; j++) { System.out.println('연산횟수: ' + cnt++); } } } }
- 이중 for문이 전체 코드의 시간 복잡도의 기준이 됨 -> O(n²)
- 위의 예제처럼 일차 for문이 100개 더 있다해도 이중 for문 한 개를 기준으로 함
- 맞는 알고리즘 쓰기
- 시간 초과 -> 내 로직이 효율적으로 짜여있는지 확인
- 가장 시간 복잡도를 크게 잡아먹는 부분!