이진 탐색 트리
이진 탐색 트리(BST)는 모든 노드가 "왼쪽 서브트리 < 현재 노드 < 오른쪽 서브트리" 조건을 만족하는 이진 트리입니다. 이 정렬 조건 덕분에 탐색·삽입·삭제 연산을 평균 O(log n)에 수행할 수 있으며, 정렬된 순서로 데이터를 순회할 수 있습니다.
핵심 개념
BST 조건
모든 노드 N에 대해 N의 왼쪽 서브트리에 있는 모든 값은 N보다 작고, 오른쪽 서브트리에 있는 모든 값은 N보다 큽니다. 이 조건이 재귀적으로 모든 서브트리에도 성립해야 합니다. 중복 값을 허용하지 않는 것이 일반적입니다.
핵심 개념
삽입 (Insert)
루트부터 시작해 삽입 값과 현재 노드를 비교합니다. 작으면 왼쪽, 크면 오른쪽으로 이동하며, null을 만나는 위치에 새 노드를 추가합니다. 비교는 트리 높이만큼만 수행하므로 키가 고르게 들어오는 평균적인 경우 O(log n)입니다.
핵심 개념
탐색 (Search)
삽입과 동일한 경로로 타깃 값을 찾습니다. 현재 노드보다 작으면 왼쪽, 크면 오른쪽으로 이동합니다. 값을 찾거나 null에 도달하면 종료합니다. 이진 탐색과 동일한 원리로 평균 O(log n)입니다.
핵심 개념
삭제 3케이스
① 리프 노드: 단순 제거. ② 자식이 1개: 삭제 노드의 부모와 자식을 직접 연결. ③ 자식이 2개: 중위 후계자(Inorder Successor — 오른쪽 서브트리의 최솟값)로 삭제 노드의 값을 대체한 뒤 후계자를 삭제합니다.
핵심 개념
불균형과 편향 트리
정렬된 순서(1→2→3→…)로 삽입하면 모든 노드가 한쪽 방향으로만 추가되어 연결 리스트와 같은 편향 트리(Skewed Tree)가 됩니다. 이 경우 높이가 n이 되어 탐색이 O(n)으로 저하됩니다. 이를 해결하기 위해 AVL 트리, Red-Black 트리 같은 자가 균형 BST가 사용됩니다.
핵심 개념
중위 순회와 정렬
BST를 중위 순회(Left → Root → Right)하면 저장된 값이 오름차순으로 정렬되어 나옵니다. 이 성질을 이용해 BST는 정렬된 데이터의 동적 유지, 범위 탐색(특정 범위의 모든 값 조회)에 활용됩니다.
이진 탐색 트리가 푸는 문제
이진 탐색 트리(BST)는 '정렬된 상태를 유지하면서 삽입과 삭제도 빠르게' 하려는 자료구조입니다. 정렬된 배열은 이진 탐색으로 빨리 찾지만 중간에 끼워 넣기 어렵고, 연결 리스트는 끼워 넣기는 쉽지만 찾기가 느립니다. BST는 트리 모양으로 둘의 장점을 노립니다. 다만 트리가 한쪽으로 치우치면 이점이 사라지므로 균형이 중요합니다.
숫자로 확인하기
자주 하는 오해
면접에서 이렇게 물어요
면접 질문
주어진 트리가 BST인지 어떻게 확인하나요?
중위 순회 결과가 오름차순인지 보거나, 재귀로 내려가며 각 노드가 허용 범위 안에 있는지 확인합니다. 자식과만 비교하면 안 됩니다.
면접 질문
BST와 해시 테이블은 언제 각각 쓰나요?
단순한 키 조회가 많으면 평균 O(1)인 해시 테이블이 유리합니다. 정렬된 순서 순회, 범위 조회, 최솟값·최댓값, 전임자·후임자가 필요하면 BST가 맞습니다.
면접 질문
자가 균형 BST는 왜 필요한가요?
삽입과 삭제 때 회전 같은 연산으로 높이를 로그 수준으로 유지해서, 입력 순서와 상관없이 최악의 경우에도 O(log n)을 보장하려는 것입니다.
면접 질문
AVL과 Red-Black 트리의 차이는 무엇인가요?
AVL은 모든 노드에서 두 서브트리 높이 차이를 1 이하로 엄격하게 유지해 탐색에 유리하고, Red-Black은 색 규칙으로 느슨하게 균형을 맞춰 삭제 때 재균형(회전) 횟수의 상한이 작습니다.
실제 시스템에서는
TreeMap 문서는 Red-Black 트리 기반의 NavigableMap 구현이며 containsKey, get, put, remove에 log(n) 시간을 보장한다고 설명합니다.
함께 보면 좋은 토픽
참고 자료
Java — BST 노드 정의 및 삽입
/* ── BST 노드 ───────────────────────────────────────────── */ public class BSTNode { int val; BSTNode left, right; BSTNode(int val) { this.val = val; } } /* ── 삽입: 루트부터 비교하며 null 위치를 찾아 삽입 ─────── */ public BSTNode insert(BSTNode root, int val) { if (root == null) return new BSTNode(val); // 삽입 위치 if (val < root.val) root.left = insert(root.left, val); // 왼쪽으로 else if (val > root.val) root.right = insert(root.right, val); // 오른쪽으로 return root; // 이미 존재하면 무시 } /* ── 탐색 ────────────────────────────────────────────────── */ public boolean search(BSTNode root, int val) { if (root == null) return false; // 탐색 실패 if (val == root.val) return true; // 탐색 성공 return val < root.val ? search(root.left, val) : search(root.right, val); }
삽입과 탐색 모두 현재 노드와 값을 비교해 왼쪽/오른쪽을 결정하는 동일한 패턴을 씁니다.
재귀 구조 덕분에 각 서브트리를 독립적으로 처리하며, 균형 잡힌 트리에서 높이는 log n이므로 평균 O(log n)입니다.
Java — 삭제 3케이스
public BSTNode delete(BSTNode root, int val) { if (root == null) return null; // 값이 없음 if (val < root.val) { root.left = delete(root.left, val); // 왼쪽 서브트리에서 삭제 } else if (val > root.val) { root.right = delete(root.right, val); // 오른쪽 서브트리에서 삭제 } else { // 삭제 대상 발견 if (root.left == null) return root.right; // Case 1,2: 왼쪽 없음 if (root.right == null) return root.left; // Case 2: 오른쪽 없음 // Case 3: 자식 2개 → 중위 후계자(오른쪽 서브트리의 최솟값)로 대체 BSTNode successor = findMin(root.right); root.val = successor.val; // 값만 교체 root.right = delete(root.right, successor.val); // 후계자 제거 } return root; } private BSTNode findMin(BSTNode node) { while (node.left != null) node = node.left; // 가장 왼쪽 = 최솟값 return node; }
Case 1(리프): 자식이 없으므로 null을 반환해 부모와의 연결을 끊습니다.
Case 2(자식 1개): 남은 자식을 반환해 부모가 손자를 직접 가리키게 합니다.
Case 3(자식 2개): 중위 후계자(오른쪽 서브트리 최솟값)의 값을 현재 노드에 복사한 뒤, 후계자를 재귀적으로 삭제합니다.
후계자는 왼쪽 자식이 없으므로 Case 1 또는 2로 처리됩니다.
이 재귀 패턴은 BST 조건을 유지하면서 O(h) 시간 안에 삭제를 완료합니다.
Java — TreeMap으로 BST 활용
/* ── TreeMap: Red-Black Tree 기반 BST — 항상 O(log n) 보장 */ TreeMap<Integer, String> map = new TreeMap<>(); map.put(50, "root"); map.put(30, "left"); map.put(70, "right"); // 정렬 순서 보장 map.firstKey(); // 30 — 최솟값 O(log n) map.lastKey(); // 70 — 최댓값 O(log n) // 범위 탐색: 30 이상 70 미만 키를 정렬 순서로 map.subMap(30, 70).forEach((k, v) -> System.out.println(k + " → " + v) ); // 50보다 작은 가장 큰 키 (전임자) map.lowerKey(50); // 30 // 50보다 크거나 같은 가장 작은 키 (후임자) map.ceilingKey(50); // 50 /* ── HashMap vs TreeMap 비교 ─────────────────────────────── */ // HashMap : 평균 O(1) 탐색/삽입, 순서 없음 // TreeMap : O(log n) 탐색/삽입, 정렬 순서 보장, 범위 탐색 지원
Java의 TreeMap/TreeSet은 Red-Black Tree를 사용해 편향 트리 문제 없이 항상 O(log n)을 보장합니다.
정렬 순서가 필요하거나 범위 탐색(subMap, headMap, tailMap), 전임자/후임자 조회가 필요하면 TreeMap을, 순서가 필요 없고 단순 탐색 속도만 중요하면 HashMap을 사용합니다.
Q1
이진 탐색 트리(BST)의 핵심 규칙은?
Q2
BST가 한쪽으로 치우쳐 삽입되면 어떤 문제가 생기나요?
Q3
BST에서 삭제하려는 노드가 자식을 2개 가진 경우 일반적인 처리 방법은?