포스트

스택·큐·힙 — 접근 규칙 하나가 만드는 강력함

스택·큐·덱·힙(우선순위 큐)의 동작과 구현을 정리합니다. LIFO와 FIFO, 이진 힙의 sift-up/down, 콜스택·BFS·다익스트라에서 어떻게 쓰이는지 그림으로.

스택·큐·힙 — 접근 규칙 하나가 만드는 강력함

“어디서 넣고 어디서 빼는가”가 전부다

배열과 연결 리스트는 데이터를 담는 그릇이었습니다. 스택·큐·힙은 그 위에 “넣고 빼는 위치를 제한하는 규칙” 을 얹은 것입니다. 규칙을 줄이는 게 어떻게 더 강력해질까요? 제약이 명확하면 연산이 전부 O(1)~O(log n)으로 단순·빨라지고, 그 규칙 자체가 알고리즘의 도구가 되기 때문입니다.

스택은 깊이 우선 탐색과 함수 호출을, 큐는 너비 우선 탐색과 작업 분배를, 힙은 “다음으로 가장 급한 것”을 뽑는 우선순위 처리를 가능하게 합니다.

스택과 큐 — LIFO vs FIFO

스택(stack) 은 한쪽 끝에서만 넣고(push) 뺍니다(pop) — 나중에 들어온 게 먼저 나오는 LIFO. 큐(queue) 는 뒤로 넣고(enqueue) 앞에서 빼서(dequeue) — 먼저 들어온 게 먼저 나오는 FIFO. 둘 다 핵심 연산이 전부 O(1) 입니다.

스택 — LIFO (위에서 넣고 위에서 뺀다) push / pop ↕ 큐 — FIFO (뒤로 넣고 앞에서 뺀다) ↑ 앞(dequeue) 뒤(enqueue) ↑

스택은 보통 동적 배열로(끝에서 push/pop), 큐는 원형 버퍼(circular buffer) 나 양 끝 연결 리스트로 구현합니다. 큐를 단순 배열의 앞에서 빼면 매번 전체를 당겨 O(n)이 되므로, 앞/뒤 인덱스를 돌리는 원형 버퍼가 정석입니다.

덱(deque) 은 양쪽 끝에서 모두 넣고 뺄 수 있는 일반화입니다(Java ArrayDeque). 스택으로도 큐로도 쓸 수 있어 실무에선 Stack/LinkedList보다 ArrayDeque가 권장됩니다.

이 규칙들은 알고리즘의 엔진입니다.

자료구조규칙대표 쓰임
스택LIFO함수 콜스택, 괄호 짝 검사, DFS, 실행 취소(undo)
큐FIFOBFS, 작업 큐, 메시지 버퍼, 프린터 스풀
덱양끝슬라이딩 윈도우 최댓값, LRU

힙 — “가장 급한 것”을 O(log n)에 뽑는다

스택·큐가 들어온 순서로 뺀다면, 힙(heap) 은 우선순위로 뺍니다. 가장 작은(또는 큰) 원소를 항상 O(log n)에 꺼내는 우선순위 큐(priority queue) 의 표준 구현입니다.

이진 힙은 완전 이진 트리이면서 “부모는 항상 자식보다 작다(min-heap)”는 힙 속성을 지킵니다. 완전 트리라 트리인데도 배열 하나로 표현됩니다 — 인덱스 i의 자식은 2i+1, 2i+2, 부모는 (i-1)/2. 포인터가 없어 캐시 친화적입니다.

삽입은 맨 끝에 넣고 부모와 비교하며 위로 올리는 sift-up, 삭제(최소값 추출)는 루트를 빼고 마지막 원소를 올린 뒤 자식과 비교하며 내리는 sift-down — 둘 다 트리 높이 O(log n).

3 8 9 10 2 새 값 2 = 맨 끝 삽입 → 부모(8,3)와 비교하며 위로 (sift-up, O(log n))

배열 표현이라 위 트리는 메모리상 [3, 8, 9, 10, 2] 한 줄이고, sift-up은 인덱스를 (i-1)/2로 거슬러 오르며 swap할 뿐입니다.

힙의 진짜 위력은 알고리즘에서 드러납니다.

  • 다익스트라 최단경로에서 “가장 가까운 미방문 정점”을 매번 O(log n)에 뽑음.
  • 힙 정렬: n개를 힙에 넣고 하나씩 빼면 O(n log n) 정렬.
  • 상위 K개(top-K), 스트리밍 중앙값(두 힙), 작업 스케줄러의 우선순위 처리.
flowchart LR
    PQ["우선순위 큐 (힙)"] --> D["다익스트라"]
    PQ --> TK["Top-K / 스트리밍 중앙값"]
    PQ --> SCH["작업 스케줄러"]
연산이진 힙정렬된 배열정렬 안 된 배열
삽입O(log n)O(n)O(1)
최소값 조회O(1)O(1)O(n)
최소값 삭제O(log n)O(n)O(n)

