C++기초

220415~220418_자료구조_이진트리(Binary Tree) 구현

2022. 4. 22. 15:29

* 필요 헤더: LinkedList.h

https://hyrule.tistory.com/45

 

220407~220411_자료구조_링크드 리스트 구현 (Linked List)

#pragma once //경고메시지를 띄우기 위한 헤더 #include /* 자료구조 : 데이터를 관리하는 방법. * 완벽한 자료구조는 없다. 상황마다 적합하고 효율적인 자료구조가 다르다. 그것을 적재적소에 활용하

hyrule.tistory.com

//BinaryTree.h

/*
[이진트리]​
* 자식노드가 최대 2개
* 구현방식: 배열기반 or 리스트기반
* 배열기반은 복잡하므로 이진트리로
​
* 이진트리를 쓰는 이유 : '탐색'이 매우 빠르다
- 추가할때, 삭제할때 규칙이 있음
ex) 루트노드보다 큰건 오른쪽, 작은건 왼쪽에 추가
->이래서 루트노드가 작은 수일 경우 한쪽으로만 추가될수도 있다 = 편향트리
만약 이럴경우 리스트보다 느려질 수 있다.
->이 문제점을 해결한 트리 = 균형 이진트리(레드블랙트리가 대표적)
- 추가 구현은 쉬우나 삭제 구현이 어렵다.
*/

//데이터 관리의 용이성을 위해 Binary Tree와 Linked List 형식을 결합할 것임.


#pragma once

#include <iostream>
#include "LinkedList.h"


//KEY값: 저장규칙 - 대소판별, VALUE값: 실제 들어갈 데이터
template <typename KEY, typename VALUE>
class CBinaryTreeNode
{
	template <typename KEY, typename VALUE>
	friend class CBinaryTree;

	template <typename KEY, typename VALUE>
	friend class CBinaryTreeIterator;

public:
	CBinaryTreeNode():
		m_Parent(nullptr),
		m_Left(nullptr),
		m_Right(nullptr),
		m_Next(nullptr),
		m_Prev(nullptr)
	{

	}
	~CBinaryTreeNode()
	{

	}

public:
	//정보 저장
	VALUE m_Data;
	//현재 노드의 키값
	KEY m_Key;
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;
};


template <typename KEY, typename VALUE>
class CBinaryTreeIterator
{
	template <typename KEY, typename VALUE>
	friend class CBinaryTree;
//밖에서 실제로 활용해야 할 기능이기 때문에 public으로 선언
public:
	CBinaryTreeIterator():
		m_Node(nullptr)
	{

	}
	~CBinaryTreeIterator()
	{

	}

private:
	CBinaryTreeNode<KEY, VALUE>* m_Node;

public:
	//연산자 추가
	bool operator == (const CBinaryTreeIterator<KEY, VALUE>& iter) const
	{
		return (m_Node == iter.m_Node);
	}
	bool operator != (const CBinaryTreeIterator<KEY, VALUE>& iter) const
	{
		return (m_Node != iter.m_Node);
	}
	void operator ++ ()
	{
		m_Node = m_Node->m_Next;
	}
	void operator -- ()
	{
		m_Node = m_Node->m_Prev;
	}
	void operator ++ (int)
	{
		m_Node = m_Node->m_Next;
	}
	void operator -- (int)
	{
		m_Node = m_Node->m_Prev;
	}
	void operator + (int addnum)
	{
		for (int i = 0; i < addnum; ++i)
		{
			if (nullptr == m_Node->m_Next)
				return;
			m_Node = m_Node->m_Next;
		}
	}
	void operator - (int addnum)
	{
		for (int i = 0; i < addnum; ++i)
		{
			if (nullptr == m_Node->m_Prev)
				return;
			m_Node = m_Node->m_Prev;
		}
	}

	VALUE& operator *()
	{
		return m_Node->m_Data;
	}
	
	//iterator.m_Node를 하지 않아도 바로 m_Node 주소를 반환해 주는 연산자 재정의
	CBinaryTreeNode<KEY, VALUE>* operator -> () const
	{
		return m_Node;
	}

};


