λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°

빈 ꡬ멍 μ±„μš°κΈ°

[자료 ꡬ쑰][Java] νž™ Heap

좜처

ChatGPT


νž™(Heap) μžλ£Œκ΅¬μ‘°λŠ” μ™„μ „ 이진 트리(Complete Binary Tree)의 ν•œ ν˜•νƒœλ‘œ, μš°μ„ μˆœμœ„ 큐(Priority Queue)λ₯Ό κ΅¬ν˜„ν•˜λŠ” 데 주둜 μ‚¬μš©λ˜λŠ” 효율적인 μžλ£Œκ΅¬μ‘°μ΄λ‹€. νž™μ€ μš”μ†Œλ“€μ˜ μš°μ„ μˆœμœ„λ₯Ό 기반으둜 λΉ λ₯΄κ²Œ μ΅œλŒ€κ°’μ΄λ‚˜ μ΅œμ†Œκ°’μ„ μ°Ύκ³ , μ‚½μž… 및 μ‚­μ œ μž‘μ—…μ„ 효율적으둜 μˆ˜ν–‰ν•  수 μžˆλ„λ‘ μ„€κ³„λ˜μ—ˆλ‹€.

 

1. νž™μ˜ μ£Όμš” νŠΉμ§•

μ™„μ „ 이진 트리

νž™μ€ λͺ¨λ“  레벨이 μ™„μ „νžˆ μ±„μ›Œμ Έ 있으며, λ§ˆμ§€λ§‰ λ ˆλ²¨λ„ μ™Όμͺ½λΆ€ν„° μ±„μ›Œμ Έ μžˆλŠ” 이진 νŠΈλ¦¬μ΄λ‹€. 이 νŠΉμ„± 덕뢄에 νž™μ€ λ°°μ—΄(Array)둜 효율적으둜 κ΅¬ν˜„λ  수 μžˆλ‹€.

νž™ 속성 Heap Property

μ΅œλŒ€ νž™ Max-Heap

λΆ€λͺ¨ λ…Έλ“œμ˜ 값이 μžμ‹ λ…Έλ“œμ˜ 값보닀 ν¬κ±°λ‚˜ 같은 ꡬ쑰이닀. λ”°λΌμ„œ 루트 λ…Έλ“œλŠ” νž™μ—μ„œ κ°€μž₯ 큰 값을 가진닀.

μ΅œμ†Œ νž™ Min-Heap

λΆ€λͺ¨ λ…Έλ“œμ˜ 값이 μžμ‹ λ…Έλ“œμ˜ 값보닀 μž‘κ±°λ‚˜ 같은 ꡬ쑰이닀. λ”°λΌμ„œ 루트 λ…Έλ“œλŠ” νž™μ—μ„œ κ°€μž₯ μž‘μ€ 값을 가진닀.

효율적인 μ ‘κ·Ό

νž™μ€ 루트 λ…Έλ“œμ—μ„œ μ΅œλŒ€κ°’ λ˜λŠ” μ΅œμ†Œκ°’μ΄ μœ„μΉ˜ν•˜κΈ° λ•Œλ¬Έμ—, μ΅œλŒ“κ°’ λ˜λŠ” μ΅œμ†Ÿκ°’μ„ O(1) μ‹œκ°„μ— μ ‘κ·Όν•  수 μžˆλ‹€.

 

2. νž™μ˜ κ΅¬ν˜„ 방법

νž™μ€ 일반적으둜  λ°°μ—΄(Array)을 μ΄μš©ν•΄ κ΅¬ν˜„λœλ‹€. 배열을 μ‚¬μš©ν•΄ νž™μ„ κ΅¬ν˜„ν•˜λ©΄ 트리의 λΆ€λͺ¨-μžμ‹ 관계λ₯Ό λ°°μ—΄μ˜ 인덱슀λ₯Ό 톡해 μ‰½κ²Œ 관리할 수 μžˆλ‹€.

1. 배열을 μ΄μš©ν•œ νž™ κ΅¬ν˜„

λ…Έλ“œ κ°„μ˜ 관계

  • λΆ€λͺ¨ λ…Έλ“œ 인덱슀 : (i - 1) / 2
  • μ™Όμͺ½ μžμ‹ λ…Έλ“œ 인덱슀 : 2 * i + 1
  • 였λ₯Έμͺ½ μžμ‹ λ…Έλ“œ 인덱슀 : 2 * i + 2

μ˜ˆμ‹œ

