시간 복잡도
문제를 해결하기 위한 알고리즘의 로직을 코드로 구현할 때, 시간 복잡도를 고려해야 한다라는 말을 보게 된다. 그건 무슨 의미일까?
입력값의 변화에 따라 연산을 실행할 때, 연산 횟수에 비해 시간이 얼마만큼 걸리는가?를 뜻한다.
이 말인 즉 입력값이 커짐에 따라 증가하는 시간의 비율을 최소화한 알고리즘을 구성하는 것이 시간 복잡도를 고려한 알고리즘 작성이라는 것이고, 효율적인 코드를 작성하는 방식이라는 것이다.
시간 복잡도를 나타내는 방법(표기법)도 있는데
- Big-O(빅-오) : 상항 점근 (가장 최악의 경우 파악)
- Big-Ω(빅-오메가) : 하한 점근 ( 가장 효율적인 경우 파악)
- Big-θ(빅-세타) : 둘의 평균( 평균 값 파악)
이 세가지가 있다. 이중에서 가장 많이 사용되는 표기법이 Big-O 표기법이다. 그 이유는 가장 최악의 경우를 고려하여 프로그램이 실행되는 과정에서 소요되는 최악의 시간까지 (가장 오래 걸리는 시간) 고려할 수 있기 때문이다.
"최소한 특정 시간 이상이 걸린다" 혹은 "이 정도 시간이 걸린다" 를 고려하는 것보다 "이 정도 시간까지 걸릴 수 있다." 를 고려해야 최악의 상황을 벗어나게 되는 것이니까
O(1)
Big-O 표기법은 입력값의 변화에 따라 연산을 실행할 때, 연산 횟수에 비해 시간이 얼마만큼 걸리는가?를 표기하는 방법이다. O(1)는 constant complexity라고 하며, 입력값이 증가하더라도 시간이 늘어나지 않는다. 다시 말해 입력값의 크기와 관계없이, 즉시 출력값을 얻어낼 수 있다는 의미이다. O(1)의 시간 복잡도를 가진 알고리즘은 아래 예시에서 확인할 수 있다.
function O_1_algorithm(arr, index) {
return arr[index];
}
let arr = [1, 2, 3, 4, 5];
let index = 1;
let result = O_1_algorithm(arr, index);
console.log(result); // 2
입력 값의 크기가 아무리 커져도 즉시 출력값을 얻어낼 수 있다. 예를 들어 arr의 길이가 100만이라도 즉시 해당 index에 접근해 반환할 수 있다. (순수함수가 O(1)일 수도 있을 거란 생각을 해봤는데 예시를 들어보니 절대 아닐 것이라는 생각이 들었다.)
O(n)
O(n)은 linear complexity라고 부르며, 입력값이 증가함에 따라 시간 또한 같은 비율로 증가하는 것을 의미한다. 예를 들어 입력값이 1일 때 1초의 시간이 걸리고, 입력값을 100배로 증가시켰을 때 1초의 100배인 100초가 걸리는 알고리즘을 구현했다면, 즉 어떤 함수를 구현했을 때, 정비례로 시간이 늘어난다면 그 알고리즘은 O(n)의 시간 복잡도를 가진다고 할 수 있다. O(n)의 시간 복잡도를 아래 예시에서 확인해볼 수 있다.
function O_n_algorithm(n) {
for (let i = 0; i < n; i++) {
// do something for 1 second
}
}
function another_O_n_algorithm(n) {
for (let i = 0; i < 2n; i++) {
// do something for 1 second
}
}
O_n_algorithm 함수에선 입력값(n)이 1 증가할 때마다 코드의 실행 시간이 1초씩 증가한다. 즉 입력값이 증가함에 따라 같은 비율로 걸리는 시간이 늘어나고 있다.
그렇다면 함수 another_O_n_algorithm 은 어떨까? 입력값이 1 증가할때마다 코드의 실행 시간이 2초씩 증가한다. 이것을 보고, "아! 그렇다면 이 알고리즘은 O(2n) 이라고 표현하겠구나!" 라고 생각할 수 있겠지만, 사실 이 알고리즘 또한 Big-O 표기법으로는 O(n)으로 표기된다. 입력값이 커지면 커질수록 계수(n 앞에 있는 수)의 의미(영향력)가 점점 퇴색되기 때문에, 같은 비율로 증가하고 있다면 2배가 아닌 5배, 10배로 증가하더라도 O(n)으로 표기한다.
(역시 단순 for문이 시간 복잡도가 늘어남에 따라 동시에 증가하는 방식이었다. 그렇다면 filter나 map은 어떻게 되는 것일까? 그것도 결국 전 구간을 순회하기때문에 시간이 걸리게 되는 것 아닐까?)
O(log n)
O(log n)은 logarithmic complexity라고 부르며 Big-O표기법중 O(1) 다음으로 빠른 시간 복잡도를 가진다. 자료구조에서 배웠던 BST(Binary Search Tree)를 기억하시나요? BST에선 원하는 값을 탐색할 때, 노드를 이동할 때마다 경우의 수가 절반으로 줄어든다. 이해하기 쉬운 게임으로 비유해 보자면 up & down을 예로 들 수 있다.
- 1~100 중 하나의 숫자를 플레이어1이 고른다 (30을 골랐다고 가정).
- 50(가운데) 숫자를 제시하면 50보다 작으므로 down을 외친다.
- 1~50중의 하나의 숫자이므로 또다시 경우의 수를 절반으로 줄이기 위해 25를 제시한다.
- 25보다 크므로 up을 외친다.
- 경우의 수를 계속 절반으로 줄여나가며 정답을 찾는다.
매번 숫자를 제시할 때마다 경우의 수가 절반이 줄어들기 때문에 최악의 경우에도 7번이면 원하는 숫자를 찾아낼 수 있게 된다. BST의 값 탐색도 같은 로직으로 O(log n)의 시간 복잡도를 가진 알고리즘(탐색기법)이다.
https://newfind.tistory.com/95
7.26 Dev.Feedback ( 검색 알고리즘 선형검색, 이진검색)
저번주부터 귀에 염증이 심하게 생겨서 제대로 된 공부를 하지 못했다. 포스팅도 당연히... 조금씩 컨디션을 챙겼고, 조금이라도 괜찮을 때마다 정리하려고 한다. 검색 알고리즘 먼저 우리가 어
newfind.tistory.com
(기존에 정리했던 방식이 이 방식이라는 O(log n) 방식이었다.
O(n2)은 quadratic complexity라고 부르며, 입력값이 증가함에 따라 시간이 n의 제곱수의 비율로 증가하는 것을 의미한다. 즉 비효율적인 코딩 방법이라고 생각하면 되겠다.
예를 들어 입력값이 1일 경우 1초가 걸리던 알고리즘에 5라는 값을 주었더니 25초가 걸리게 된다면, 이 알고리즘의 시간 복잡도는 O(n2)라고 표현한다. O(n2)의 시간 복잡도를 가진 알고리즘은 아래 예시에서 볼 수 있다.
function O_quadratic_algorithm(n) {
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
// do something for 1 second
}
}
}
function another_O_quadratic_algorithm(n) {
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
for (let k = 0; k < n; k++) {
// do something for 1 second
}
}
}
}
2n, 5n 을 모두 O(n)이라고 표현하는 것처럼, n3과 n5 도 모두 O(n2)로 표기한다. n이 커지면 커질수록 지수가 주는 영향력이 점점 퇴색되기 때문에 이렇게 표기한다.
O(2의 n제곱)은 exponential complexity라고 부르며 Big-O 표기법 중 가장 느린 시간 복잡도를 가진다. 종이를 42번 접으면 그 두께가 지구에서 달까지의 거리보다 커진다는 이야기를 들어봤을 것이다. 고작 42번 만에 얇은 종이가 그만한 두께를 가질 수 있는 것은, 매번 접힐 때마다 두께가 2배로 늘어나기 때문이다. 구현한 알고리즘의 시간 복잡도가 O(2의 n 제곱)이라면 다른 접근 방식을 고민해 보는 것이 좋다.
function fibonacci(n) {
if (n <= 1) {
return 1;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
재귀로 구현하는 피보나치 수열은 O(2n)의 시간 복잡도를 가진 대표적인 알고리즘이다. 브라우저 개발자 창에서 n을 40으로 두어도 수초가 걸리는 것을 확인할 수 있으며, n이 100 이상이면 평생 결과를 반환받지 못할 수도 있다.
(이래서 memoization을 사용하여 시간 복잡도를 생각했다.)
- 가장 빠른/느린 시간 복잡도는 무엇일까? 가장 빠른 시간 복잡도는 O(logN) 느린 시간 복잡도는 O(2의 n 제곱)
- 효율적인 알고리즘을 구성했다 함은 무엇을 의미할까? 입력값이 올라가도, 수행하는 시간이 가파르게 올라가지 않는 알고리즘
- 시간 복잡도는 A와 B의 비례함이 어느 정도인지를 나타냅니다. A와 B는 무엇일까? A는 입력값, B는 시간
전부 읽어보고, 문제를 직접 풀어본 후에 검은 바를 드래그 해보기
'개발 R.I.P.' 카테고리의 다른 글
9.03 Dev.Feedback (Mongo DB #1) (0) | 2021.09.03 |
---|---|
8.26 Dev.Feedback (SQL) (0) | 2021.08.26 |
8.08 Dev.Feedback (Request properties) (0) | 2021.08.08 |
8.05 Dev.Feedback (스프린트 Test Server, Client 작동) (0) | 2021.08.05 |
8.03 Dev.Feedback (Package.json) (0) | 2021.08.03 |