ABOUT

성능과 운영 안정성을 함께 끌어올리는 개발자입니다.

92% Positional Error Reduction
79% p95 Latency Improvement
90%+ Long Tasks Reduction

2022.02 · 한국장학재단

우수 멘티

한국장학재단 사회 리더 대학생 멘토링 IT

2022.10 · 동작구청

우수 인재상

동작구청 우수 SW 인재

2025.05 · (주) 그랩

프로그래밍 우수상

(주) 그랩 우수 프로그램 개발

2025.05 · AWSKRUG

AWS한국사용자모임 발표

AI agent 스크립트 튜닝 관련 발표

ComputerScience

Development

Engineering

Trouble Shooting

GUESTBOOK

첫 마음부터
함께 나누는 온기

방명록 작성하러 가기

SUBSCRIBE

최신소식을
편하게 만나보세요.

힙 (Heap)

도입

힙(Heap)은 정렬 검색 트리가 아니라, 가장 작은 값이나 가장 큰 값을 빠르게 꺼내기 위해 부모-자식 우선순위 규칙을 구조에 넣은 완전 이진 트리다.

BST가 왼쪽과 오른쪽에 정렬 규칙을 넣어 검색 범위를 줄이는 구조라면, 힙은 부모와 자식 사이에만 우선순위 규칙을 넣어 루트에서 항상 가장 중요한 값을 꺼낼 수 있게 만든 구조입니다.

그래서 힙은 전체 데이터가 완전히 정렬되어 있지는 않지만, 현재 시점에 가장 작은 값이나 가장 큰 값을 반복해서 처리해야 하는 문제에서 매우 강합니다. 대표적으로 priority queue와 heapsort가 여기에 해당합니다.

즉 힙을 이해한다는 것은 트리 구조를 하나 더 아는 것이 아니라, 정렬 전체보다 우선순위의 루트 접근을 더 중요하게 보는 자료구조 설계를 이해하는 일에 가깝습니다.

필요성

모든 값을 완전히 정렬할 필요는 없지만, 다음에 처리할 최솟값이나 최댓값을 반복적으로 빠르게 꺼내야 할 때 힙이 가장 자연스러운 선택이 된다

예를 들어 작업 스케줄링, 이벤트 처리, shortest path 계열 알고리즘, top-k 문제처럼 “지금 가장 우선순위가 높은 원소 하나”를 계속 뽑아야 하는 문제는 생각보다 많습니다.

이런 상황에서 전체 정렬을 매번 다시 하는 것은 비효율적입니다. 힙은 전체 순서를 완벽히 유지하는 대신, 루트가 항상 가장 작은 값 또는 가장 큰 값이 되도록 유지해 필요한 원소를 빠르게 얻도록 설계됩니다.

그래서 힙의 강점은 “전체를 다 정렬한다”보다 “다음 우선순위 하나를 반복해서 뽑는다”는 작업 모델에 있습니다.

힙이 특히 강한 지점
  • 최솟값 또는 최댓값을 반복적으로 꺼내야 할 때
  • priority queue를 효율적으로 구현하고 싶을 때
  • 전체 정렬보다 루트 우선 접근이 중요한 경우
  • 완전 이진 트리의 배열 표현으로 메모리를 compact하게 쓰고 싶을 때

정의

힙은 complete tree 위에 heap property를 얹은 구조이며, 보통 실무에서는 complete binary tree 기반의 binary heap을 뜻한다

힙의 정의는 크게 두 가지 조건으로 이루어집니다.

첫째는 shape property입니다. 힙은 보통 complete binary tree여야 합니다. 즉 마지막 레벨을 제외한 모든 레벨이 꽉 차 있고, 마지막 레벨은 왼쪽부터 채워집니다.

둘째는 heap property입니다. min-heap이라면 부모가 자식보다 작거나 같고, max-heap이라면 부모가 자식보다 크거나 같습니다. 이 규칙 덕분에 루트가 항상 가장 작은 값 또는 가장 큰 값을 가집니다.

핵심 메시지

"힙의 본질은 전체 정렬이 아니라

루트가 항상 가장 중요한 값이 되도록 부모-자식 관계만 강하게 제약하는 데 있습니다."

핵심 원리

힙의 핵심은 형제 노드나 서브트리 전체를 정렬하는 것이 아니라, 부모와 자식 사이에만 우선순위 규칙을 유지해 루트 접근을 빠르게 만드는 데 있다

