BST는 각 노드에서 왼쪽 서브트리 값이 더 작고 오른쪽 서브트리 값이 더 크다는 규칙을 갖습니다. 균형이 잡혀 있으면 탐색, 삽입, 삭제가 평균 O(log n)에 가깝습니다. 하지만 한쪽으로 치우치면 linked list처럼 O(n)이 될 수 있습니다.
BST를 "이진 탐색을 자료구조로 만든 것"으로 이해하면 존재 이유가 명확해집니다. 정렬된 배열에서도 이진 탐색으로 O(log n)에 찾을 수 있지만, 중간에 값을 삽입하거나 삭제하려면 뒤의 원소를 전부 밀거나 당겨야 해서 O(n)이 듭니다. BST는 "절반씩 좁히는" 탐색 구조를 포인터로 연결해, 삽입과 삭제도 포인터 몇 개만 바꾸면 되게 만든 것입니다.
즉 배열 이진 탐색은 탐색만 빠르고, BST는 탐색과 수정이 모두 빠릅니다. 대신 배열이 공짜로 주던 "가운데가 어디인지"를 트리 모양이 대신 보장해야 하고, 그 보장이 깨진 상태가 치우친 트리입니다.
꼬리질문
조금 더 깊게 물어본다면
답변 뒤에 이어질 수 있는 질문들을 하나씩 열어볼 수 있어요.
BST의 inorder traversal 결과는 어떻게 되나요?
정렬된 순서로 값이 나옵니다. 왼쪽-root-오른쪽 순서가 BST의 크기 규칙과 맞기 때문입니다.
왼쪽 서브트리의 모든 값은 root보다 작고 오른쪽은 모두 크므로, "왼쪽 전부 → root → 오른쪽 전부" 순서로 방문하면 자연스럽게 오름차순이 됩니다. 각 서브트리 안에서도 같은 논리가 재귀적으로 성립합니다.
이 성질은 실전 문제에서 자주 활용됩니다. 어떤 트리가 유효한 BST인지 검증할 때 inorder 순회 결과가 오름차순인지 확인하면 되고, k번째로 작은 원소를 찾을 때는 inorder 순회를 k번째까지만 진행하면 됩니다. 정렬되지 않은 배열을 BST에 전부 삽입한 뒤 inorder로 꺼내면 정렬이 되는데, 이것이 tree sort입니다.
BST 탐색이 O(log n)이 아닌 경우는 언제인가요?
트리가 한쪽으로 치우친 경우입니다. 높이가 n에 가까워지면 탐색도 O(n)이 됩니다.
치우침을 만드는 대표 입력이 정렬된 데이터입니다. 1, 2, 3, 4를 순서대로 삽입하면 매번 "더 크다" 판정만 나와 모든 노드가 오른쪽 자식으로만 이어집니다. 이미 정렬됐거나 거의 정렬된 데이터는 실무에서 흔한 입력이라, 순진한 BST는 하필 흔한 입력에서 최악이 됩니다.
반대로 삽입 순서가 무작위라면 기대 높이가 O(log n)으로 유지된다는 것이 증명되어 있습니다. 문제는 입력 순서를 통제할 수 없다는 점이고, 그래서 입력 운에 맡기는 대신 트리 스스로 모양을 고치는 자가 균형(self-balancing) 트리가 필요해집니다.
균형 트리가 필요한 이유는 무엇인가요?
트리 높이를 낮게 유지해야 탐색, 삽입, 삭제 비용을 안정적으로 유지할 수 있기 때문입니다.
BST의 모든 연산 비용은 결국 "root에서 얼마나 내려가는가", 즉 높이에 비례합니다. n개 노드로 만들 수 있는 이진 트리의 높이는 최소 log₂n(완전 균형)에서 최대 n(한 줄)까지 벌어지므로, 같은 자료구조라도 모양에 따라 백만 개 기준 20번 비교와 백만 번 비교의 차이가 납니다.
실무 자료구조는 이 문제를 이미 해결한 형태로 제공됩니다. C++의 map/set과 Java의 TreeMap은 Red-Black Tree로 구현되어 있고, 데이터베이스 인덱스는 디스크 접근에 맞게 한 노드의 분기 수를 크게 늘린 균형 트리인 B-Tree를 씁니다. 모두 "삽입 순서와 무관하게 높이를 보장한다"는 같은 목표의 변형입니다.
BST에서 삭제가 삽입보다 복잡한 이유는 무엇인가요?
삭제할 노드가 자식을 둘 갖는 경우 대체 노드를 찾아도 BST 순서 규칙을 유지해야 하기 때문입니다.
삭제는 자식 수에 따라 세 경우로 나뉩니다. 자식이 없으면 그냥 제거하고, 하나면 그 자식을 끌어올리면 됩니다. 문제는 자식이 둘일 때입니다. 노드를 빼면 두 서브트리가 갈 곳을 잃는데, 아무 노드나 그 자리에 올리면 크기 규칙이 깨집니다.
해법은 삭제할 노드의 successor(오른쪽 서브트리에서 가장 작은 값)나 predecessor(왼쪽 서브트리에서 가장 큰 값)로 자리를 대체하는 것입니다. successor는 왼쪽 서브트리의 모든 값보다 크고 오른쪽 서브트리의 나머지 값보다 작으므로, 그 자리에 놓아도 규칙이 유지됩니다. 그리고 successor는 정의상 왼쪽 자식이 없어서, successor 자신을 원래 자리에서 빼는 일은 다시 쉬운 경우(자식 0개 또는 1개)로 환원됩니다.
BST가 한쪽으로 치우치면 탐색 과정은 어떻게 변하나요?
노드가 계속 한쪽 자식으로만 이어지면 tree라기보다 linked list에 가까워집니다. 이 경우 탐색할 때 절반씩 줄이는 효과가 사라지고, 최악에는 모든 노드를 따라가야 합니다.
그래서 BST의 성능을 말할 때는 단순히 O(log n)이라고 끝내기보다, 균형이 유지된다는 전제가 필요합니다.
"절반씩 줄이는 효과"가 사라진다는 것을 수치로 보면, 균형 트리에서는 비교 한 번마다 후보가 절반이 되어 100만 개 중 하나를 약 20번 만에 찾습니다. 완전히 치우친 트리에서는 비교 한 번에 후보가 하나씩만 줄어 최악 100만 번 비교합니다. 구조는 트리인데 동작은 linked list의 순차 탐색과 같아집니다.
실무에서 이 문제를 직접 겪는 일이 드문 이유는 표준 라이브러리의 트리 자료구조가 모두 자가 균형 트리이기 때문입니다. 다만 순서가 필요 없다면 hash table이 평균 O(1)로 더 빠르므로, "정렬 순서 유지나 범위 검색이 필요한가"가 트리를 선택하는 기준이 됩니다.
부가 설명
정렬된 데이터를 빠르게 찾고 싶을 때 매번 처음부터 훑는 것은 아깝습니다. BST는 현재 노드와 비교해서 찾는 값이 작으면 왼쪽, 크면 오른쪽으로 내려갑니다. 한 번 비교할 때마다 후보 영역이 줄어드는 구조라 균형만 유지되면 효율적입니다.
문제는 입력 순서가 나쁘면 트리가 기울어진다는 점입니다. 이미 정렬된 값을 그대로 넣으면 모든 노드가 한쪽 자식으로만 이어져 사실상 linked list가 됩니다. 그래서 실무 자료구조에서는 AVL Tree, Red-Black Tree처럼 균형을 맞추는 변형이 중요해집니다.