ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 트리(Tree)와 이진 탐색 트리(Binary Search Tree)
    프로그래밍 기초/자료구조 2021. 3. 29. 19:33

      트리 

    각각의 자료가 노드를 이루고 각 노드가 부모-자식 관계를 형성하는 자료구조를 말한다.

     

    트리의 구성 요소

    트리는 하나의 루트와 여러 개의 자식 노드로 구성된다.

    • 노드 트리를 이루는 각각의 요소
    • 루트 트리의 최상단 요소
    • 리프 트리의 최말단 요소
    • 차수 각 노드가 가진 자식의 수
      • 트리의 차수 해당 트리에 존재하는 각 노드의 차수 중 가장 큰 값
    • 높이 트리의 노드의 레벨 중 가장 큰 값

     

    이진트리

    리프 노드를 제외한 노드의 자식이 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
    ----------------------
    ----------------------

     

    댓글

Designed by Tistory.