C++ 컨테이너 어댑터 | stack·queue·priority_queue 완벽 가이드

C++ 컨테이너 어댑터 | stack·queue·priority_queue 완벽 가이드

이 글의 핵심

C++ 컨테이너 어댑터 완벽 가이드. stack (LIFO), queue (FIFO), priority_queue (힙)의 사용법과 실무 활용. DFS·BFS·다익스트라 알고리즘 구현 예제를 다룹니다.

들어가며

C++의 컨테이너 어댑터기존 컨테이너를 특정 인터페이스로 감싼 것입니다. stack (LIFO), queue (FIFO), priority_queue (힙) 세 가지가 있습니다.

비유로 말씀드리면, stack접시 쌓기 (마지막에 올린 것을 먼저 내림), queue줄 서기 (먼저 온 사람이 먼저 나감), priority_queue응급실 (우선순위가 높은 사람이 먼저 진료)에 가깝습니다.

이 글을 읽으면

  • stack, queue, priority_queue의 사용법을 이해합니다
  • 각 어댑터의 시간 복잡도를 파악합니다
  • DFS, BFS, 다익스트라 알고리즘을 구현합니다
  • 실무 활용 패턴과 주의사항을 확인합니다

목차

  1. 컨테이너 어댑터 개요
  2. 실전 구현
  3. 고급 활용
  4. 성능 비교
  5. 실무 사례
  6. 트러블슈팅
  7. 마무리

컨테이너 어댑터 개요

비교표

어댑터자료구조삽입삭제접근기반 컨테이너
stackLIFOpushpoptopdeque (기본)
queueFIFOpushpopfront/backdeque (기본)
priority_queuepushpoptopvector (기본)

실전 구현

1) stack (LIFO)

#include <iostream>
#include <stack>

int main() {
    std::stack<int> s;
    
    // push
    s.push(1);
    s.push(2);
    s.push(3);
    
    // top
    std::cout << s.top() << std::endl;  // 3
    
    // pop
    s.pop();
    std::cout << s.top() << std::endl;  // 2
    
    // size
    std::cout << s.size() << std::endl;  // 2
    
    // empty
    while (!s.empty()) {
        std::cout << s.top() << " ";
        s.pop();
    }
    std::cout << std::endl;  // 2 1
    
    return 0;
}

2) queue (FIFO)

#include <iostream>
#include <queue>

int main() {
    std::queue<int> q;
    
    // push
    q.push(1);
    q.push(2);
    q.push(3);
    
    // front/back
    std::cout << q.front() << std::endl;  // 1
    std::cout << q.back() << std::endl;   // 3
    
    // pop
    q.pop();
    std::cout << q.front() << std::endl;  // 2
    
    // size
    std::cout << q.size() << std::endl;  // 2
    
    // empty
    while (!q.empty()) {
        std::cout << q.front() << " ";
        q.pop();
    }
    std::cout << std::endl;  // 2 3
    
    return 0;
}

3) priority_queue (힙)

#include <iostream>
#include <queue>
#include <vector>

int main() {
    // 최대 힙 (기본)
    std::priority_queue<int> maxHeap;
    
    maxHeap.push(3);
    maxHeap.push(1);
    maxHeap.push(4);
    maxHeap.push(2);
    
    while (!maxHeap.empty()) {
        std::cout << maxHeap.top() << " ";  // 4 3 2 1
        maxHeap.pop();
    }
    std::cout << std::endl;
    
    // 최소 힙
    std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;
    
    minHeap.push(3);
    minHeap.push(1);
    minHeap.push(4);
    minHeap.push(2);
    
    while (!minHeap.empty()) {
        std::cout << minHeap.top() << " ";  // 1 2 3 4
        minHeap.pop();
    }
    std::cout << std::endl;
    
    return 0;
}

4) 기반 컨테이너 선택

#include <deque>
#include <iostream>
#include <list>
#include <queue>
#include <stack>
#include <vector>

int main() {
    // stack (기본: deque)
    std::stack<int> s1;
    std::stack<int, std::vector<int>> s2;  // vector 기반
    std::stack<int, std::list<int>> s3;    // list 기반
    
    // queue (기본: deque)
    std::queue<int> q1;
    std::queue<int, std::list<int>> q2;
    
    // priority_queue (기본: vector)
    std::priority_queue<int> pq1;
    std::priority_queue<int, std::deque<int>> pq2;
    
    return 0;
}

고급 활용

1) 괄호 검증 (stack)

#include <iostream>
#include <stack>
#include <string>

bool isValid(const std::string& s) {
    std::stack<char> st;
    
    for (char c : s) {
        if (c == '(' || c == '{' || c == '[') {
            st.push(c);
        } else {
            if (st.empty()) return false;
            
            char top = st.top();
            st.pop();
            
            if (c == ')' && top != '(') return false;
            if (c == '}' && top != '{') return false;
            if (c == ']' && top != '[') return false;
        }
    }
    
    return st.empty();
}

