개념

왼쪽 < 부모 < 오른쪽 규칙을 만족하는 이진 트리

효율적인 탐색, 삽입, 삭제를 위한 정렬된 트리 구조


BST의 특징

        8
       / \
      3   10
     / \    \
    1   6    14
       / \   /
      4   7 13

규칙

  • 왼쪽 서브트리: 부모보다 작은 값
  • 오른쪽 서브트리: 부모보다 큰 값
  • 중위 순회: 오름차순으로 정렬

시간복잡도

연산평균최악
탐색O(log n)O(n)
삽입O(log n)O(n)
삭제O(log n)O(n)

최악의 경우: 편향 트리 (한쪽으로만 치우침)


장점과 단점

장점

  • 빠른 탐색: 평균 O(log n)
  • 정렬 유지: 중위 순회로 정렬된 순서
  • 범위 검색: 특정 범위의 값을 효율적으로 찾기

단점

  • 편향 가능: 최악의 경우 O(n)
  • 균형 유지 불가: 삽입 순서에 따라 성능 저하
  • 해결책: AVL 트리, Red-Black 트리 등 자가 균형 트리

Python 구현

1. BST 클래스

class TreeNode:
    def __init__(self, val=0):
        self.val = val
        self.left = None
        self.right = None
 
class BST:
    def __init__(self):
        self.root = None

2. 삽입 (Insert)

def insert(self, val):
    self.root = self._insert_recursive(self.root, val)
 
def _insert_recursive(self, node, val):
    # Base Case: 빈 자리 발견
    if not node:
        return TreeNode(val)
 
    # 값 비교하여 왼쪽/오른쪽 결정
    if val < node.val:
        node.left = self._insert_recursive(node.left, val)
    elif val > node.val:
        node.right = self._insert_recursive(node.right, val)
    # val == node.val이면 중복이므로 삽입 안 함
 
    return node
 
# 반복문 버전
def insert_iterative(self, val):
    if not self.root:
        self.root = TreeNode(val)
        return
 
    current = self.root
    while True:
        if val < current.val:
            if not current.left:
                current.left = TreeNode(val)
                break
            current = current.left
        elif val > current.val:
            if not current.right:
                current.right = TreeNode(val)
                break
            current = current.right
        else:
            break  # 중복
 
# 시간복잡도: O(log n) 평균, O(n) 최악

def search(self, val):
    return self._search_recursive(self.root, val)
 
def _search_recursive(self, node, val):
    # Base Case
    if not node or node.val == val:
        return node
 
    # 값 비교
    if val < node.val:
        return self._search_recursive(node.left, val)
    else:
        return self._search_recursive(node.right, val)
 
# 반복문 버전
def search_iterative(self, val):
    current = self.root
 
    while current:
        if val == current.val:
            return current
        elif val < current.val:
            current = current.left
        else:
            current = current.right
 
    return None
 
# 시간복잡도: O(log n) 평균, O(n) 최악

4. 삭제 (Delete)

def delete(self, val):
    self.root = self._delete_recursive(self.root, val)
 
def _delete_recursive(self, node, val):
    if not node:
        return None
 
    # 값 찾기
    if val < node.val:
        node.left = self._delete_recursive(node.left, val)
    elif val > node.val:
        node.right = self._delete_recursive(node.right, val)
    else:
        # 삭제할 노드 발견
 
        # Case 1: 자식이 없거나 하나만 있는 경우
        if not node.left:
            return node.right
        if not node.right:
            return node.left
 
        # Case 2: 자식이 둘 다 있는 경우
        # 오른쪽 서브트리의 최솟값을 찾아 대체
        min_node = self._find_min(node.right)
        node.val = min_node.val
        node.right = self._delete_recursive(node.right, min_node.val)
 
    return node
 
def _find_min(self, node):
    while node.left:
        node = node.left
    return node
 
# 시간복잡도: O(log n) 평균, O(n) 최악

