큐
마지막 업데이트
큐에는 살아 있는 끝이 두 개 있습니다. 새 값은 뒤에서 들어오고 값은 앞에서 빠져나가므로, 가장 오래 기다린 것이 가장 먼저 처리됩니다. 이것이 선입선출(FIFO)이며, 창구 앞에 늘어선 줄이 움직이는 방식과 정확히 같습니다. 뒤에 서고 앞에서부터 처리되기 때문에 기다림이 공정해집니다. 위의 재생을 누르면 값이 한쪽으로 들어와 반대쪽으로 나가는 모습을 볼 수 있습니다.
각 끝을 저마다의 인덱스나 포인터로 추적하기 때문에 두 연산 모두 O(1)이고, 어느 쪽도 나머지 데이터를 옮기지 않습니다. 그래서 큐는 도착 순서대로 일을 처리하는 모든 것을 떠받칩니다. 인쇄 작업, 작업 큐와 메시지 큐, 요청 버퍼, 그리고 너비 우선 탐색이 그렇습니다. 너비 우선 탐색이 그래프를 레벨 단위로 방문하는 이유는 바로 프런티어를 큐에 담아 두기 때문입니다. 제거하는 끝을 뒤로 옮기면 대신 스택이 됩니다.
시간 및 공간 복잡도
링 버퍼나 연결 리스트로 구현한 큐, 즉 두 가지 표준 구현 기준:
| 연산 | 복잡도 | 비고 |
|---|---|---|
| 인큐(enqueue) | O(1) | 뒤에 쓰고 뒤 인덱스를 한 칸 전진시킨다. |
| 디큐(dequeue) | O(1) | 앞에서 읽고 앞 인덱스를 한 칸 전진시키며, 이동은 없다. |
| 피크(맨 앞 조회) | O(1) | 제거하지 않고 맨 앞 값을 읽는다. |
| 탐색 | O(n) | 큐의 용도가 아니다. 안을 보려면 모두 꺼내야 한다. |
| 공간 | O(n) | 기다리는 값 하나당 슬롯 하나. |
단계별 진행
| 단계 | 무슨 일이 일어나는가 |
|---|---|
| 1 | 큐는 비어 있는 상태로 시작하고, 앞과 뒤가 같은 슬롯을 가리킨다. |
| 2 | 인큐는 뒤에 값을 쓴 다음 뒤를 한 칸 전진시킨다. |
| 3 | 이후의 인큐는 이미 기다리는 값들 뒤에 놓인다. |
| 4 | 디큐는 맨 앞의 값을 읽은 다음 앞을 한 칸 전진시킨다. |
| 5 | 돌아오는 값은 항상 가장 오래 기다린 값이다. |
| 6 | 앞이 뒤와 만나면 큐는 다시 비고, 거기서 더 디큐하면 오류가 된다. |
풀이 예제
3, 7, 5를 인큐한 뒤 큐를 모두 비우는 과정:
| 연산 | 큐 (앞에서 뒤로) | 반환값 |
|---|---|---|
enqueue(3) | [3] | 없음 |
enqueue(7) | [3, 7] | 없음 |
enqueue(5) | [3, 7, 5] | 없음 |
dequeue() | [7, 5] | 3, 가장 오래된 값 |
dequeue() | [5] | 7 |
dequeue() | [] | 5, 가장 새로운 값이 마지막 |
큐를 사용해야 할 때
Queue 코드
Python, JavaScript, Java, C++, C로 작성된 깔끔하고 실행 가능한 Queue 구현입니다. 언어를 선택해 코드를 복사하거나 Coddy 플레이그라운드에서 바로 열어보세요.
Python로 구현한 Queue 코드
1from collections import deque2
3queue = deque()4
5# Enqueue three values at the rear6for value in [3, 7, 5]:7 queue.append(value)8 print(f"enqueue {value} -> {list(queue)}")9
10# Dequeue them from the front: first in, first out11while queue:12 value = queue.popleft()13 print(f"dequeue {value} -> {list(queue)}")14
15print("empty:", len(queue) == 0)JavaScript로 구현한 Queue 코드
1// A plain array makes dequeue O(n): shift() moves every element left.2// Track a head index instead, the fix the queue article describes.3const queue = { items: [], head: 0 };4
5function enqueue(value) {6 queue.items.push(value);7}8
9function dequeue() {10 const value = queue.items[queue.head];11 queue.items[queue.head] = undefined; // free the slot12 queue.head += 1;13 // Reclaim space once the consumed prefix dominates.14 if (queue.head * 2 >= queue.items.length) {15 queue.items = queue.items.slice(queue.head);16 queue.head = 0;17 }18 return value;19}20
21const size = () => queue.items.length - queue.head;22
23for (const value of [3, 7, 5]) {24 enqueue(value);25 console.log(`enqueue ${value} -> size ${size()}`);26}27
28// Dequeue from the front: first in, first out, amortized O(1)29while (size() > 0) {30 console.log(`dequeue ${dequeue()} -> size ${size()}`);31}32
33console.log('empty:', size() === 0);Java로 구현한 Queue 코드
1import java.util.ArrayDeque;2import java.util.Queue;3
4public class Main {5 public static void main(String[] args) {6 Queue<Integer> queue = new ArrayDeque<>();7
8 // Enqueue three values at the rear9 for (int value : new int[] {3, 7, 5}) {10 queue.add(value);11 System.out.println("enqueue " + value + " -> " + queue);12 }13
14 // Dequeue them from the front: first in, first out15 while (!queue.isEmpty()) {16 int value = queue.remove();17 System.out.println("dequeue " + value + " -> " + queue);18 }19
20 System.out.println("empty: " + queue.isEmpty());21 }22}C++로 구현한 Queue 코드
1#include <iostream>2#include <queue>3
4int main() {5 std::queue<int> queue;6
7 // Enqueue three values at the rear8 for (int value : {3, 7, 5}) {9 queue.push(value);10 std::cout << "enqueue " << value << " -> size " << queue.size() << "\n";11 }12
13 // Dequeue them from the front: first in, first out14 while (!queue.empty()) {15 int value = queue.front();16 queue.pop();17 std::cout << "dequeue " << value << " -> size " << queue.size() << "\n";18 }19
20 std::cout << "empty: " << std::boolalpha << queue.empty() << "\n";21 return 0;22}C로 구현한 Queue 코드
1#include <stdio.h>2
3#define CAP 164
5int queue[CAP];6int front = 0;7int rear = 0; /* index of the next free slot */8
9int main(void) {10 int values[3] = {3, 7, 5};11
12 /* Enqueue three values at the rear */13 for (int i = 0; i < 3; i++) {14 queue[rear++] = values[i];15 printf("enqueue %d -> size %d\n", values[i], rear - front);16 }17
18 /* Dequeue them from the front: first in, first out */19 while (front < rear) {20 int value = queue[front++];21 printf("dequeue %d -> size %d\n", value, rear - front);22 }23
24 printf("empty: %d\n", front == rear);25 return 0;26}큐 자주 묻는 질문
FIFO는 무슨 뜻인가요?
큐와 스택의 차이는 무엇인가요?
O(1)로 추가합니다. 큐는 앞에서 빼고(FIFO), 스택은 추가한 바로 그 끝에서 뺍니다(LIFO). 그 밖의 복잡도 표는 서로 동일합니다.큐의 주요 연산은 무엇인가요?
enqueue는 뒤에 값을 추가하고, dequeue는 맨 앞 값을 빼서 반환하며, peek(또는 front)은 제거하지 않고 맨 앞을 읽고, is_empty는 기다리는 것이 있는지 알려줍니다. 네 가지 모두 O(1)입니다.평범한 배열을 쓰면 왜 디큐가 느린가요?
O(n)이 되기 때문입니다. 실제 구현은 앞 인덱스를 전진시키는 링 버퍼나 머리 포인터를 가진 연결 리스트로 이를 피합니다. 파이썬의 collections.deque와 자바의 ArrayDeque는 이를 대신 해 주지만 list.pop(0)은 그렇지 않습니다.원형 큐란 무엇인가요?
n인 큐는 배열 끝을 벗어나지 않고 무기한 계속 동작합니다.