7.1 KiB
7.1 KiB
id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
| id | title | category | status | source_trust_level | verification_status | created_at | updated_at | tags | tech_stack | applied_in | aliases | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| cs-big-o-practical | Big-O 실전 — 실제 성능 / 측정 / 함정 | Coding | draft | B | conceptual | 2026-05-09 | 2026-05-09 |
|
|
|
Big-O 실전
Big-O 가 알고리즘의 핵심 지표. 그러나 hidden constant + cache locality + 입력 크기 도 중요. O(N) 가 O(log N) 보다 느릴 수 있다 (작은 N).
📖 핵심 개념
- O(1): 상수.
- O(log N): 이진 검색.
- O(N): linear scan.
- O(N log N): merge sort, sort.
- O(N²): nested loop.
- O(2^N), O(N!): 거의 사용 X (작은 N).
💻 코드 패턴
자주 쓰는 자료구조 복잡도
Array (JS):
push/pop: O(1) amortized
shift/unshift: O(N) — 큰 시작 element 이동
index access: O(1)
indexOf: O(N)
Map / Set (V8):
get/set/has: O(1) average (hash collision 시 worst O(N))
iteration: insertion order
Object:
property access: O(1) average
LinkedList:
prepend/append: O(1)
index access: O(N)
Heap (priority queue):
insert/pop: O(log N)
Binary search:
on sorted array: O(log N)
Sort:
Array.sort: O(N log N) (TimSort in V8)
Common 변환
// O(N²) — nested
for (const x of arr) {
for (const y of arr) {
if (x.id === y.parent) ...;
}
}
// O(N) with Map
const byId = new Map(arr.map(x => [x.id, x]));
for (const x of arr) {
const parent = byId.get(x.parent);
if (parent) ...;
}
// O(N²) — array.includes
for (const x of arr) {
if (otherArr.includes(x.id)) ...; // O(M) per loop
}
// O(N+M)
const set = new Set(otherArr);
for (const x of arr) {
if (set.has(x.id)) ...; // O(1)
}
Hidden constant
O(log N) binary search:
- ~20 comparison for 1M items
- 매 comparison = 비교 + index calc
O(N) linear with simd:
- 1M cmp = 100ns 같은 cache friendly
→ 작은 N (<100) = 상수 차이 큼.
N = 100K → log N (17) vs N = 차이 명확.
Cache locality
// ✅ Sequential access — cache 친화
for (let i = 0; i < arr.length; i++) sum += arr[i];
// ❌ Random — cache miss
for (const i of shuffled) sum += arr[i];
// → 5-20x 느림
Amortized
// Array.push: 보통 O(1).
// 가끔 capacity 확장 → 모두 copy = O(N).
// 평균 O(1) (amortized).
// Hash map resize: 같은 원리.
작은 vs 큰 N
N = 10: O(N²) = 100, OK.
N = 1000: O(N²) = 1M, 느려질 수 있음.
N = 1M: O(N²) = 10^12, 절대 X. O(N log N).
N = 1G: O(N) 도 비쌈. 분산.
→ 입력 크기 모르면 안전하게 좋은 알고리즘.
측정
const t = performance.now();
const result = myFunction(input);
console.log('took', performance.now() - t, 'ms');
// 더 정확
const COUNT = 10000;
let total = 0;
for (let i = 0; i < COUNT; i++) {
const t = performance.now();
myFunction(input);
total += performance.now() - t;
}
console.log('avg', total / COUNT, 'ms');
Common O(N log N)
// Sort
arr.sort((a, b) => a.value - b.value);
// 그 후 binary search
function find(target: number): number | undefined {
let lo = 0, hi = arr.length - 1;
while (lo <= hi) {
const mid = (lo + hi) >> 1;
if (arr[mid].value === target) return arr[mid];
if (arr[mid].value < target) lo = mid + 1;
else hi = mid - 1;
}
}
→ Sort 한 번 + N 검색 = O(N log N). N 검색 X = O(N²).
Top-K (heap)
import Heap from 'heap-js';
function topK(arr: number[], k: number): number[] {
const minHeap = new Heap<number>();
for (const x of arr) {
minHeap.push(x);
if (minHeap.size() > k) minHeap.pop(); // 가장 작은 제거
}
return minHeap.toArray(); // top K
}
// O(N log K) — N log N (sort) 보다 빠름.
Sliding window
// 합 N 길이 array 의 K 길이 sub 의 max
function maxSumWindow(arr: number[], k: number): number {
let sum = 0;
for (let i = 0; i < k; i++) sum += arr[i];
let max = sum;
for (let i = k; i < arr.length; i++) {
sum += arr[i] - arr[i - k]; // O(1) update
max = Math.max(max, sum);
}
return max;
}
// O(N) — 매번 sum 다시 X.
Two pointer
// Sorted array — sum = target?
function twoSum(arr: number[], target: number): [number, number] | null {
let lo = 0, hi = arr.length - 1;
while (lo < hi) {
const sum = arr[lo] + arr[hi];
if (sum === target) return [lo, hi];
if (sum < target) lo++;
else hi--;
}
return null;
}
// O(N).
Dynamic programming
// Fibonacci O(2^N) → O(N) memo
const memo = new Map<number, number>();
function fib(n: number): number {
if (n <= 1) return n;
if (memo.has(n)) return memo.get(n)!;
const r = fib(n - 1) + fib(n - 2);
memo.set(n, r);
return r;
}
Graph
// BFS — O(V + E)
function bfs(start: Node) {
const visited = new Set<Node>();
const queue = [start];
while (queue.length > 0) {
const node = queue.shift()!; // ❌ O(N) shift
// 또는 더 나은 = deque 또는 index
if (visited.has(node)) continue;
visited.add(node);
queue.push(...node.neighbors);
}
}
→ Array shift 가 O(N). 큰 BFS = deque library.
When to optimize
1. Profile first — hot path 만.
2. Big-O 가 일반 답.
3. Cache / locality 가 micro-opt.
4. Algorithm > implementation.
"Premature optimization is the root of all evil." (Knuth)
But: O(N²) → O(N) 는 premature 가 아님.
V8 (JavaScript) 특이
.shift() / .unshift(): O(N).
.push() / .pop(): O(1) amortized.
Object property access:
- 같은 shape: O(1)
- 다른 shape mix: 느림 (위 V8 문서)
Memory complexity
// O(N²) memory
const matrix: number[][] = [];
for (let i = 0; i < N; i++) {
matrix[i] = [];
for (let j = 0; j < N; j++) matrix[i][j] = ...;
}
// 큰 N (10K+) = OOM 가능.
Algorithm 선택 cheat sheet
Search:
Sorted: binary search O(log N)
Unsorted: linear O(N) 또는 hash set O(1)
Sort:
General: TimSort (V8) O(N log N)
Small (<10): insertion O(N²) but 빠름
Distinct integers: counting / radix O(N)
Top-K:
K << N: heap O(N log K)
K close to N: sort O(N log N)
Group:
reduce / Map O(N)
Distinct:
Set O(N)
Combinations: backtracking O(2^N) — 작은 N 만.
🤔 의사결정 기준
| 입력 크기 | 알고리즘 |
|---|---|
| <100 | 어떤 거나 OK |
| <10K | O(N²) 일부 가능 |
| <1M | O(N log N) 안정 |
| <1B | O(N) 또는 분산 |
| 1B+ | 분산 + sampling |
❌ 안티패턴
Array.includesin loop: O(N²). Set / Map.arr.shift()반복: O(N²). Index 또는 deque.- 모든 곳 micro-opt: 가독성 잃음.
- Big-O 무시 + V8 trick: 알고리즘 우선.
- Sort 매번: cache.
- Recursion + 작은 N — overhead 가 cost: iterative.
- JSON.parse 큰 string: O(N) but 큰 constant. stream parser.
🤖 LLM 활용 힌트
- Big-O 우선 — 그 후 micro.
- Set / Map = 80% 개선의 답.
- Profile → hot path.