Java - Data Structure - Binary Heap

4 분 소요

Java - Data Structure - Binary Heap

  • Java로 Binary Heap을 구현했습니다.
  • Binary Heap은 다음 조건을 만족하는 자료 구조를 말하죠. Heap은 작을수록 우선순위를 가지는 MinHeap과, 클수록 우선순위를 가지는 MaxHeap으로 나뉘는데, 여기서는 MinHeap을 기준으로 설명하겠습니다.
    • 각 node는 node의 subtree중에서 가장 작다. 즉 parent node는 모든 child node보다 값이 작다.
    • complete tree이거나 almost complete tree이다. 이건 binary tree에 대해서 “parent level이 꽉 채워지지 않은 상태에서 child node가 존재할 수 없다”라고 이해하셔도 됩니다. 대략 그림으로 보시죠. 그림을 보면, 왼쪽부터 node가 순차적으로 채워져 있는 것을 알 수 있죠. 이런 형태를 complete binary tree라고 합니다.

Complete binary tree

  • Heap은 보통 정렬(sorting)할 때 자주 사용됩니다. Heap을 이용한 정렬을 “HeapSort”라고 부르죠.
  • 정렬되지 않은 list 등에서 Heap을 만들 때는 O(n)의 시간이 소요되고, 값을 빼낼 때는 각각 O(log n)이 소요됩니다(모든 원소에 대해서 수행한다면 n * O(log n)). 따라서, (대략적인) 총 소요시간은 O(n) + n * O(log n)이 소요되죠.
  • 특히, 만약 앞의 k개만 필요하다면, O(n) + k * O(log n)로 단축되게 되죠. 즉, HeapSort는 처음부터 모든 원소를 정렬해두는 것이 아니라, 필요할 때마다 조금씩, O(log n)만큼 더 시간이 소요됩니다. 그러므로, 빈번하게 최소 혹은 최대값을 가져오지 않는 경우, 그리고 몇 개의 최소값만 가져오는 경우에 사용하여 시간을 단축할 수 있죠.\

Java - Binary Heap Implementation

  • java를 사용하여 Binary Heap을 구현하였습니다.
public static class MinBinaryHeap {
    /*
    - Binary Tree에서는 보통 Node를 만들어서 처리하지만 
    Heap은 complete Tree이므로 다음과 같이 그냥 List<Integer>로 만들어서 처리했습니다.
    혹은 그냥 Array를 사용해도 되죠. 
    - binary tree의 원소들이 다음의 index를 가진다고 생각하면 됩니다.
    즉, container.get(0)을 하면 root Node인 가장 작은 Node에 접근할 수 있죠.
    0 
    1 2 
    3 4 5 6 
    7 8 9 10
    */
    private List<Integer> container;