template <typename KEY, typename VALUE>
class CBinaryTree
{
public:
	CBinaryTree() :
		m_Root(nullptr)
	{
		m_Begin = new NODE;
		m_End = new NODE;

		m_Begin->m_Next = m_End;
		m_End->m_Prev = m_Begin;
	}
	~CBinaryTree()
	{
		PNODE Del = m_Begin;
		while (Del)
		{
			PNODE Next = Del->m_Next;
			delete Del;
			Del = Next;
		}


	}

private:

	typedef CBinaryTreeNode<KEY, VALUE>		NODE;
	typedef CBinaryTreeNode<KEY, VALUE>* PNODE;
	typedef CBinaryTreeIterator<KEY, VALUE> iterator;

	//루트 노드의 주소 관리
	PNODE m_Root;

	//링크드 리스트 주소 관리
	PNODE m_Begin;
	PNODE m_End;

	int m_Size;




public:
	//기본 기능들 구현.
	iterator begin() const
	{
		iterator iter;
		iter.m_Node = m_Begin->m_Next;
		return iter;
	}
	iterator end() const
	{
		iterator iter;
		iter.m_Node = m_End;
		return iter;
	}
	int getsize() const
	{
		return m_Size;
	}
	void clear()
	{
		if (m_Size == 0)
			return;

		PNODE del = m_Begin->m_Next;
		PNODE end = m_End;

		while (del != end)
		{
			PNODE next = del->m_Next;
			delete del;
			del = next;
		}

		m_Begin->m_Next = m_End;
		m_End->m_Prev = m_Begin;

		m_Root = nullptr;
		m_Size = 0;
	}
//////////////////////////////////////////



public:
	//삽입 기능 구현
	void insert(const KEY& Key, const VALUE& Value)
	{
		//루트 노드가 생성되어 있지 않다면 생성
		if (!m_Root)
		{
			//트리 부분
			m_Root = new NODE;
			m_Root->m_Key = Key;
			m_Root->m_Data = Value;

			//리스트 부분
			m_Root->m_Next = m_End;
			m_Root->m_Prev = m_Begin;

			m_Begin->m_Next = m_Root;
			m_End->m_Prev = m_Root;

			++m_Size;

		}
		else
		{
			//이외의 경우 재귀호출 시작
			PNODE NewNode = insert(Key, Value, m_Root);
		}

	}

private:
	//재귀호출용 오버라이드 함수
	PNODE insert(const KEY& Key, const VALUE& Value, PNODE Node)
	{
		if (!Node)
			return nullptr;

		//인자로 들어온 노드의 키 값이 Key보다 작을 경우
		if (Node->m_Key > Key)
		{
			//m_Left값이 존재하지 않으면 바로 추가.
			if (!Node->m_Left)
			{
				//트리 부분
				PNODE NewNode = new NODE;
				NewNode->m_Data = Value;
				NewNode->m_Key = Key;

				//부모-자식 관계로 연결.
				Node->m_Left = NewNode;
				NewNode->m_Parent = Node;
				////////////////////////////

				//리스트 부분
				//m_Left에 들어가는 상황이므로 부모 노드의 키 값은 무조건 나보다 크다.

				//연결을 위해 임시로 주소를 받아놓고
				PNODE Prev = Node->m_Prev;

				NewNode->m_Prev = Prev;
				NewNode->m_Next = Node;

				Prev->m_Next = NewNode;
				Node->m_Prev = NewNode;


				++m_Size;

				//새로 만들어진 노드 주소를 반환
				return NewNode;
			}

			//m_Left값이 존재하면 한번 더 타고들어가는 재귀함수 호출.
			return insert(Key, Value, Node->m_Left);
		}

		//인자로 들어온 노드의 키 값이 Key보다 클 경우
		if (Node->m_Key < Key)
		{
			//m_Left값이 존재하지 않으면 바로 추가.
			if (!Node->m_Right)
			{
				//트리 부분
				PNODE NewNode = new NODE;
				NewNode->m_Data = Value;
				NewNode->m_Key = Key;

				//부모-자식 관계로 연결.
				Node->m_Right = NewNode;
				NewNode->m_Parent = Node;
				////////////////////////////

				//리스트 부분
				//m_Right에 들어가는 상황이므로 부모 노드의 키 값은 무조건 나보다 크다.

				//연결을 위해 임시로 주소를 받아놓고
				PNODE Next = Node->m_Next;

				NewNode->m_Next = Next;
				NewNode->m_Prev = Node;

				Next->m_Prev = NewNode;
				Node->m_Next = NewNode;
				//////////////////////////

				++m_Size;
				return NewNode;
			}

			//m_Right값이 존재하면 한번 더 타고들어가는 재귀함수 호출.
			return insert(Key, Value, Node->m_Right);
		}

	}
////////////////////////////////////////////////////////////////////






public:
	//키값을 입력하면 삭제해주는 기능
	iterator erase(const KEY& key)
	{
		iterator iter = Find(key);
		if (iter == end())
			return end();

		//본체에 찾은 iterator을 넣어 검색
		return erase(iter);

	}

