카테고리 없음

[자료구조] 이원 탐색 트리(BST) / JAVA

프로틴형님 2023. 1. 15. 19:18

노드 N의 형식

- <LP, <K, A>, RP>

  • K : 데이터 레코드의 키 값
  • A : 키 값으로 K를 가진 데이터 레코드가 저장된 위치에 대한 포인터
  • LP, RP : 좌/우측 서브트리에 대한 포인터

 

class Node{
    Integer key;
    Integer height;
    Node llink;
    Node rlink;

    public Node(Integer key, Integer height, Node llink, Node rlink){
        this.key = key;
        this.height = height;
        this.llink = llink;
        this.rlink = rlink;
    }

    public Node(){
        this(null, 0, null, null);
    }
}

 


이원 탐색 트리 T의 정의

-  T는 이진 트리(binary tree)이다.

-  T의 모든 노드 N에 대해 

  1. N의 왼쪽 서브 트리에 속하는 모든 키 값은 N의 키 값보다 작다.
  2. N의 오른쪽 서브 트리에 속하는 모든 키 값은 N의 키 값보다 크다.

=> 결과적으로 T의 모든 노드 N은 상이한 키 값을 가지며, 노드 N의 양쪽 서브 트리도 모두 BST이다.

 


BST 검색 (임의 접근) 알고리즘

// 스택에 상위 노드를 저장하는 동안 새 키를 삽입할 위치 찾기
        while (p != null) {
            if (newKey == p.key){
                System.out.println(String.format("i %d : The key already exist", newKey));
                return;
            }
            
            q = p;
            stack.add(q);

            if (newKey < p.key){
                p = p.llink;
            } else{
                p = p.rlink;
            }            
        }

1. 트리가 공백 : 검색은 노드를 찾지 못하고 실패
2. K=Ki : 노드 Ni가 원하는 노드
3. K<Ki : Ni의 왼쪽 서브트리를 검색
4. K>Ki : Ni의 오른쪽 서브트리를 검색


BST  삽입  알고리즘

public void insertBST(int newKey){
        Node p = this.root;
        Node q = null;
        Deque<Node> stack = new LinkedList<Node>();
        
        // 스택에 상위 노드를 저장하는 동안 새 키를 삽입할 위치 찾기
        while (p != null) {
            if (newKey == p.key){
                System.out.println(String.format("i %d : The key already exist", newKey));
                return;
            }
            
            q = p;
            stack.add(q);

            if (newKey < p.key){
                p = p.llink;
            } else{
                p = p.rlink;
            }            
        }
        
        // 새 노드 생성
        Node newNode = getNode();
        newNode.key = newKey;

        // q의 자식으로 newNode 삽입
        if (this.root == null){
            this.root = newNode;
        } else if (newKey < q.key){
            q.llink = newNode;
        } else {
            q.rlink = newNode;
        }


        // 스택에서 상위 노드의 높이 업데이트
        while (!stack.isEmpty()){
            q = stack.pop();
            int val;
            if ((q.llink == null) && (q.rlink == null)){
                continue;
            } else if (q.rlink == null) {
                q.height = q.llink.height + 1;
            } else if (q.llink == null) {                
                q.height = q.rlink.height + 1;
            } else {
                val = (q.llink.height < q.rlink.height) ? q.rlink.height : q.llink.height;
                q.height = 1 + val;
            }
        }
    }

1. 트리가 공백 : K를 루트 노드로 삽입
2. K=Ki : 트리에 값은 키 값이 존재하므로 삽입을 거부
3. K<Ki : Ni의 왼쪽 서브트리로 이동하여 삽입
4. K>Ki : Ni의 오른쪽 서브트리로 이동하여 삽입


BST  삭제 알고리즘

1. 자식이 없는 리프 노드(차수가 0인 노드)의 삭제
    => 단순히 그 노드를 삭제



2. 자식이 하나인 노드(차수가 1인 노드)의 삭제
    => 삭제되는 노드 자리에 자식 노드를 위치



3. 자식이 둘인 노드(차수가 2인 노드)의 삭제
    => 삭제할 노드를 왼쪽 서브트리에서 제일 큰 키 값 또는 오른쪽 서브트리에서 제일 작은 키 값으로 대체.
        (두 경우 중 높이가 높은 서브트리를 선택하는 것이 유리, 균형 트리 유지)

 