// 배열을 μ΄μš©ν•œ μ΅œλŒ€ νž™ μ˜ˆμ‹œ
int[] heap = {50, 30, 20, 15, 10, 8, 16};

2. 객체λ₯Ό μ΄μš©ν•œ νž™ κ΅¬ν˜„

객체λ₯Ό μ‚¬μš©ν•΄ νž™μ„ κ΅¬ν˜„ν•  μˆ˜λ„ 있으며, 각 λ…Έλ“œλŠ” λ‹€μŒκ³Ό 같은 속성을 가진닀.

  • κ°’ Value : λ…Έλ“œμ˜ 데이터
  • μ™Όμͺ½ μžμ‹ Left Child
  • 였λ₯Έμͺ½ μžμ‹ Right Child
  • λΆ€λͺ¨ λ…Έλ“œ Parent
class HeapNode {
    int value;
    HeapNode left;
    HeapNode right;
    HeapNode parent;

    HeapNode(int value) {
        this.value = value;
    }
}

 

3. νž™μ˜ μ£Όμš” μ—°μ‚°

1. μ‚½μž… Insertion

μƒˆλ‘œμš΄ μ›μ†Œλ₯Ό νž™μ— μΆ”κ°€ν•œλ‹€.

  1. μƒˆ λ…Έλ“œλ₯Ό νž™μ˜ λ§ˆμ§€λ§‰ μœ„μΉ˜μ— μ‚½μž…ν•œλ‹€.
  2. νž™ 속성을 μœ μ§€ν•˜κΈ° μœ„ν•΄ νž™ μ—…(Heap Up) 연산을 μˆ˜ν–‰ν•œλ‹€.
    • μΆ”κ°€ν•œ λ…Έλ“œμ™€ λΆ€λͺ¨ λ…Έλ“œμ™€ 비ꡐ해 ν•„μš” μ‹œ μœ„μΉ˜λ₯Ό κ΅ν™˜ν•œλ‹€. 
    • μ΅œλŒ€ νž™μ˜ 경우, λΆ€λͺ¨ λ…Έλ“œλ³΄λ‹€ 크면 κ΅ν™˜ν•œλ‹€.
    • μ΅œμ†Œ νž™μ˜ 경우, λΆ€λͺ¨ λ…Έλ“œλ³΄λ‹€ μž‘μœΌλ©΄ κ΅ν™˜ν•œλ‹€.
    • 이 과정을 λ£¨νŠΈκΉŒμ§€ λ°˜λ³΅ν•œλ‹€.
  • νž™ μ—… Heap Up : νž™μ— μƒˆλ‘œμš΄ μš”μ†Œλ₯Ό μ‚½μž…ν•  λ•Œ ν•΄λ‹Ή μš”μ†Œκ°€ λΆ€λͺ¨ λ…Έλ“œλ³΄λ‹€ μž‘κ±°λ‚˜ 크면(μ΅œμ†Œ νž™ λ˜λŠ” μ΅œλŒ€ νž™μ— 따라) λΆ€λͺ¨μ™€ μœ„μΉ˜ κ΅ν™˜(swap)을 ν•˜λ©΄μ„œ μœ„λ‘œ μ΄λ™ν•˜λŠ” 과정이닀. 이 과정은 트리의 루트 λ…Έλ“œμ— λ„λ‹¬ν•˜κ±°λ‚˜ νž™ 속성이 μœ μ§€λ  λ•ŒκΉŒμ§€ κ³„μ†λœλ‹€.

μ‹œκ°„ λ³΅μž‘λ„ : O(log n)

 

κ΅¬ν˜„ μ˜ˆμ‹œ : μ΅œλŒ€ νž™

public void insert(int value) {
    // λ°°μ—΄μ˜ λ‹€μŒ 빈 곡간에 κ°’ μ‚½μž…
    heap[size] = value;
    size++;

    // νž™ μ—…
    int current = size - 1;
    while (current > 0) {
        int parent = (current - 1) / 2;
        if (heap[current] > heap[parent]) { // μ΅œλŒ€ νž™ κΈ°μ€€
            // κ΅ν™˜
            int temp = heap[current];
            heap[current] = heap[parent];
            heap[parent] = temp;
            current = parent;
        } else {
            break;
        }
    }
}

 

μ˜ˆμ‹œ (μ΅œλŒ€ νž™μ—  25 μ‚½μž…)

초기 νž™:
      20
     /  \
   15    18
  /  \   /
 10   12 17

1. 25λ₯Ό λ§ˆμ§€λ§‰ μœ„μΉ˜μ— μΆ”κ°€:
      20
     /  \
   15    18
  /  \   / \
 10   12 17 25