BST는 왼쪽 전체가 더 작고 오른쪽 전체가 더 크다는 강한 규칙을 가집니다. 반면 힙은 부모와 자식 사이에만 비교 규칙이 있습니다. 따라서 같은 레벨의 형제 노드끼리는 누가 더 큰지, 더 작은지가 보장되지 않습니다.

이 점 때문에 힙은 정렬 검색 구조라기보다 우선순위 구조에 더 가깝습니다. 대신 complete binary tree라는 shape property를 갖기 때문에 높이가 작고, 배열 기반으로 compact하게 표현하기 쉽습니다.

결국 힙은 “전체 순서를 아는 구조”가 아니라 “루트 하나는 확실히 안다”는 구조입니다. 이 한 가지를 강하게 보장하는 대신, 그 외의 정렬 정보는 최소한만 유지합니다.

그래서 힙은 정렬 자체보다는 next minimum 또는 next maximum을 반복해서 뽑아야 하는 문제와 가장 잘 맞습니다.

Heap Thinking
1) complete binary tree 형태를 유지한다
2) 부모-자식 사이의 우선순위 규칙을 지킨다
3) 루트가 항상 최소값 또는 최대값이 된다
4) 삽입은 아래에 넣고 위로 올린다
5) 루트 제거는 마지막 원소를 올리고 아래로 내린다

핵심 불변식

힙을 힙답게 만드는 것은 shape property와 heap property 두 가지이며, 둘 중 하나라도 깨지면 구조의 의미가 무너진다
불변식 의미 실무 해석
Shape Property complete binary tree 형태 유지 배열 기반 표현이 가능하고 높이가 작게 유지됨
Min-Heap Property 부모 ≤ 자식 루트가 항상 최소값
Max-Heap Property 부모 ≥ 자식 루트가 항상 최대값
Partial Order 전체 정렬이 아니라 부분 정렬 형제나 다른 서브트리끼리 순서는 보장되지 않음

기본 구조

binary heap은 complete binary tree라는 모양 덕분에 포인터보다 배열 표현이 훨씬 자연스럽고 실용적이다

일반 이진 트리는 보통 노드 객체와 포인터로 구현하지만, binary heap은 배열로 표현하는 편이 훨씬 자연스럽습니다. complete binary tree는 노드의 위치가 사실상 하나의 shape로 정해지기 때문입니다.

0-based 배열 표현을 쓰면 루트는 인덱스 0에 있고, 왼쪽 자식은 2i+1, 오른쪽 자식은 2i+2, 부모는 (i-1)/2 로 계산할 수 있습니다. 별도 포인터를 저장하지 않아도 된다는 점이 큰 장점입니다.

binary-heap/
├── array
│   ├── root at index 0
│   ├── left(i)   = 2i + 1
│   ├── right(i)  = 2i + 2
│   └── parent(i) = (i - 1) / 2
└── complete binary tree shape
    └── level-order 로 빈칸 없이 저장

기본 구현

힙을 이해하는 가장 빠른 방법은 삽입은 bubble-up, 루트 제거는 trickle-down 이라는 두 동작을 같이 보는 것이다
class MinHeap {
    private java.util.ArrayList<Integer> a = new java.util.ArrayList<>();

    private int parent(int i) { return (i - 1) / 2; }
    private int left(int i)   { return 2 * i + 1; }
    private int right(int i)  { return 2 * i + 2; }

    public void push(int x) {
        a.add(x);
        bubbleUp(a.size() - 1);
    }

    public int peek() {
        return a.get(0);
    }

    public int pop() {
        int root = a.get(0);
        int last = a.remove(a.size() - 1);

        if (!a.isEmpty()) {
            a.set(0, last);
            trickleDown(0);
        }
        return root;
    }

    private void bubbleUp(int i) {
        while (i > 0) {
            int p = parent(i);
            if (a.get(p) <= a.get(i)) break;
            swap(i, p);
            i = p;
        }
    }

    private void trickleDown(int i) {
        while (true) {
            int l = left(i);
            int r = right(i);
            int smallest = i;

            if (l < a.size() && a.get(l) < a.get(smallest)) smallest = l;
            if (r < a.size() && a.get(r) < a.get(smallest)) smallest = r;

            if (smallest == i) break;
            swap(i, smallest);
            i = smallest;
        }
    }