5. 순회 및 유틸리티

def inorder(self):
    """중위 순회: 오름차순 정렬"""
    result = []
    self._inorder_recursive(self.root, result)
    return result
 
def _inorder_recursive(self, node, result):
    if node:
        self._inorder_recursive(node.left, result)
        result.append(node.val)
        self._inorder_recursive(node.right, result)
 
def find_min(self):
    """최솟값 찾기"""
    if not self.root:
        return None
    current = self.root
    while current.left:
        current = current.left
    return current.val
 
def find_max(self):
    """최댓값 찾기"""
    if not self.root:
        return None
    current = self.root
    while current.right:
        current = current.right
    return current.val

자주 나오는 문제 유형

1. BST 검증

문제: 주어진 트리가 유효한 BST인지 확인

def is_valid_bst(root):
    def validate(node, min_val, max_val):
        if not node:
            return True
 
        # 범위 체크
        if node.val <= min_val or node.val >= max_val:
            return False
 
        # 왼쪽은 max를 현재 노드로, 오른쪽은 min을 현재 노드로
        return (validate(node.left, min_val, node.val) and
                validate(node.right, node.val, max_val))
 
    return validate(root, float('-inf'), float('inf'))
 
# 시간복잡도: O(n)
# 공간복잡도: O(h) - h: 높이

핵심: 각 노드가 유효한 범위 내에 있는지 확인


2. K번째로 작은 원소

문제: BST에서 K번째로 작은 원소 찾기

def kth_smallest(root, k):
    # 중위 순회로 k번째 원소 찾기
    count = 0
    result = None
 
    def inorder(node):
        nonlocal count, result
 
        if not node or result is not None:
            return
 
        # 왼쪽 방문
        inorder(node.left)
 
        # 현재 노드 처리
        count += 1
        if count == k:
            result = node.val
            return
 
        # 오른쪽 방문
        inorder(node.right)
 
    inorder(root)
    return result
 
# 시간복잡도: O(k)
# 공간복잡도: O(h)

핵심: 중위 순회로 오름차순 접근, K번째에서 중단


3. BST를 균형 BST로 변환

문제: 정렬된 배열을 높이 균형 BST로 변환

def sorted_array_to_bst(nums):
    def build(left, right):
        if left > right:
            return None
 
        # 중간값을 루트로
        mid = (left + right) // 2
        node = TreeNode(nums[mid])
 
        # 재귀적으로 서브트리 생성
        node.left = build(left, mid - 1)
        node.right = build(mid + 1, right)
 
        return node
 
    return build(0, len(nums) - 1)
 
# 테스트
# nums = [-10, -3, 0, 5, 9]
#       0
#      / \
#    -3   9
#    /   /
#  -10  5
 
# 시간복잡도: O(n)
# 공간복잡도: O(log n)

핵심: 중간값을 루트로 하여 균형 유지


4. 두 BST 노드의 합

문제: BST에서 합이 K가 되는 두 노드 찾기

def find_target(root, k):
    # 중위 순회로 정렬된 리스트 생성
    def inorder(node):
        if not node:
            return []
        return inorder(node.left) + [node.val] + inorder(node.right)
 
    nums = inorder(root)
 
    # 투 포인터로 합 찾기
    left, right = 0, len(nums) - 1
 
    while left < right:
        total = nums[left] + nums[right]
        if total == k:
            return True
        elif total < k:
            left += 1
        else:
            right -= 1
 
    return False
 
# 시간복잡도: O(n)
# 공간복잡도: O(n)

핵심: BST를 정렬된 배열로 변환 후 투 포인터


5. BST에서 범위 합

문제: L 이상 R 이하 값의 합 구하기