int main() {
    std::cout << isValid("()[]{}") << std::endl;     // 1
    std::cout << isValid("([)]") << std::endl;       // 0
    std::cout << isValid("{[()]}") << std::endl;     // 1
    
    return 0;
}

2) BFS (queue)

#include <iostream>
#include <queue>
#include <vector>

void bfs(const std::vector<std::vector<int>>& graph, int start) {
    std::vector<bool> visited(graph.size(), false);
    std::queue<int> q;
    
    q.push(start);
    visited[start] = true;
    
    while (!q.empty()) {
        int node = q.front();
        q.pop();
        
        std::cout << node << " ";
        
        for (int neighbor : graph[node]) {
            if (!visited[neighbor]) {
                visited[neighbor] = true;
                q.push(neighbor);
            }
        }
    }
    std::cout << std::endl;
}

int main() {
    std::vector<std::vector<int>> graph = {
        {1, 2},     // 0 → 1, 2
        {0, 3, 4},  // 1 → 0, 3, 4
        {0, 4},     // 2 → 0, 4
        {1},        // 3 → 1
        {1, 2}      // 4 → 1, 2
    };
    
    bfs(graph, 0);  // 0 1 2 3 4
    
    return 0;
}

3) 다익스트라 (priority_queue)

#include <iostream>
#include <queue>
#include <vector>

using Edge = std::pair<int, int>;  // {거리, 노드}

std::vector<int> dijkstra(const std::vector<std::vector<Edge>>& graph, int start) {
    int n = graph.size();
    std::vector<int> dist(n, INT_MAX);
    std::priority_queue<Edge, std::vector<Edge>, std::greater<Edge>> pq;
    
    dist[start] = 0;
    pq.push({0, start});
    
    while (!pq.empty()) {
        auto [d, u] = pq.top();
        pq.pop();
        
        if (d > dist[u]) continue;
        
        for (auto [w, v] : graph[u]) {
            if (dist[u] + w < dist[v]) {
                dist[v] = dist[u] + w;
                pq.push({dist[v], v});
            }
        }
    }
    
    return dist;
}

int main() {
    std::vector<std::vector<Edge>> graph = {
        {{1, 1}, {4, 2}},  // 0 → 1(1), 2(4)
        {{2, 2}, {5, 3}},  // 1 → 2(2), 3(5)
        {{1, 3}},          // 2 → 3(1)
        {}                 // 3
    };
    
    auto dist = dijkstra(graph, 0);
    
    for (int i = 0; i < dist.size(); ++i) {
        std::cout << "0 → " << i << ": " << dist[i] << std::endl;
    }
    
    return 0;
}

출력:

0 → 0: 0
0 → 1: 1
0 → 2: 3
0 → 3: 4

성능 비교

시간 복잡도

연산stackqueuepriority_queue
pushO(1)O(1)O(log n)
popO(1)O(1)O(log n)
top/frontO(1)O(1)O(1)
sizeO(1)O(1)O(1)
emptyO(1)O(1)O(1)

실무 사례

사례 1: 작업 스케줄러

#include <iostream>
#include <queue>
#include <string>
#include <vector>

struct Task {
    int priority;
    std::string name;
    
    bool operator>(const Task& other) const {
        return priority > other.priority;  // 낮은 우선순위가 먼저
    }
};

int main() {
    std::priority_queue<Task, std::vector<Task>, std::greater<Task>> scheduler;
    
    scheduler.push({3, "낮은 우선순위"});
    scheduler.push({1, "높은 우선순위"});
    scheduler.push({2, "중간 우선순위"});
    
    while (!scheduler.empty()) {
        Task task = scheduler.top();
        scheduler.pop();
        
        std::cout << "실행: " << task.name << " (우선순위 " << task.priority << ")" << std::endl;
    }
    
    return 0;
}

출력:

실행: 높은 우선순위 (우선순위 1)
실행: 중간 우선순위 (우선순위 2)
실행: 낮은 우선순위 (우선순위 3)

사례 2: 되돌리기 (Undo)

#include <iostream>
#include <stack>
#include <string>

class TextEditor {
private:
    std::string text_;
    std::stack<std::string> history_;
    
public:
    void write(const std::string& str) {
        history_.push(text_);
        text_ += str;
    }
    
    void undo() {
        if (!history_.empty()) {
            text_ = history_.top();
            history_.pop();
        }
    }
    
    std::string getText() const {
        return text_;
    }
};

int main() {
    TextEditor editor;
    
    editor.write("Hello");
    std::cout << editor.getText() << std::endl;  // Hello
    
    editor.write(" World");
    std::cout << editor.getText() << std::endl;  // Hello World
    
    editor.undo();
    std::cout << editor.getText() << std::endl;  // Hello
    
    editor.undo();
    std::cout << editor.getText() << std::endl;  // (빈 문자열)
    
    return 0;
}

사례 3: 프린터 큐

#include <iostream>
#include <queue>
#include <string>

struct PrintJob {
    int priority;
    std::string document;
    
