트리(Tree)와 이진 탐색 트리(Binary Search Tree)
트리
각각의 자료가 노드를 이루고 각 노드가 부모-자식 관계를 형성하는 자료구조를 말한다.
트리의 구성 요소
트리는 하나의 루트와 여러 개의 자식 노드로 구성된다.

- 노드 트리를 이루는 각각의 요소
- 루트 트리의 최상단 요소
- 리프 트리의 최말단 요소
- 차수 각 노드가 가진 자식의 수
- 트리의 차수 해당 트리에 존재하는 각 노드의 차수 중 가장 큰 값
- 높이 트리의 노드의 레벨 중 가장 큰 값
이진트리
리프 노드를 제외한 노드의 자식이 1개 혹은 2개로 이루어진 트리로 각 서브 트리는 다음과 같은 구조로 이루어진다.

※ 각 자식 노드를 왼쪽 자식 노드와 오른쪽 자식 노드로 구분하며 각각의 자식 노드가 자식 노드를 가진 경우 자식 노드를 루트로 하는 노드 집단을 서브 트리라고 한다.
이진 트리의 특징
- n개의 노드를 진 경우 n-1 개의 간선을 가진다.
- 높이가 h인 이진트리가 가질 수 있는 노드의 최소 개수는 h-1 개이며 최대 개수는 2^(h+1)-1 개가 된다.
이진 트리의 분류
- 포화 이진 트리 모든 리프 노드의 높이가 h이고 리프 느도를 제외한 모든 노드가 두개의 자식을 가지는 경우
- 완전 이진 트리 포화 이진 트리에서 마지막 리프 노드가 빈 경우
- 편향 이진 트리 모든 노드가 왼쪽 자식만을 가지거나 오른쪽 자식만을 가지는 경우

이진 트리의 순회
이진 트리에 존재하는 모든 노드를 한 번씩 방문하여 데이터를 처리하는 과정을 순회라고 하며 다음과 같이 구분한다.
- 전위 순회 데이터를 처리 후 왼쪽 노드, 오른쪽 노드 순서로 이동
- 중위 순회 왼쪽 노드로 이동한 뒤 데이터 처리 후 오른쪽 노드로 이동
- 후위 순회 왼쪽 노드로 이동한 뒤 오른쪽 노드로 이동 후 데이터 처리
이진 트리의 순회 예제

위와 같은 트리에 대해 모든 순회 방법에 따른 경로를 표시하면 다음과 같다.
- 전위 순회 A - B - D - E - C
- 중위 순회 D - B - E - A - C
- 후위 순회 D - E - B - C - A
이진 트리를 순차 자료구조로 표현하면 다음과 같이 표현할 수 있다.
| 인덱스 | 0 (root) | 1 | 2 | 3 | 4 |
| 데이터 | A | B | C | D | E |
root = 0 일 때
left child = (parent * 2) + 1
right child = (parent * 2) + 2
이진 탐색 트리
탐색을 위한 이진 트리로 데이터의 크기에 따라 노드의 위치를 결정한다.
이진 탐색 트리의 정의
- 모든 원소는 서로 다른 유일한 키를 갖는다.
- 왼쪽 서브 트리에 있는 원소의 키들은 그 루트의 키보다 작다.
- 오른쪽 서브 트리에 있는 원소의 키들은 그 루트의 키보다 크다.
- 왼쪽 서브 트리와 오른쪽 서브 트리도 이진 탐색 트리다.
이진 탐색 트리의 탐색 연산
값을 찾을 때에는 루트에서부터 찾으려는 값을 비교하며 진행한다.
- 노드의 값 = 찾으려는 값 탐색 성공
- 노드의 값 > 찾으려는 값 왼쪽 서브 트리로 이동하여 탐색 연산 수행
- 노드의 값 < 찾으려는 값 오른쪽 서브 트리로 이동하여 탐색 연산 수행
이진 탐색 트리의 삽입 연산
삽입하려는 값을 탐색한 뒤 트리에 값이 존재하지 않으면 값을 트리에 추가한다.