def range_sum_bst(root, low, high):
    if not root:
        return 0
 
    total = 0
 
    # 현재 노드가 범위 내
    if low <= root.val <= high:
        total += root.val
 
    # 왼쪽 탐색 (현재 값이 low보다 크면)
    if root.val > low:
        total += range_sum_bst(root.left, low, high)
 
    # 오른쪽 탐색 (현재 값이 high보다 작으면)
    if root.val < high:
        total += range_sum_bst(root.right, low, high)
 
    return total
 
# 테스트
#       10
#      /  \
#     5    15
#    / \     \
#   3   7    18
# range_sum_bst(root, 7, 15) → 32 (7 + 10 + 15)
 
# 시간복잡도: O(n)

핵심: BST 특성을 이용한 가지치기


6. BST의 최소 공통 조상 (LCA)

문제: BST에서 두 노드의 최소 공통 조상 찾기

def lowest_common_ancestor_bst(root, p, q):
    # p와 q가 모두 왼쪽에 있으면 왼쪽으로
    if p.val < root.val and q.val < root.val:
        return lowest_common_ancestor_bst(root.left, p, q)
 
    # p와 q가 모두 오른쪽에 있으면 오른쪽으로
    if p.val > root.val and q.val > root.val:
        return lowest_common_ancestor_bst(root.right, p, q)
 
    # 분기점 → 현재 노드가 LCA
    return root
 
# 반복문 버전
def lca_iterative(root, p, q):
    while root:
        if p.val < root.val and q.val < root.val:
            root = root.left
        elif p.val > root.val and q.val > root.val:
            root = root.right
        else:
            return root
 
# 시간복잡도: O(h) - h: 높이
# 공간복잡도: O(1) - 반복문

핵심: BST 특성으로 일반 트리의 O(n)을 O(h)로 개선


추천 연습 문제

기초

중급

고급


핵심 요약

개념설명
BST 규칙왼쪽 < 부모 < 오른쪽
중위 순회오름차순 정렬
평균 시간O(log n)
최악 시간O(n) - 편향 트리

BST vs 이진 트리

특성이진 트리BST
정렬없음있음
탐색O(n)O(log n) 평균
삽입O(1)O(log n) 평균
중위 순회임의 순서오름차순

BST 문제 해결 패턴

1. 범위 검증 패턴

def validate(node, min_val, max_val):
    if not node:
        return True
 
    if node.val <= min_val or node.val >= max_val:
        return False
 
    return (validate(node.left, min_val, node.val) and
            validate(node.right, node.val, max_val))

2. BST → 정렬 배열 패턴

def inorder_to_array(root):
    result = []
    def inorder(node):
        if node:
            inorder(node.left)
            result.append(node.val)
            inorder(node.right)
    inorder(root)
    return result

3. 정렬 배열 → BST 패턴

def array_to_bst(nums):
    if not nums:
        return None
 
    mid = len(nums) // 2
    root = TreeNode(nums[mid])
    root.left = array_to_bst(nums[:mid])
    root.right = array_to_bst(nums[mid+1:])
    return root

BST 최적화

균형 잡힌 BST

# AVL 트리, Red-Black 트리 등으로 균형 유지
# Python의 sortedcontainers.SortedDict 사용 가능
 
from sortedcontainers import SortedDict
 
sd = SortedDict()
sd[3] = 'three'
sd[1] = 'one'
sd[2] = 'two'
 
print(list(sd.keys()))  # [1, 2, 3]

BST vs Hash Table

특성BSTHash Table
탐색O(log n)O(1)
정렬유지없음
범위 검색효율적비효율적
메모리적음많음

주의사항

  1. 중복 값 처리

    • 보통 중복 허용 안 함
    • 필요시 노드에 카운트 저장
  2. 편향 방지

    • 무작위 순서로 삽입
    • 자가 균형 트리 사용 (AVL, Red-Black)
  3. 삭제 시 주의

    • 자식이 둘인 경우 후속자(successor) 찾기
    • 오른쪽 서브트리의 최솟값 또는 왼쪽 서브트리의 최댓값