    bool operator<(const PrintJob& other) const {
        return priority < other.priority;  // 높은 우선순위가 먼저
    }
};

int main() {
    std::priority_queue<PrintJob> printer;
    
    printer.push({1, "문서1.pdf"});
    printer.push({3, "긴급.pdf"});
    printer.push({2, "문서2.pdf"});
    
    while (!printer.empty()) {
        PrintJob job = printer.top();
        printer.pop();
        
        std::cout << "인쇄: " << job.document << " (우선순위 " << job.priority << ")" << std::endl;
    }
    
    return 0;
}

출력:

인쇄: 긴급.pdf (우선순위 3)
인쇄: 문서2.pdf (우선순위 2)
인쇄: 문서1.pdf (우선순위 1)

사례 4: DFS (stack)

#include <iostream>
#include <stack>
#include <vector>

void dfs(const std::vector<std::vector<int>>& graph, int start) {
    std::vector<bool> visited(graph.size(), false);
    std::stack<int> s;
    
    s.push(start);
    
    while (!s.empty()) {
        int node = s.top();
        s.pop();
        
        if (visited[node]) continue;
        
        visited[node] = true;
        std::cout << node << " ";
        
        for (int neighbor : graph[node]) {
            if (!visited[neighbor]) {
                s.push(neighbor);
            }
        }
    }
    std::cout << std::endl;
}

int main() {
    std::vector<std::vector<int>> graph = {
        {1, 2},     // 0 → 1, 2
        {0, 3, 4},  // 1 → 0, 3, 4
        {0, 4},     // 2 → 0, 4
        {1},        // 3 → 1
        {1, 2}      // 4 → 1, 2
    };
    
    dfs(graph, 0);  // 0 2 4 1 3
    
    return 0;
}

트러블슈팅

문제 1: top() 후 pop() 누락

증상: 메모리 누수 또는 중복 처리

// ❌ pop 누락
std::stack<int> s;
s.push(1);
int x = s.top();
// s.pop() 누락!

// ✅ pop 호출
int x = s.top();
s.pop();

문제 2: empty() 체크 누락

증상: 미정의 동작

// ❌ 빈 스택
std::stack<int> s;
// std::cout << s.top() << std::endl;  // 미정의 동작

// ✅ empty() 체크
if (!s.empty()) {
    std::cout << s.top() << std::endl;
}

문제 3: priority_queue 순서 혼동

증상: 의도와 다른 순서

// 기본: 최대 힙
std::priority_queue<int> maxHeap;
maxHeap.push(3);
maxHeap.push(1);
maxHeap.push(4);
std::cout << maxHeap.top() << std::endl;  // 4 (최대값)

// 최소 힙
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;
minHeap.push(3);
minHeap.push(1);
minHeap.push(4);
std::cout << minHeap.top() << std::endl;  // 1 (최소값)

문제 4: 반복자 미제공

증상: 순회 불가

// ❌ 반복자 없음
std::stack<int> s;
s.push(1);
s.push(2);
// for (int x : s) {}  // 에러: 반복자 없음

// ✅ pop으로 순회
while (!s.empty()) {
    std::cout << s.top() << " ";
    s.pop();
}

마무리

컨테이너 어댑터기존 컨테이너를 특정 인터페이스로 감싼 것입니다.

핵심 요약

  1. stack (LIFO)

    • push, pop, top
    • DFS, 괄호 검증, 되돌리기
    • O(1)
  2. queue (FIFO)

    • push, pop, front, back
    • BFS, 작업 큐
    • O(1)
  3. priority_queue (힙)

    • push, pop, top
    • 다익스트라, 작업 스케줄링
    • O(log n)
  4. 기반 컨테이너

    • stack/queue: deque (기본)
    • priority_queue: vector (기본)

선택 가이드

상황권장이유
DFSstackLIFO
BFSqueueFIFO
다익스트라priority_queue최소값
괄호 검증stackLIFO
작업 큐queueFIFO
작업 스케줄링priority_queue우선순위

코드 예제 치트시트

// stack
std::stack<int> s;
s.push(1);
s.top();
s.pop();

// queue
std::queue<int> q;
q.push(1);
q.front();
q.back();
q.pop();

// priority_queue (최대 힙)
std::priority_queue<int> maxHeap;
maxHeap.push(1);
maxHeap.top();
maxHeap.pop();

// priority_queue (최소 힙)
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;

다음 단계

  • queue/stack: C++ queue/stack
  • 힙 알고리즘: C++ 힙 알고리즘
  • 수치 알고리즘: C++ 수치 알고리즘

참고 자료

한 줄 정리: stack은 LIFO, queue는 FIFO, priority_queue는 힙으로, 각각 DFS·BFS·다익스트라 등의 알고리즘에 활용된다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • C++ queue/stack | “자료구조” 완벽 정리 [BFS/DFS 활용]
  • C++ Algorithm Heap | “힙 알고리즘” 가이드
  • C++ Algorithm Numeric | “수치 알고리즘” 가이드

관련 글

  • C++ queue/stack |