이진 탐색 트리 규칙 : 작으면 왼쪽, 크면 오른쪽
전위 순회(preorder traversal) : root -> left -> right 순서
중위 순회(inorder traversal) : left -> root -> right 순서
후위 순회(postorder traversal) : left -> right -> root 순서
완전 이진 트리(Complete binary tree) : 마지막 레벨 빼고 모두 차있는 경우
포화 이진 트리(perfect binary tree) : 마지막 레벨까지 모두 차있는 경우
편향 트리(skewed binary tree) : 한쪽으로만 자라는 tree. 예를 들어 아래와 같은 tree
insert, find는 현재 노드보다 큰지, 작은지에 따라 좌우 케이스로 나눠서 재귀적으로 구현.
erase는 leaf 노드/leaf 노드가 아닌 경우로 1차적으로 나눈다.
leaf 노드가 아닌 경우에는 기준 노드의 왼쪽 노드가 존재할 때/존재하지 않을 때로 나눈다.
기준 노드의 왼쪽 노드가 존재할 경우에는 왼쪽 subtree에서 가장 큰 노드를 찾아서 기준 노드를 대체하고, 왼쪽 subtree에서 가장 큰 노드를 delete.
기준 노드의 왼쪽 노드가 존재하지 않을 경우에는 오른쪽 subtree에서 가장 작은 노드를 찾아서 기준 노드를 대체하고, 오른쪽 subtree에서 가장 작은 노드를 delete.
//BinaryTree.h
#pragma once
template <typename KEY, typename VALUE>
class CBinaryTreeNode
{
template <typename KEY, typename VALUE>
friend class CBinaryTree;
template <typename KEY, typename VALUE>
friend class CBinaryTreeIterator;
private:
CBinaryTreeNode() :
m_Parent(nullptr),
m_Left(nullptr),
m_Right(nullptr),
m_Next(nullptr),
m_Prev(nullptr)
{
}
~CBinaryTreeNode()
{
}
private:
CBinaryTreeNode<KEY, VALUE>* m_Parent;
CBinaryTreeNode<KEY, VALUE>* m_Left;
CBinaryTreeNode<KEY, VALUE>* m_Right;
CBinaryTreeNode<KEY, VALUE>* m_Next;
CBinaryTreeNode<KEY, VALUE>* m_Prev;
// **** main에서 iterator->first, iterator->second로 접근하려고 ****
// **** public으로 선언 ****
public:
KEY first;
VALUE second;
};
template <typename KEY, typename VALUE>
class CBinaryTreeIterator
{
template <typename KEY, typename VALUE>
friend class CBinaryTree;
private:
CBinaryTreeNode<KEY, VALUE>* m_Node;
// **** CBinaryTreeNode와 달리 main에서도 사용을 해야하는데 ****
// **** private이 되면 main에서 접근을 못하므로 ****
// **** 꼭 생성자를 public으로 해주기 ****
public:
CBinaryTreeIterator()
: m_Node(nullptr)
{
}
~CBinaryTreeIterator()
{
}
public:
void operator ++ ()
{
m_Node = m_Node->m_Next;
}
void operator ++ (int)
{
m_Node = m_Node->m_Next;
}
void operator -- ()
{
m_Node = m_Node->m_Prev;
}
void operator -- (int)
{
m_Node = m_Node->m_Prev;
}
bool operator == (const CBinaryTreeIterator<KEY, VALUE>& iter) const
{
// **** return *this == iter 이렇게 쓰면 ****
// **** 또 == 연산자 오버로딩 함수를 호출하므로 절대 이렇게 쓰면 안된다 ****
return m_Node == iter.m_Node;
}
bool operator != (const CBinaryTreeIterator<KEY, VALUE>& iter) const
{
// **** return *this != iter 이렇게 쓰면 ****
// **** 또 != 연산자 오버로딩 함수를 호출하므로 절대 이렇게 쓰면 안된다 ****
return m_Node != iter.m_Node;
}
bool operator == (const CBinaryTreeNode<KEY, VALUE>* Node) const
{
return m_Node == Node;
}
bool operator != (const CBinaryTreeNode<KEY, VALUE>* Node) const
{
return m_Node != Node;
}
CBinaryTreeNode<KEY, VALUE>* operator -> () const
{
return m_Node;
}
};
template <typename KEY, typename VALUE>
class CBinaryTree
{
public:
CBinaryTree()
{
m_Root = nullptr;
m_Size = 0;
m_Begin = new NODE;
m_End = new NODE;
m_Begin->m_Next = m_End;
m_End->m_Prev = m_Begin;
}
~CBinaryTree()
{
PNODE DeleteNode = m_Begin;
while (DeleteNode)
{
PNODE Next = DeleteNode->m_Next;
delete DeleteNode;
DeleteNode = Next;
}
}
public:
typedef CBinaryTreeIterator<KEY, VALUE> iterator;
private:
typedef CBinaryTreeNode<KEY, VALUE>* PNODE;
typedef CBinaryTreeNode<KEY, VALUE> NODE;
private:
PNODE m_Root;
PNODE m_Begin;
PNODE m_End;
int m_Size;
public:
int size() const
{
return m_Size;
}
bool empty() const
{
return m_Size == 0;
}
iterator end() const
{
iterator iter;
iter.m_Node = m_End;
return iter;
}
iterator begin() const
{
iterator iter;
iter.m_Node = m_Begin->m_Next;
return iter;
}
void clear()
{
PNODE Node = m_Begin->m_Next;
while (Node != m_End)
{
PNODE Next = Node->m_Next;
delete Node;
Node = Next;
}
m_Begin->m_Next = m_End;
m_End->m_Prev = m_Begin;
m_Size = 0;
m_Root = nullptr;
}
void PreOrder(void (*pFunc)(const KEY&, const VALUE&))
{
PreOrder(pFunc, m_Root);
}
void InOrder(void (*pFunc)(const KEY&, const VALUE&))
{
InOrder(pFunc, m_Root);
}
void PostOrder(void (*pFunc)(const KEY&, const VALUE&))
{
PostOrder(pFunc, m_Root);
}
// 없는 노드를 찾으려하면 end() 리턴
iterator Find(const KEY& key) const
{
return Find(key, m_Root);
}
void insert(const KEY& key, const KEY& value)
{
// 처음 노드를 삽입하는 경우
if (!m_Root)
{
m_Root = new NODE;
m_Root->first = key;
m_Root->second = value;
m_Begin->m_Next = m_Root;
m_Root->m_Prev = m_Begin;
m_End->m_Prev = m_Root;
m_Root->m_Next = m_End;
}
else
{
insert(key, value, m_Root);
}
++m_Size;
}
iterator erase(const KEY& key)
{
iterator iter = Find(key);
if (iter == end() || iter == m_Begin)
return iter;
return erase(iter);
}
// 지운 노드의 다음 노드의 iterator를 리턴
iterator erase(const iterator& iter)
{
// 지우려는 노드가 리프노드인 경우
if (iter.m_Node->m_Left == nullptr && iter.m_Node->m_Right == nullptr)
{
// 트리에 루트노드 하나밖에 없는 경우
if (iter.m_Node == m_Root)
{
delete m_Root;
m_Root = nullptr;
m_Begin->m_Next = m_End;
m_End->m_Prev = m_Begin;
--m_Size;
return end();
}
PNODE Parent = iter->m_Parent;
if (Parent->m_Left == iter.m_Node)
{
Parent->m_Left = nullptr;
}
else
{
Parent->m_Right = nullptr;
}
PNODE Prev = iter->m_Prev;
PNODE Next = iter->m_Next;
Prev->m_Next = Next;
Next->m_Prev = Prev;
delete iter.m_Node;
--m_Size;
iterator result;
result.m_Node = Next;
return result;
}
// 지우려는 노드가 리프노드가 아닌 경우
else
{
// 지운 노드 기준 왼쪽에서 가장 큰 노드로
// 지운 노드 자리를 대체하자
if (iter->m_Left)
{
PNODE LeftMax = FindMax(iter->m_Left);
// iter인자는 const iterator& 인데 이렇게 바꿔줘도 되나?
iter.m_Node->first = LeftMax->first;
iter.m_Node->second = LeftMax->second;
PNODE Parent = LeftMax->m_Parent;
// 지우려는 노드의 왼쪽에서 가장 큰 노드의 왼쪽 자식
// 오른쪽 자식은 있을리 없다
// 없으면 nullptr일 것이다
PNODE LeftMaxLeftChild = LeftMax->m_Left;
if (Parent->m_Left == LeftMax)
{
Parent->m_Left = LeftMaxLeftChild;
}
else
{
Parent->m_Right = LeftMaxLeftChild;
}
if (LeftMaxLeftChild)
{
LeftMaxLeftChild->m_Parent = Parent;
}
PNODE Prev = LeftMax->m_Prev;
PNODE Next = LeftMax->m_Next;
Prev->m_Next = Next;
Next->m_Prev = Prev;
delete LeftMax;
--m_Size;
iterator result;
result.m_Node = Next;
return result;
}
// 오른쪽에서 가장 작은 노드를 찾아서
// 지운 노드 자리를 대체하자
else
{
PNODE RightMin = FindMin(iter.m_Node->m_Right);
iter.m_Node->first = RightMin->first;
iter.m_Node->second = RightMin->second;
PNODE Parent = RightMin->m_Parent;
// 지우려는 노드의 오른쪽에서 가장 작은 노드의 오른쪽 자식
// 왼쪽 자식은 있을리 없다
// 없으면 nullptr일 것이다
PNODE RightMinRightChild = RightMin->m_Right;
if (Parent->m_Left == RightMin)
{
Parent->m_Left = RightMinRightChild;
}
else
{
Parent->m_Right = RightMinRightChild;
}
if (RightMinRightChild)
{
RightMinRightChild->m_Parent = Parent;
}
PNODE Prev = RightMin->m_Prev;
PNODE Next = RightMin->m_Next;
Prev->m_Next = Next;
Next->m_Prev = Prev;
delete RightMin;
--m_Size;
iterator result;
result.m_Node = Next;
return result;
}
}
}
void PreOrder(void (*pFunc)(const KEY&, const VALUE&), PNODE Node)
{
if (!Node)
return;
pFunc(Node->first, Node->second);
PreOrder(pFunc, Node->m_Left);
PreOrder(pFunc, Node->m_Right);
}
void InOrder(void(*pFunc)(const KEY&, const VALUE&), PNODE Node)
{
if (!Node)
return;
InOrder(pFunc, Node->m_Left);
pFunc(Node->first, Node->second);
InOrder(pFunc, Node->m_Right);
}
void PostOrder(void(*pFunc)(const KEY&, const VALUE&), PNODE Node)
{
if (!Node)
return;
PostOrder(pFunc, Node->m_Left);
PostOrder(pFunc, Node->m_Right);
pFunc(Node->first, Node->second);
}
private:
PNODE FindMax(PNODE Node) const
{
if (Node->m_Right)
{
return FindMax(Node->m_Right);
}
return Node;
}
PNODE FindMin(PNODE Node) const
{
if (Node->m_Left)
{
return FindMin(Node->m_Left);
}
return Node;
}
// 새로 삽입한 노드를 리턴
PNODE insert(const KEY& key, const KEY& value, PNODE node)
{
if (node->first > key)
{
if (node->m_Left)
{
return insert(key, value, node->m_Left);
}
PNODE NewNode = new NODE;
NewNode->first = key;
NewNode->second = value;
NewNode->m_Parent = node;
node->m_Left = NewNode;
PNODE Prev = node->m_Prev;
Prev->m_Next = NewNode;
NewNode->m_Prev = Prev;
node->m_Prev = NewNode;
NewNode->m_Next = node;
return NewNode;
}
else
{
if (node->m_Right)
{
return insert(key, value, node->m_Right);
}
PNODE NewNode = new NODE;
NewNode->first = key;
NewNode->second = value;
NewNode->m_Parent = node;
node->m_Right = NewNode;
PNODE Next = node->m_Next;
Next->m_Prev = NewNode;
NewNode->m_Next = Next;
NewNode->m_Prev = node;
node->m_Next = NewNode;
return NewNode;
}
}
iterator Find(const KEY& key, PNODE node) const
{
if (!node)
{
return end();
}
else if (node == m_End || node == m_Begin)
{
return end();
}
else if (node->first == key)
{
iterator iter;
iter.m_Node = node;
return iter;
}
if (node->first > key)
{
return Find(key, node->m_Left);
}
return Find(key, node->m_Right);
}
};
// main.cpp
#include <iostream>
#include "BinaryTree.h"
#include <crtdbg.h>
void Output(const int& Key, const int& Value)
{
std::cout << "Key : " << Key << " Value : " << Value << " => ";
}
int main()
{
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
CBinaryTree<int, int> tree;
tree.insert(5, 5);
tree.insert(3, 3);
tree.insert(1, 1);
tree.insert(4, 4);
tree.insert(10, 10);
tree.insert(8, 8);
tree.insert(15, 15);
tree.insert(9, 9);
tree.erase(10);
CBinaryTree<int, int>::iterator iter;
for (iter = tree.begin(); iter != tree.end(); ++iter)
{
std::cout << "key : " << iter->first << " value : " <<
iter->second << std::endl;
}
iter = tree.Find(500);
if (iter == tree.end())
std::cout << "찾는 데이터가 없습니다." << std::endl;
else
{
std::cout << "Find Key : " << iter->first << " Find Value : " <<
iter->second << std::endl;
}
tree.erase(5);
tree.erase(1);
tree.erase(4);
system("cls");
std::cout << "=========== PreOrder ===========" << std::endl;
tree.PreOrder(Output);
std::cout << std::endl;
std::cout << "=========== InOrder ===========" << std::endl;
tree.InOrder(Output);
std::cout << std::endl;
std::cout << "=========== PostOrder ===========" << std::endl;
tree.PostOrder(Output);
std::cout << std::endl;
std::cout << std::endl;
for (iter = tree.begin(); iter != tree.end(); ++iter)
{
std::cout << "key : " << iter->first << " value : " <<
iter->second << std::endl;
}
return 0;
}
조금 특이한 점은 binary tree이면서 동시에 list기반으로 구현해서 오름차순으로 정렬된 노드들을 순회할 수 있다는 점이다.
구현해보면서 한 가지 신기한점을 알게 됐는데, -> operator 오버로딩을 하면 ->연산자가 자동으로 한번 더 호출된다는 점이다. 위에서는 아래와 같이 CBinaryTreeIterator 클래스에서 ->연산자 오버로딩을 해두었다.
CBinaryTreeNode<KEY, VALUE>* operator -> () const
{
return m_Node;
}
이렇게 오버로딩을 해두고 다음과 같이 CBinaryTreeNode의 멤버에 접근이 가능하다.
int main()
{
iterator iter;
std::cout<< iter->first << std::endl; // CBinaryTreeNode의 public 멤버변수 first
return 0;
}
일반적인 생각으로는 iter->까지만 쓰면 m_Node가 리턴되니까 여기에 ->를 한번 더 쓰고 first에 접근 가능할 것 같다. 즉 (iter->)->first 이런식으로 써야 first라는 멤버 CBinaryTreeNode에 접근 가능할 것 같지만 실제론 그냥 iter->first 이렇게 접근이 가능하다. 그 이유는 ->연산자를 오버로딩해서 객체.operator->()를 호출하면 자동적으로
object.operator->()->
이렇게 화살표 연산자가 한번 더 붙는다. 그래서 iter->first 이렇게 만으로도 접근이 가능한 것이다. 그래서 -> 연산자를 오버로딩 할 때 주의해야 할 점이 있다.
struct A
{
B b;
A operator->()
{
return *this;
}
};
위와 같이 ->연산자를 재정의하면
A objA;
objA->
이렇게 쓰면 오버로딩된 A operator->() 함수가 호출되고, 자기 자신이 리턴되고, 위에서 언급했듯이 -> 연산자 오버로딩을 호출하면 ->연산자가 바로 뒤에 추가적으로 하나 더 붙는다. 따라서 또 오버로딩된 A operator->() 함수가 호출된다. 결국 이렇게 무한 루프에 빠지게 된다. 따라서 -> 연산자 오버로딩시 return *this; 처럼 자기 자신을 리턴하면 안된다.
Reference
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=herbbread&logNo=221302089225
'공부 > Data Structure' 카테고리의 다른 글
자료구조 별 장단점(vector, list, queue, stack, map, hash map, tree) (0) | 2022.09.15 |
---|---|
해시 테이블(Hash Table) (0) | 2021.08.12 |
AVL Tree (0) | 2021.08.06 |
Linked list 구현 (0) | 2021.08.02 |