2. λΆ€λͺ¨μ™€ λΉ„κ΅ν•˜μ—¬ 상ν–₯ 이동:
- 25 > 18μ΄λ―€λ‘œ κ΅ν™˜
      20
     /  \
   15    25
  /  \   / \
 10   12 17 18
 
- 25 > 20μ΄λ―€λ‘œ κ΅ν™˜
      25
     /  \
   15    20
  /  \   / \
 10   12 17 18
 - μ΅œμ’… μ΅œλŒ€ νž™ μ™„λ£Œ

 

2. μ‚­μ œ Deletion

μ΅œλŒ€ νž™μ—μ„œλŠ” κ°€μž₯ 큰 값을, μ΅œμ†Œ νž™μ—μ„œλŠ” κ°€μž₯ μž‘μ€ 값을 μ œκ±°ν•˜λŠ” μ—°μ‚°

  1. 루트 λ…Έλ“œ(κ°€μž₯ 큰 κ°’ λ˜λŠ” κ°€μž₯ μž‘μ€ κ°’)λ₯Ό μ œκ±°ν•œλ‹€.
  2. νž™μ˜ λ§ˆμ§€λ§‰ λ…Έλ“œλ₯Ό 루트둜 μ΄λ™μ‹œν‚¨λ‹€.
  3. νž™ λ‹€μš΄(Heap Down) 연산을 톡해 νž™ 속성을 μœ μ§€ν•œλ‹€.
    • λΆ€λͺ¨ λ…Έλ“œλ₯Ό μžμ‹ λ…Έλ“œμ™€ 비ꡐ해 ν•„μš” μ‹œ μœ„μΉ˜λ₯Ό κ΅ν™˜ν•œλ‹€.
    • μ΅œλŒ€ νž™μ˜ 경우, μžμ‹ λ…Έλ“œ 쀑 더 큰 κ°’κ³Ό κ΅ν™˜ν•œλ‹€.
    • μ΅œμ†Œ νž™μ˜ 경우, μžμ‹ λ…Έλ“œ 쀑 더 μž‘μ€ κ°’κ³Ό κ΅ν™˜ν•œλ‹€.
  • νž™ λ‹€μš΄ Heap Down : νž™μ—μ„œ 루트 λ…Έλ“œμ˜ 값을 μ‚­μ œν•œ ν›„, νž™μ˜ 속성(μ΅œλŒ€ νž™ λ˜λŠ” μ΅œμ†Œ νž™ 속성)을 μœ μ§€ν•˜κΈ° μœ„ν•΄ μžμ‹ λ…Έλ“œμ™€ λΉ„κ΅ν•˜λ©΄μ„œ μ•„λž˜λ‘œ μ΄λ™μ‹œν‚€λŠ” κ³Όμ •

μ‹œκ°„ λ³΅μž‘λ„ : O(log n)

 

κ΅¬ν˜„ μ˜ˆμ‹œ : μ΅œλŒ€ νž™

public int remove() {
    if (size == 0) throw new NoSuchElementException("Heap is empty");

    int root = heap[0];
    heap[0] = heap[size - 1];
    size--;

    // νž™ λ‹€μš΄
    int current = 0;
    while (current < size) {
        int left = 2 * current + 1;
        int right = 2 * current + 2;
        int largest = current;

        if (left < size && heap[left] > heap[largest]) {
            largest = left;
        }
        if (right < size && heap[right] > heap[largest]) {
            largest = right;
        }
        if (largest != current) {
            // κ΅ν™˜
            int temp = heap[current];
            heap[current] = heap[largest];
            heap[largest] = temp;
            current = largest;
        } else {
            break;
        }
    }

    return root;
}

 

μ˜ˆμ‹œ (μ΅œμ†Œ νž™μ—μ„œ μ‚­μ œ)

초기 νž™ :
      2
     / \
    3   4
   / \
  7   6

1. 루트 λ…Έλ“œ 제거
(2 μ‚­μ œ)

2. λ§ˆμ§€λ§‰ μ›μ†Œλ₯Ό 루트둜 이동
      6
     / \
    3   4
   /
  7
  
  3. μžμ‹κ³Ό 비ꡐ해 ν•˜ν–₯ 이동
  - 6 < 3μ΄λ―€λ‘œ κ΅ν™˜
      3
     / \
    6   4
   /
  7
  -μ΅œμ†Œ νž™ μ΅œμ’… μ™„λ£Œ