	//erase 본체는 이거
	//삭제 후에도 규칙을 유지할 수 있어야 하므로 추가나 탐색보다 훨씬 복잡하다.
	iterator erase(const iterator& iter)
	{

		if (iter == end())
			return end();
		

		//1-1. 리프노드인지 확인한다.(리프노드면 바로 삭제하면 되므로)
		if (!iter.m_Node->m_Left && !iter.m_Node->m_Right)
		{
			//(예외처리)혹시나 지울 대상이 마지막 남은 루트노드라면
			//(자식이 없는 루트노드 -> 노드가 딱 하나라는 뜻)
			//해당 루트노드를 삭제하고 end()를 반환
			if (iter.m_Node == m_Root)
			{
				m_Begin->m_Next = m_End;
				m_End->m_Prev = m_Begin;

				delete iter.m_Node;

				m_Root = nullptr;

				--m_Size;
				return end();
			}


			//1-2. 지울 노드가 오른쪽 노드인지 왼쪽 노드인지 확인한다.
			if (iter.m_Node->m_Parent->m_Left == iter.m_Node)
			{
				//이진트리 부모자식관계 정리
				iter.m_Node->m_Parent->m_Left = nullptr;
			}
			else
			{
				iter.m_Node->m_Parent->m_Right = nullptr;
			}

			//1-3. 링크드리스트 제거
			PNODE Next = iter.m_Node->m_Next;
			PNODE Prev = iter.m_Node->m_Prev;
			Next->m_Prev = Prev;
			Prev->m_Next = Next;


			//1-4. 노드 제거
			delete iter.m_Node;

			//1-5. 사이즈 하나 감소
			--m_Size;


			//1-6. 삭제된 노드의 다음 노드를 반복자에 담아 반환.
			iterator result;

			//Next가 end()로 반환될 경우 에러가 났는지 헷갈릴수도 있으므로
			//end 바로 전인 Prev를 반환.
			if (Next = end().m_Node)
			{
				result.m_Node = Prev;
				return result;
			}
			result.m_Node = Next;
			return result;
		}

		//2-1. 리프 노드가 아니라면,

		//2.2. 그리고, 왼쪽 노드가 존재한다면 왼쪽 노드의 '최댓값'을
		// 지울 노드의 위치로 가져온다.
		//참고)왼쪽 노드의 최댓값은 절대로 오른쪽 노드를 가지지 않는다.
		// 왼쪽 노드의 최댓값의 오른쪽 자식 노드 = 최댓값보다 더 큰 값이여야 함 
		// -> 말이 안됨.
		//하지만, 왼쪽 자식 노드는 가질 수도 있다.
		//이 경우, 왼쪽 자식 노드를 부모 노드 위치로 가져온다.
		PNODE FindNode = nullptr;
		PNODE ChildNode = nullptr;
		if (iter.m_Node->m_Left)
		{
			//왼쪽 노드의 최댓값 탐색
			FindNode = FindMax(iter.m_Node->m_Left);

			//자식 노드 주소를 받아옴. 없다면 nullptr을 받아올 것임.
			ChildNode = FindNode->m_Left;
		}

		//만약 왼쪽 노드가 존재하지 않을 경우, 오른쪽 노드의 값을 가져온다.
		else
		{
			FindNode = FindMin(iter.m_Node->m_Right);
			ChildNode = FindNode->m_Right;
		}


		//가져온 값을, 지울 키값의 노드에 복사.
		iter.m_Node->m_Key = FindNode->m_Key;
		iter.m_Node->m_Data = FindNode->m_Data;


		//찾은 왼쪽 노드 중 최댓값 / 오른쪽 노드 중 최솟값은
		//이진트리의 규칙 상 절대로 자식 노드를 두개 가질 수 없다.
		//잘 생각해보자.
		//왼쪽 노드 중 최댓값이 오른쪽 노드가 있다면, 
		//그 값이 최댓값이여야 한다.
		//마찬가지로 오른쪽 노드 중 최솟값이 왼쪽 노드가 있다면,
		//그 값이 최솟값이여야 한다.
		if(FindNode == FindNode->m_Parent->m_Left)
		{
			FindNode->m_Parent->m_Left = ChildNode;
		} 
		else if (FindNode == FindNode->m_Parent->m_Right)
		{
			FindNode->m_Parent->m_Right = ChildNode;
		}

		//그리고 만약 자식노드가 있었다면
		//해당 노드를 find노드의 부모 노드와 연결한다.
		if (ChildNode)
		{
			ChildNode->m_Parent = FindNode->m_Parent;
		}
		


		//링크드 리스트 정리
		//연결시켜주어야 하는 노드는 
		//FindNode 노드의 prev와 next이다.(왼쪽, 오른쪽 동일)
		FindNode->m_Prev->m_Next = FindNode->m_Next;
		FindNode->m_Next->m_Prev = FindNode->m_Prev;


		//최종적으로 FindNode를 제거한다.
		delete FindNode;

		//그리고 지운 값이 있던 노드를 반환한다
		//(해당 노드는 새 값으로 변경되었음)
		iterator result;
		result.m_Node = iter.m_Node;

		--m_Size;
		return result;
	}
//////////////////////////////////////////////////////////////////////




private:
	//기준 노드의 오른쪽으로 쭉 타고 가면 최댓값이 나온다.
	PNODE FindMax(PNODE Node)
	{
		if (!Node->m_Right)
			return Node;

		return FindMax(Node->m_Right);
	}
	//반대로 똑같이
	PNODE FindMin(PNODE Node)
	{
		if (!Node->m_Left)
			return Node;

		return FindMax(Node->m_Left);
	}








//탐색 함수
public:
	iterator Find(const int& Key) const
	{
		return Find(Key, m_Root);
	}

private:
	iterator Find(const int& Key, PNODE Node) const
	{
		if (!Node)
			return end();

		
		//일치하는 키를 찾으면 이터레이터에 해당 노드를 담아서 리턴
		if(Node->m_Key == Key)
		{
			iterator iter;
			iter.m_Node = Node;
			return iter;
		}
		//만약 키 값이 노드의 키보다 작으면 왼쪽으로 탐색
		else if (Node->m_Key > Key)
		{
			return Find(Key, Node->m_Left);
		}
		
		//if문을 전부 통과해도 못 찾았을 경우 오른쪽으로 탐색 시작
		return Find(Key, Node->m_Right);
	}




//전위순회: 루트->왼쪽->오른쪽 순
//중위순회: 왼쪽->루트->오른쪽 순
//후위순회: 왼쪽->오른쪽->루트 순(리프노드부터)
//함수의 재귀구조는 동일하고, 출력 순서만 바꿔주면 됨.
public:
	void PreOrder(void (*Func)(const int&, const int&, const int&, const int&, const int&))
	{
		PreOrder(Func, m_Root);
	}
private:
	void PreOrder(void (*Func)(const int&, const int&, const int&, const int&, const int&), PNODE Node)
	{
		if (!Node)
			return;

		int ParentKey = 0, RightKey = 0, LeftKey = 0;

		if (Node->m_Parent)
			ParentKey = Node->m_Parent->m_Key;
		if (Node->m_Right)
			RightKey = Node->m_Right->m_Key;
		if (Node->m_Left)
			LeftKey = Node->m_Left->m_Key;

		Func(Node->m_Key, Node->m_Data, ParentKey, LeftKey, RightKey);

		PreOrder(Func, Node->m_Left);
		PreOrder(Func, Node->m_Right);
	}

public:
	void InOrder(void (*Func)(const int&, const int&, const int&, const int&, const int&))
	{
		InOrder(Func, m_Root);
	}
private:
	void InOrder(void (*Func)(const int&, const int&, const int&, const int&, const int&), PNODE Node)
	{
		if (!Node)
			return;

		int ParentKey = 0, RightKey = 0, LeftKey = 0;

		if (Node->m_Parent)
			ParentKey = Node->m_Parent->m_Key;
		if (Node->m_Right)
			RightKey = Node->m_Right->m_Key;
		if (Node->m_Left)
			LeftKey = Node->m_Left->m_Key;


		InOrder(Func, Node->m_Left);
		Func(Node->m_Key, Node->m_Data, ParentKey,LeftKey, RightKey);
		InOrder(Func, Node->m_Right);

	}

public:
	void PostOrder(void (*Func)(const int&, const int&, const int&, const int&, const int&))
	{
		PostOrder(Func, m_Root);
	}
private:
	void PostOrder(void (*Func)(const int&, const int&, const int&, const int&, const int&), PNODE Node)
	{
		if (!Node)
			return;

		int ParentKey = 0, RightKey = 0, LeftKey = 0;

		if (Node->m_Parent)
			ParentKey = Node->m_Parent->m_Key;
		if (Node->m_Right)
			RightKey = Node->m_Right->m_Key;
		if (Node->m_Left)
			LeftKey = Node->m_Left->m_Key;


		PostOrder(Func, Node->m_Left);
		PostOrder(Func, Node->m_Right);

		Func(Node->m_Key, Node->m_Data, ParentKey, LeftKey, RightKey);

	}

};

