포스트

이진 탐색 트리와 균형 — 한쪽으로 쏠리는 트리를 회전으로 바로 세우기

이진 탐색 트리가 한쪽으로 쏠려 O(n)으로 퇴화하는 문제와, AVL·레드블랙 트리가 회전으로 균형을 잡는 원리. TreeMap·HashMap 트리화 사례와 함께.

이진 탐색 트리와 균형 — 한쪽으로 쏠리는 트리를 회전으로 바로 세우기

정렬된 채로 빠르게 넣고, 찾고, 지운다

배열은 인덱스 접근이 O(1)이지만 중간 삽입이 O(n)이고, 정렬을 유지하려면 매번 자리를 밀어야 합니다. 해시 테이블은 조회가 평균 O(1)이지만 순서가 없습니다 — “100 이상 200 미만”이나 “다음으로 큰 값” 같은 범위·순서 질의를 못 합니다.

이진 탐색 트리(BST) 는 그 빈틈을 메웁니다. 정렬을 유지하면서 삽입·탐색·삭제를 모두 트리 높이 $h$에 비례($O(h)$)하게 처리합니다. 규칙은 단 하나입니다.

모든 노드에서, 왼쪽 서브트리의 모든 값 < 자신 < 오른쪽 서브트리의 모든 값.

탐색은 매 단계 절반을 버린다

값을 찾을 때는 루트에서 시작해, 찾는 값이 작으면 왼쪽, 크면 오른쪽으로 내려갑니다. 이진 탐색과 똑같은 원리로 매 비교마다 후보의 절반을 버립니다. 삽입도 같습니다 — 내려갈 자리를 찾아 잎(leaf)에 매답니다.

삽입: 40 (작으면 왼쪽 ←, 크면 오른쪽 →) 50 30 70 20 40

그리고 중위 순회(in-order traversal) — 왼쪽 → 자신 → 오른쪽 순으로 방문하면, 값이 오름차순으로 정렬되어 나옵니다. BST가 “정렬을 내장한 자료구조”인 이유입니다.

flowchart LR
    subgraph 순회["순회 종류"]
      A["전위 pre-order<br/>자신→왼→오"]
      B["중위 in-order<br/>왼→자신→오 = 정렬!"]
      C["후위 post-order<br/>왼→오→자신"]
    end

문제는 “쏠림” — $h$가 $n$이 되는 순간

$O(h)$가 좋은 건 $h$가 $\log n$일 때입니다. 그런데 이미 정렬된 데이터(1, 2, 3, 4, …)를 순서대로 넣으면 트리가 한쪽으로만 자라 사실상 연결 리스트 가 됩니다. 그러면 $h = n$, 모든 연산이 O(n) 으로 퇴화합니다.

상태높이 $h$탐색/삽입/삭제
균형 잡힌 BST$\Theta(\log n)$$O(\log n)$
한쪽으로 쏠린 BST$\Theta(n)$$O(n)$ — 망함

실무에서 입력은 종종 정렬돼 있거나(타임스탬프·자동증가 ID) 적대적입니다. 그래서 스스로 균형을 잡는 트리가 필요합니다.

회전 — 트리를 한 단계 굴려 키를 낮춘다

균형 트리의 핵심 도구는 회전(rotation) 입니다. 부모-자식 관계를 한 칸 돌려 중위 순서(정렬)는 그대로 유지한 채 높이만 줄입니다. 아래는 오른쪽으로 3단 쏠린 트리가 한 번의 회전으로 가운데를 위로 올려 균형을 회복하는 모습입니다.

회전 전 — 높이 3 (쏠림) 10 20 30 ⟳ 왼쪽 회전 회전 후 — 높이 2 (균형) 20 10 30

회전은 포인터 몇 개만 바꾸는 O(1) 연산입니다. 이 값싼 도구로 어떻게 균형을 정의·복구하느냐에 따라 두 갈래의 명작이 나옵니다.

AVL vs 레드블랙 — 엄격함과 느슨함의 트레이드오프

AVL 트리는 모든 노드에서 좌우 서브트리 높이 차(균형 인수)를 −1, 0, +1로 강제합니다. 삽입·삭제 후 깨지면 회전(LL·RR·LR·RL)으로 즉시 복구. 매우 엄격해 높이가 $1.44\log n$ 이하로 아주 납작 → 조회가 빠릅니다. 대신 수정 때 회전이 잦습니다.

레드블랙 트리(RB) 는 더 느슨합니다. 노드에 빨강/검정 색을 부여하고 다섯 규칙(루트는 검정, 빨강의 자식은 검정, 임의 노드에서 잎까지 검정 노드 수가 동일…)으로 “가장 긴 경로 ≤ 2 × 가장 짧은 경로“만 보장합니다. 높이는 $2\log n$까지 느슨하지만, 삽입·삭제당 회전이 최대 상수 번 → 수정이 빈번할 때 유리.

 AVL레드블랙
균형 엄격도높음(높이차 ≤1)느슨(≤2배 경로)
높이~$1.44\log n$~$2\log n$
조회더 빠름빠름
삽입/삭제 회전많을 수 있음상수 번
대표 사용처읽기 위주 인덱스범용 — 아래 참고