    private void swap(int i, int j) {
        int tmp = a.get(i);
        a.set(i, a.get(j));
        a.set(j, tmp);
    }
}
실전 포인트
삽입은 새 값을 배열 끝에 넣은 뒤 부모와 비교하면서 위로 올리고, 삭제는 루트를 제거한 뒤 마지막 원소를 루트로 올려 자식과 비교하며 아래로 내립니다. 힙 구현의 핵심은 결국 이 두 정리 과정이며, complete binary tree 모양 자체는 배열 append/remove로 자연스럽게 유지됩니다.

패턴 1. 최소 힙과 최대 힙

힙은 본질적으로 min-heap 과 max-heap 두 방향으로 나뉘며, 차이는 비교 방향뿐이지만 실제 사용 목적은 꽤 다르다

min-heap에서는 가장 작은 값이 루트에 있고, max-heap에서는 가장 큰 값이 루트에 있습니다. 비교 연산 방향만 바꾸면 구현은 거의 대칭적입니다.

차이는 사용 목적에서 드러납니다. 작업 스케줄링에서 가장 이른 마감 시간을 먼저 꺼내고 싶다면 min-heap이 자연스럽고, 우선순위가 가장 큰 작업을 먼저 처리하려면 max-heap이 더 잘 맞습니다.

Min-Heap
- 부모 ≤ 자식
- root = 최소값

Max-Heap
- 부모 ≥ 자식
- root = 최대값

패턴 2. 우선순위 큐와 BST 차이

힙은 priority queue에 최적화된 구조이고, BST는 정렬 검색에 최적화된 구조이므로 둘은 같은 트리라도 목적이 다르다
관점 힙(Heap) BST
핵심 목표 루트에서 최소/최대값 빠르게 꺼내기 정렬 기반 검색과 순서 질의
정렬 정보 부모-자식 사이만 부분 정렬 왼쪽 전체/오른쪽 전체에 대한 강한 정렬 규칙
루트 의미 항상 최소값 또는 최대값 중앙 분기 기준점
중위 순회 정렬 순이 아님 정렬 순이 됨
대표 용도 priority queue, heapsort ordered set/map, predecessor/successor 검색

즉 힙은 “가장 작은 것 하나” 또는 “가장 큰 것 하나”를 빠르게 꺼내는 구조이고, BST는 “정렬 질의를 풍부하게 처리하는 구조”라고 보면 차이가 분명해집니다.

패턴 3. heapify 와 heapsort

힙은 빈 구조에 하나씩 넣어도 만들 수 있지만, 기존 배열을 한 번에 heapify 하는 경로와 이를 이용한 heapsort 까지 이어지는 흐름을 같이 봐야 실무적으로 의미가 살아난다

힙을 만드는 방법은 두 가지로 생각할 수 있습니다. 하나는 빈 힙에 원소를 하나씩 넣는 방식이고, 다른 하나는 이미 있는 배열을 한 번에 heap으로 바꾸는 heapify 방식입니다.

또 힙은 정렬 알고리즘과도 직접 연결됩니다. NIST가 설명하듯 heapsort는 힙을 만든 다음 최대값을 반복해서 추출하는 방식으로 동작합니다. 즉 힙은 우선순위 큐 구조이면서 동시에 정렬의 기반이기도 합니다.

Heap Workflow
1) 배열을 heapify 하거나
2) 하나씩 push 해서 힙을 만든다
3) root를 반복적으로 pop 하면
4) 우선순위 처리 또는 정렬 과정으로 연결된다

한계와 주의점

힙은 루트 우선 접근에는 강하지만 전체 정렬 구조가 아니기 때문에, 정렬 순 검색이나 임의 위치 탐색에는 BST 같은 구조만큼 자연스럽지 않다

힙의 가장 큰 장점은 루트 하나를 빠르게 얻는 것이지만, 그만큼 다른 순서 정보는 많이 버립니다. 그래서 힙 안에서 임의 값을 효율적으로 찾거나, predecessor/successor 같은 ordered query를 처리하는 데는 적합하지 않습니다.

또 complete binary tree 모양을 유지해야 하므로, 포인터 기반 일반 트리처럼 자유롭게 가지를 붙이는 구조와는 사용감이 다릅니다. 힙은 우선순위 문제에 매우 좋지만, 모든 트리 문제의 일반 해법은 아닙니다.

마지막으로 힙이 정렬 구조처럼 보여도 실제로는 partial order만 유지한다는 점을 잊기 쉽습니다. 이 오해 때문에 힙 내부를 순서대로 훑으면 정렬되어 있을 것이라고 기대하는 실수가 자주 생깁니다.

