Algorithm|Javascript Algorithm

알고리즘의 성능 성적표

1
알고리즘의 성능 성적표

크리스가 시니어 개발자에게 코드 리뷰를 받고 있다.

자신 있게 제출한 이중 반복문 코드를 보며 시니어 개발자가 심각한 표정으로 말했다.

"크리스님, 이 로직은 시간 복잡도가 O(N^2)라서 데이터가 조금만 늘어나도 서버가 터질 것 같은데요? 최소한 O(N)이나 O(N log N)으로 줄여야 합니다."

크리스는 당황했다.

'오... 엔 제곱? 로그 엔? 수학 시간에 잤던 바로 그 로그(Log) 말인가?'

개발자들 사이에서 암호처럼 쓰이는 이 ”O”의 정체는 무엇일까? 오늘은 알고리즘의 효율성을 나타내는 만국 공용어, 빅오 표기법(Big-O Notation)을 완벽하게 이해해 보자.

1. 초시계 대신 '단계'를 센다

"이 코드가 얼마나 빠른가?"를 측정할 때, 우리는 흔히 시간(초)을 떠올린다.

"내 컴퓨터에서는 0.1초 걸렸어!"

하지만 이건 객관적인 지표가 될 수 없다. 슈퍼컴퓨터에서 돌린 0.1초와, 10년 된 노트북에서 돌린 0.1초는 다르기 때문이다. 그래서 컴퓨터 과학자들은 '시간(Seconds)' 대신 '연산 횟수(Steps)'를 세기로 약속했다.

  • 시간 복잡도(Time Complexity)란, "입력 데이터($N$)가 늘어날 때, 처리 시간(연산 횟수)이 얼마나 늘어나는가?"를 나타내는 비율이다.
  • 2. 빅오(Big-O)의 세계: 빠름과 느림의 계급

    빅오 표기법은 알고리즘의 성장 추세를 단순화해서 보여준다. 자주 쓰이는 4가지 계급만 알면 된다.

    1계급: O(1) - 상수 시간 (Constant Time)

    "데이터가 얼마나 많든 상관없어."

    가장 이상적이고 빠른 속도다. 입력 데이터 $N$이 1개든 100만 개든 처리 속도가 똑같다.

  • 예시: 배열의 인덱스 접근(arr[5]), 스택의 push/pop, 큐의 enqueue.
  • 비유: 넷플릭스에서 영화 고르기. 영화가 100편이든 1만 편이든, 내가 보고 싶은 걸 클릭해서 재생하는 시간은 똑같다.
  • 2계급: O(log N) - 로그 시간 (Logarithmic Time)

    "데이터가 2배 늘어나도, 할 일은 딱 한 단계만 늘어나."

    $O(1)$ 다음으로 빠르다. 대량의 데이터를 처리할 때 매우 강력하다.

  • 예시: 이진 탐색(Binary Search), 거대한 전화번호부에서 이름 찾기.
  • 비유: 업다운(Up-Down) 게임. 1부터 100 사이의 숫자를 맞힐 때, 50을 불러서 "Down"이면 51~100은 쳐다볼 필요도 없다. 절반씩 버리면서 찾기 때문에 데이터가 100만 개라도 단 20번이면 찾을 수 있다.
  • 3계급: O(N) - 선형 시간 (Linear Time)

    "데이터가 늘어나는 만큼 정직하게 시간이 걸려."

    데이터가 10배 늘어나면 시간도 10배 걸린다. 우리가 짜는 대부분의 for 문이 여기에 해당한다.

  • 예시: 배열 전체 순회, filter, map, find, 그리고 저번 글에서 배운 shift.
  • 비유: 책의 첫 페이지부터 끝 페이지까지 읽어서 특정 단어 찾기. 책이 두꺼울수록 오래 걸린다.
  • 4계급: O(N^2) - 이차 시간 (Quadratic Time)

    "데이터가 10배 늘어나면, 시간은 100배 걸려." (위험 구간 🚨)

    보통 이중 반복문(Nested Loop)을 쓸 때 발생한다. 데이터가 적을 땐 티가 안 나지만, 조금만 많아지면 성능이 지수적으로 나빠져서 서버를 다운시킨다.

  • 예시: 구구단 출력, 크리스가 처음 짰던 중복 검사 로직(filter 안에 includes).
  • 비유: 100명의 사람이 서로 한 번씩 모두와 악수하기.
  • 3. 왜 '빅오(O)'인가? (최악의 시나리오)

    "가끔은 운 좋게 한 번에 찾을 수도 있잖아요?"

    맞다. 배열의 맨 앞에 찾는 값이 있다면 O(1) 만에 끝날 수도 있다. (이를 최선의 경우라고 한다.)

    하지만 빅오 표기법은 항상 최악의 경우(Worst Case)를 기준으로 한다.

    알고리즘의 성능을 보장하려면, "아무리 운이 나빠도 이 정도 시간 안에는 끝납니다"라는 상한선이 필요하기 때문이다.

    그래서 우리는 "평균적으로 빠르다"보다는 "최악의 상황에서도 O(N)을 넘지 않는다"라고 말하는 것을 선호한다.

    4. 자바스크립트 코드의 빅오 판별법

    이제 크리스는 코드를 볼 때 머릿속으로 N을 대입해 본다.

    typescript
    // Case 1: 단순 반복
    function printAll(arr) {
      for (let i = 0; i < arr.length; i++) { // N번 반복
        console.log(arr[i]);
      }
    }
    // -> O(N)
    
    // Case 2: 중첩 반복
    function printPairs(arr) {
      for (let i = 0; i < arr.length; i++) {    // N번
        for (let j = 0; j < arr.length; j++) {   // N번
          console.log(arr[i], arr[j]);
        }
      }
    }
    // -> N * N = O(N^2)
    
    // Case 3: 반복문이 따로 있을 때
    function doSomething(arr) {
      for (let i = 0; i < arr.length; i++) {} // N번
      for (let j = 0; j < arr.length; j++) {} // N번
    }
    // -> N + N = 2N. 하지만 상수는 무시한다. -> O(N)

    : 빅오 표기법에서는 상수를 무시한다. O(2N)이나 O(N + 100)이나, N이 무한대로 커지면 차이가 미미하기 때문에 그냥 O(N)으로 퉁친다. 중요한 건 기울기(추세)다.

    핵심 정리

  • 시간 복잡도: 데이터 양(N)에 따라 연산 횟수가 어떻게 증가하는지 나타내는 지표다.
  • 성능 순서: O(1)(최고) > O(log N) > O(N) > O(N log N) > O(N^2) (피해야 함).
  • 기준: 항상 최악의 경우를 기준으로 생각해야 안전한 소프트웨어를 만들 수 있다.
  • 적용: 이중 for 문이 보이면 본능적으로 "이거 O(N^2)인데 괜찮나?"라고 의심해야 한다.
  • 시간(Time)에 대한 개념을 잡았다. 그런데 알고리즘에는 시간만큼 중요한 자원이 하나 더 있다. 바로 공간(Space), 즉 메모리다.

    엄청나게 빠르지만 램(RAM)을 100GB나 차지하는 코드는 좋은 코드일까?

    다음 글, "공간 복잡도: 속도만큼 중요한 메모리 이야기"에서 계속된다.


    🔗 참고 링크

  • Big-O Cheat Sheet
  • HackerRank - Big O Notation
  • 댓글 (0)

    0/1000
    댓글을 불러오는 중...