C.W.K.
Stream
Lesson 05 of 07 · published

균형 잡기: 트리를 정직하게 유지하기

~12 min · trees, balancing, avl, red-black

Level 0호기심 많은 입문자
0 XP0/85 lessons0/19 achievements
0/100 XP to next level100 XP to go0% complete
"평범한 BST 는 정렬 입력 하나면 연결 리스트로 무너져. 자가 균형 트리는 그걸 안 일어나게 거부해 — 조용히 자기를 다시 빚어서 높이가 절대 폭발 못 하게."

고침: 스스로 재균형하는 트리

지난 lesson 의 악당은 일그러진 트리였어: 정렬 데이터 삽입, O(n) 사슬. 자가 균형 트리가 그걸 풀어, 각 삽입이나 삭제 후 자동으로 재구조화해서, 데이터가 어떤 순서로 오든 높이를 ~log n 에 핀으로 고정해. 결과는 단단한 보장: O(log n) 검색, 삽입, 삭제 — 평균만이 아니라 최악. BST 를 '운 좋으면 빠름' 에서 '빠름, 끝' 으로 바꿔.

메커니즘: 회전 (rotation)

마법은 회전이라는 지역 연산이야. 트리 한쪽이 너무 커지면, 회전이 노드랑 그 자식을 피벗해 — 자식을 승격, 부모를 강등 — 그게 무거운 쪽 높이를 줄이면서 BST 불변식을 보존해 (왼쪽 여전히 작고, 오른쪽 여전히 큼). 회전은 O(1), 순전히 지역 포인터 재연결이고, 어떤 삽입/삭제 후 그거 몇 번이면 트리 전체를 균형으로 유지하기 충분해. 균형 트리를 쓰려고 회전 케이스를 외울 필요는 없지만 — '재균형 = O(1) 회전 몇 번' 을 아는 게 보장이 왜 유지하기 싼지 demystify 해.

유명한 두 맛

  • AVL 트리 — 엄격히 균형 (형제 높이가 많아야 1 차이). 더 빡빡한 균형 = 더 빠른 조회지만 쓰기에 회전 더. 읽기가 지배할 때 좋아.
  • 레드-블랙 트리 — 느슨히 균형 (노드 "색" 이랑 규칙으로), 그래서 삽입/삭제에 회전을 덜 해 약간 더 큰 트리를 대가로. 쓰기가 잦을 때 좋아 — 그래서 대부분 표준 라이브러리를 받쳐: Java 의 TreeMap, C++ 의 std::map, Linux 커널 일부까지.

그리고 디스크 위 데이터엔 (데이터베이스, 파일시스템), 변종이 B-트리: 각 노드가 많은 키를 들어, 트리가 짧고 넓어 — 레벨 적음 = 조회당 느린 디스크 읽기 적음. 네가 쿼리한 모든 관계형 DB 인덱스가 거의 확실히 B-트리야.

자가 균형 트리는 각 삽입/삭제 후 O(1) 회전으로 높이를 ~log n 으로 유지해, O(log n) 최악을 보장해. AVL = 더 빡빡 (읽기-빠름); 레드-블랙 = 더 느슨 (쓰기-빠름, 라이브러리 기본); B-트리 = 디스크용 넓은 노드 (DB 인덱스).

실전 Python 현실

사람들을 놀라게 하는 사실: Python 엔 내장 균형 BST 가 없어. TreeMap 없음, 표준 라이브러리에 std::map 동등물 없음. 그래서 Python 에서 진짜 순서 연산이 필요하면, 손을 뻗어: bisect 모듈 (정렬된 리스트로의 이진 탐색이랑 삽입 — 삽입보다 읽기를 훨씬 많이 할 때 좋아), 또는 서드파티 sortedcontainers 라이브러리 (O(log n) 삽입의 SortedList/SortedDict). 이론은 가 필요한지 알려주고; 생태계는 어느 도구가 실제로 그걸 주는지 알려줘.

피파의 고백

난 Python 의 TreeMap 을 찾으러 갔다가... 없어. 분개했어 — 다른 모든 언어는 표준 라이브러리에 균형 트리가 있잖아! 아빠가 설계 선택을 설명했어: Python 의 dict (해싱) 가 흔한 케이스에 너무 좋아서 순서 맵은 bisect 랑 서드파티에 남겨졌어. 교훈은 트리에 관한 게 아니라; '맞는 구조' 가 '네 언어에서 실제로 가용한 것' 을 포함한다는 거였어 — 이론이랑 생태계 둘 다 결정의 일부야.

Code

회전, 그리고 Python 의 bisect 대체물·python
# 우회전, 개념적으로: BST 순서를 그대로 두고 재균형.
#     y            x
#    / \          / \
#   x   C   -->  A   y
#  / \              / \
# A   B            B   C
# 전: A < x < B < y < C.  후: A < x < B < y < C.  순서 보존!
class N:
    def __init__(s, k): s.k=k; s.left=None; s.right=None

def rotate_right(y):
    x = y.left
    y.left = x.right    # B 가 y 아래로 이동
    x.right = y         # y 가 x 의 오른쪽 자식이 됨
    return x            # x 가 새 서브트리 루트 (이제 왼쪽이 더 짧음)

# 순서 연산용 실전 Python 답: 정렬된 리스트에 bisect 모듈.
import bisect
sorted_keys = [1, 3, 6, 8, 10]
bisect.insort(sorted_keys, 7)        # 정렬 유지 삽입: O(n) 밀기, O(log n) 찾기
print(sorted_keys)                    # [1, 3, 6, 7, 8, 10]
i = bisect.bisect_left(sorted_keys, 7)
print("7 is at index", i)             # 3 — O(log n) 검색
# 무거운 삽입엔, 서드파티 'sortedcontainers' 가 O(log n) 삽입도 줘.
# Python 엔 내장 균형 BST 가 없어 — 이게 관용적 대체물이야.

External links

Exercise

트리 회전이 왜 BST 순서 불변식을 안 깨고 서브트리를 재균형할 수 있는지 설명해 (힌트: 서브트리 A, B, C 가 전후로 어디 떨어지는지 추적). 그다음 골라: 읽기 무거운 순서 맵 (조회 많고, 삽입 드묾) 엔 AVL 트리랑 레드-블랙 트리 중 뭘 선호하고 왜?
Hint
회전은 노드만 재부모화해; 상대 순서 A < x < B < y < C 가 전후로 동일해, 그래서 검색이 여전히 작동. 읽기 무거움 → AVL: 더 빡빡한 균형 = 더 얕은 트리, 더 빠른 조회, 삽입이 드무니 높은 회전 비용을 거의 안 치러.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.