자료구조

자료구조 - 히프

tita 2024. 6. 10. 14:02

[히프]

히프(heap)는 여러 개의 값들 중에서 가장 큰 값이나 가장 작은 값을 빠르게 찾아내도록 만들어진 자료 구조입니다.

간단히 말하면 부모 노드의 키 값이 자식 노드의 키 값보다 항상 큰 이진 트리입니다.

key(부모노드) >= key(자식노드)

 

히프 트리에서는 중복 값을 허용한다는 것을 유의해야 합니다.

 

 

히프의 종류

최대 히프(max heap) : 부모 노드의 키값이 자식 노드의 키 값보다 크거나 같은 완전 이진 트리입니다.
최소 히프(min heap) : 부모 노드의 키값이 자식 노드의 키 값보다 작거나 같은 완전 이진 트리입니다.

 

 

 

히프의 구현

히프는 완전 이진 트리이기 때문에 각각의 노드에 차례대로 번호를 붙여서 배열의 인덱스에 넣을 수 있습니다.

프로그램 구현을 쉽게 하기 위해서 배열의 첫 번째 인덱스인 0은 사용되지 않습니다.

 

 

 

히프의 부모와 자식 관계에 대해서 알아보겠습니다.

부모 노드 인덱스: (i - 1) / 2
왼쪽 자식 노드 인덱스: (i * 2) + 1
오른쪽 자식 노드 인덱스: (i * 2) + 2

 

 

 

[히프의 삽입과 삭제 연산]

 

삽입 연산

1. 새로운 요소를 힙의 마지막 위치에 삽입합니다.

2. 새로운 요소를 올바른 위치로 이동시키기 위해 상향 조정을 수행합니다 (sift-up).

 

 

void insert(int data) 
{
    // 새로운 데이터를 힙의 마지막 위치에 삽입
    heap.push_back(data);
    
    // 상향 조정을 통해 힙 속성 유지
    siftUp(heap.size() - 1);
}

void siftUp(int index) 
{
    while (index > 0) 
    {
        int parent = (index - 1) / 2;
        
        // 부모 노드가 현재 노드보다 크면 힙 속성 유지
        if (heap[index] <= heap[parent]) 
        {
            break;
        }
        
        // 현재 노드와 부모 노드 교환
        std::swap(heap[index], heap[parent]);
        index = parent;
    }
}

 

 

 

삭제 연산

1. 루트 노드를 제거하고, 힙의 마지막 요소를 루트로 이동시킵니다.

2. 루트 노드를 올바른 위치로 이동시키기 위해 하향 조정을 수행합니다 (sift-down).

void deleteMax() 
{
    if (heap.empty()) 
    {
        throw std::runtime_error("Queue is empty");
    }
    
    // 루트 노드와 마지막 노드를 교환
    std::swap(heap.front(), heap.back());
    
    // 마지막 노드 제거
    heap.pop_back();
    
    // 하향 조정을 통해 힙 속성 유지
    siftDown(0);
}

void siftDown(int index) 
{
    int size = heap.size();
    while (true) 
    {
        int largest = index;
        int left = 2 * index + 1;
        int right = 2 * index + 2;
        
        // 왼쪽 자식이 현재 노드보다 크면 largest 갱신
        if (left < size && heap[left] > heap[largest]) 
        {
            largest = left;
        }
        
        // 오른쪽 자식이 현재 노드보다 크면 largest 갱신
        if (right < size && heap[right] > heap[largest]) 
        {
            largest = right;
        }
        
        // 힙 속성이 유지되면 루프 종료
        if (largest == index) 
        {
            break;
        }
        
        // 현재 노드와 largest 노드 교환
        std::swap(heap[index], heap[largest]);
        index = largest;
    }
}

 

 

 

 

히프 구현 전체 코드

#include <iostream>
#include <vector>
#include <stdexcept>

class PriorityQueueHeap 
{
    public:
        void insert(int data) 
        {
            // 새로운 데이터를 힙의 마지막 위치에 삽입
            heap.push_back(data);
        
            // 상향 조정을 통해 힙 속성 유지
            siftUp(heap.size() - 1);
        }

        void deleteMax() 
        {
            if (heap.empty()) 
            {
                throw std::runtime_error("Queue is empty");
            }
        
            // 루트 노드와 마지막 노드를 교환
            std::swap(heap.front(), heap.back());
            
            // 마지막 노드 제거
            heap.pop_back();
        
            // 하향 조정을 통해 힙 속성 유지
            siftDown(0);
        }

        int peekMax() 
        {
            if (heap.empty()) 
            {
                throw std::runtime_error("Queue is empty");
            }
            // 힙의 루트 노드 반환
            return heap.front();
        }        

    private:
        std::vector<int> heap;    

        void siftUp(int index) 
        {
            while (index > 0) 
            {
                int parent = (index - 1) / 2;
                
                // 부모 노드가 현재 노드보다 크면 힙 속성 유지
                if (heap[index] <= heap[parent])
                {
                    break;
                }
            
                // 현재 노드와 부모 노드 교환
                std::swap(heap[index], heap[parent]);
                index = parent;
            }
        }

        void siftDown(int index)
        {
            int size = heap.size();
            while (true) 
            {
                int largest = index;
                int left = 2 * index + 1;
                int right = 2 * index + 2;
                // 왼쪽 자식이 현재 노드보다 크면 largest 갱신
                if (left < size && heap[left] > heap[largest]) 
                {
                    largest = left;
                }
                
                // 오른쪽 자식이 현재 노드보다 크면 largest 갱신
                if (right < size && heap[right] > heap[largest]) 
                {
                    largest = right;
                }
                
                // 힙 속성이 유지되면 루프 종료
                if (largest == index) 
                {
                    break;
                }
                
                // 현재 노드와 largest 노드 교환
                std::swap(heap[index], heap[largest]);
                index = largest;
            }
        }
};    

int main()
{
    PriorityQueueHeap pq;
    pq.insert(3);
    pq.insert(5);
    pq.insert(1);

    std::cout << "Max: " << pq.peekMax() << std::endl; // 5
    pq.deleteMax();
    std::cout << "Max after delete: " << pq.peekMax() << std::endl; // 3

    return 0;
}