주의해야 할 지점
  • 힙은 전체 정렬 구조가 아니라 partial order 구조임
  • 루트는 보장되지만 형제 노드 사이 순서는 보장되지 않음
  • 임의 값 탐색이나 ordered query에는 BST보다 부자연스러움
  • shape property 와 heap property 둘 다 동시에 유지해야 함

자주 하는 실수

힙을 어렵게 만드는 가장 흔한 원인은 힙을 정렬 트리로 착각하거나, shape property 와 heap property 를 따로 생각하지 않는 데 있다
  • 힙 내부를 왼쪽부터 보면 정렬되어 있을 것이라고 생각함
  • BST와 힙을 둘 다 “검색 트리” 정도로 뭉뚱그려 생각함
  • 삽입에서 bubble-up 을 빼먹어 heap invariant 를 깨뜨림
  • 루트 제거 후 trickle-down 을 하지 않아 구조를 망가뜨림
  • complete binary tree shape 를 유지하지 않고 임의 위치에 노드를 붙임
  • min-heap 과 max-heap 비교 방향을 뒤섞음

실무 루틴

힙을 사용할 때는 먼저 정말 필요한 것이 전체 정렬인지, 아니면 next min/max 반복 추출인지부터 구분하는 습관이 중요하다
  1. 먼저 필요한 것이 전체 정렬 인지, 최소/최대 반복 추출 인지 구분한다.
  2. 루트가 최소여야 하는지 최대여야 하는지 보고 min-heap 또는 max-heap 을 정한다.
  3. complete binary tree 라면 포인터보다 배열 표현 을 먼저 고려한다.
  4. 삽입은 bubble-up, 제거는 trickle-down 으로 생각하면 구현이 단순해진다.
  5. 기존 배열이 있다면 하나씩 넣기보다 heapify 경로도 검토한다.
  6. 정렬 질의가 많다면 heap 대신 BST 또는 balanced BST 가 더 맞는지 다시 본다.

디버깅

힙 디버깅은 값 하나를 보는 것이 아니라, 배열 인덱스 관계와 heap invariant 와 complete shape 가 동시에 유지되는지 확인하는 과정이다
1
먼저 min-heap 인지 max-heap 인지 방향을 분명히 정하고 부모-자식 비교식을 다시 확인한다.
2
배열 기반이라면 parent(i), left(i), right(i) 계산이 맞는지 먼저 본다.
3
삽입 뒤에는 bubble-up 이 끝까지 수행됐는지, 루트 제거 뒤에는 trickle-down 이 제대로 돌았는지 확인한다.
4
루트는 항상 최소값 또는 최대값이어야 하므로 peek() 결과가 바로 깨졌는지부터 본다.
5
성능이 이상하면 하나씩 push 한 build 경로인지, heapify 경로인지, 배열 resize 비용이 섞였는지 같이 본다.
점검 체크리스트
- min-heap / max-heap 방향이 맞는가
- complete binary tree shape 가 유지되는가
- parent / left / right index 계산이 맞는가
- bubble-up / trickle-down 이 빠지지 않았는가
- root 가 항상 최소값 또는 최대값인가

요약

힙의 핵심은 complete binary tree 위에 부모-자식 우선순위 규칙을 유지해 루트에서 항상 최소값 또는 최대값을 빠르게 꺼낼 수 있게 만드는 데 있으며, 전체 정렬보다 priority queue 문제에 더 잘 맞는 구조다
  • ✅ 힙은 complete binary tree 와 heap property 를 함께 갖는 구조다.
  • ✅ min-heap 은 부모 ≤ 자식, max-heap 은 부모 ≥ 자식이다.
  • ✅ 루트는 항상 최소값 또는 최대값을 가진다.
  • ✅ binary heap 은 배열로 compact 하게 구현하는 편이 자연스럽다.
  • ✅ 0-based 배열에서 왼쪽 자식은 2i+1, 오른쪽 자식은 2i+2, 부모는 (i-1)/2 다.
  • ✅ 삽입은 bubble-up, 루트 제거는 trickle-down 으로 heap invariant 를 복구한다.
  • ✅ 힙은 전체 정렬 구조가 아니라 priority queue 에 최적화된 partial order 구조다.
  • ✅ heapify 와 heapsort 까지 연결해서 보면 힙의 실무적 의미가 더 분명해진다.

 

 

728x90