public void deleteBST(int deletekey){
        Node p = this.root;
        Node q = null;
        Deque<Node> stack = new LinkedList<Node>();

        // 스택에 상위 노드를 저장하는 동안 deleteKey 위치 찾기
        while ((p != null) && (deletekey != p.key)){
            q = p;
            stack.add(q);

            if (deletekey < p.key){
                p = p.llink;
            } else {
                p = p.rlink;
            }
        }
        
        if (p == null) {
            System.out.println(String.format("d %d : The key does not exist", deletekey));
            return;
        }

        // 자식수가 2의 경우는 0의 경우 또는 1의 경우로 축소된다.
        if (p.llink != null && p.rlink != null) {
            stack.add(p);
            Node temp = p;

            if (height(p.llink) < height(p.rlink)){
                // minNode 
                p = minNode(p.rlink, stack);
            } else if (height(p.llink) > height(p.rlink)){
                // maxNode
                p = maxNode(p.llink, stack);
            } else {
                // System.out.println(noNodes(p.llink) + " " + noNodes(p.rlink));
                if (noNodes(p.llink) >= noNodes(p.rlink)){
                    p = maxNode(p.llink, stack);
                } else {
                    p = minNode(p.rlink, stack);
                }
            }

            temp.key = p.key;
            q = stack.getLast();
        }

        // 이제 p의 도수는 0 또는 1이다.
        // T에서 p를 삭제하다
        if (p.llink == null && p.rlink == null) {
            if (q == null) {
                this.root = null;
            } else if (q.llink == p){
                q.llink = null;
            } else {
                q.rlink = null;
            }
        // case of degree 1
        } else {
            if (p.llink != null) {
                if (q == null) {
                    this.root = this.root.llink;
                } else if (q.llink == p) {
                    q.llink = p.llink;
                } else {
                    q.rlink = p.llink;
                }
            } else {
                if (q == null) {
                    this.root = this.root.rlink;
                } else if (q.llink == p) {
                    q.llink = p.rlink;
                } else {
                    q.rlink = p.rlink;
                }
            }
        }

        p = null;

        // 스택에서 상위 노드를 pop 하면서 높이 업데이트
        while (!stack.isEmpty()){
            q = stack.pop();
            int val;
            if ((q.llink == null) && (q.rlink == null)){
                continue;
            } else if (q.rlink == null) {
                q.height = q.llink.height + 1;
            } else if (q.llink == null) {                
                q.height = q.rlink.height + 1;
            } else {
                val = (q.llink.height < q.rlink.height) ? q.rlink.height : q.llink.height;
                q.height = 1 + val;
            }
        }
    }

BST 성능

편향 이원 탐색 트리(skewed BST)
리프 노드의 탐색 시간은 최악
노드 개수가 N개인 이원 탐색 트리에서 최악의 탐색 시간 : N번의 노드 탐색

 

N개의 노드를 갖는 높이 h BST의 최소 높이
N <= 2h – 1
N + 1 <= 2h
[log2(N+1)] <= h


BST의 최소/최대 높이
[log2(N+1)] <= h <= N


Best case : Complete binary tree
Worst case : Skewed binary tree


BST 성능 개선

이원 탐색 트리의 특징 (단점)
삽입, 삭제 후 효율적 접근을 위한 균형 유지 부담
작은 분기율(branching factor)에 따른 긴 탐색 경로와 검색 시간

    (분기율=2 이므로, 각 노드는 많아야 두 개의 서브트리)


성능 개선 방향


균형 트리(balanced tree)
모든 노드에 대해, 양쪽 서브트리의 높이가 가능한 같게 만들어, 트리의 최대 경로 길이를 최소화.
, 삽입 그리고 삭제 후에는 트리가 균형을 유지하도록 트리를 재구성(restructuring)


재구성 시간의 부담으로, 완벽한 균형 트리는 유지 불가능함.


전체 코드

import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

class Node{
    Integer key;
    Integer height;
    Node llink;
    Node rlink;

    public Node(Integer key, Integer height, Node llink, Node rlink){
        this.key = key;
        this.height = height;
        this.llink = llink;
        this.rlink = rlink;
    }

    public Node(){
        this(null, 0, null, null);
    }
}

public class BST{
    Node root;
    Deque<Integer> list;
    Integer totalSub;

    public BST(){
        this.root = null;
    }

    public Node getNode(){
        return new Node();
    }
    
    public void insertBST(int newKey){
        Node p = this.root;
        Node q = null;
        Deque<Node> stack = new LinkedList<Node>();
        
        // 스택에 상위 노드를 저장하는 동안 새 키를 삽입할 위치 찾기
        while (p != null) {
            if (newKey == p.key){
                System.out.println(String.format("i %d : The key already exist", newKey));
                return;
            }
            
            q = p;
            stack.add(q);

            if (newKey < p.key){
                p = p.llink;
            } else{
                p = p.rlink;
            }            
        }
        
        // 새 노드 생성
        Node newNode = getNode();
        newNode.key = newKey;

        // q의 자식으로 newNode 삽입
        if (this.root == null){
            this.root = newNode;
        } else if (newKey < q.key){
            q.llink = newNode;
        } else {
            q.rlink = newNode;
        }


        // 스택에서 상위 노드의 높이 업데이트
        while (!stack.isEmpty()){
            q = stack.pop();
            int val;
            if ((q.llink == null) && (q.rlink == null)){
                continue;
            } else if (q.rlink == null) {
                q.height = q.llink.height + 1;
            } else if (q.llink == null) {                
                q.height = q.rlink.height + 1;
            } else {
                val = (q.llink.height < q.rlink.height) ? q.rlink.height : q.llink.height;
                q.height = 1 + val;
            }
        }
    }
    