프로덕션에서 마주치는 함정

함정증상해법
단순 배열 앞에서 dequeue매번 당기기 → O(n)원형 버퍼 / ArrayDeque
Java Stack·Vector 사용동기화 오버헤드·레거시ArrayDeque로 스택/큐
힙 안 원소의 우선순위 변경위치 못 찾아 못 고침인덱스 맵 보유, 또는 (재삽입+지연삭제)
PriorityQueue 비교자 누락자연 순서로 엉뚱 정렬Comparator 명시
콜스택 재귀 폭주StackOverflow명시적 스택으로 반복 전환

면접/리뷰 단골 질문

  • Q. 스택과 큐의 차이 한 줄? → 스택은 LIFO(같은 끝에서 넣고 뺌), 큐는 FIFO(뒤로 넣고 앞에서 뺌).
  • Q. 힙이 배열 하나로 트리를 표현하는 방법? → 완전 이진 트리라 인덱스 i의 자식 2i+1·2i+2, 부모 (i-1)/2로 계산. 포인터 불필요, 캐시 친화적.
  • Q. 힙 삽입/삭제가 O(log n)인 이유? → sift-up/down이 트리 높이만큼만 비교·교환하기 때문. 완전 트리 높이는 log n.
  • Q. 우선순위 큐를 정렬 배열로 만들면? → 조회 O(1)이나 삽입 O(n). 힙은 삽입·삭제 O(log n)으로 균형이 좋다.
  • Q. 힙 안 특정 원소의 우선순위를 바꾸려면? → 기본 힙은 못 찾음. 값→인덱스 맵을 같이 유지하는 인덱스 힙(decrease-key)으로.

정리

  • 스택(LIFO)·큐(FIFO)·덱은 접근 위치를 제약해 모든 핵심 연산을 O(1)로 만든 도구다.
  • 힙은 우선순위로 뺀다 — 완전 이진 트리를 배열로 표현, 삽입/삭제 O(log n)·최소값 조회 O(1).
  • 스택은 DFS·콜스택, 큐는 BFS·작업분배, 힙은 다익스트라·Top-K·스케줄러의 엔진이다.
  • 실무에선 ArrayDeque(스택/큐)와 PriorityQueue(힙)를 쓰고, 비교자·원형 버퍼 같은 디테일을 챙긴다.

다음 글은 또 다른 핵심 자료구조 해시 테이블입니다. 순서도 우선순위도 아닌, “키로 곧장 찾는” 평균 O(1)의 비밀을 봅니다.

</content> </invoke>

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

시리즈 · 알고리즘 A-Z (20편)

  1. 복잡도 분석 — Big-O, 점근 표기, 그리고 분할상환이라는 거짓말 같은 진실
  2. 배열과 연결 리스트 — 같은 O(n)인데 왜 한쪽이 수십 배 느린가
  3. 스택·큐·힙 — 접근 규칙 하나가 만드는 강력함 지금 보는 글
  4. 해시 테이블 — 평균 O(1)의 비밀과 그 평균이 깨지는 순간
  5. 비교 정렬 — 퀵·머지·힙, 그리고 Ω(n log n)이라는 넘을 수 없는 벽
  6. 선형 시간 정렬 — 비교를 버리면 Ω(n log n) 벽이 사라진다
  7. 이진 탐색 — 반씩 버리는 마법, 그리고 "답을 이분하라"
  8. 재귀와 분할정복 — 콜스택에 쌓이는 신뢰, 마스터 정리로 푸는 비용
  9. 이진 탐색 트리와 균형 — 한쪽으로 쏠리는 트리를 회전으로 바로 세우기
  10. 그래프 순회 — BFS의 물결, DFS의 깊이, 그리고 위상정렬
  11. 최단 경로 — 다익스트라의 확신, A*의 직감, 벨만-포드의 인내
  12. 최소 신장 트리와 Union-Find — 가장 싸게 잇기, 그리고 거의 O(1)의 마법
  13. 동적계획법 — 같은 계산을 두 번 하지 않는다는 단 하나의 원칙
  14. 그리디 — 눈앞의 최선만 집었는데 전체가 최적일 때
  15. 문자열 매칭 — 어긋났을 때 얼마나 똑똑하게 다시 시작하는가
  16. 확률적 자료구조 — 정확함을 메모리와 맞바꾸는 기술 (Bloom·HyperLogLog·Count-Min)
  17. 일관된 해싱 — 노드가 들고날 때 데이터를 통째로 옮기지 않는 법
  18. 합의 알고리즘 — 죽는 서버들 사이에서 하나의 진실을 정하는 법 (Raft·Paxos)
  19. 레이트 리미팅과 스트리밍 알고리즘 — 폭주를 막고, 끝없는 흐름에서 표본을 뽑다
  20. 의미로 검색하다 — 벡터 검색(ANN·HNSW)과 PageRank, 그리고 시리즈의 끝