프로그래머스/스택,큐

큐 문제 풀이 기술

johsjoshjosh 2026. 1. 7. 01:11

파이썬으로 큐(Queue) 문제를 풀 때는 리스트(list)를 사용하기보다 표준 라이브러리인 collections.deque를 사용하는 것이 핵심입니다.

리스트의 pop(0)은 시간 복잡도가 $O(N)$이라 효율성 테스트에서 탈락하기 쉽지만, deque의 popleft()는 **$O(1)$**로 매우 빠르기 때문입니다.


1. 큐의 기본 연산 (Enqueue, Dequeue)

collections.deque를 활용한 표준적인 큐 구현 방식입니다.

Python
 
from collections import deque

# 1. 큐 생성
queue = deque()

# 2. 데이터 추가 (Enqueue)
queue.append(1)
queue.append(2)
queue.append(3)

# 3. 데이터 꺼내기 (Dequeue) - 왼쪽에서 제거 및 반환
first_element = queue.popleft() # 1이 제거됨

# 4. 맨 앞 데이터 확인 (Peek)
if queue:
    front = queue[0]

# 5. 큐가 비었는지 확인
if not queue:
    print("큐가 비어있습니다.")

2. 우선순위와 함께 데이터 관리하기

프로그래머스 "프로세스(구 프린터)" 문제처럼 (데이터, 우선순위)를 묶어서 처리해야 할 때 자주 쓰이는 패턴입니다.

Python
 
from collections import deque

def process_example(priorities, location):
    # (우선순위, 원래 인덱스) 형태의 튜플로 큐 생성
    queue = deque([(p, i) for i, p in enumerate(priorities)])
    print(f"초기 큐 상태: {queue}")
    
    # 예시: 하나씩 꺼내며 조건 확인
    while queue:
        current = queue.popleft()
        # 큐에 더 높은 우선순위가 있는지 확인하는 로직 등...
        # if any(current[0] < q[0] for q in queue):
        #     queue.append(current)

이 코드는 보통 코딩 테스트에서 **"프린터"**나 "프로세스 스케줄링" 문제로 자주 등장하는 로직의 기초 단계입니다. 핵심은 우선순위가 높은 것을 먼저 처리하되, 원래의 위치(index)를 기억해야 한다는 점입니다.


1. 코드 핵심 포인트

① enumerate와 튜플 사용

Python
 
queue = deque([(p, i) for i, p in enumerate(priorities)])

단순히 우선순위 값만 큐에 넣으면, 나중에 값이 같은 것들끼리 섞였을 때 "내가 찾던 그 작업"이 무엇인지 알 수 없게 됩니다. 그래서 **(우선순위, 원래 인덱스)**를 한 쌍으로 묶어서 저장하는 것입니다.

  • 예: priorities = [2, 1, 3] → queue = [(2, 0), (1, 1), (3, 2)]

② deque.popleft()의 활용

일반적인 리스트의 pop(0)은 시간 복잡도가 $O(n)$이지만, collections.deque의 popleft()는 **$O(1)$**입니다. 큐(Queue)의 FIFO(First-In-First-Out) 원칙을 가장 효율적으로 구현한 방식입니다.


2. 주석 처리된 로직의 의미 (중요)

주석으로 처리된 부분은 이 알고리즘의 결정적인 규칙을 담고 있습니다.

Python
 
if any(current[0] < q[0] for q in queue):
    queue.append(current)
  1. any(...): "큐 안에 지금 내가 꺼낸 것보다 우선순위가 높은 게 하나라도 있니?"라고 묻는 것입니다.
  2. append: 만약 더 높은 게 있다면, 지금 꺼낸 것을 다시 맨 뒤로 보냅니다. (순서가 밀리는 것이죠.)
  3. 처리 완료: 만약 내가 꺼낸 게 가장 높은 우선순위라면, 다시 넣지 않고 그대로 종료(인쇄/처리)합니다.

3. 전체 흐름 예시

만약 priorities = [1, 2, 3], location = 0(첫 번째 1이 언제 나가는지 궁금함) 이라면:

  1. 초기: [(1, 0), (2, 1), (3, 2)]
  2. 1회전: (1, 0)을 꺼냈는데, 뒤에 2, 3이 있으므로 다시 뒤로 보냄 → [(2, 1), (3, 2), (1, 0)]
  3. 2회전: (2, 1)을 꺼냈는데, 뒤에 3이 있으므로 다시 뒤로 보냄 → [(3, 2), (1, 0), (2, 1)]
  4. 3회전: (3, 2)를 꺼냄. 뒤에 더 큰 게 없으므로 처리 완료! (1번째로 나감)
  5. 4회전: (1, 0)을 꺼냄. 뒤에 2가 있으므로 다시 뒤로 보냄... (반복)

