⚡ 한 줄 요약: 데이터의 출입 순서와 방향에 따라 달라지는 세 가지 핵심 자료구조의 특성을 이해하고, 모든 연산은 로 처리하는 최적의 구현 방법을 배웁니다.
자료구조의 핵심은 데이터를 단순히 저장하는 것을 넘어, 필요한 시점에 어떤 순서로 꺼내 쓸 것인가에 있습니다.
뒤로 가기를 눌렀을 때 직전 페이지가 나와야 하는 상황과, 프린터에 보낸 문서가 순서대로 출력되어야 하는 상황은 전혀 다른 논리를 필요로 합니다.
🧐 Why:
🎯 Goal:
스택은 데이터를 한쪽 끝에서만 넣고 뺄 수 있는 제한적인 구조를 가집니다.
이 '제한적'이라는 특성이 오히려 특정 상황에서는 강력한 무기가 됩니다.
동작 원리
활용 사례
스택의 모든 주요 연산은 맨 위의 데이터만 다루기 때문에 매우 빠릅니다.
| 연산 | 설명 | 시간 복잡도 |
|---|---|---|
push(e) | 스택의 맨 위에 요소를 추가합니다. | |
pop() | 스택의 맨 위 요소를 제거하고 반환합니다. | |
peek() | 스택의 맨 위 요소를 확인만 합니다. | |
isEmpty() | 스택이 비어 있는지 확인합니다. |
스택은 배열로도 구현할 수 있지만, 메모리를 효율적으로 쓰기 위해 연결 리스트를 활용하는 경우가 많습니다.
last라는 포인터가 항상 스택의 맨 위(최신 데이터)를 가리키게 설계합니다.
class Node:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Stack:
def __init__(self):
self.last = None
def push(self, val):
# 새로운 노드를 만들고 기존의 last를 next로 연결합니다.
self.last = Node(val, self.last)
def pop(self):
if not self.last: return "Stack is Empty"
popped = self.last.val
self.last = self.last.next # 포인터를 이전 노드로 옮깁니다.
return popped
스택에서 중간 데이터를 꺼낼 수 있나요?
pop해야 합니다.Stack Overflow
push를 시도할 때 발생하는 에러입니다.
큐의 핵심은 '공정함'입니다. 데이터가 들어오는 통로와 나가는 통로가 분리되어 있어, 먼저 들어온 데이터가 대기열의 맨 앞에서 먼저 처리되는 구조를 가집니다.
동작 원리
활용 사례
큐 역시 스택과 마찬가지로 양 끝단의 데이터만 다루기 때문에 매우 효율적입니다.
| 연산 | 설명 | 시간 복잡도 |
|---|---|---|
enqueue(e) | 큐의 맨 뒤에 요소를 추가합니다. | |
dequeue() | 큐의 맨 앞 요소를 제거하고 반환합니다. | |
peek() | 큐의 맨 앞 요소를 확인만 합니다 (제거 X) | |
isEmpty() | 큐가 비어 있는지 확인합니다. |
큐를 배열로 구현하면 데이터를 뺄 때마다 뒤의 요소들을 앞으로 당겨야 해서 의 비용이 들 수 있습니다.
그래서 실무와 면접에서는 연결 리스트를 활용해 양방향 접근을 최적화하는 방식을 선호합니다.
class Node:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Queue:
def __init__(self):
self.head = None # 맨 앞(Front)
self.last = None # 맨 뒤(Back)
def enqueue(self, val):
new = Node(val)
if not self.head: # 비어있는 상태라면
self.head = new
self.last = new
else:
self.last.next = new
self.last = self.last.next # last 포인터 이동
def dequeue(self):
if not self.head: return "Queue is Empty"
popped = self.head.val
self.head = self.head.next # head 포인터를 다음 노드로 이동
if not self.head: # 마지막 노드까지 뺐다면
self.last = None
return popped
list를 사용해 list.pop(0)으로 큐를 구현하면, 첫 번째 요소를 뺀 뒤 모든 요소를 앞으로 한 칸씩 옮겨야 하므로 이 걸립니다.collections.deque)를 제안하는 것이 좋습니다.큐는 FIFO 원칙을 지키며 대기열을 관리하는 자료구조로, 모든 핵심 연산은 로 처리됩니다.
덱(Double-ended Queue)은 이름 그대로 '양방향' 대기열입니다.
데이터가 들어오고 나가는 입구가 하나였던 스택, 입구와 출구가 정해져 있던 큐와 달리, 덱은 앞(Front)과 뒤(Rear) 어디서든 요소를 넣고 뺄 수 있습니다.
스택 + 큐의 결합
압도적인 속도
| 연산 | 설명 | 시간 복잡도 |
|---|---|---|
append(e) | 덱의 맨 뒤에 요소 추가 | |
appendleft(e) | 덱의 맨 앞에 요소 추가 | |
pop() | 덱의 맨 뒤 요소 제거 및 반환 | |
popleft() | 덱의 맨 앞 요소 제거 및 반환 | |
isEmpty() | 덱이 비어 있는지 확인 |
파이썬에서는 collections.deque 모듈을 사용해 deque를 효율적으로 구현할 수 있습니다.
from collections import deque
dq = deque()
# 뒤에서 추가 (append)
dq.append(10)
dq.append(20)
# 앞에서 추가 (appendleft)
dq.appendleft(5)
dq.appendleft(1)
print(dq) # deque([1, 5, 10, 20])
# 뒤에서 제거 (pop)
print(dq.pop()) # 30 제거
# 앞에서 제거 (popleft)
print(dq.popleft()) # 1 제거
pop(0)은 첫 번째 요소를 뺀 후 모든 데이터를 앞으로 한 칸씩 당겨야 해서 이 걸립니다.popleft()는 데이터 이동 없이 포인터만 조작하므로 데이터가 백만 개라 하더라도 즉시 처리됩니다.덱은 양방향 연산을 보장하는 다재다능한 자료구조로, 성능 최적화가 필요한 대기열 관리의 핵심 도구입니다.
| 특성 | 스택(Stack) | 큐(Queue) | 덱(Deque) |
|---|---|---|---|
| 관리 원칙 | LIFO (후입선출) | FIFO (선입선출) | 양방향 자유 출입 |
| 주요 연산 | push, pop | enqueue, dequeue | append, appendleft 등 |
| 시간 복잡도 | 전 연산 | 전 연산 | 전 연산 |
| 실무 예시 | 뒤로 가기, Undo/Redo, 콜 스택 | 프린터 대기열, 메시지 큐 | 양방향 스케줄링, 슬라이딩 윈도우 |
| 핵심 포인트 | 중간 접근 불가 | 배열 구현 시 주의 | 스택과 큐의 장점 결합 |