Tree


실제 나무를 거꾸로 한 것과 같은 모양을 하고 있기 때문에 트리라고 부른다. 나뭇가지처럼 노드들이 서로 연결된 비선형 계층 구조를 가지고 있다. 또한 트리 내에 다른 하위 트리가 있고 그 하위 트리 안에는 또 다른 하위 트리가 있는 재귀적 자료구조이기도 하다. 컴퓨터의 direcory구조가 트리 구조의 대표적인 예가 될 수 있다.

 

트리와 관련된 용어


  • 루트 노드(root node): 부모가 없는 노드, 트리는 하나의 루트 노드만을 가진다.
  • 단말 노드(leaf node): 자식이 없는 노드, ‘말단 노드’ 또는 ‘잎 노드’라고도 부른다.
  • 내부(internal) 노드: 단말 노드가 아닌 노드
  • 간선(edge): 노드를 연결하는 선 (link, branch라고도 부름)
  • 형제(sibling): 같은 부모를 가지는 노드
  • 노드의 크기(size): 자신을 포함한 모든 자손 노드의 개수
  • 노드의 깊이(depth): 루트에서 어떤 노드에 도달하기 위해 거쳐야 하는 간선의 수
  • 노드의 레벨(level): 트리의 특정 깊이를 가지는 노드의 집합
  • 노드의 차수(degree): 하위 트리 개수 / 간선 수 (degree) = 각 노드가 지닌 가지의 수
  • 트리의 차수(degree of tree): 트리의 최대 차수
  • 트리의 높이(height): 루트 노드에서 가장 깊숙히 있는 노드의 깊이

 

이진 탐색 트리


일반적인 이진트리란 각각의 노드가 최대 2개의 자식 노드를 가질 수 있는 트리이다. 반면 이진 탐색 트리는 기본적인 특징은 이진트리와 같지만 하나 다른 점은 자기 왼쪽에는 자신보다 값이 작은 노드가 오른쪽에는 자신보다 값이 큰 노드가 와야 한다. 이와 같은 특징으로 인해 delete나 insert를 할 시 올바른 위치를 찾아 곧바로 넣거나 뺄 수 있으며 최댓값과 최솟값을 쉽게 찾을 수 있다. 시간 복잡도 또한 평균적으로 O(log n)이며 최악의 경우는 O(n)으로 우수한 자료구조라고 할 수 있다. 이러한 이유로 트리 중에서 가장 널리 쓰이는 자료구조이다.

 

 

이진 탐색 트리의 구조


  • 최상위 레벨에 루트 노드가 있다.
  • 루트 노드는 0개 이상 2개 이하의 자식 노드를 갖고 있다.
  • 그 자식 노드 또한 0개 이상 2개 이하의 자식 노드를 갖고 있고 이는 반복적으로 정의된다.
  • 노드들과 노드들을 연결하는 간선들로 구성되어 있으며 사이클이 존재할 수 없다.
  • 왼쪽 노드는 해당 노드보다 작은 값, 오른쪽 노드는 해당 노드보다 큰 값을 가지고 있다.

 

 

이진 탐색 트리 insert, select 구현


public class MyTree{
    
    //트리의 최상위 노드
    public Node head;
    
    //각각의 노드
    public class Node{
        int data;
        Node left = null;
        Node right = null;
        
        public Node(int data){
            this.data = data;
        }
    }
    
    //트리에 노드를 삽입하는 메소드
    public boolean insertNode(int data){
        
        if(this.head == null){
            //트리에 노드가 하나도 저장이 안되어 있을 때
            this.head = new Node(data);
            return true;
        } else {
            //트리에 노드가 하나 이상 저장이 되어 있을 때
            Node findNode = this.head;
            while(true){
                //insert하려는 data가 저장되어 있는 data보다 작을 때
                if(data < findNode.data){
                    if(findNode.left == null){
                        //노드가 저장이 안되어 있으면 저장하기
                        findNode.left = new Node(data);
                        break;
                    } else {
                        //노드가 저장되어 있으면 왼쪽 노드로 이동하기
                        findNode = findNode.left;
                    }
                } else {
                    //insert하려는 data가 저장되어 있는 data보다 클 때
                    if(findNode.right == null){
                        //노드가 저장이 안되어 있으면 저장하기
                        findNode.right = new Node(data);
                        break;
                    } else {
                        //노드가 저장되어 있으면 오른쪽 노드로 이동하기
                        findNode = findNode.right;
                    }
                }
            }
            return true;
        }
    }
    
    //트리의 노드를 검색하는 메소드
    public Node searchNode(int data){
        
        if(this.head == null){
            //트리에 노드가 하나도 없을 시
            return null;
        } else {
            //트리에 노드가 하나 이상 있을 시
            Node findNode = this.head;
            while(findNode != null){
                if(data == findNode.data){
                    //search하려는 data와 노드의 data가 동일할 시
                    return findNode;
                } else if(data < findNode.data){
                    //search하려는 data가 노드의 data보다 작을 시
                    findNode = findNode.left;
                } else {
                    //search하려는 data가 노드의 data보다 클 시
                    findNode = findNode.right;
                }
            }
            //해당 트리에 일치하는 노드가 없을 시
            return null;
        }
    }
    
}

 

 

