자료구조·알고리즘

[자료구조] 이진 탐색 트리

junga 2022. 10. 7. 23:57

이진 탐색 트리


이진 트리로 만들어진 탐색을 위한 자료구조로 4가지 조건을 가진다.

  • 왼쪽 자식 노드의 키는 부모 노드의 키보다 작다.
  • 오른쪽 자식 노드의 키는 부모 노드의 키보다 크다.
  • 서브 트리들도 이진 탐색 트리를 유지한다.
  • 중복된 키를 허용하지 않는다.

왼쪽 자식 노드 < 부모노드 < 오른쪽 자식 노드

이진 탐색 트리



탐색


  • 찾고자 하는 데이터를 루트 노드부터 비교 시작
  • 대소 비교를 하여 찾는 데이터가 작으면 왼쪽, 크면 오른쪽 노드로 이동
  • 찾는 데이터가 없으면 null 반환
  • 어떤 데이터를 찾더라도 최대 트리 높이 만큼의 탐색이 이루어진다.


이진 트리 탐색



삽입


  • 탐색과 마찬가지로 루트부터 비교 시작 (중복 키 발견 시 추가 없이 종료)
  • 삽입키가 현재 노드의 키보다 작으면 왼쪽으로 이동
  • 삽입키가 현재 노드의 키보다 크면 오른쪽으로 이동
  • Leaf 노드에 도달 후 마지막으로 비교하고 삽입


이진 트리 삽입



삭제


1. 삭제 대상 노드가 Leaf 노드인 경우

  • 삭제 후 부모 노드의 해당 자식 링크 null로 변경


삭제할 노드가 Leaf 노드일 경우



2. 삭제할 노드에 자식 노드가 하나 있는 경우

  • 삭제할 노드의 부모 노드에 자식 노드를 연결 후 삭제


삭제할 노드에 자식 노드가 하나 있는 경우



3. 삭제 대상 노드에 자식 노드가 둘인 경우

두 가지 방법이 있다.

Successor

  • 선택한 노드의 오른쪽 서브트리중 가장 작은 값을 가지는 노드를 의미한다.
  • 즉, 삭제할 노드의 오른쪽 서브트리 -> 왼쪽 노드로 계속 이동(최소값)
  • Successor를 삭제 대상 노드 위치로 옮긴다.


Successor를 이용한 삭제



Predecessor

  • 선택한 노드의 키보다 작으면서 가장 큰 값을 가지는 노드로 Successor의 반대이다.
  • 삭제할 노드의 왼쪽 서브트리 -> 오른쪽 노드로 계속 이동(최대값)
  • Predecessor을 삭제 대상 노드 위치로 옮긴다.


Predecessor를 이용한 삭제



이진 탐색 트리 구현


import java.util.LinkedList;
import java.util.Queue;

// 노드 구성: key, 왼쪽 노드, 오른쪽 노드
class Node {
    int key;
    Node left;
    Node right;

    Node(int key, Node left, Node right) {
        this.key = key;
        this.left = left;
        this.right = right;
    }
}

// 이진 탐색 트리
class BinarySearchTree {
    Node head;

    BinarySearchTree(int key) {
        this.head = new Node(key, null, null);
    }

    // 노드 삽입
    public void addNode(int key) {
        if (this.head == null) {
            this.head = new Node(key, null, null);
        } else {
            Node cur = this.head;

            // 크기 비교해가며 왼 or 오른쪽에 삽입
            while (true) {
                Node pre = cur;

                if (key < cur.key) {
                    cur = cur.left;

                    if (cur == null) {
                        pre.left = new Node(key, null, null);
                        break;
                    }
                } else {
                    cur = cur.right;

                    if (cur == null) {
                        pre.right = new Node(key, null, null);
                        break;
                    }
                }
            }
        }
    }

    // 노드 삭제
    public void removeNode(int key) {
        Node parent = null;
        Node successor = null;
        Node prev = null;
        Node child = null;

        Node cur = this.head;
        while (cur != null) {
            if (key == cur.key) {
                break;
            }
            parent = cur;
            if (key < cur.key) {
                cur = cur.left;
            } else {
                cur = cur.right;
            }
        }


        // 조건에 따라 다른 삭제 코드


        // 트리에 key가 없는 경우
        if (cur == null) {
            System.out.println("key에 해당하는 노드 없음");
        }


        // 1. 삭제할 노드가 Leaf 노드인 경우
        if (cur.left == null && cur.right == null) {
            if (parent == null) {
                this.head = null;
            } else {
                if (parent.left == cur) {
                    parent.left = null;
                } else {
                    parent.right = null;
                }
            }
          // 2. 삭제할 노드의 자식 노드가 하나인 경우
        } else if (cur.left != null && cur.right == null || cur.left == null && cur.right != null) {
            if (cur.left != null) {
                child = cur.left;
            } else {
                child = cur.right;
            }

            if (parent == null) {
                this.head = child;
            } else {
                if (parent.left == cur) {
                    parent.left = child;
                } else {
                    parent.right = child;
                }
            }
            // 3. 삭제할 노드의 자식 노드가 2개인 경우
        } else {
            prev = cur;
            successor = cur.right; // 오른쪽 서브트리 이동

            while (successor.left != null) {
                prev = successor;
                successor = successor.left;
            }

            prev.left = successor.right;
            successor.left = cur.left;
            successor.right = cur.right;

            if (parent == null) {
                this.head = successor;
            } else {
                if (parent.left == cur) {
                    parent.left = successor;
                } else {
                    parent.right = successor;
                }
            }
        }
    }

    // 레벨 순회(결과 확인용)
    public void levelOrder(Node node) {
        Queue<Node> queue = new LinkedList();
        queue.add(node);
        while (!queue.isEmpty()) {
            Node cur = queue.poll();

            System.out.print(cur.key + " ");
            if (cur.left != null) {
                queue.offer(cur.left);
            }

            if (cur.right != null) {
                queue.offer(cur.right);
            }
        }
        System.out.println();
    }
}


이진 탐색 트리 사용

public class Main {
    public static void main(String[] args) {
        // 노드 삽입
        BinarySearchTree bst = new BinarySearchTree(10);
        bst.addNode(5);
        bst.addNode(17);
        bst.addNode(1);
        bst.addNode(6);
        bst.addNode(14);
        bst.addNode(21);
        bst.levelOrder(bst.head); // 10 5 17 1 6 14 21

        // 노드 삭제(Successor 예제)
        bst.removeNode(10);
        bst.levelOrder(bst.head); // 14(root) 5 17 1 6 21
    }
}

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

구간 합  (0) 2023.06.23
배열과 리스트  (0) 2023.06.23
[자료구조] 트리  (0) 2022.10.06