✏️ 트리는 그래프의 일종이다.
(순환하지 않는 그래프로, 루트로부터 어떤 노드로 가는 경로는 유일하다.)
용어
✏️ 가능한 자식의 최대 수에 따라 트리를 구분할 수 있다.
- 제한 X: 일반적인 트리
- : binary tree
이진 트리의 조건
Full Binary Tree
✏️ Full Tree와 Complete Tree
: 이진 트리의 특성을 나타내는 용어
- Full Tree
: 모든 leaves가 같은 레벨에 있고, 모든 non-leaf 노드가 두 개의 자식을 갖는 이진 트리
- Complete Tree
: 마지막 레벨을 제외한 모든 레벨에서는 노드가 완전히 채워져 있고, 마지막 레벨에서는 왼쪽부터 순서대로 노드가 채워진 이진 트리
template<class ItemType>
struct TreeNode {
ItemType info;
TreeNode* left;
TreeNode* right;
};
: 포인터를 사용하는 동적 할당 구조TreeType
클래스#include <fstream.h>
#include <iostream.h>
template<class ItemType>
struct TreeNode;
enum OrderType {PRE_ORDER, IN_ORDER, POST_ORDER};
template<class ItemType>
class TreeType {
public:
TreeType(); // 생성자
~TreeType(); // 소멸자
TreeType(const TreeType<ItemType>&); // 복사 생성자
void operator=(const TreeType<ItemType>&); // 대입 연산자
void MakeEmpty();
bool isEmpty() const;
bool isFull() const;
int LengthIs() const;
void RetrieveItem(ItemType&, bool& found);
void InsertItem(ItemType);
void DeleteItem(ItemType);
void ResetTree(OrderType);
void GetNextItem(ItemType&, OrederType, bool&);
void PrintTree(ofstream&) const;
private:
TreeNode<ItemType>* root;
};
bool TreeType::IsFull() const
{
NodeType* location;
try
{
location = new NodeType;
delete location;
return false;
}
catch(std::bad_alloc exception)
{
return true;
}
}
bool TreeType::IsEmpty() const
{
return root == NULL;
}
// CountNodes
int TreeType::LengthIs() const
{
return CountNodes(root);
}
int CountNodes(TreeNode* tree)
{
if (tree == NULL)
return 0;
else
return CountNodes(tree->left) + CountNodes(tree->right) + 1;
// 왼쪽 서브 트리의 노드 개수 + 오른쪽 서브 트리의 노드 개수
}
RetrieveItem
// RetrieveItem
// : wrraper 함수, 시작점을 루트로 설정하고 Retrieve 호출
void TreeType::RetrieveItem(ItemType& item, bool& found)
{
Retrieve(root, item, found);
}
void Retrieve(TreeNode* tree, ItemType& item, bool& found)
{
if (tree == NULL) // base case 1
found = false;
else if (item < tree->info) // general case 1
Retrieve(tree->left, item, found);
else if (item > tree->info) // general case 2
Retrieve(tree->right, item, found);
else { // base case 2
item = tree->info;
found = true;
}
}
✏️ 만약 재귀가 아닌 반복(iteration)으로 구현한다면…
void FindNode(TreeNode* tree, ItemType item, TreeNode*& nodePtr, TreeNode*& parentPtr) { nodePtr = tree; parentPtr = NULL; bool found = false; while (nodePtr != NULL && !found) { if (item < nodePtr->info) { parentPtr = nodePtr; nodePtr = nodePtr->left; } else if (item > nodePtr->info) { parentPtr = nodePtr; nodePtr = nodePtr->right; } else found = true; } }
InsertItem
// 트리 클래스 내의 멤버 함수
// wrapper 함수로 시작점을 루트로 설정하고 Insert 호출
void TreeType::InsertItem(ItemType item)
{
Insert(root, item)
}
void Insert(TreeNode*& tree, ItemType item)
{
if (tree == NULL) // insertion place found.
{
tree = new TreeNode;
tree->right = NULL;
tree->left = NULL;
tree->info = tiem;
}
else if (item < tree->info)
Insert(tree->left, item);
else
Insert(tree->right, item);
}
✏️ 만약 재귀가 아닌 반복으로 구현한다면…
void TreeType::InsertItem(ItemType item) { TreeNode* newNode; TreeNode* nodePtr; TreeNode& parentPtr; newNode = new TreeNode; newNode->info = item; newNode->left = NULL; newNode->right = NULL; FindNode(root, item, nodePtr, parentPtr); if (parentPtr == NULL) root = newNode; else if (item < parentPtr->info) parentPtr->left = newNode; else parentPtr->right = newNode; } }
DeleteItem
이진 검색 트리 내에 존재하는 특정 아이템을 삭제하는 함수
삭제할 노드(아이템)의 위치에 따른 세 가지 분류
코드
void TreeType::DeleteItem(ItemType item)
{
Delete(root, item);
}
void Delete(TreeNode*& tree, ItemType item)
{
if (item < tree->info)
Delete(tree->left, item);
else if (item > tree->info)
Delete(tree->right, item)
else
DeleteNode(tree, item); // 삭제할 노드를 찾은 경우
}
void DeleteNode(TreeNode*& tree, ItemType data)
// tree: 삭제하고자 하는 노드를 가리키는 포인터 변수
{
TreeNode* tempPtr;
tempPtr = tree;
// 삭제할 노드의 자식이 0 또는 1개인 경우
if (tree->left == NULL) { // 삭제할 노드의 오른쪽 자식만 존재하는 경우
tree = tree->right; // 오른쪽 자식의 포인터 수정
delete tempPtr // 삭제
}
else if (tree->right == NULL) // 삭제할 노드의 왼쪽 자식만 존재하는 경우
tree = tree->left; // 왼쪽 자식의 포인터 수정
delete tempPtr; // 삭제
}
// 삭제할 노드의 자식이 2개인 경우
else
{
GetPredecessor(tree->left, data); // predecessor 설정
tree->info = data; // 식제할 노드의 값, predecessor로 대체
Delete(tree->left, data); // 기존의 predecessor 노드 삭제
}
}
void GetPredecessor(TreeNode* tree, ItemType& data)
{
while (tree->right != NULL)
tree = tree->right;
data = tree->info;
}
✏️ 만약 재귀가 아닌 반복으로 구현한다면 (FindItem 사용)
<template class ItemType> void TreeType::DeleteItem(ItemType item) { TreeNode* nodePtr; TreeNode* parentPtr; FindNode(root, item, nodePtr, parentPtr) // item이 삽입될 위치 find if (nodePtr == root) DeleteNode(root); else { if (parent->left == nodePtr) DeleteNode(parentPtr->left); else DeleteNode(parentPtr->right); } }
void PrintTree(TreeNode* tree, ofstream& outFile)
{
if (tree != NULL)
{
PrintTree(tree->left, outFile);
outFile << tree->info;
PrintTree(tree->right, outFile);
}
}
✏️ 트리 순회, inorder
- left에 대해 재귀
- 자기 자신 처리
- right에 대해 재귀
template<class ItemType>
TreeType<ItemType>::TreeType()
{
root = NULL;
}
template<class ItemType>
TreeType<ItemType>::~TreeType()
{
Destroy(root);
}
void Destroy(TreeNode*& tree)
{
if (tree != NULL)
{
Destroy(tree->left);
Destroy(tree->right);
delete tree;
}
}
✏️ 트리 순회, post order
- left 재귀 호출
- right 재귀 호출
- 자기 자신(root 처리): 메모리 해제
delete
TreeType::TreeType(const TreeType& originalTree)
{
CopyTree(root, originalTree.root);
}
void CopyTree(TreeNode*& copy, const TreeNode* originalTree)
{
if (originalTree == NULL)
copy = NULL;
else
{
copy = new TreeNode;
copy->info = originalTree->info;
CopyTree(copy->left, originalTree->left);
CopyTree(copy->right, originalTree->right);
}
}
✏️ 트리 순회, pre order
- 자기 자신 처리
- left 재귀 호출
- right 재귀 호출
void InOrder(TreeNode* tree, QueType& inQue)
{
if (tree != NULL)
{
InOrder(tree->left, inQue);
InQue.Enqueue(tree->info);
InOrder(tree->right, inQue);
}
}
void PostOrder(TreeNode* tree, QueType& postQue)
{
if (tree != NULL)
{
PostOrder(tree->left, postQue);
PostOrder(tree->right, postQue);
postQue.Enqueue(tree->info);
}
}
void PreOrder(TreeNode* tree, QueType& preQue)
{
if (tree != NULL)
{
preQue.Enqueue(tree->info);
PreOrder(tree->left, preQue);
PreOrder(tree->right, preQue);
}
}
글이 너무 깔끔해요! 잘 읽고 갑니다~~