3. μ°ΎκΈ° Search

  • μ΅œλŒ€ νž™μ—μ„œ μ΅œλŒ“κ°’ μ°ΎκΈ° : O(1)
  • μ΅œμ†Œ νž™μ—μ„œ μ΅œμ†Ÿκ°’ μ°ΎκΈ° : O(1)
  • 일반적인 κ°’ μ°ΎκΈ° : O(n)
    • νž™ 전체λ₯Ό 순차적으둜 탐색해야 ν•œλ‹€. λ„ˆλΉ„ μš°μ„  탐색(BFS), 깊이 μš°μ„  탐색(DFS) 방식을 μ‚¬μš©ν•œλ‹€.

 

4. νž™μ˜ κ΅¬ν˜„ μ˜ˆμ‹œ

μžλ°”μ—μ„œ 배열을 μ΄μš©ν•œ νž™μ˜ κ΅¬ν˜„

import java.util.Arrays;

public class MinHeap {
    private int[] heap;
    private int size;
    private int capacity;

    public MinHeap(int capacity) {
        this.capacity = capacity;
        this.size = 0;
        this.heap = new int[capacity];
    }

    // λΆ€λͺ¨ λ…Έλ“œ 인덱슀
    private int getParentIndex(int index) { return (index - 1) / 2; }
    
    // μ™Όμͺ½ μžμ‹ λ…Έλ“œ 인덱슀
    private int getLeftChildIndex(int index) { return 2 * index + 1; }
    
    // 였λ₯Έμͺ½ μžμ‹ λ…Έλ“œ 인덱슀
    private int getRightChildIndex(int index) { return 2 * index + 2; }

    // μ‚½μž… μ—°μ‚°
    public void insert(int value) {
        if (size == capacity) throw new IllegalStateException("Heap is full");

        heap[size] = value;
        size++;
        heapifyUp(size - 1);  // νž™ 속성 μœ μ§€ (상ν–₯식 μ •λ ¬)
    }

    // νž™ μ •λ ¬ (상ν–₯식)
    private void heapifyUp(int index) {
        while (index > 0 && heap[index] < heap[getParentIndex(index)]) {
            swap(index, getParentIndex(index));
            index = getParentIndex(index);
        }
    }

    // μ΅œμ†Ÿκ°’(루트) μ‚­μ œ
    public int removeMin() {
        if (size == 0) throw new IllegalStateException("Heap is empty");

        int min = heap[0];
        heap[0] = heap[size - 1];
        size--;
        heapifyDown(0);  // νž™ 속성 μœ μ§€ (ν•˜ν–₯식 μ •λ ¬)
        return min;
    }

    // νž™ μ •λ ¬ (ν•˜ν–₯식)
    private void heapifyDown(int index) {
        int smallest = index;
        int left = getLeftChildIndex(index);
        int right = getRightChildIndex(index);

        if (left < size && heap[left] < heap[smallest]) {
            smallest = left;
        }

        if (right < size && heap[right] < heap[smallest]) {
            smallest = right;
        }

        if (smallest != index) {
            swap(index, smallest);
            heapifyDown(smallest);
        }
    }

    // 두 μš”μ†Œ κ΅ν™˜
    private void swap(int i, int j) {
        int temp = heap[i];
        heap[i] = heap[j];
        heap[j] = temp;
    }

    // νž™ λ‚΄μš© 좜λ ₯
    public void printHeap() {
        System.out.println(Arrays.toString(Arrays.copyOfRange(heap, 0, size)));
    }

    public static void main(String[] args) {
        MinHeap minHeap = new MinHeap(10);
        minHeap.insert(20);
        minHeap.insert(15);
        minHeap.insert(30);
        minHeap.insert(5);
        minHeap.insert(10);

        minHeap.printHeap();  // [5, 10, 30, 20, 15]

        System.out.println("Min value removed: " + minHeap.removeMin());  // 5
        minHeap.printHeap();  // [10, 15, 30, 20]
    }
}

 

5. νž™μ˜ μ‘μš©

νž™μ€ μ—¬λŸ¬ μ•Œκ³ λ¦¬μ¦˜μ—μ„œ μ€‘μš”ν•œ 역할을 ν•˜λ©°, λ‹€μ–‘ν•œ μ‘μš© 사둀가 μžˆλ‹€.

1. μš°μ„ μˆœμœ„ 큐 Priority Queue