    public MinBinaryHeap() {
        // 생성자입니다.
        this.container = new ArrayList<>();
    }
    public void swapNode(int idx1, int idx2) {
        // this.container에서 idx1에 위치한 원소와 idx2에 위치한 우너소를 변경해줍니다.
        Integer temp = this.container.get(idx1);
        this.container.set(idx1, this.container.get(idx2));
        this.container.set(idx2, temp);
    }
    public void insertNode(Integer newValue) {
        // 끝에 새로운 value를 넣어주고, 그 index를 리턴
        // 그리고 들어온 새로운 원소가 heap의 조건인 "parent node가 child node보다 작아야 한다"는 조건을
        // 만족하지 않을 수도 있으므로 minifyHeapBottomToTop를 사용하여 아래에서 위로 진행하며 swap
        this.container.add(newValue);
        this.minifyHeapBottomToTop();

    }
    public Integer extractMin(){
        /*
        - 가장 작은 원소인 rootNode의 값을 리턴하고 Heap 내에서 삭제해줍니다.
        - Heap에서는 이 메소드를 수행할 때, 다음과 같은 순서로 수행됩니다.
          - 가장 작은 값(rootNode)의 값을 리턴하고
          - this.container의 끝의 원소와 rootNode의 값을 변경한 다음, 
          - this.conateinr의 끝의 원소를 삭제해주고(값을 바꾼 후 삭제했으므로 기존 rootNode의 값을 삭제해준 것임)
          - Heap은 parent node의 값이 항상 child node의 값보다 작아야 하는데, 지금 해당 성질을 만족하지 않으므로 해결해준다
          그리고 이 부분을 해결해주는 메소드가 바로 minifyHeapTopToBottom() 입니다.
        */
        if (this.container.size() == 0) {
            return null;
        } else {
            Integer returnInteger = this.container.get(0);
            swapNode(0, this.container.size() - 1);
            this.container.remove(this.container.size() - 1);
            minifyHeapTopToBottom();
            return returnInteger;
        }
    }
    public List<Integer> getContainer() {
        // 그냥 현재 컨테이너를 리턴하는 메소드
        return this.container;
    }
    public int getParentIndex(int childIndex) {
        // 현재 ArrayList에서 childIndex의 부모 주소를 리턴
        return (childIndex - 1) / 2;
    }
    public int getLeftChildIndex(int currentIndex) {
        // 현재 ArrayList에서 childIndex의 왼쪽 자식 주소를 리턴
        int returnIndex = currentIndex * 2 + 1;
        if (this.container.size() <= returnIndex) {
            return -1;
        } else {
            return returnIndex;
        }
    }
    public int getRightChildIndex(int currentIndex) {
        // 현재 ArrayList에서 childIndex의 오른쪽 자식 주소를 리턴
        int returnIndex = currentIndex * 2 + 2;
        if (this.container.size() <= returnIndex) {
            return -1;
        } else {
            return returnIndex;
        }
    }
    public void minifyHeapBottomToTop() {
        /*
        * MinHeap는 child node가 무조건 parent node보다 커야 하는데
        * 마지막에 들어온 원소가 이를 충족시켜주지 못하므로, 아래에서 위로 내려가면서 
        swap을 통해 해결해줌
        */
        int childNodeIndex = this.container.size() - 1;
        int parentNodeIndex = getParentIndex(childNodeIndex);

        while (true) {
            if (childNodeIndex == parentNodeIndex) {
                break;
            } else {
                Integer childNodeValue = this.container.get(childNodeIndex);
                Integer parentNodeValue = this.container.get(parentNodeIndex);

                if (childNodeValue < parentNodeValue) {
                    swapNode(childNodeIndex, parentNodeIndex);
                    childNodeIndex = parentNodeIndex;
                    parentNodeIndex = getParentIndex(childNodeIndex);
                } else {
                    break;
                }
            }
        }
    }
    public void minifyHeapTopToBottom() {
        /*
        * MinHeap는 child node가 무조건 parent node보다 커야 하는데
        * 마지막에 들어온 원소가 이를 충족시켜주지 못하므로, swap을 통해 해결해줌
        * 위에서 아래로 내려가면서 자식 중 작은 놈과 swap을 진행해줍니다.
        */  
        if (this.container.size() != 0) {
            int currentNodeIndex = 0;
            while (true) {
                int leftChildIndex = getLeftChildIndex(currentNodeIndex);
                int rightChildIndex = getRightChildIndex(currentNodeIndex);
                int childIndex = -1;
                if (leftChildIndex == -1 && rightChildIndex == -1) {
                    break;
                } else if (leftChildIndex != -1 && rightChildIndex != -1) {
                    Integer leftChildValue = this.container.get(leftChildIndex);
                    Integer rightChildValue = this.container.get(rightChildIndex);
                    if ( leftChildValue < rightChildValue) {
                        childIndex = leftChildIndex;
                    } else {
                        childIndex = rightChildIndex;

                    }
                    // find which is max between left and right
                    Integer currentNodeValue = this.container.get(currentNodeIndex);
                    Integer childNodeValue = this.container.get(childIndex);
                    if (currentNodeValue < childNodeValue) {
                        break;
                    } else {
                        swapNode(currentNodeIndex, childIndex);
                    }
                } else {
                    if (leftChildIndex != -1) {
                        childIndex = leftChildIndex;
                    } else {
                        childIndex = rightChildIndex;
                    }
                    Integer currentNodeValue = this.container.get(currentNodeIndex);
                    Integer childNodeValue = this.container.get(childIndex);
                    if (currentNodeValue < childNodeValue) {
                        break;
                    } else {
                        swapNode(currentNodeIndex, childIndex);
                    }
                }
            }
        } else {
            // container has nothing
        }
    }
}
  • 실제로 다음과 같이 실행해보면 잘 되는 것을 알 수 있습니다.
import java.util.*;

class Main {
  public static class MinBinaryHeap {...}
    public static void main(String[] args) throws Exception {
        MinBinaryHeap mbh = new MinBinaryHeap();
        mbh.insertNode(3);
        mbh.insertNode(2);
        mbh.insertNode(1);

        System.out.printf("Extract MIN: %3d, current Heap: %s \n", mbh.extractMin(), mbh.getContainer());
        // Extract MIN:   1, current Heap: [2, 3] 
        System.out.printf("Extract MIN: %3d, current Heap: %s \n", mbh.extractMin(), mbh.getContainer());
        // Extract MIN:   2, current Heap: [3] 
        System.out.printf("Extract MIN: %3d, current Heap: %s \n", mbh.extractMin(), mbh.getContainer());
        // Extract MIN:   3, current Heap: [] 
    }
}

댓글남기기