    public int height(Node T){
        return T.height;
    }

    public Node minNode(Node T, Deque<Node> stack){
        while (T.llink != null){
            stack.add(T);
            T = T.llink;
        }
        return T;
    }

    public Node maxNode(Node T, Deque<Node> stack){
        while (T.rlink != null){
            stack.add(T);
            T = T.rlink;
        }
        return T;
    }

    public int noNodes(Node T){
        this.totalSub = 0;
        dfs(T);
        return this.totalSub;
    }

    public void deleteBST(int deletekey){
        Node p = this.root;
        Node q = null;
        Deque<Node> stack = new LinkedList<Node>();

        // 스택에 상위 노드를 저장하는 동안 deleteKey 위치 찾기
        while ((p != null) && (deletekey != p.key)){
            q = p;
            stack.add(q);

            if (deletekey < p.key){
                p = p.llink;
            } else {
                p = p.rlink;
            }
        }
        
        if (p == null) {
            System.out.println(String.format("d %d : The key does not exist", deletekey));
            return;
        }

        // 자식수가 2의 경우는 0의 경우 또는 1의 경우로 축소된다.
        if (p.llink != null && p.rlink != null) {
            stack.add(p);
            Node temp = p;

            if (height(p.llink) < height(p.rlink)){
                // minNode 
                p = minNode(p.rlink, stack);
            } else if (height(p.llink) > height(p.rlink)){
                // maxNode
                p = maxNode(p.llink, stack);
            } else {
                // System.out.println(noNodes(p.llink) + " " + noNodes(p.rlink));
                if (noNodes(p.llink) >= noNodes(p.rlink)){
                    p = maxNode(p.llink, stack);
                } else {
                    p = minNode(p.rlink, stack);
                }
            }

            temp.key = p.key;
            q = stack.getLast();
        }

        // 이제 p의 도수는 0 또는 1이다.
        // T에서 p를 삭제하다
        if (p.llink == null && p.rlink == null) {
            if (q == null) {
                this.root = null;
            } else if (q.llink == p){
                q.llink = null;
            } else {
                q.rlink = null;
            }
        // case of degree 1
        } else {
            if (p.llink != null) {
                if (q == null) {
                    this.root = this.root.llink;
                } else if (q.llink == p) {
                    q.llink = p.llink;
                } else {
                    q.rlink = p.llink;
                }
            } else {
                if (q == null) {
                    this.root = this.root.rlink;
                } else if (q.llink == p) {
                    q.llink = p.rlink;
                } else {
                    q.rlink = p.rlink;
                }
            }
        }

        p = null;

        // 스택에서 상위 노드를 pop 하면서 높이 업데이트
        while (!stack.isEmpty()){
            q = stack.pop();
            int val;
            if ((q.llink == null) && (q.rlink == null)){
                continue;
            } else if (q.rlink == null) {
                q.height = q.llink.height + 1;
            } else if (q.llink == null) {                
                q.height = q.rlink.height + 1;
            } else {
                val = (q.llink.height < q.rlink.height) ? q.rlink.height : q.llink.height;
                q.height = 1 + val;
            }
        }
    }

    public void inorderBST(Node head){
        this.list = new LinkedList<Integer>();
        inorder(head);
        for (int key : list) {
            System.out.print(key + " ");
        }
        System.out.println();
    }

    public void inorder(Node travel){
        if (travel != null){
            if (travel.llink != null) {
                inorder(travel.llink);
            }
            this.list.add(travel.key);
            if (travel.rlink != null) {
                inorder(travel.rlink);
            }
        }
    }

    public void dfs(Node sub_root){
        if (sub_root == null){
            return;
        }
        dfs(sub_root.llink);
        this.totalSub += 1;
        dfs(sub_root.rlink);
    }

    public static void main(String[] args) {
        Random random = new Random();
        BST tree = new BST();
        List<Integer> list = new ArrayList<Integer>();

        for (int i=0; i<30; i++){
            int val = random.nextInt(1000);
            while (list.contains(val)){
                val = random.nextInt(1000);
            }
            tree.insertBST(val);
            list.add(val);
            tree.inorderBST(tree.root);
        }

        for (int i=0; i<30; i++){
            int idx = random.nextInt(list.size());
            tree.deleteBST(list.get(idx));
            list.remove(idx);
            tree.inorderBST(tree.root);
        }
    }
}