이진 탐색 트리 delete 구현


delete의 구현을 케이스로 나누면 하기와 같은 케이스로 정리할 수 있다.

  •  삭제 대상인 노드가 leaf 노드 일 경우 : 삭제할 노드의 부모 노드가 삭제 대상 노드를 가리키지 않도록 한다.
  •  삭제 대상인 노드가 자식 노드를 한 개 가지고 있을 경우 : 삭제할 노드의 부모 노드가 삭제할 노드의 자식 노드를 가리키도록 한다
  • 삭제 대상인 노드가 자식 노드를 두 개 가지고 있을 경우 :
    •   삭제할 노드의 오른쪽 자식 중 가장 작은 값을 삭제할 노드의 부모 노드가 가리키도록 한다.
    •   삭제할 노드의 왼쪽 자식 중 가장 큰 값을 삭제할 노드의 부모 노드가 가리키도록 한다.
          //트리의 노드를 삭제하는 메소드
          public boolean deleteNode(int data){
              if(this.head == null){
                  //트리에 노드가 하나도 없을 시
                  return false;
              } else if(this.head.data == data && this.head.left == null && this.head.right == null){
                  //트리에 노드가 하나있고 해당 노드가 삭제 대상의 노드일 시
                  this.head = null;
                  return true;
              } else {
                  //트리에 노드가 여러개 있고 삭제 대상 노드가 존재하는지 모를 시
                  
                  //삭제대상 노드가 트리에 있는지 체크하는 플래그
                  boolean searchedFlag = false;
                  
                  Node curNode = this.head;
                  Node curParentNode = this.head;
                  
                  //삭제 대상 노드가 트리에 있는지 체크
                  while(curNode != null){
                      if(data == curNode.data){
                          //삭제 대상 노드가 있을 시
                          searchedFlag = true;
                          break;
                      } else if(data < curNode.data){
                          curParentNode = curNode;
                          curNode = curNode.left;
                      } else {
                          curParentNode = curNode;
                          curNode = curNode.right;
                      }
                  }
                  
                  if(searchedFlag == false){
                      //삭제 대상 노드가 트리에 없을 시
                      return false;
                  }
                  
                  if(curNode.left == null && curNode.right == null) {
                      //1. 삭제 대상 노드가 리프 노드일 경우
                      if(data < curParentNode.data){
                          curParentNode.left = null;
                          return true;
                      } else {
                          curParentNode.right = null;
                          return true;
                      }
                  } else if(curNode.left != null && curNode.right == null) {
                      //2-1.삭제 대상 노드가 왼쪽 자식 노드만을 가지고 있을 경우
                      if(data < curParentNode.data) {
                          curParentNode.left = curNode.left;
                          return true;
                      } else {
                          curParentNode.right = curNode.left;
                          return true;
                      }
                  } else if(curNode.left == null && curNode.right != null) {
                      //2-2. 삭제 대상 노드가 오른쪽 자식 노드만을 가지고 있을 경우
                      if(data < curParentNode.data){
                          curParentNode.right = curParentNode.right;
                          return true;
                      } else {
                          curParentNode.right = curParentNode.left;
                          return true;
                      }
                  } else {
                      //3. 삭제 대상 노드가 두개의 자식 노드를 가지고 있을 경우
                      Node changeNode = curNode.right;
                      Node changeParentNode = curNode.right;
      
                      //삭제 대상 노드의 오른쪽 자식 노드들 중 가장 왼쪽 값찾기
                      //(즉, 오른쪽 자식 노드 들 중 가장 작은 값 찾기)
                      while(changeNode.left != null){
                          changeParentNode = changeNode;
                          changeNode = changeNode.left;
                      }
      
                      if(changeNode.right != null){
                          //삭제 대상 노드의 오른쪽 자식 중 가장 작은 값을 가진 노드의 오른쪽에 자식 노드가 있을 사
                          changeParentNode.left = changeNode.right;
                      } else {
                          //삭제 대상 노드의 오른쪽 자식 중 가장 작은 값을 가진 노드의 오른쪽에 자식 노드가 없을 시
                          changeParentNode.left = null;
                      }
                      
                      if(data < curParentNode.data){
                           //삭제 대상의 부모 노드의 왼쪽 자식 노드에 삭제할 노드의 오른쪽 자식중
                          //가장 작은 값을 가진 노드를 연결
                          curParentNode.left = changeNode;
                          changeNode.left = curNode.left;
                          changeNode.right = curNode.right;
                          
                          curNode = null;
                          
                          return true;
                      } else {
                          //삭제 대상의 부모 노드의 오른쪽 자식 노드에 삭제할 노드의 오른쪽 자식중
                          //가장 작은 값을 가진 노드를 연결
                          curParentNode.right = changeNode;
                          changeNode.left = curNode.left;
                          changeNode.right = curNode.right;
                          
                          curNode = null;
                          return true;
                      }
                  }
              }
          }

 

 

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

Heap  (0) 2021.12.21
Hash Table  (0) 2021.10.24
Double Linked List  (0) 2021.10.15
Single Linked List  (0) 2021.10.15
Stack  (0) 2021.10.15

+ Recent posts