공부/Data Structure

이진 탐색 트리(Binary Search Tree), 화살표 연산자 오버로딩(-> operator overloading, arrow operator overloading)

sudo 2021. 8. 6. 01:07

이진 탐색 트리 규칙 : 작으면 왼쪽, 크면 오른쪽

전위 순회(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

https://stackoverflow.com/questions/28315718/skewed-trees-relation-to-binary-search-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;
}

main.cpp 출력 결과

 

조금 특이한 점은 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 

 

c++ operator->() 를 오버로딩할 때 일어나는 현상 및 주의점

1. operator->() 를 오버로딩하고 화살표 연산자(arrow operator)에 의해 operator->() 가 호출될 경...

blog.naver.com

 

'공부 > 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