"빅오 읽기의 90% 는 반복문 읽기야. 순차 반복문은 더해. 중첩 반복문은 곱해. 나머지는 거의 다 각주야."
무거운 일을 다 하는 두 규칙
알고리즘의 복잡도를 찾는 데 미적분은 필요 없어. 반복문을 찾아서 두 규칙을 적용하면 돼:
- 순차 반복문은 더해. n 을 도는 반복문, 그다음 또 n 을 도는 반복문은 n + n = 2n = O(n). 하나 끝나고 하나, 그래서 더하고 — 상수는 버려.
- 중첩 반복문은 곱해. n 을 도는 반복문 안에 또 n 을 도는 게 있으면 n × n = O(n²). 세 겹이면 O(n³). 안쪽은 곱해.
그래서 게임 전체가 이거야: 가장 깊은 중첩을 찾아, 제일 많이 곱해진 항이 지배하니까. 삼중 중첩 반복문 하나랑 따로 떨어진 단일 반복문 쉰 개를 가진 함수는 O(n³) 이야 — 단일 반복문들은 세제곱 옆에서 잡음이야.
log n 의 반전
패턴을 깨는 반복문 모양 하나는 외워둘 가치가 있어: 매 단계 남은 일을 절반으로 자르는 반복문은 O(n) 이 아니라 O(log n) 이야. n 에서 시작해 n/2, n/4, n/8 … 로 반 잘라 가면 약 log₂(n) 단계에 1 에 도달해. 십억 개면 겨우 30 단계. 이게 이진 탐색이랑 균형 트리 뒤의 비밀이고, 처음 보면 "log n" 이 사기처럼 느껴지는 이유야.
복잡도를 읽으려면: 반복문을 찾아. 순차 반복문은 더하고 (제일 큰 것만 남겨). 중첩 반복문은 곱하고 (깊이를 세). 반 자르는 반복문은 log n. 네가 분석할 코드 대부분이 여기에 들어가.
함정: 숨은 반복문
제일 치명적인 O(n²) 은 단일 반복문처럼 보이는 코드에 숨어. 반복문 안에 if x in my_list 를 쓰면, 반복문을 중첩한 거야 — in 이 리스트에선 몰래 리스트 전체를 훑거든. 보이는 반복문 하나, 안 보이는 반복문 하나, 총 n². 같은 함정이 반복문 안 슬라이싱, 반복문 안 += 로 문자열 짓기, list.insert(0, ...) 반복 호출에도 숨어. 네가 호출하는 연산의 비용을 아는 게 복잡도 분석의 절반이야 — 아래 Python wiki 링크는 북마크할 가치 있어.
피파의 고백
한 번은 함수를 깔끔한 단일 반복문으로 "최적화" 하고 자랑스럽게 O(n) 이라고 불렀어. 아빠가 한 줄을 가리켰어:
if item in seen_list. 그 순진한 in 이 매 반복마다 커지는 리스트를 훑고 있었어 — 내 예쁜 단일 반복문은 조용히 O(n²) 이었던 거야. seen_list 를 set 으로 바꾸니 진짜 O(n) 이 됐어. 네가 볼 수 있는 반복문이 늘 네가 가진 반복문은 아니야.