Post

알고리즘 시간복잡도, Big-O

시간복잡도와 Big-O

시간 복잡도는 알고리즘을 처리하는 데 걸리는 시간을 정량화한 것이다.

Big-O를 통해 시간 복잡도를 표현할 수 있다.


O(1) : Constant Time

입력 데이터의 크기와 상관없이 언제나 일정한 시간이 걸린다.

ex) pop(), push()

1
2
3
const names = ["Luis", "John", "Jose"];
names.push("Aaron");
console.log(names); // (4) ["Luis", "John", "Jose", "Aaron"]

O(n) : Linear Time

입력 데이터의 크기와 처리 시간이 비례한다.

ex) for문, map(), filter()

1
2
3
4
const fruits = ["apple", "banana", "cherry", "orange"];
for (let i = 0; i < fruits.length; i++) {
  console.log(fruits[i]);
}

O(n^2) : Quadratic Time

입력 데이터 크기의 제곱만큼 처리시간이 걸린다.

데이터의 길이가 늘어날 때마다 처리시간이 크게 증가한다.

ex) 2중 for문 등

1
2
3
4
5
6
const fruits = ["apple", "banana", "cherry", "orange"];
for (let i = 0; i < fruits.length; i++) {
  for (let j = 0; j < fruits.length; j++) {
    console.log(fruits[i] === fruits[j]);
  }
}

O(2^n)

ex) 피보나치 수열 : 입력한 숫자 번째의 피보나치 수열에서 전 값과 전전 값을 더한다. (5->3+2, 6->5+3)

전 숫자와 전전 숫자를 재귀 함수로 전달하므로 함수가 호출될 때마다 두 번씩 호출된다.

O(n^2)보다 데이터의 증가에 따른 처리시간이 더 크다.

1
2
3
4
5
6
7
8
const fibonacci = (num) => {
  // if(num < 0) return 0;
  if (num < 2) {
    return num;
  } else {
    return fibonacci(num - 1) + fibonacci(num - 2);
  }
};

O(log n)

ex) 이진 탐색(Binary Search)

배열을 반으로 나눠가며 값의 위치를 탐색한다.

데이터의 양이 많아도 절반씩 줄이며 탐색하기 때문에 시간복잡도가 낮다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function binarySearch(sortedArray, key) {
  let start = 0;
  let end = sortedArray.length - 1;

  while (start <= end) {
    let middle = Math.floor((start + end) / 2);
    // Math.ceil을 써도 같은 결과 출력

    if (sortedArray[middle] === key) {
      // found the key
      return middle;
    } else if (sortedArray[middle] < key) {
      // continue searching to the right
      start = middle + 1;
    } else {
      // search searching to the left
      end = middle - 1;
    }
  }
  // key wasn't found
  return -1;
}

Big-O 계산 규칙

Worst Case

1
2
3
4
5
6
const fruits = ["apple", "banana", "cherry", "orange"];
for (let i = 0; i < fruits.length; i++) {
  if (fruits[i] === "banana") {
    return console.log(fruits[i]);
  }
}

위 코드의 for문에서 중간에 원하는 요소를 찾으면 return 되어 중단된다.

하지만 Worst Case, 즉 최악의 경우를 고려하면 찾는 요소가 배열의 맨 마지막에 있을 수 있으므로 중간에 return을 하더라도 Big-O는 변하지 않는다.

Remove Constants

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function printItems(items) {
  console.log(items[0]); // O(1)
  let middleIndex = Math.floor(items.length / 2);
  let index = 0;
  while (index < middleIndex) {
    // O(n/2)
    console.log(items[index]);
    index++;
  }
  for (let i = 0; i < 100; i++) {
    // O(100)
    console.log("hi");
  }
}

위 코드의 Big-O를 계산하면 O(1 + n/2 + 100)이다.

그러나 만약 items의 크기가 엄청나게 커진다면, 1을 더하든 100을 더하든 상관 없어진다.

결국 이 경우에도 Big-O는 O(1 + n/2 + 100)에서 상수를 제거한 O(n)이다.

1
2
3
4
5
6
7
8
9
10
function compareBoxes(boxes) {
  boxes.forEach(function (boxes) {
    // O(n)
    console.log(boxes);
  });
  boxes.forEach(function (boxes) {
    // O(n)
    console.log(boxes);
  });
}

위 코드도 O(2n)이지만 마찬가지로 상수를 제거하면 O(n)이다.

Big-O 계산은 정확한 속도를 계산하려고 하는 것이 아니라는 것을 기억하자.

Different Terms for Inputs

1
2
3
4
5
6
7
8
9
10
function compareBoxes(boxes, boxes2) {
  boxes.forEach(function (boxes) {
    // O(a)
    console.log(boxes);
  });
  boxes2.forEach(function (boxes2) {
    // O(b)
    console.log(boxes);
  });
}

위 코드의 경우는 함수의 인자 값이 다르므로 따로 계산을 해야 한다.

이 경우 Big-O는 O(a+b)이다.

Drop Non Dominants

1
2
3
4
5
6
7
8
9
10
11
12
function printAllNumber(numbers) {
  numbers.forEach(function (number) {
    // O(n)
    console.log(number);
  });
  numbers.forEach(function (firstNumber) {
    numbers.forEach(function (secondNumber) {
      console.log(firstNumber + secondNumber); // O(n^2)
    });
  });
}
printAllNumber([1, 2, 3, 4, 5]);

위 코드는 O(n + n^2)이지만 배열의 길이가 커질수록 n^2가 압도적으로 커지기 때문에 결국 Big-O는 O(n^2)이다.

참고사이트

[자료구조] 시간복잡도 with JavaScript
자바스크립트 Array 메소드 및 예제에 대한 시간 복잡도 Big O

This post is licensed under CC BY 4.0 by the author.