본문 바로가기
자료구조

트리

by kcj3054 2021. 12. 15.

트리란?

  • 정의는 사이클이 발생하지 않으면서 모든 노드끼리 연결되어있는 것이다.

  • 트리는 rooted tree, unrooted tree로 두가지가있다.

  • 노드가 하나라도 트리라고 부를 수 있다.

이진트리란?

  • 이진트리는 자식이 두개가 있는 것이아니라, 자식을 최대 2개로 제한하는 것이다. 1개나 2개가 될 수도 있다.

  • 이진트리는 구현하기가 쉽다 배열로도 구현이 쉽다 i번 노드에 대해서 왼쪽 노드는 i * 2, 오른쪽 노드는 i * 2 + 1이다

  • 반대로 자식들의 부모 노드는 자식 노드에 대해서 /2를 하면된다.

트리의 순회

  • 트리의 순회에서는 트리를 만들고 난 뒤 전위순회, 후위순회, 중위순회가 있다.
  • 재귀를 통해서 만들 수 있다

만들기

  • Tree를 struct구조체로 만들어서 상태를 관리하는데 해당 트리들은 왼쪽 자식 정보, 오른쪽 자식 정보, 현재 노드 위치가 필요하다
struct Tree {

    int idx;
    int x;
    int y;
    Tree *Left;
    Tree *Right;

};

전위 순회

  • 전위탐색은 부모 -> 왼쪽 자식 순회 -> 오른쪽자식순으로 탐색인데 여기서 부모를 먼저 색칠한 후 왼쪽 자식들을 방문하고, 그 이후에 오른쪽 자식들을 방문하는 것이다..

후위 순회

  • 후위 순회는 해당 루트노드를 마지막으로 검사하겠다는 것이다.
  • 그래서 순서가 LEFT- RIGHT - ROOT이다
  • 위의 그림에서는
  • 처음 '루트' 9에서 왼쪽으로간다 그럼 4번노드가 또 다시 새로운 '루트'가되고 4번의 왼쪽으로 가서 1번노드에 입장한다.
  • 1번 노드입장에서는 해당 노드가 왼쪽이기에 1번을 보고 1번 입장에서 '루트인 4번'의 right노드인 3번을 본다
  • 또 다시 3번입장에서는 해당 노드가 '루트'노드가 되고 해당 노드의 왼쪽이 없다면 right노드를 보게되고 2번노드를 보게된다.
  • void PostOrder(Tree * Root, vector<int> &anwer) { if(Root == NULL) return; PostOrder(Root -> Left, anwer); PostOrder(Root -> Right, anwer); anwer.push_back(Root -> idx); }

전위 순회

  • 전위순회는 해당 루트노드를 먼저 (전위) 검사하겠다는 의미이다.
  • 순서는 root -> left -> right이다
  • 루트를 먼저보니 1번을 보고 왼쪽인 2번으로 내려가는데 2번입장에서는 해당 노드가 '루트'노드이기에 해당 노드를 검사하고 다시 왼쪽으로 쭉 쭉 간다 이런식으로 진행하면된다.
void preOrder(Tree * Root, vector<int> &answer) {

    if(Root == NULL) return ;
    answer.push_back(Root -> idx);
    preOrder(Root -> Left, answer);
    preOrder(Root -> Right, answer);
}

순회에서 문제!! (두종류 순회의 결과를 알고 있다면 한 종류의 결과도 알 수 있다)

이진트리 vs 이진 탐색트리 vs 완전이진트리

  • 이진트리는 노드의 갯수가 최대 2개로 제한되는 것이고 이진 탐색트리는 루트의 왼쪽에는 자신보다 작은 값이 오른쪽은 자신보다 큰 값을 두는 것이다..

  • 완전이진트리는 트리의 모든 값이 왼쪽에서 순서대로 차 있는 것을 의미힌다..

이진탐색트리에서 삽입

  • null에 도달하기 전까지 계속 진행하여 삽입하려는 값 x가 들어갈 위치를 찾아줘야한다

  • node가 도달한 null인 지점에서의 parent 정보를 알아야지만, 삽입이 가능하다는 것을 알 수 있다.

  • node가 null이 되면 parent는 바로 그 위의 부모 노드를 가리킨다.

  • 노드를 추가할 때 3가지 경우가 존재한다

    • parent가 null일때 root를 새로운 노드로 정한다.
    • parent가 삽입하려는 값보다 더 큰 경우는 삽입하려는 값을 parent의 왼쪽에 넣어두자
    • parent < node->data 인경우는 parent의 오른쪽에 두자..

