이진 탐색 트리(Binary Search Tree, BST)는 정렬된 상태를 유지하는 이진 트리로, 모든 노드가 다음과 같은 성질을 만족해야 합니다.
- 노드의 오른쪽 서브트리에는 부모 노드의 키보다 큰 키가 위치합니다.
- 노드의 왼쪽 서브트리에는 부모 노드의 키보다 작은 키가 위치합니다.
- 모든 키 값은 중복 없이 고유합니다.
- 각 노드는 최대 두 개의 자식 노드만 가질 수 있습니다.
이러한 구조 덕분에 BST는 탐색, 삽입, 삭제 연산을 평균적으로 O(log n)의 시간 복잡도로 빠르게 처리할 수 있습니다.
클래스 설계 개요
이 예제에서 구현하는 BST 클래스는 다음과 같은 주요 기능을 제공합니다.
- search() : 트리에서 특정 값을 찾고, 해당 값이 위치한 깊이(depth)를 출력합니다. 루트부터 시작해 값의 크기를 비교하며 왼쪽 또는 오른쪽 자식으로 이동합니다.
- insert() : 새로운 값을 트리에 삽입합니다. 트리가 비어 있으면 루트로 삽입하고, 그렇지 않으면 값의 크기를 비교해 왼쪽 또는 오른쪽 자식으로 재귀적으로 내려가 적절한 위치에 추가합니다.
- del() : 트리에서 특정 값을 삭제합니다. 삭제 대상 노드의 자식 상태에 따라 네 가지 경우(case)로 나누어 처리합니다.
- casea() : 왼쪽·오른쪽 자식이 모두 없는 리프(leaf) 노드인 경우
- caseb() : 자식이 하나만 있는 경우(왼쪽만 존재하거나 오른쪽만 존재)
- casec() : 자식이 둘 다 있는 경우 — 오른쪽 서브트리에서 가장 작은 값(중위 후속자, inorder successor)을 찾아 대체합니다.
- inorder() : 왼쪽 → 루트 → 오른쪽 순서로 순회합니다(오름차순 출력).
- preorder() : 루트 → 왼쪽 → 오른쪽 순서로 순회합니다.
- postorder() : 왼쪽 → 오른쪽 → 루트 순서로 순회합니다.
- show() : 트리의 구조를 들여쓰기 형태로 화면에 출력합니다.
전체 예제 코드
#include <iostream>
#include <cstdlib>
using namespace std;
struct nod // 노드 선언
{
int info;
struct nod *l;
struct nod *r;
} *r;
class BST
{
public: // 함수 선언
void search(nod *, int);
void find(int, nod **, nod **);
void insert(nod *, nod *);
void del(int);
void casea(nod *, nod *);
void caseb(nod *, nod *);
void casec(nod *, nod *);
void preorder(nod *);
void inorder(nod *);
void postorder(nod *);
void show(nod *, int);
BST()
{
r = NULL;
}
};
// 항목의 위치를 찾는 함수 (부모와 위치를 함께 반환)
void BST::find(int i, nod **par, nod **loc)
{
nod *ptr, *ptrsave;
if (r == NULL)
{
*loc = NULL;
*par = NULL;
return;
}
if (i == r->info)
{
*loc = r;
*par = NULL;
return;
}
if (i < r->info)
ptr = r->l;
else
ptr = r->r;
ptrsave = r;
while (ptr != NULL)
{
if (i == ptr->info)
{
*loc = ptr;
*par = ptrsave;
return;
}
ptrsave = ptr;
if (i < ptr->info)
ptr = ptr->l;
else
ptr = ptr->r;
}
*loc = NULL;
*par = ptrsave;
}
// 탐색 함수: 깊이 정보와 함께 결과 출력
void BST::search(nod *root, int data)
{
int depth = 0;
nod *temp = root;
while (temp != NULL)
{
depth++;
if (temp->info == data)
{
cout << "\n데이터를 깊이 " << depth << "에서 찾았습니다" << endl;
return;
}
else if (temp->info > data)
temp = temp->l;
else
temp = temp->r;
}
cout << "\n데이터를 찾을 수 없습니다" << endl;
return;
}
// 삽입 함수
void BST::insert(nod *tree, nod *newnode)
{
if (r == NULL)
{
r = new nod;
r->info = newnode->info;
r->l = NULL;
r->r = NULL;
cout << "루트 노드가 추가되었습니다" << endl;
return;
}
if (tree->info == newnode->info)
{
cout << "이미 트리에 존재하는 원소입니다" << endl;
return;
}
if (tree->info > newnode->info)
{
if (tree->l != NULL)
{
insert(tree->l, newnode);
}
else
{
tree->l = newnode;
(tree->l)->l = NULL;
(tree->l)->r = NULL;
cout << "왼쪽에 노드가 추가되었습니다" << endl;
return;
}
}
else
{
if (tree->r != NULL)
{
insert(tree->r, newnode);
}
else
{
tree->r = newnode;
(tree->r)->l = NULL;
(tree->r)->r = NULL;
cout << "오른쪽에 노드가 추가되었습니다" << endl;
return;
}
}
}
// 삭제 함수
void BST::del(int i)
{
nod *par, *loc;
if (r == NULL)
{
cout << "트리가 비어 있습니다" << endl;
return;
}
find(i, &par, &loc);
if (loc == NULL)
{
cout << "트리에 해당 항목이 없습니다" << endl;
return;
}
if (loc->l == NULL && loc->r == NULL)
{
casea(par, loc);
cout << "항목이 삭제되었습니다" << endl;
}
if (loc->l != NULL && loc->r == NULL)
{
caseb(par, loc);
cout << "항목이 삭제되었습니다" << endl;
}
if (loc->l == NULL && loc->r != NULL)
{
caseb(par, loc);
cout << "항목이 삭제되었습니다" << endl;
}
if (loc->l != NULL && loc->r != NULL)
{
casec(par, loc);
cout << "항목이 삭제되었습니다" << endl;
}
free(loc);
}
// casea: 자식이 없는 노드 삭제
void BST::casea(nod *par, nod *loc)
{
if (par == NULL)
{
r = NULL;
}
else
{
if (loc == par->l)
par->l = NULL;
else
par->r = NULL;
}
}
// caseb: 자식이 하나인 노드 삭제
void BST::caseb(nod *par, nod *loc)
{
nod *child;
if (loc->l != NULL)
child = loc->l;
else
child = loc->r;
if (par == NULL)
{
r = child;
}
else
{
if (loc == par->l)
par->l = child;
else
par->r = child;
}
}
// casec: 자식이 둘인 노드 삭제 (중위 후속자로 대체)
void BST::casec(nod *par, nod *loc)
{
nod *ptr, *ptrsave, *suc, *parsuc;
ptrsave = loc;
ptr = loc->r;
while (ptr->l != NULL)
{
ptrsave = ptr;
ptr = ptr->l;
}
suc = ptr;
parsuc = ptrsave;
if (suc->l == NULL && suc->r == NULL)
casea(parsuc, suc);
else
caseb(parsuc, suc);
if (par == NULL)
{
r = suc;
}
else
{
if (loc == par->l)
par->l = suc;
else
par->r = suc;
}
suc->l = loc->l;
suc->r = loc->r;
}
// 전위 순회 (루트 - 왼쪽 - 오른쪽)
void BST::preorder(nod *ptr)
{
if (r == NULL)
{
cout << "트리가 비어 있습니다" << endl;
return;
}
if (ptr != NULL)
{
cout << ptr->info << " ";
preorder(ptr->l);
preorder(ptr->r);
}
}
// 중위 순회 (왼쪽 - 루트 - 오른쪽)
void BST::inorder(nod *ptr)
{
if (r == NULL)
{
cout << "트리가 비어 있습니다" << endl;
return;
}
if (ptr != NULL)
{
inorder(ptr->l);
cout << ptr->info << " ";
inorder(ptr->r);
}
}
// 후위 순회 (왼쪽 - 오른쪽 - 루트)
void BST::postorder(nod *ptr)
{
if (r == NULL)
{
cout << "트리가 비어 있습니다" << endl;
return;
}
if (ptr != NULL)
{
postorder(ptr->l);
postorder(ptr->r);
cout << ptr->info << " ";
}
}
// 트리 구조 출력
void BST::show(nod *ptr, int level)
{
int i;
if (ptr != NULL)
{
show(ptr->r, level + 1);
cout << endl;
if (ptr == r)
cout << "Root->: ";
else
{
for (i = 0; i < level; i++)
cout << " ";
}
cout << ptr->info;
show(ptr->l, level + 1);
}
}
int main()
{
int c, n, item;
BST bst;
nod *t;
while (1)
{
cout << "1.원소 삽입" << endl;
cout << "2.원소 삭제" << endl;
cout << "3.원소 탐색" << endl;
cout << "4.중위 순회" << endl;
cout << "5.전위 순회" << endl;
cout << "6.후위 순회" << endl;
cout << "7.트리 출력" << endl;
cout << "8.종료" << endl;
cout << "메뉴를 선택하세요 : ";
cin >> c;
switch (c)
{
case 1:
t = new nod;
cout << "삽입할 숫자 입력 : ";
cin >> t->info;
bst.insert(r, t);
break;
case 2:
if (r == NULL)
{
cout << "트리가 비어 있어 삭제할 것이 없습니다" << endl;
continue;
}
cout << "삭제할 숫자 입력 : ";
cin >> n;
bst.del(n);
break;
case 3:
cout << "탐색:" << endl;
cin >> item;
bst.search(r, item);
break;
case 4:
cout << "BST 중위 순회 결과:" << endl;
bst.inorder(r);
cout << endl;
break;
case 5:
cout << "BST 전위 순회 결과:" << endl;
bst.preorder(r);
cout << endl;
break;
case 6:
cout << "BST 후위 순회 결과:" << endl;
bst.postorder(r);
cout << endl;
break;
case 7:
cout << "BST 출력:" << endl;
bst.show(r, 1);
cout << endl;
break;
case 8:
exit(1);
default:
cout << "잘못된 선택입니다" << endl;
}
}
}실행 결과
1.원소 삽입
2.원소 삭제
3.원소 탐색
4.중위 순회
5.전위 순회
6.후위 순회
7.트리 출력
8.종료
메뉴를 선택하세요 : 1
삽입할 숫자 입력 : 6
루트 노드가 추가되었습니다
...
메뉴를 선택하세요 : 1
삽입할 숫자 입력 : 7
오른쪽에 노드가 추가되었습니다
...
메뉴를 선택하세요 : 1
삽입할 숫자 입력 : 5
왼쪽에 노드가 추가되었습니다
...
메뉴를 선택하세요 : 1
삽입할 숫자 입력 : 4
왼쪽에 노드가 추가되었습니다
...
메뉴를 선택하세요 : 3
탐색:
7
데이터를 깊이 2에서 찾았습니다
...
메뉴를 선택하세요 : 3
탐색:
1
데이터를 찾을 수 없습니다
...
메뉴를 선택하세요 : 4
BST 중위 순회 결과:
4 5 6 7
...
메뉴를 선택하세요 : 5
BST 전위 순회 결과:
6 5 4 7
...
메뉴를 선택하세요 : 6
BST 후위 순회 결과:
4 5 7 6
...
메뉴를 선택하세요 : 7
BST 출력:
7
Root->: 6
5
4
...
메뉴를 선택하세요 : 2
삭제할 숫자 입력 : 1
트리에 해당 항목이 없습니다
...
메뉴를 선택하세요 : 2
삭제할 숫자 입력 : 5
항목이 삭제되었습니다
...
메뉴를 선택하세요 : 7
BST 출력:
7
Root->: 6
4
...
메뉴를 선택하세요 : 8정리
이 프로그램은 C++ 클래스를 활용해 이진 탐색 트리의 핵심 연산인 탐색, 삽입, 삭제, 세 가지 순회 방식을 모두 구현한 예제입니다. 특히 삭제 연산은 자식 노드의 개수에 따라 세 가지 경우로 나누어 처리하며, 자식이 둘인 경우에는 오른쪽 서브트리의 최솟값(중위 후속자)으로 대체하는 표준적인 방법을 사용합니다. 중위 순회 결과가 항상 오름차순으로 정렬되어 출력되는 점을 확인하면 BST의 정렬 특성을 쉽게 이해할 수 있습니다.