4. 이 코드가 쓰이는 곳

  • 운영체제(OS)의 스케줄링: 우선순위가 높은 프로세스를 먼저 처리해야 할 때.
  • 프린터 대기열: 문서의 중요도에 따라 인쇄 순서를 바꿀 때.

3. 원형 큐 (Circular Queue) 느낌 구현하기

큐의 맨 앞 원소를 꺼내서 다시 맨 뒤로 보내는 작업은 **"요세푸스 문제"**나 "카드 게임" 유형에서 매우 자주 나옵니다.

Python
 
from collections import deque

cards = deque([1, 2, 3, 4, 5])

# 맨 앞 장을 버리고, 그다음 장을 맨 뒤로 보내는 예시
while len(cards) > 1:
    cards.popleft()          # 1 버림
    moved = cards.popleft()  # 2 꺼냄
    cards.append(moved)      # 2를 뒤로 보냄

print(f"마지막 남은 카드: {cards[0]}")

이 코드는 카드 게임이나 특정 규칙에 따라 데이터를 조작할 때 자주 사용되는 "카드 섞기/버리기" 로직입니다. 백준(BOJ)의 '카드2'와 같은 문제의 핵심 풀이법이기도 합니다.

이 알고리즘의 핵심은 **deque를 이용한 원형 회전(Rotation)**입니다.


1. 코드의 동작 단계

카드가 한 장 남을 때까지 다음 두 단계를 반복합니다.

  1. cards.popleft(): 큐의 가장 앞에 있는 카드를 완전히 제거합니다. (카드를 바닥에 버리는 동작)
  2. moved = cards.popleft() 후 cards.append(moved): 그다음 장을 꺼내서 맨 뒤로 다시 집어넣습니다. (카드를 아래로 옮기는 동작)

2. 시뮬레이션 (cards = [1, 2, 3, 4, 5])

과정을 한 단계씩 따라가 보면 왜 특정 숫자가 남는지 이해하기 쉽습니다.

단계 현재 큐 상태 동작 설명 남은 상태
시작 [1, 2, 3, 4, 5] 초기 상태 -
1회전 [1, 2, 3, 4, 5] 1 버리고, 2를 뒤로 보냄 [3, 4, 5, 2]
2회전 [3, 4, 5, 2] 3 버리고, 4를 뒤로 보냄 [5, 2, 4]
3회전 [5, 2, 4] 5 버리고, 2를 뒤로 보냄 [4, 2]
4회전 [4, 2] 4 버리고, 2를 뒤로 보냄 [2]
종료 [2] 길이가 1이 되어 중단 결과: 2

3. 왜 deque를 사용하나요?

리스트(list)를 사용해서 list.pop(0)을 하면, 첫 번째 요소를 뺀 뒤 뒤에 있는 모든 요소를 한 칸씩 앞으로 당겨야 하므로 시간이 오래 걸립니다($O(n)$).

반면, collections.deque는 양쪽 끝에서의 추가와 삭제가 즉시($O(1)$) 이루어지도록 설계되어 있어, 카드가 수만 장인 경우에도 매우 빠르게 동작합니다.


4. 핵심 정리

  • 패턴: 버리고 -> 옮기고 -> 버리고 -> 옮기고...
  • 종료 조건: 큐의 길이가 1이 될 때까지.
  • 활용: 요셉푸스(Josephus) 문제와 같이 순환 구조를 가진 문제를 풀 때 유용합니다.

4. BFS (너비 우선 탐색) 기초 스킬

큐는 그래프 탐색 알고리즘인 BFS의 핵심 도구입니다. 최단 거리를 구할 때 아래와 같은 템플릿을 사용합니다.

Python
 
from collections import deque

def bfs(graph, start_node):
    visited = []
    queue = deque([start_node])
    
    while queue:
        node = queue.popleft()
        if node not in visited:
            visited.append(node)
            # 인접 노드들을 큐에 추가
            queue.extend(graph[node])
    return visited

이 코드는 그래프 탐색의 가장 기본이 되는 BFS(Breadth-First Search, 너비 우선 탐색) 알고리즘을 구현한 것입니다.

