HOWTO · Python
Python의 대기열 구현
이 기사에서는 Python의 큐 데이터 구조에 대한 세 가지 구현에 대해 설명합니다. 또한 최상의 대기열 구현과 Python 프로그램에서 사용해야 하는 구현에 대해서도 설명합니다.
이 페이지의 내용
큐는 항목을 선입선출(FIFO) 순서로 저장합니다. 새 항목은 뒤에 넣고, 앞에 있는 항목부터 꺼냅니다. Python에서는 이 동작을 여러 방식으로 구현할 수 있습니다. 이 가이드에서는 리스트와 연결 리스트로 큐를 구현한 다음, 표준 라이브러리의 collections.deque를 사용하는 방법을 살펴봅니다.
큐 동작 방식과 기본 연산
앞은 다음에 나갈 항목이 있는 곳이고, 뒤는 새 항목이 들어오는 곳입니다. 예를 들어 큐에 1, 2, 3이 있으면 1이 앞에, 3이 뒤에 있습니다. 4를 넣으면 큐는 1, 2, 3, 4가 됩니다. 한 번 꺼내면 1이 반환되고 2, 3, 4가 남습니다.
다음은 뒤에 항목을 넣고 앞에서 꺼내는 간단한 실행 코드입니다. 제공된 패킷에는 이 코드 조각의 실행 결과가 없으므로, 출력 결과를 제시하지 않습니다.
이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.
queue = []
queue.append(1)
queue.append(2)
queue.append(3)
queue.append(4)
removed = queue.pop(0)
print(removed)
print(queue)
유용한 큐 인터페이스에는 네 가지 연산이 있습니다. enqueue는 항목을 추가하고, dequeue는 앞의 항목을 꺼내 반환합니다. 비어 있는지 확인하는 연산은 항목이 남아 있는지 알려 주고, 길이 연산은 항목 수를 알려 줍니다. 아래 구현은 빈 큐에서 dequeue를 호출하면 (False, None)을 반환하고, 성공하면 (True, item)을 반환합니다. 성공 여부를 별도로 반환하면 실제 항목인 None과 큐가 비어 있는 경우를 구분할 수 있습니다.
Python 리스트로 큐 구현하기
리스트는 간결한 학습용 예제를 만드는 데 적합합니다. 항목은 리스트 끝에 추가하고, 첫 항목은 pop(0)으로 제거합니다. 코드가 단순하다는 장점이 있지만, 앞 항목을 제거할 때 나머지 항목이 이동하므로 큐가 커질수록 dequeue에 선형 시간이 걸립니다. 이 예제는 None을 포함한 모든 Python 객체를 지원합니다.
다음 예제를 실행해 보세요.
이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.
class ListQueue:
def __init__(self):
self.items = []
def enqueue(self, item):
self.items.append(item)
def dequeue(self):
if not self.items:
return False, None
return True, self.items.pop(0)
def is_empty(self):
return len(self.items) == 0
def __len__(self):
return len(self.items)
queue = ListQueue()
queue.enqueue("first")
queue.enqueue("second")
print(queue.dequeue())
print(queue.is_empty(), len(queue))
print(queue.dequeue())
print(queue.is_empty(), len(queue))
print(queue.dequeue())
헤드 인덱스를 사용하는 리스트는 항목을 제거할 때마다 나머지를 이동하는 일을 피할 수 있지만, 리스트를 압축할 때까지 제거된 항목을 보관합니다. 이 방식에는 논리적인 큐 위치와 리스트 인덱스를 대응시키고, 저장 공간을 안전하게 초기화하거나 압축하기 위한 추가 관리가 필요합니다. 일반적인 FIFO 용도에는 collections.deque가 대체로 더 명확한 선택입니다.
연결 리스트로 큐 구현하기
단일 연결 큐는 각 항목을 노드에 저장하며, 노드의 next 참조는 뒤쪽을 향합니다. 앞과 뒤 양쪽의 참조를 유지하세요. enqueue는 rear 뒤에 새 노드를 연결하고, dequeue는 front를 다음 노드로 옮깁니다. 마지막 노드가 빠져나가면 rear도 비워야 합니다. 그러지 않으면 다음 enqueue에서 더 이상 유효하지 않은 노드에 항목을 연결하려 할 수 있습니다. 이 구현에서 enqueue와 dequeue는 상수 시간이 걸리고, 길이를 구할 때는 노드를 세므로 선형 시간이 걸립니다.
이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.
class Node:
def __init__(self, value):
self.value = value
self.next = None
class LinkedQueue:
def __init__(self):
self.front = None
self.rear = None
def enqueue(self, item):
node = Node(item)
if self.rear is None:
self.front = self.rear = node
else:
self.rear.next = node
self.rear = node
def dequeue(self):
if self.front is None:
return False, None
item = self.front.value
self.front = self.front.next
if self.front is None:
self.rear = None
return True, item
def is_empty(self):
return self.front is None
def __len__(self):
count = 0
current = self.front
while current is not None:
count += 1
current = current.next
return count
queue = LinkedQueue()
queue.enqueue(10)
queue.enqueue(20)
print(queue.dequeue())
print(queue.is_empty(), len(queue))
print(queue.dequeue())
print(queue.is_empty(), len(queue))
print(queue.dequeue())
불변 조건은 큐가 비어 있을 때에만 두 참조가 모두 None이라는 것입니다. 큐에 항목이 있으면 front는 다음에 제거할 항목을 가리키고 rear.next는 None입니다. 이 조건을 유지하면 항목이 하나만 남은 경우와 큐가 비워진 뒤 다시 사용하는 경우를 모두 처리할 수 있습니다.
큐에 collections.deque 사용하기
일반적인 FIFO 작업에는 Python의 collections.deque가 실용적인 기본 선택입니다. 오른쪽 끝에 추가하고 popleft()로 왼쪽 끝에서 제거합니다. 양 끝 연산 모두 효율적으로 수행되도록 설계되어 있으며, len()과 참·거짓 확인을 사용하면 나머지 인터페이스도 간결하게 만들 수 있습니다. 빈 deque에서 popleft()를 호출하면 IndexError가 발생하므로, 이 래퍼는 먼저 큐가 비었는지 확인하고 다른 예제와 같은 성공 여부 값을 반환합니다.
이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.
from collections import deque
class DequeQueue:
def __init__(self):
self.items = deque()
def enqueue(self, item):
self.items.append(item)
def dequeue(self):
if not self.items:
return False, None
return True, self.items.popleft()
def is_empty(self):
return not self.items
def __len__(self):
return len(self.items)
queue = DequeQueue()
queue.enqueue("first")
queue.enqueue("second")
print(queue.dequeue())
print(queue.is_empty(), len(queue))
print(queue.dequeue())
print(queue.is_empty(), len(queue))
print(queue.dequeue())
deque는 collections 모듈이 제공하는 클래스입니다. 여기서는 공개 인터페이스만 사용하면 됩니다. 내부 표현에 관한 가정에 의존하지 마세요. 용량을 제한하거나 생산자 스레드와 소비자 스레드 사이의 조정이 필요하다면, Python의 queue 모듈에 있는 용도별 큐 클래스를 고려하세요.
구현 방식 비교
| 구현 | Enqueue | Dequeue | 길이 | 실용적인 장단점 |
|---|---|---|---|---|
append()와 pop(0)을 사용하는 리스트 |
분할 상환 O(1) | O(n) | O(1) | 작고 이해하기 쉽지만, 앞에서 제거할 때 뒤 항목들이 이동합니다. |
| 단일 연결 리스트 | O(1) | O(1) | 이 예제에서는 O(n) | 포인터 동작을 보여 주지만 노드와 포인터를 추가로 관리해야 합니다. |
collections.deque |
O(1) | O(1) | O(1) | 간결한 FIFO 연산을 제공하는 표준 라이브러리 선택지입니다. |
연결 리스트의 시간 비용은 큐가 front와 rear를 모두 저장한다고 가정합니다. 길이 정보를 따로 저장하지 않으므로 길이를 셀 때는 각 노드를 방문해야 합니다. 별도 카운터를 두면 길이를 상수 시간에 구할 수 있지만, 모든 연산에서 갱신해야 하는 불변 조건이 하나 더 생깁니다. 대부분의 프로그램에서는 collections.deque를 선택하세요. 큐가 아주 작거나 앞에서 항목을 반복해 제거하지 않는다면 리스트를 사용하고, 자료 구조를 학습하거나 노드 단위의 동작이 특별히 필요한 경우에는 연결 리스트를 구현해 보세요.