yong_ღ'ᴗ'ღ 2022. 9. 14. 00:15
  • 용어

       - 루트(root): 가장 윗부분에 위치하는 노드. 하나의 트리에는 하나의 루트가 있음

       - 리프(leaf): 트리의 가장 아랫부분에 위치하는 노드 

       - 레벨(level): 루트로부터 얼마나 떨어져 있는지에 대한 값 (루트의 레벨은 0)

       - 차수(degree): 노드가 갖는 자식의 수. 모든 노드의 차수가 n 이하인 트리를 n진 트리라고 한다.

       - 높이(height): 루트로부터 가장 멀리 떨어진 리프까지의 거리 (리프 레벨의 최댓값)

  • 너비 우선 탐색(BFS): 낮은 레벨에서 시작해서 왼쪽 → 오른쪽 방향으로 검색
  • 깊이 우선 탐색(DFS): 리프까지 내려가면서 검색하는 것을 우선순위로 하는 탐색 방법
  • 전위 순회(Preorder): 노드 방문 → 왼쪽 자식 → 오른쪽 자식
  • 중위 순회(Inorder): 왼쪽 자식 → 노드 방문 → 오른쪽 자식
  • 후위 순회(Postorder): 왼쪽 자식 → 오른쪽 자식 → 노드 방문
  • 이진트리(binary tree): 각 노드의 자식은 2명 이하만 유지
  • 완전이진트리(complete binary tree): 루트부터 노드가 채워져 있으면서, 같은 레벨에서는 왼쪽에서 오른쪽으로 노드가 채워져 있는 이진 트리 (마지막 레벨은 반드시 끝까지 채울 필요는 없음. 마지막 레벨 제외 노드 가득 채움)

 

 

  • 이진검색트리(binary search tree)

- 특징

  · 이진검색트리를 중위 순회 하면 키 값의 오름차순으로 노드를 얻을 수 있다.

  · 구조가 단순하다.

  · 이진검색과 비슷한 방식으로 검색이 가능하다.

  · 노드의 삽입이 쉽다.

 

- 조건

1) 어떤 노드 N을 기준으로 왼쪽 서브 트리 노드의 모든 키 값은 노드 N의 키 값보다 작아야 한다.

2) 오른쪽 서브 트리 노드의 키 값은 노드 N의 키 값보다 커야 한다.

3) 같은 키 값을 갖는 노드는 없다.

public class BinTree<K,V> {
    static class Node<K,V> {
        private K key;
        private V data;
        private Node<K,V> left;
        private Node<K,V> right;
        
        Node(K key, V data, Node<K,V> left, Node<K,V> right) {
            this.key = key;
            this.data = data;
            this.left = left;
            this.right = right;
        }
        
        // 키 값을 반환
        K getKey() {
            return key;
        }
        
        // 데이터를 반환
        V getValue() {
            return data;
        }
        
        // 데이터를 출력
        void print() {
            System.out.println(data);
        }
    }
    
    private Node<K,V> root;                             // 루트
    private Comparator<? super K> comparator = null;    // 비교자
    
    // 생성자1: 비어 있는 이진검색트리 생성, 자연 순서에 따라 키 값을 비교
    //-- K 타입이 Comparable() 구현하고 있는 Integer, String... 등에 알맞음!
    public BinTree() {
        root = null;
    }
    
    // 생성자2: 비어 있는 이진검색트리 생성, 비교자로 키 값을 비교
    public BinTree(Comparator<? super K> c) {
        this();
        comparator = c;
    }
    
    // 두 키 값을 비교
    private int comp(K key1, K key2) {
        return (comparator == null) ? ((Comparable<K>)key1).compareTo(key2)
                : comparator.compare(key1, key2);
    }
    
    // 키에 의한 검색
    public V search(K key) {
        Node<K,V> p = root;
        
        while (true) {
            if (p == null)
                return null;
            int cond = comp(key, p.getKey());
            
            // 같으면 검색 성공
            if (cond == 0)
                return p.getValue();
            // key가 작으면 왼쪽 서브 트리로 
            else if (cond < 0)
                p = p.left;
            // key가 크면 오른쪽 서브 트리로 
            else 
                p = p.right;
        }
    }
    
    // node를 루트로 하는 서브 트리에 노드<K,V>를 삽입
    private void addNode(Node<K,V> node, K key, V data) {
        int cond = comp(key, node.getKey());
        if (cond == 0)
            return;     // key가 이미 존재함 (삽입할 수 없음)
        else if (cond < 0) {
            if (node.left == null)
                node.left = new Node<K,V>(key, data, null, null);
            else 
                addNode(node.left, key, data);
        } else {
            if (node.right == null)
                node.right = new Node<K,V>(key, data, null, null);
            else 
                addNode(node.right, key, data);
        }
    }
    
    // 노드 삽입
    public void add(K key, V data) {
        if (root == null)
            root = new Node<K,V>(key, data, null, null);
        else 
            addNode(root, key, data);
    }
    
    // 키 값이 key인 노드 삭제
    public boolean remove(K key) {
        Node<K,V> p = root;             // 스캔 중인 노드
        Node<K,V> parent = null;        // 스캔 중인 노드의 부모 노드
        boolean isLeftChild = true;     // p는 부모의 왼쪽 자식 노드?
        
        // 삭제할 key 검색
        while (true) {
            if (p == null)
                return false;
            int cond = comp(key, p.getKey());
            if (cond == 0)
                break;
            else {
                parent = p;
                if (cond < 0) {
                    isLeftChild = true;
                    p = p.left;
                } else {
                    isLeftChild = false;
                    p = p.right;
                }
            }
        }
        
        // 노드 삭제
        // 1) 자식 노드 없는 경우 & 자식 노드 1개인 경우
        if (p.left == null) {
            if (p == root)
                root = p.right;
            else if (isLeftChild)
                parent.left = p.right;
            else 
                parent.right = p.right;
        } else if (p.right == null) {
            if (p == root)
                root = p.left;
            else if (isLeftChild)
                parent.left = p.left;
            else 
                parent.right = p.left;
            
        // 2) 자식 노드가 2개인 경우
        } else {
            parent = p;
            Node<K,V> left = p.left;    // 서브 트리 가운데 가장 큰 노드 
            isLeftChild = true;
            
            // 가장 큰 노드 left를 검색
            while (left.right != null) {
                parent = left;
                left = left.right;
                isLeftChild = false;
            }
            
            // left의 key, data를 p로 옮김
            p.key = left.key;
            p.data = left.data;
            if (isLeftChild)
                parent.left = left.left;    // left 삭제
            else 
                parent.right = left.left;   // left 삭제
        }
        return true;
    }
    
    // node를 루트로 하는 서브 트리의 노드를 키 값의 오름차순으로 출력 (재귀메서드)
    private void printSubTree(Node node) {
        if (node != null) {
            printSubTree(node.left);    // 왼쪽 서브 트리를 키 값의 오름차순으로 출력
            System.out.println(node.key + " " + node.data);     // node 출력
            printSubTree(node.right);   // 오른쪽 서브 트리를 키 값의 오름차순으로 출력
        }
    }
    
    // 모든 노드를 키 값의 오름차순으로 출력
    public void print() {
        printSubTree(root);
    }
}