레드블랙 트리는 우리가 매일 쓰는 곳에 박혀 있습니다 — Java TreeMap/TreeSet, C++ std::map/std::set, 리눅스 커널의 CFS 스케줄러(실행 큐), 그리고 Java 8 HashMap이 한 버킷에 충돌이 8개 이상 쌓이면 연결 리스트를 레드블랙 트리로 바꿔 최악을 $O(n)$에서 $O(\log n)$으로 막는 것까지. 디스크 기반이라면 한 노드에 키를 많이 담아 높이를 더 낮춘 B-트리가 DB 인덱스의 표준입니다.

프로덕션에서 마주치는 함정

함정증상해법
일반 BST에 정렬 입력트리가 리스트로 퇴화, O(n)균형 트리(TreeMap) 또는 입력 셔플
순서 필요 없는데 트리불필요한 $\log n$ 오버헤드순서 불필요면 해시 테이블
재귀 순회 깊이 폭발쏠린 트리에서 스택 오버플로반복+명시적 스택
디스크에 이진 트리노드마다 랜덤 I/O 폭증디스크는 B-트리/B+트리(팬아웃↑)
삭제 구현 버그두 자식 노드 삭제 시 트리 깨짐중위 후속자(successor)로 대체 후 삭제

면접/리뷰 단골 질문

  • Q. BST가 최악 O(n)이 되는 경우? → 정렬·역정렬 입력을 일반 BST에 넣으면 한쪽으로 쏠려 높이 $n$. 그래서 균형 트리가 필요.
  • Q. 회전이 정렬을 깨지 않는 이유? → 회전은 부모-자식만 한 칸 돌리고 중위 순서를 보존한다. O(1).
  • Q. AVL vs 레드블랙 언제 무엇? → 조회 위주면 더 납작한 AVL, 삽입·삭제가 잦으면 회전이 상수인 레드블랙. 범용 라이브러리는 대부분 RB.
  • Q. 해시 테이블 두고 왜 트리? → 트리는 순서·범위·다음 값 질의가 가능(중위 순회=정렬). 해시는 순서가 없다.
  • Q. DB 인덱스는 왜 B-트리? → 디스크는 블록 단위 I/O. 한 노드에 키를 많이 담아 팬아웃을 키우면 높이가 낮아져 디스크 접근 횟수가 준다.

정리

  • BST는 “왼쪽<자신<오른쪽” 규칙으로 정렬을 유지하며 삽입·탐색·삭제를 $O(h)$에 처리하고, 중위 순회 = 정렬이다.
  • 쏠리면 $h=n$으로 퇴화 → 회전(O(1), 정렬 보존)으로 균형을 잡는다.
  • AVL(엄격·조회 유리) vs 레드블랙(느슨·수정 유리). RB는 TreeMap·std::map·리눅스 CFS·Java HashMap 트리화의 기반.
  • 순서가 필요 없으면 해시 테이블, 디스크면 B-트리. 자료구조 선택은 질의 패턴이 결정한다.

이전 글은 재귀와 분할정복이었습니다. 다음 글부터는 노드가 한 줄·한 갈래가 아니라 그물처럼 얽힌 그래프와 그 순회로 나아갑니다.

</content> </invoke>

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

시리즈 · 알고리즘 A-Z (20편)

  1. 복잡도 분석 — Big-O, 점근 표기, 그리고 분할상환이라는 거짓말 같은 진실
  2. 배열과 연결 리스트 — 같은 O(n)인데 왜 한쪽이 수십 배 느린가
  3. 스택·큐·힙 — 접근 규칙 하나가 만드는 강력함
  4. 해시 테이블 — 평균 O(1)의 비밀과 그 평균이 깨지는 순간
  5. 비교 정렬 — 퀵·머지·힙, 그리고 Ω(n log n)이라는 넘을 수 없는 벽
  6. 선형 시간 정렬 — 비교를 버리면 Ω(n log n) 벽이 사라진다
  7. 이진 탐색 — 반씩 버리는 마법, 그리고 "답을 이분하라"
  8. 재귀와 분할정복 — 콜스택에 쌓이는 신뢰, 마스터 정리로 푸는 비용
  9. 이진 탐색 트리와 균형 — 한쪽으로 쏠리는 트리를 회전으로 바로 세우기 지금 보는 글
  10. 그래프 순회 — BFS의 물결, DFS의 깊이, 그리고 위상정렬
  11. 최단 경로 — 다익스트라의 확신, A*의 직감, 벨만-포드의 인내
  12. 최소 신장 트리와 Union-Find — 가장 싸게 잇기, 그리고 거의 O(1)의 마법
  13. 동적계획법 — 같은 계산을 두 번 하지 않는다는 단 하나의 원칙
  14. 그리디 — 눈앞의 최선만 집었는데 전체가 최적일 때
  15. 문자열 매칭 — 어긋났을 때 얼마나 똑똑하게 다시 시작하는가
  16. 확률적 자료구조 — 정확함을 메모리와 맞바꾸는 기술 (Bloom·HyperLogLog·Count-Min)
  17. 일관된 해싱 — 노드가 들고날 때 데이터를 통째로 옮기지 않는 법
  18. 합의 알고리즘 — 죽는 서버들 사이에서 하나의 진실을 정하는 법 (Raft·Paxos)
  19. 레이트 리미팅과 스트리밍 알고리즘 — 폭주를 막고, 끝없는 흐름에서 표본을 뽑다
  20. 의미로 검색하다 — 벡터 검색(ANN·HNSW)과 PageRank, 그리고 시리즈의 끝