
BST, 즉 Binary Search Tree에서 root를 remove하는데에는 2가지 방법이 있습니다.
predecessor와 successor가 있는데 두 차이점과 어떻게 구한하는지를 적어보려 합니다.
BST 삭제가 어려운 이유
BST Delete가 어려운 근본적인 이유는 단순합니다. 삭제 후에도 BST의 핵심 성질을 반드시 지켜야 하기 때문입니다.
BST의 성질: Left 서브트리 < 현재 노드 < Right 서브트리
삽입은 이 규칙에 따라 내려가다가 빈 자리에 꽂으면 끝지만, 근데 삭제는 다릅니다. 노드를 없애면 그 자리가 비어버리고, 그 아래 연결된 서브트리들이 붕 뜨게 됩니다. 이걸 BST 성질을 깨지 않으면서 다시 연결해야 합니다.
BST 노드 삭제 과정은 크게 세 가지로 나뉩니다.
| Case 1 | 자식이 없는 노드 (리프 노드) | 그냥 지운다. |
| Case 2 | 자식이 1개인 노드 | 지운 자리에 자식 하나를 올린다. |
| Case 3 | 자식이 2개인 노드 | Predecessor 또는 Successor를 사용. |
아래와 같은 BST가 있다고 해봅시다.
50
/ \
30 70
/ \ / \
20 40 60 80
여기서 루트 노드 50을 삭제하려고 합니다.
50을 그냥 없애버리면 30과 70이 연결을 잃습니다. 둘 중 하나를 루트로 올리면 되지 않을까요? 예를 들어 70을 루트로 올리면?
70
/ \
30 80
/ \
20 40
60은?? → 어디 갔지?
60이 사라집니다. BST 성질도 깨지고, 데이터도 잃습니다. 그래서 단순히 자식 중 하나를 올리는 방법은 쓸 수 없습니다.
핵심 질문: "50 자리에 들어올 수 있는 값은 어떤 값이어야 할까?"
50 자리에 오는 값은 왼쪽 서브트리(20, 30, 40)보다 크고, 오른쪽 서브트리(60, 70, 80)보다 작아야 합니다. 이 조건을 만족하는 값은 트리 안에 딱 두 개밖에 없어요. 바로 40(Predecessor) 과 60(Successor) 입니다.
BST Predecessor
Predecessor = 삭제할 노드보다 작은 값들 중에서 가장 큰 값
"왼쪽으로 한 번, 그 다음 오른쪽으로 끝까지"
왼쪽 서브트리에서 가장 오른쪽에 있는 노드가 Predecessor입니다.
위 예시에서 50의 Predecessor를 찾는 과정:
- 왼쪽으로 이동 → 30
- 오른쪽으로 끝까지 → 40 (40의 오른쪽 자식이 없으므로 멈춤)
- Predecessor = 40
Predecessor의 특징:
- 왼쪽 서브트리의 최댓값이므로, 왼쪽 서브트리의 어떤 값보다도 큽니다.
- 삭제 노드보다 작으므로, 오른쪽 서브트리의 어떤 값보다도 작습니다.
- 그래서 삭제 노드 자리에 들어와도 BST 성질이 유지됩니다.
// C++ - Predecessor 찾기
Node* findPredecessor(Node* root) {
Node* current = root->left; // 왼쪽으로 한 번
while (current && current->right) // 오른쪽 끝까지
current = current->right;
return current;
}
BST Successor
Successor = 삭제할 노드보다 큰 값들 중에서 가장 작은 값
Predecessor의 반대 방향입니다.
"오른쪽으로 한 번, 그 다음 왼쪽으로 끝까지"
오른쪽 서브트리에서 가장 왼쪽에 있는 노드가 Successor입니다.
위 예시에서 50의 Successor를 찾는 과정:
- 오른쪽으로 이동 → 70
- 왼쪽으로 끝까지 → 60 (60의 왼쪽 자식이 없으므로 멈춤)
- Successor = 60
// C++ - Successor 찾기
Node* findSuccessor(Node* root) {
Node* current = root->right; // 오른쪽으로 한 번
while (current && current->left) // 왼쪽 끝까지
current = current->left;
return current;
}
50 ← 삭제 대상
/ \
30 70
/ \ / \
20 40 60 80
- Predecessor = 40 (왼→오른쪽 끝)
- Successor = 60 (오른→왼쪽 끝)
두 방법의 차이 | O(N)
| 대체 노드 위치 | 왼쪽 서브트리의 최댓값 | 오른쪽 서브트리의 최솟값 |
| 탐색 방향 | 왼쪽 → 오른쪽 끝 | 오른쪽 → 왼쪽 끝 |
| 삭제 후 영향 | 왼쪽 서브트리 높이 감소 가능 | 오른쪽 서브트리 높이 감소 가능 |
| BST 성질 유지 | 유지됨 | 유지됨 |
| 삭제 후 트리 모양 | 서로 다름 | 서로 다름 |
| 정답/오답 | 정답 | 정답 |
| 최선 (균형 잡힌 트리) | O(log n) | 트리 높이가 log n |
| 평균 | O(log n) | 랜덤 입력 기준 |
| 최악 (편향 트리) | O(n) | 한쪽으로만 치우친 경우 |
BST 삭제 알고리즘 자체는 결국 트리 높이에 비례합니다. Predecessor나 Successor를 찾는 탐색이 트리 높이만큼 내려가기 때문입니다.
균형이 잡혀 있으면 O(log n), 최악의 경우(모든 노드가 한 방향으로 삽입된 경우)엔 O(n)이 됩니다.
Predecessor: 왼→ 오른쪽 끝 (L → R_end)
Successor: 오→ 왼쪽 끝 (R → L_end)
더 쉽게 외우는 방법:
- Predecessor = Previous (이전) → 나보다 작은 것 중 최대 → 왼쪽 서브트리 최댓값
- Successor = Succeeding (다음) → 나보다 큰 것 중 최소 → 오른쪽 서브트리 최솟값
(방향 기준):
P는 L-R: Predecessor → Left로 가서 Right 끝
S는 R-L: Successor → Right로 가서 Left 끝
이 두 문장만 외우고 시험장에 들어가면 BST 삭제 문제는 절대 틀리지 않습니다.
전체 C++ 코드
#include <iostream>
using namespace std;
struct Node {
int data;
Node *left, *right;
Node(int val) : data(val), left(nullptr), right(nullptr) {}
};
// Predecessor 왼쪽 → 오른쪽 끝
Node* findPredecessor(Node* node) {
Node* current = node->left;
while (current && current->right)
current = current->right;
return current;
}
// Successor 오른쪽 → 왼쪽 끝
Node* findSuccessor(Node* node) {
Node* current = node->right;
while (current && current->left)
current = current->left;
return current;
}
'자료구조' 카테고리의 다른 글
| 파이썬 sys.stdin.readline 써도 시간초과? 느린 이유 3가지와 os.read 완전 정복 (입출력 최적화) (0) | 2026.04.22 |
|---|---|
| [Python] "틀렸습니다" sys.stdin.readline에서 strip() 안 쓰면 생기는 문제 (0) | 2026.04.21 |
| 코딩 테스트 소수점 출력, 왜 틀릴까? (부동소수점 오차 해결 방법) (0) | 2026.04.21 |
| 백준 서비스 종료… 이제 어디로 가야 할까? (대체 사이트 총정리) (0) | 2026.04.17 |
| 백준 11724 (DFS를 사용한 풀이) C++ (5) | 2025.08.11 |