'C++기초' 카테고리의 다른 글

220419_자료구조_자가균형트리_AVL트리(AVL Tree) 구현  (0) 2022.04.24
220415~220418_자료구조_이진트리(Binary Tree) 실행  (0) 2022.04.22
220414_자료구조_일반 트리(Tree)  (0) 2022.04.20
220413_자료구조_배열형 큐(써클 큐, Circle Queue) 구현 및 실행  (0) 2022.04.18
220413_자료구조_리스트형 큐 구현 및 실행  (0) 2022.04.18
'C++기초' 카테고리의 다른 글
  • 220419_자료구조_자가균형트리_AVL트리(AVL Tree) 구현
  • 220415~220418_자료구조_이진트리(Binary Tree) 실행
  • 220414_자료구조_일반 트리(Tree)
  • 220413_자료구조_배열형 큐(써클 큐, Circle Queue) 구현 및 실행
hyrule
hyrule
hyrule
C++ 프로그래밍 공부
hyrule
전체
오늘
어제
  • 분류 전체보기 (205)
    • C++기초 (50)
    • WIN32API FrameWork (109)
      • 한단계씩 직접 구현 (82)
      • 원본 (15)
      • 코드별 설명 개별저장(검색용) (12)
    • 자습 (21)
    • C++ TIPS (11)
    • 연습 노트 (3)
    • ETC (6)
    • DX2D StarCraft 모작 (1)

블로그 메뉴

  • 홈
  • 방명록

공지사항

인기 글

태그

  • C++
  • hello
  • Windows 11
  • notion
  • 스타크래프트
  • Tistory

최근 댓글

최근 글

hELLO · Designed By 정상우.
hyrule
220415~220418_자료구조_이진트리(Binary Tree) 구현
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.