시간 복잡도 분석이 필요한 이유는 무엇입니까?
통계 및 모니터링을 통해 알고리즘의 실행 시간과 점유 메모리 크기를 얻을 수 있지만 이 통계 방법에는 다음과 같은
많은 단점이 있습니다.
- 테스트 결과는 테스트 환경에 따라 달라지는데, 예를 들어 테스트 PC의 칩을 i7에서 i5로 바꾸면 실행 시간이 늘어난다.
- 소규모 데이터 정렬과 같이 테스트 데이터의 크기에 따라 테스트 결과가 달라지며 삽입 정렬이 빠른 정렬보다 빠릅니다.
시간 복잡도의 표현
Big O 표기법(중요)
정의: 모든 n >= n0, f(n) <= cg(n)에 대해 두 개의 매개변수 c > 0, n0 > 0이 있는 경우에만 f(n) = O(g(n ))
, 그림과 같이:
Big O 표기법의 시간 복잡도 예:
int cal(int n) {
int sum = 0;
int i = 1;
int j = 1;
for (; i <= n; ++i) {
j = 1;
for (; j <= n; ++j) {
sum = sum + i * j;
}
}
return sum;
}
위의 코드를 가정하면 한 줄의 코드를 실행하는 데 걸리는 시간은 t이고 소요된 총 시간은 입니다 (2*n^2+2*n+4)*t
. n이 매우 큰 경우 위 코드가 소요하는 시간은
n^2에만 의존합니다. 즉, T(n) = O(n 2), 위 코드의 시간 복잡도는 O(n 2) 입니다.
간단히 말해서, 빅오 표기법은 공식에서 상수, 하위 차수 및 계수를 무시하고 가장 큰 차수의 크기만 기록하면 됩니다.
빅Ω표기(이해해 주십시오)
정의: 양수 c와 n0이 있는 경우 모든 n >= n0,
f(n) >= cg(n)에 대해 그림과 같이 f(n) = Ω(g(n)) :
큰 θ 표기법(그냥 이해하세요)
정의: 집합 O(g(n))과 집합 Ω(g(n )) 모두에 있는 경우 함수를 θ(g(n))이라고 합니다. 즉, 상한과 하한이 같으면 그림과 같이 큰 θ 표현을 사용할 수 있습니다.
일반적으로 사용되는 시간복잡도 척도
- 상수 차수 O(1)
O(1)은 한 줄의 코드만 실행되는 것이 아니라 상수 수준의 시간 복잡도를 나타내는 것일 뿐입니다. 예를 들어, 이 코드는 3줄이더라도 시간 복잡도는 O(3)이
아니라 O(1)입니다.
int i = 8;
int j = 6;
int sum = i + j;
n의 증가에 따라 코드의 실행 시간이 증가하지 않는 한 코드의 시간복잡도는 O(1)로 기록된다. 또는 일반적으로 알고리즘에 루프문이나 재귀문이 없는 한
수만 줄의 코드가 있더라도 시간 복잡도는 Ο(1)입니다.
- 대수 차수 O(logn)
- 선형 순서 O(n)
- 선형 대수 순서 O(n * logn)
- 제곱차 O(n 2), 입법차수 O(n 3), k 제곱차 O(n^k) 등
- 지수 순서 O(2^n)
- 계승 순서 O(n!)
시간 복잡도 분석
실행 횟수를 계산하여 결정
일부 알고리즘의 시간 복잡도는 단순히 실행 횟수를 세어 결정할 수 있습니다.
예제 1, 두 배열의 합을 계산하는 알고리즘:
int count(int[] array1,int m,int[] array2,int n){
int sum = 0;
for(int i = 0;i < m;i++){
//运行 m 次
sum += array1[i];
}
for(int i = 0;i < n;i++){
//运行 n 次
sum += array2[i];
}
return sum;
}
m과 n이 같지 않기 때문에 실행 횟수는 m+n이므로 시간복잡도는 O(m+n)
예제 2, 버블 정렬 알고리즘:
private static void sort(int[] data){
if(data.length <= 1)return;
int n = data.length;
for (int i = 0; i < n; i++) {
for (int j = 0; j+1 < n - i; j++) {
if(data[j] > data[j+1]){
int cache = data[j];
data[j] = data[j+1];
data[j+1] = cache;
}
}
}
}
위 코드의 실행 횟수는 n*(n+1)/2 이며, big O 표기법으로 인해 상수, 하위 차수, 계수를 제거해야 하므로 시간 복잡도는 O(n^2)
예 3, 시간 복잡도를 얻으려면 약간의 수학이 필요합니다.
i=1;
while (i <= n) {
i = i * 2;
}
코드에서 보듯이 이 프로그램의 실행 시간 x는 n = 2^0 * 2^1 * 2^2 * ... *2^x, 즉 x = logn을 만족하므로 시간 복잡도는 O( 로그)
실행 트리
재귀 함수의 경우 재귀 호출 수가 입력 크기에 따라 거의 선형으로 확장되지 않습니다. 이 경우 재귀 함수의 실행 흐름을 나타내는 트리인 실행 트리를 사용하는 것이 좋다. 트리의 각 노드는 재귀 함수에 대한 호출을 나타냅니다. 따라서 트리의 총 노드 수는 실행 중 재귀 호출 수에 해당합니다.
다음은 leetcode의 피보나치 수 프로그래밍 문제 입니다.
斐波那契数,通常用 F(n) 表示,形成的序列称为斐波那契数列。
该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
给定 N,计算 F(N)。
응답 코드는 다음과 같습니다(다음 코드는 메모이제이션으로 최적화할 수 있습니다)
class Solution {
public int fib(int N) {
if(N == 0) return 1;
return fib(N-1)+fib(N-2);
}
}
아래 다이어그램(이미지 출처 leetcode)은 피보나치 수 f(4)를 계산하는 데 사용되는 실행 트리를 보여줍니다.
n 레벨이 있는 완전한 이진 트리에서 총 노드 수는 2^n -1입니다. 따라서 f(n)의 재귀 횟수에 대한 상한(엄격하지는 않지만)도 2^n -1입니다. 그런 다음 알고리즘의 시간 복잡도를 O(2^n)으로 추정할 수 있습니다.
다른
최고, 최악, 평균 사례 시간 복잡도
예를 들어 배열에서 x의 위치를 찾으려면 다음을 수행하십시오.
// n表示数组array的长度
int find(int[] array, int n, int x) {
int i = 0;
int pos = -1;
for (; i < n; ++i) {
if (array[i] == x) {
pos = i;
break;
}
}
return pos;
}
x가 배열의 i=0 위치에 있을 때 프로그램은 한 번만 실행하면 되며 시간 복잡도는 O(1)입니다.
x가 배열에 없으면 프로그램은 배열을 순회해야 하며 시간 복잡도는 O(n)입니다.
서로 다른 상황에서 코드의 서로 다른 시간 복잡도를 표현하기 위해 세 가지 개념을 도입해야 합니다: 최상의 시간 복잡도 , 최악의 시간 복잡도 및 평균 시간 복잡도 . 그러나 최고와 최악의 시간복잡도의 확률은 작기 때문에 평균의 경우의 복잡도를 더 잘 나타내기 위해서는 평균의 시간복잡도가 필요하다.
위의 코드를 예로 들면 x가 존재할 확률은 1/2이고 x가 존재할 확률은 0 ~ n-1 위치에 나타날 확률은 (1+2+...+n)/2n , x가 존재하지 않을 확률은 n/2, 즉 (3*n+1)/4이며, big O 표기법을 사용하면 시간복잡도는 O(n)
상각 시간 복잡도
대부분의 코드 실행 복잡도는 저수준 복잡도이며, 개별 사례가 고수준 복잡도이고 타이밍 관계가 있을 때 개별 고수준 복잡도는 저수준 복잡도에 고르게 분포될 수 있습니다
. 기본적으로 상각된 결과는 낮은 수준의 복잡성과 같습니다.
为了更全面,更准确的描述代码的时间复杂度,才要引入最好、最坏、平均情况时间复杂度以及均摊时间复杂度。但是只有代码复杂度在不同情况下出现量级差别时才需要区别这四种复杂度。大多数情况下,是不需要区别分析它们的。
공통 알고리즘의 시간 복잡도
면접 때 코드를 손으로 직접 써달라는 부탁을 자주 받는데, 그나저나 알고리즘의 시간복잡도가 어떻게 되는지, 일반 알고리즘의 시간복잡도를 기억하는 것이 매우 필요합니다.
정렬 알고리즘
정렬 알고리즘 | 시간 복잡도 |
---|---|
버블 정렬 | 오(n^2) |
삽입 정렬 | 오(n^2) |
선택 정렬 | 오(n^2) |
빠른 정렬 | 오(nlogn) |
병합 정렬 | 오(nlogn) |
버킷 정렬 | 에) |
카운팅 정렬 | 에) |
기수 정렬 | 에) |
이진 트리 순회 알고리즘
이진 트리 순회 알고리즘 | 시간 복잡도 |
---|---|
선주문 순회(재귀적 구현) | 에) |
선주문 순회(반복 구현) | 에) |
중위 순회(재귀적 구현) | 에) |
중위 순회(반복 구현) | 에) |
사후 순회(재귀적 구현) | 에) |
사후 순회(반복 구현) | 에) |
참고
- leetcode 재귀의 시간 복잡도 분석
- 중국 대학 MOOC - 데이터 구조 및 알고리즘 코스
- Geek Time의 "데이터 구조 및 알고리즘의 아름다움" 칼럼