왼쪽 트리에 4를 삽입하면 다음의 과정을 거쳐 오른쪽 트리가 된다.
- 노드의 값(8) > 찾으려는 값(4) 왼쪽 서브 트리로 이동하여 탐색 연산 진행
- 노드의 값(3) < 찾으려는 값(4) 오른쪽 서브 트리로 이동하여 탐색 연산 진행
- 노드의 값(5) > 찾으려는 값(4) 왼쪽 서브 트리로 이동하여 탐색 연산 진행
- 탐색 연산 실패 해당 위치에 4를 삽입하고 연산 종료
이진 탐색 트리의 삭제 연산
삭제 연산의 경우 삭제되는 노드의 자식에 따라 연산이 달라진다.
- 자식 노드가 없는 경우 대상 노드를 제거하고 연산을 종료한다.
- 자식 노드가 하나인 경우 대상 노드를 제거하고 자식 노드와 위치를 바꾼다.
- 자식 노드가 두 개인 경우 왼쪽 자식을 선택할지 오른쪽 자식을 선택할지에 따라 연산이 달라진다.
- 왼쪽 자식에게 계승하는 경우 대상 노드 삭제 후 왼쪽 서브 트리에서 가장 큰 값을 가진 노드와 위치를 바꾼다.
- 오른쪽 자식에게 계승하는 경우 대상 노드 삭제 후 오른쪽 서브 트리에서 가장 작은 값을 가진 노드와 위치를 바꾼다.
이진 탐색 트리 구현
구현에 필요한 메서드 및 속성은 다음과 같다.
- 속성
- Count BinarySearchTree에 포함된 데이터의 개수
- Max BinarySearchTree에 추가된 데이터 중 가장 큰 값
- Min BinarySearchTree에 추가된 데이터 중 가장 작은 값
- 메서드
- Search(data) 데이터가 저장되어 있는지 여부 확인
- Insert(data) BinarySearchTree의 규칙에 맞게 데이터를 저장
- Delete(data) 일치하는 데이터를 제거하고 실행 결과를 반환
- Clear() 저장되어 있는 모든 데이터 삭제
구현 코드
BinarySearchTree.h
#pragma once
#include <iostream>
class BinarySearchTree
{
public:
#pragma region 노드
struct Node
{
Node() {}
Node(int value) { m_data = value; }
int m_data{ 0 };
Node* m_left{ nullptr };
Node* m_right{ nullptr };
};
#pragma endregion
#pragma region 생성자
BinarySearchTree();
~BinarySearchTree();
#pragma endregion
#pragma region 속성
const size_t Count() { return m_count; }
const int Max()
{
if (m_root == nullptr)
return 0;
Node* target{ m_root };
while (target->m_left != nullptr)
{
target = target->m_left;
}
return target->m_data;
}
const int Min()
{
if (m_root == nullptr)
return 0;
Node* target{ m_root };
while (target->m_right != nullptr)
{
target = target->m_right;
}
return target->m_data;
}
#pragma endregion
#pragma region 메서드
void Insert(int value);
void Delete(int value);
void Clear();
bool Search(int value);
void PrintInfo();
private:
void PrintInfo(Node* node, size_t depth = 0);
#pragma endregion
private:
#pragma region Class Util
Node* PopNode(int value);
void PushNode(Node* node);
#pragma endregion
#pragma region 변수
size_t m_count;
Node* m_root;
Node* m_free;
#pragma endregion
};
BinarySearchTree.cpp
#include "BinarySearchTree.h"
#pragma region 생성자
/// <summary>
/// 비어있는 BinarySearchTree를 생성한다.
/// </summary>
BinarySearchTree::BinarySearchTree()
: m_count(0), m_root(nullptr), m_free(nullptr)
{
}
/// <summary>
/// 메모리 누수를 막기 위해 동적 생성한 노드들을 제거한다.
/// </summary>
BinarySearchTree::~BinarySearchTree()
{
Clear();
while (m_free != nullptr)
{
Node* curNode{ m_free };
m_free = m_free->m_right;
delete curNode;
}
}
#pragma endregion
#pragma region 메서드
/// <summary>
///
/// </summary>
/// <param name="value">추가할 값</param>
void BinarySearchTree::Insert(int value)
{
if (m_root == nullptr)
{
m_root = PopNode(value);
return;
}
Node* target{ m_root };
while (target != nullptr)
{
if (target->m_data == value)
{
break;
}
if (target->m_data > value)
{
if (target->m_left == nullptr)
{
target->m_left = PopNode(value);
m_count++;
}
target = target->m_left;
}
else
{
if (target->m_right == nullptr)
{
target->m_right = PopNode(value);
m_count++;
}
target = target->m_right;
}
}
}
/// <summary>
/// BinarySearchTree에서 지정된 값을 가진 노드를 제거한다.
/// </summary>
/// <param name="value">제거할 값</param>
void BinarySearchTree::Delete(int value)
{
if (m_root == nullptr)
{
return;
}
Node* target{ m_root };
Node* parent{ nullptr };
while ((target != nullptr) && (target->m_data != value))
{
parent = target;
if (target->m_data > value)
{
target = target->m_left;
}
else
{
target = target->m_right;
}
}
if (target == nullptr)
{
return;
}
if (target->m_left == nullptr && target->m_right == nullptr)
{
if (parent == nullptr)
{
m_root = nullptr;
}
else if (parent->m_left == target)
{
parent->m_left = nullptr;
}
else
{
parent->m_right = nullptr;
}
}
else if (target->m_left != nullptr && target->m_right == nullptr
|| target->m_left == nullptr && target->m_right != nullptr)
{
Node* child{ target->m_left != nullptr ? target->m_left : target->m_right };
if (parent == nullptr)
{
m_root = child;
}
else if(parent->m_left == target)
{
parent->m_left = child;
}
else
{
parent->m_right = child;
}
}
else
{
parent = target;
Node* successor{ target->m_left };
while (successor->m_right != nullptr)
{
parent = successor;
successor = successor->m_right;
}
if (parent->m_left == successor)
{
parent->m_left = successor->m_left;
}
else
{
parent->m_right = successor->m_left;
}
target->m_data = successor->m_data;
target = successor;
}
PushNode(target);
}
/// <summary>
/// BinarySearchTree의 모든 노드를 제거한다.
/// </summary>
void BinarySearchTree::Clear()
{
while (m_root != nullptr)
{
Delete(m_root->m_data);
}
}
/// <summary>
/// BinarySearchTree에 지정된 값이 존재하는지 검색한다.
/// </summary>
/// <param name="value">검색할 값</param>
bool BinarySearchTree::Search(int value)
{
if (m_root == nullptr)
{
return false;
}
Node* target{ m_root };
while (target != nullptr)
{
if (target->m_data == value)
{
break;
}
if (target->m_data > value)
{
target = target->m_left;
}
else
{
target = target->m_right;
}
}
return target != nullptr;
}
/// <summary>
/// 테스트용 리스트 정보 출력 함수
/// </summary>
void BinarySearchTree::PrintInfo()
{
std::cout << "----------------------\n";
PrintInfo(m_root);
std::cout << "----------------------\n\n";
}
/// <summary>
/// 테스트용 리스트 정보 출력 함수
/// </summary>
void BinarySearchTree::PrintInfo(Node* node, size_t depth)
{
if (node == nullptr)
{
return;
}
for (int i = 0; i < depth; i++)
{
std::cout << " ";
}
std::cout << "└ " << node->m_data << '\n';
PrintInfo(node->m_left, depth + 1);
PrintInfo(node->m_right, depth + 1);
}
#pragma endregion
#pragma region Class Util
/// <summary>
/// 자유 공간 리스트에서 노드를 가져오거나 새로 생성한다.
/// </summary>
/// <param name="value">노드 생성시 초기값</param>
/// <returns>새 노드</returns>
BinarySearchTree::Node* BinarySearchTree::PopNode(int value)
{
Node* newNode{ nullptr };
if (m_free == nullptr)
{
newNode = new Node(value);
}
else
{
newNode = m_free;
m_free = m_free->m_right;
newNode->m_data = value;
newNode->m_right = nullptr;
}
return newNode;
}
/// <summary>
/// 자유 공간 리스트에 제거된 노드를 저장한다.
/// </summary>
/// <param name="node">제거된 노드</param>
void BinarySearchTree::PushNode(Node* node)
{
node->m_left = nullptr;
node->m_right = m_free;
m_free = node;
}
#pragma endregion
테스트 코드
main.cpp
#include <iostream>
#include "BinarySearchTree/BinarySearchTree.h"
// 생성한 자료구조 테스트용 메인
int main()
{
BinarySearchTree bst;
bst.Insert(5);
bst.Insert(3);
bst.Insert(1);
bst.Insert(0);
bst.Insert(4);
bst.Insert(8);
bst.Insert(7);
bst.Insert(9);
bst.PrintInfo();
bst.Delete(5);
bst.PrintInfo();
std::cout << std::boolalpha << bst.Search(5) << '\n';
std::cout << std::boolalpha << bst.Search(9) << '\n';
bst.Clear();
bst.PrintInfo();
}
실행 결과
----------------------
└ 5
└ 3
└ 1
└ 0
└ 4
└ 8
└ 7
└ 9
----------------------
----------------------
└ 4
└ 3
└ 1
└ 0
└ 8
└ 7
└ 9
----------------------
false
true
----------------------
----------------------