이진 탐색트리에서 삭제

  • 자식이 없는 경우 해당 노드만 그냥 지우면된다.

  • 자식이 하나만 있는 경우 해당 자식을 지울 노드위치로 올리면된다.

  • 자식이 두개가 있을 경우.. 지울려는 노드의 next값(지울려는 노드보다 크면서 최솟값)을 찾아서 next값을 지울려는 node위치에 두고 next를 지우면된다.

            20
         10       30
              25  40
               26    50
  • 위의 경우에서 20을 지울려고할 때 next는 26이다 그럼 26을 20자리로 올리고 26자리를 지우면된다.

    이진 탐색 트리에서 발생할 수 있는 문제점

  • 만약 이진탐색트리가 균형잡혀 있지 않는 경우에서는 시간복잡도가 o(n)이 될 수도 있다. 해당 경우를 방지하기 위해서 노드를 회전 등의 작업을 통해 적절하게 조절하여 왼쪽과 오른쪽 자식간의 높이 차가 크게 벌어지지 않도록 하여 시간복잡도를 줄일 수 있다.

이진 탐색 트리 소스 1

#pragma once
#include <iostream>
using namespace std;

/*
*   10 
*        20
*            30
*/
struct Node
{
    Node* parent = nullptr;
    Node* left = nullptr;
    Node* right = nullptr;
    int key = 0;
};

class BinarySearchTree
{
public:
    void Print_Inorder() { Print_Inorder(_root); }
    void Print_Inorder(Node* node);

    void Insert(int key);

    void Delete(Node* node);

    void Replace(Node* u, Node* v);
    Node* Search(Node* node, int key);

    Node* Min(Node* node);
    Node* Max(Node* node);
    Node* Next(Node* node);

private:
    Node* _root = nullptr;
};

이진 탐색트리 소스2

#include "BinarySearchTree.h"

void BinarySearchTree::Print_Inorder(Node* node)
{
    if (node == nullptr)
        return;

    cout << node->key << endl;

    Print_Inorder(node->left);

    Print_Inorder(node->right);

}

void BinarySearchTree::Insert(int key)
{
    Node* newNode = new Node();
    newNode->key = key;


    if (_root == nullptr)
    {
        _root = newNode;
        return;
    }

    Node* node = _root;
    Node* parent = _root;

    //어디에 놓을 것인지 찾아주기 
    while (node != nullptr)
    {
        parent = node;
        if (node->key < key)
        {
            node = node->right;
        }
        else node = node->left;
    }

    // 붙이기작업시작..
    newNode->parent = parent;
    if (key < parent->key)
    {
        parent->left = newNode;
    }
    else parent->right = newNode;


}

void BinarySearchTree::Delete(Node* node)
{
    //자식이 없으면 그냥 해당 노드만 지우면된다.

    //자식이 하나만 있어도 쉽다 해당 자식만 지울 노드위치로
    //올리면된다..

    //자식이 두명 다 있을때는 조금힘들다..
    /*
    *      20
    *   10    30
    *       25  40
    *        26   50
    * 
    * 위에서 20을 지울려고하는데 자식이 둘다있다.
    * 그래서 20의  successor, next값을 구해서..
    * 그값을 20의 위치에 두고 next인 25는 지우기전에 
    * 26을 25의 위치에올린다..
    */

    //자식이 한명만 있을때 왼족인지 오른쪽인지 구분하지않고..

    if (node->left == nullptr)
        Replace(node, node->right);
    else if (node->right == nullptr)
        Replace(node, node->left);
    else
    {
        //successor찾기 다음데이터찾기 
        Node* next = Next(node);
        node->key = next->key;
        Delete(next);
    }
}

//u서브트리를 v서브트리로 교체 
void BinarySearchTree::Replace(Node* u, Node* v)
{
    if (u->parent == nullptr)
        _root = v;
    else if (u == u->parent->left)
        u->parent->left = v;
    else
        u->parent->right = v;

    if (v)
        v->parent = u->parent;

}

Node* BinarySearchTree::Search(Node* node, int key)
{
    if (node == nullptr || node->key == key)
        return node;

    if (key < node->key)
        Search(node->left, key);
    else
        Search(node->right, key);

    return nullptr;
}

Node* BinarySearchTree::Min(Node* node)
{
    while (node)
    {
        node = node->left;
    }
    return node;
}

Node* BinarySearchTree::Max(Node* node)
{
    while (node)
    {
        node = node->right;
    }
    return node;
}

Node* BinarySearchTree::Next(Node* node)
{
    //node의 오른쪽이 존재한다면 
    if (node->right)
    {
        return Min(node->right);
    }

    //node의 오른쪽이 존재하지않는다면..
    Node* parent = node->parent;

    /*
        20
    10        30
          25  40
           26  50
    중에서 26다음을 찾을 때 26이 내부모의 왼쪽자식이면
    부모를 바로 리턴하면된다.
    그렇지 않을 경우 부모중에서 나보다 큰 값을 찾아야한다..


    */
    while (parent && node == parent->right)
    {
        node = parent;
        parent = parent->parent;
    }

    return parent;


}



레드블랙 트리

레드 블랙 트리란?

  • 루트노드는 블랙이다.

  • 모든 리프 노드들은 블랙이다.

  • 레드 노드의 자식은 언제나 블랙이다. 레드 - 레드는 연속할 수 없다.

  • 각 노드로부터 ~ 리프까지 가는 경로들은 모두 같은 수의 black을 만난다..

red - black - tree의 삭제

'자료구조' 카테고리의 다른 글

list 구현  (0) 2022.04.22
stack 구현  (0) 2022.04.09