노트

빅오 표기법

Big O Notation

CS#algorithm · 연결된 개념 17개

쉽게 말하면

빅오는 '데이터가 10배로 늘면 일이 몇 배로 늘까?'를 나타내는 표기예요. 정확한 시간 대신 늘어나는 모양만 보니까, 데이터가 커졌을 때 느려질 코드를 미리 알아챌 수 있어요.

비유가 깨지는 곳 모양만 보고 상수를 버리기 때문에 복잡도가 낮다고 항상 빠른 건 아니에요. n이 작으면 상수가 큰 '좋은' 알고리즘이 더 느릴 수 있어서, 결국 측정이 먼저예요.

입력 크기 n이 커질 때 알고리즘의 실행 시간(시간 복잡도, Time Complexity)이나 메모리(공간 복잡도, Space Complexity)가 얼마나 빨리 늘어나는지를 대략적으로 나타내는 표기. 정확한 횟수가 아니라 증가하는 모양을 본다. 그래서 상수와 작은 항은 버린다(2n + 5는 O(n)).

세는 요령

  • 산술 연산, 변수 대입, 인덱스로 배열 접근, 키로 객체 접근은 상수 시간 O(1)
  • 반복문은 "반복 횟수 × 안에서 하는 일"이다. 중첩 반복은 곱해진다
  • O(log n)은 대략 n을 1 이하가 될 때까지 2로 나눈 횟수다. 매번 절반을 버리는 이진 탐색이 그렇다
  • 코드를 보면 "데이터가 10개면 몇 번? 100만 개면?"을 습관처럼 묻는다

자주 쓰는 연산

연산복잡도
배열 push·pop, 인덱스 접근O(1)
배열 shift·unshift, spliceO(n), 나머지 원소를 당기거나 민다
find·includes·indexOf·map·filterO(n)
sortO(n log n)
Map·Set·객체의 조회·추가·삭제평균 O(1)

실무에서 보이는 패턴

// O(n·m): 사용자마다 주문 전체를 훑는다
users.map(u => orders.filter(o => o.userId === u.id))
 
// O(n + m): 한 번 묶어 두고 조회한다
const byUser = Map.groupBy(orders, o => o.userId)
users.map(u => byUser.get(u.id) ?? [])

렌더마다 도는 find 안의 find처럼 숨은 중첩이 성능 문제의 단골 원인이다. Map·Set으로 바꾸는 것이 가장 흔한 처방이다(해시 테이블, 빈도수 세기 패턴). 공간도 함께 본다. map은 새 배열을 만들어 O(n) 메모리를 더 쓴다.

복잡도가 낮다고 항상 빠른 건 아니다. n이 작으면 상수가 큰 "좋은" 알고리즘이 더 느릴 수 있다(롭 파이크의 프로그래밍 5규칙). 측정이 먼저다(섣부른 최적화). 많은 항목을 다 그리지 않는 리스트 가상화은 n 자체를 줄이는 방법이다.

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • 동적 프로그래밍

    복잡한 문제를 더 작은 부분 문제로 나눠 풀되, 한 번 푼 부분 문제의 답을 저장해 다시 계산하지 않는 방법. 두 조건이 맞을 때 쓴다.

  • 재귀

    함수가 자기 자신을 다시 호출해 문제를 푸는 방식. 같은 함수를 점점 작은 입력으로 부르다가 더 나눌 필요가 없는 지점(기저 조건, Base Case)에서 멈춘다.

  • 청킹과 코드 읽기

    여러 정보를 의미 있는 덩어리 하나로 묶어 기억하는 것. 아는 것이 많을수록 코드를 큰 덩어리로 읽는다.

  • 코드 냄새

    당장 버그는 아니지만 이해나 변경 비용을 높이는 구조적 신호. 리팩터링을 언제 시작하고 멈출지에 정확한 공식은 없어서, 냄새라는 어휘로 직관을 공유한다.

  • 언어적 안티패턴

    이름, 타입, 주석이 말하는 것과 코드가 실제로 하는 일이 어긋나는 것. 읽는 사람을 잘못된 추측으로 이끈다.

보기 옵션