BFS는 시작 지점에서 가까운 지점부터 차례대로 멀어지며 탐색하는 방식입니다. 마치 잔잔한 호수에 돌을 던졌을 때 파동이 사방으로 퍼져나가는 모습과 비슷합니다.


1. 코드의 주요 구성 요소

  • visited (방문 목록): 이미 방문한 노드를 기록하여 **무한 루프(재방문)**에 빠지는 것을 방지합니다.
  • queue (대기열): 다음에 방문할 노드들을 순서대로 저장합니다. 먼저 들어온 노드를 먼저 처리하기 위해 deque를 사용합니다.
  • graph (인접 리스트): 각 노드가 어떤 노드들과 연결되어 있는지를 나타내는 딕셔너리 형태의 데이터 구조입니다.

2. BFS의 작동 원리 (진행 단계)

  1. 시작: start_node를 큐에 넣고 시작합니다.
  2. 추출: 큐의 맨 앞 노드를 꺼냅니다(popleft).
  3. 검사: 꺼낸 노드가 이미 방문한 곳인지 확인합니다.
  4. 방문 처리: 방문하지 않았다면 visited에 추가하고, 그 노드와 직접 연결된 모든 이웃 노드를 큐의 뒤에 추가합니다(extend).
  5. 반복: 큐가 빌 때까지 2~4번 과정을 반복합니다.

3. 쉬운 비유: SNS 친구 찾기

BFS는 내 인맥을 넓혀가는 과정과 똑같습니다.

  1. 나(시작 노드)를 큐에 넣습니다.
  2. 나를 꺼내고, 나의 **직접적인 친구들(1촌)**을 모두 큐에 넣습니다.
  3. 그다음, 1촌 친구들을 한 명씩 꺼내어 그들의 친구들(나에겐 2촌)을 큐에 넣습니다.
  4. 결과적으로 나 -> 1촌들 -> 2촌들 -> 3촌들 순서로 탐색하게 됩니다.

4. 이 코드의 특징과 주의점

  • 최단 경로: BFS는 가중치가 없는 그래프에서 시작점에서 특정 노드까지의 최단 거리를 구할 때 주로 사용됩니다. (가까운 곳부터 먼저 가기 때문입니다.)
  • 효율성: 코드 내의 if node not in visited 부분은 리스트에서 검색하므로 데이터가 많아지면 느려질 수 있습니다. 성능을 높이려면 visited를 set 자료형으로 쓰는 것이 좋습니다.

5. 한눈에 보는 실행 예시

graph = {'A': ['B', 'C'], 'B': ['D'], 'C': ['E'], 'D': [], 'E': []} 일 때:

  1. queue: [A]
  2. A 꺼냄 -> visited: [A], queue: [B, C]
  3. B 꺼냄 -> visited: [A, B], queue: [C, D]
  4. C 꺼냄 -> visited: [A, B, C], queue: [D, E]
  5. D, E 차례로 꺼내며 종료.

결과: [A, B, C, D, E] (층별로 탐색 완료)


5. 큐 활용 팁 & 주의사항

  1. list.pop(0)은 절대 금지: 데이터 개수가 많아지면 연산 속도가 기하급수적으로 느려집니다. 반드시 from collections import deque를 외우세요.
  2. rotate() 메소드 활용: deque에는 queue.rotate(-1) 같은 함수가 있어, 큐를 왼쪽이나 오른쪽으로 회전시킬 때 매우 유용합니다. (맨 앞을 뒤로 보낼 때 popleft 후 append 대신 사용 가능)
  3. 최대 길이 제한: deque(maxlen=3)처럼 최대 길이를 정해두면, 꽉 찼을 때 새 데이터를 넣으면 자동으로 가장 오래된 데이터가 삭제됩니다. (슬라이딩 윈도우 유형에 유용)

6. 실전 연습 문제 추천 (프로그래머스)

  1. Level 2: 프로세스 (가장 전형적인 큐 문제)
  2. Level 2: 다리를 지나는 트럭 (큐를 이용한 구현/시뮬레이션)
  3. Level 2: 기능개발 (작업 완료 순서 계산)
  4. Level 1: 카드 뭉치 (기초적인 큐 원리 활용)

큐는 스택보다 "시뮬레이션" 성격의 문제에 많이 쓰이므로, 문제를 읽고 **"선입선출(First In, First Out)"**이 필요한지 판단하는 연습이 중요합니다!

'프로그래머스 > 스택,큐' 카테고리의 다른 글

주식 가격  (1) 2026.03.16
스택 문제 풀이 기술  (0) 2026.01.07