Big-O
Big-O 표기법이란 알고리즘의 효율성을 나타내는 지표 혹은 언어를 말한다.
보통 알고리즘의 시간복잡도와 공간복잡도를 나타낼 때 사용한다.
-
시간복잡도 : 알고리즘의 시간 효율성
-
공간복잡도 : 알고리즘의 공간(메모리) 효율성
-
크기가 N인 배열을 만들고자 한다면 공간복잡도는 O(N)-
크기가 N*N인 2차원 배열을 만들고자 한다면 공간복잡도는 O(N^2)-
공간복잡도 계산 시 재귀호출에서 사용하는 스택공간을 포함하여 계산해야한다.
비교 그래프
자주 사용하는 Big-O 표기법을 효율성이 좋은 것 부터 나열해보자면 아래와 같다.
O(1) < O(logN) < O(N) < O(NlogN) < O(N^2) < O(2N) < O(N!) < O(N^N)
(아이패드 첫 사용 기념으로 애플펜슬로 그려봤는데 발로 그린 게 더 낫겠다.)
최선, 최악, 평균
하나의 알고리즘에서 최선, 최악, 평균 시간을 구할 수 있다.
퀵정렬을 예로 들어 간단히 설명하자면, N을 배열의 크기라고 가정했을 때
-
최선 : 모든 원소들이 같은 값일 때 시간복잡도 -> O(N)
-
최악 : 배열의 가장 큰 원소가 계속 축이 될 때 시간복잡도 -> O(N^2)
-
배열을 절반으로 나누지 못하고 계속 한 개씩만 줄어든 배열로 리턴 -
평균 : 배열을 절반으로 나누어 정렬할 때 시간복잡도 -> O(NlogN)
퀵정렬이란?
축이 되는 원소를 무작위로 하나 정하여 축보다 작은 원소들은 앞으로, 큰 원소들은 뒤로 놓이도록 Swap을 반복하여 정렬하는 방법
특징 1 - 사소한 부분 무시
Big-O는 효율성의 비율을 나타내는 개념으로 입력값이 충분히 크다고 가정한다.
따라서 사소한 부분(계산에 큰 영향을 미치지 않는)은 무시할 수 있다.
-
상수항 무시
O(2N) -> O(N) -
수식에서 지배적이지 않은 항 무시
O(N^2 + N) -> O(N^2)O(N + logN) -> O(N)
O(5*2^N + 1000N^100) -> O(2^N)
특징 2 - 덧셈 / 곱셈 수행시간
실행순서에 따라 수행시간 계산 방식이 달라진다.
-
O(A + B) : A를 끝마친 후에 B 수행
for(int a : arrA){ print(a); } for(int b : arrB){ print(b); }
-
O(A * B) : A가 할 때마다 B 수행
for(int a : arrA){ for(int b : arrB){ print(a + ", " + b); } }
logN 수행시간
O(logN) : 이진탐색, 균형 이진탐색트리 사용 시 확인할 수 있다.
-
탐색 과정
-
원소 x와 배열의 중간값 m을 비교
-
x==m 일 때 반환 또는 x<m 일 때 배열의 왼쪽 부분 재탐색
-
-
처음 N개에서 한 단계씩 거치면서 탐색할 원소의 개수가 N/2, N/4, … 로 줄어든다.
따라서 총 수행시간은 N을 절반씩 나누는 과정에서 몇 단계만에 1이 되는지에 따라 결정된다.
-
N이 16일 때 16에서 1로 감소하는 순서를 뒤집어서 1에서 16으로 증가하는 순서로 생각해보면,
숫자 1에 2를 4번 곱해야 N이 된다. 즉, 2^k = N을 만족하는 k를 구할 때 사용되는 것이 log이다.
이진탐색이란? N개의 정렬된 원소가 들어있는 배열에서 원소 x를 찾을 때 사용하는 방법이다.
재귀호출 수행시간
-
깊이가 N인 이진트리(각 노드는 두개의 자식 노드를 가진다.)를 예시로 들면, O(분기^깊이) -> O(2^깊이) -> O(2^N)
-
깊이가 한 단계 깊어질 때마다 재귀호출은 2배씩 많아진다.
참고 - 2의 승수의 합
깊이가 N인 트리의 전체 노드의 개수 = 2의 승수의 합 = ( 2^(N+1) - 1 )
-
2의 승수의 합 : 2^0 + 2^1 + 2^2 + … + 2^n 의 결과는?
승수 2진수 10진수 2^0 00001 1 2^1 00010 2 2^2 00100 4 2^3 01000 8 2^4 10000 16 합 = 2^5 -1 11111 32-1 = 31 따라서 수열의 연산을 2진수로 표현하면 1이 연속으로 (n+1) 개 나열된 값과 같다. 이는 2^n+1 -1과 같다.
참고자료
게일 라크만 맥도웰, 코딩 인터뷰 완전 분석, 인사이트, 2017