νž™ μžλ£Œκ΅¬μ‘°λŠ” μš°μ„ μˆœμœ„ 큐(Priority Queue)λ₯Ό κ΅¬ν˜„ν•˜λŠ” 데 많이 μ‚¬μš©λœλ‹€. μœ μ„ μˆœμœ„ νμ—μ„œλŠ” μš”μ†Œκ°€ λ“€μ–΄μ˜¨ μˆœμ„œκ°€ μ•„λ‹ˆλΌ μš°μ„ μˆœμœ„μ— 따라 처리되며, νž™μ€ μ΅œλŒ“κ°’ λ˜λŠ” μ΅œμ†Ÿκ°’μ„ 효율적으둜 κ΄€λ¦¬ν•˜λŠ” 데 μœ λ¦¬ν•˜λ‹€.

μžλ°”μ—μ„œλŠ” PriorityQueue 클래슀λ₯Ό 톡해 μ΅œμ†Œ νž™ 기반의 μš°μ„ μˆ˜μœ„ 큐λ₯Ό μ œκ³΅ν•œλ‹€. 기본적으둜 μ˜€λ¦„μ°¨μˆœμœΌλ‘œ λ™μž‘ν•˜λ©°, μ‚¬μš©μžκ°€ μ •μ˜ν•œ μš°μ„ μˆœμœ„λ‘œ μ •λ ¬ν•  수 μžˆλ‹€.

2. νž™ μ •λ ¬ Heap Sort

νž™ μ •λ ¬(Heap Sort)은 νž™ 자료ꡬ쑰λ₯Ό μ΄μš©ν•œ μ •λ ¬ μ•Œκ³ λ¦¬μ¦˜μœΌλ‘œ, μ΅œλŒ€ νž™μ΄λ‚˜ μ΅œμ†Œ νž™μ„ κ΅¬μ„±ν•œ ν›„, 루트 λ…Έλ“œλ₯Ό μ œκ±°ν•˜κ³  μž¬μ •λ ¬ν•˜λŠ” 과정을 λ°˜λ³΅ν•΄ 정렬을 μˆ˜ν–‰ν•œλ‹€.

  • νž™ μ •λ ¬μ˜ μ‹œκ°„ λ³΅μž‘λ„λŠ” O(log n)으둜, 비ꡐ 기반 μ •λ ¬ μ•Œκ³ λ¦¬μ¦˜ 쀑 효율적인 νŽΈμ΄λ‹€.
  • 제자리 μ •λ ¬(in-place sorting)이 κ°€λŠ₯ν•˜μ§€λ§Œ, μ•ˆμ •μ μΈ μ •λ ¬(stable sorting)은 μ•„λ‹ˆλ‹€.

3. λ‹€μ΅μŠ€νŠΈλΌ μ•Œκ³ λ¦¬μ¦˜ Dijkstra's Algorithm

λ‹€μ΅μŠ€νŠΈλΌ μ•Œκ³ λ¦¬μ¦˜μ€ μ΅œλ‹¨ 경둜 μ•Œκ³ λ¦¬μ¦˜ 쀑 ν•˜λ‚˜λ‘œ, μš°μ„ μˆœμœ„ 큐λ₯Ό μ‚¬μš©ν•΄ κ°€μ€‘μΉ˜κ°€ μžˆλŠ” κ·Έλž˜ν”„μ—μ„œ μ΅œλ‹¨ 경둜λ₯Ό μ°ΎλŠ”λ‹€. 이 κ³Όμ •μ—μ„œ μ΅œμ†Œ νž™μ΄ μ‚¬μš©λ˜μ–΄, ν˜„μž¬κΉŒμ§€ κ°€μž₯ μž‘μ€ 거리λ₯Ό 가진 정점을 효율적으둜 선택할 수 μžˆλ‹€.

4. 이진 νž™(Binary Heap)κ³Ό νŽ˜μ–΄λ§ νž™(Pairing Heap)

νž™μ˜ μ—¬λŸ¬ λ³€ν˜• 쀑 ν•˜λ‚˜μΈ 이진 νž™κ³Ό νŽ˜μ–΄λ§ νž™μ€ 각기 λ‹€λ₯Έ νŠΉμ§•κ³Ό μž₯점을 가지고 있으며, 이진 νž™μ€ κ°€μž₯ 기본적인 ν˜•νƒœμ΄κ³ , νŽ˜μ–΄λ§ νž™μ€ 더 λ³΅μž‘ν•˜μ§€λ§Œ μ‚½μž…κ³Ό μ‚­μ œκ°€ λ”μš± νš¨μœ¨μ μ΄λ‹€.