Queue & Stack
큐 (Queue)
큐는 먼저 저장한 데이터가 먼저 출력되는 선입선출 (First In First Out) 형식으로 데이터를 저장하는 자료구조이다. 큐는 줄 서기와 비슷한 개념으로, 은행에서 고객이 줄을 서서 기다리면, 먼저 온 고객이 서비스를 받게 되는 맥락과 같은 개념이다.
주요 연산
- Enqueue (인큐, 삽입)
- 큐에 데이터를 추가하는 작업. 새로운 데이터가 큐의 뒤쪽에 추가된다.
- 시간복잡도: O(1)
- Dequeue (디큐, 삭제)
- 큐에서 데이터를 제거하는 작업. 가장 앞에 있는 데이터가 제거된다.
- 시간복잡도: O(1) / Array로 구현시 O(N)
- Peek (피크, 조회)
- 큐의 가장 앞에 있는 데이터를 조회하는 작업. 데이터를 제거하지는 않는다.
- 시간복잡도: O(1)
- isEmpty (비어있는지 확인)
- 큐가 비어있는지 여부를 확인하는 작업.
- 시간복잡도: O(1)
특징
- 장점
- 데이터의 삽입, 삭제가 빠르다.
- 데이터를 입력 순서에 따라 처리해야 하는 작업에 적합하다.
- 단점
- 많은 큐 구현에서는 초기화 시 고정된 크기를 설정해야 한다. 큐가 가득 차면 더 이상 데이터를 삽입할 수 없기 때문에, 이를 해결하기 위해서는 큐의 크기를 늘리거나 데이터를 제거해야 한다. 크기를 동적으로 조정하는 큐(예: Python의
deque
)도 있지만, 이러한 경우에도 크기 변경 시 성능 저하가 발생할 수 있다. - 순차적으로 데이터를 삽입하고 삭제하는 데 적합하지만, 특정 위치의 데이터를 직접 접근하거나 수정하는 작업은 비효율적이다.
- 많은 큐 구현에서는 초기화 시 고정된 크기를 설정해야 한다. 큐가 가득 차면 더 이상 데이터를 삽입할 수 없기 때문에, 이를 해결하기 위해서는 큐의 크기를 늘리거나 데이터를 제거해야 한다. 크기를 동적으로 조정하는 큐(예: Python의
- 사용 사례
- 작업 대기열
- 여러 작업이 들어오면 가장 먼저 들어온 작업이 가장 먼저 수행되어야 하는 상황에서 큐를 활용할 수 있다.
- 너비 우선 탐색(BFS)
- 그래프에서 너비 우선 탐색을 수행할 때 큐를 사용하여 노드를 탐색 순서대로 저장하고 탐색할 수 있다.
- 캐시(Cache) 구현
- 캐시에 데이터를 저장하고 가져올 때에도 큐를 사용하여 가장 최근에 사용된 데이터를 저장하고 오래된 데이터를 제거할 수 있다.
- 작업 대기열
구현
from collections import deque
# Queue 생성
queue = deque()
# Queue에 데이터 삽입
queue.append('A')
queue.append('B')
queue.append('C')
# Queue에서 데이터 제거 (선입선출)
first_element = queue.popleft()
print(f'Removed Element: {first_element}') # 출력: Removed Element: A
# Queue 상태 출력
print(f'Queue: {list(queue)}') # 출력: Queue: ['B', 'C']
deque
는 Python의collections
모듈에서 제공하는 양방향 큐로, 큐의 구현에 적합하다.append()
메서드로 데이터를 추가하고,popleft()
메서드로 데이터를 제거할 수 있다.
시간복잡도
연산 | 시간 복잡도 |
---|---|
삽입(Enqueue) | O(1) |
삭제(Dequeue) | O(1) |
접근(Acess) | O(n) |
탐색(Search) | O(n) |
스택 (Stack)
스택은 시간 순서상 가장 최근에 추가한 데이터가 가장 먼저 나오는 후입선출 LIFO(Last In First Out) 형식으로 데이터를 저장하는 자료구조이다.
주요 연산
- Push (푸시)
- 스택에 데이터를 추가하는 연산. 새로운 데이터가 스택의 맨 위에 쌓이게 된다.
- 시간복잡도: O(1)
- Pop (팝)
- 스택에서 가장 위에 있는 데이터를 제거하는 연산. 가장 최근에 추가된 데이터가 제거된다.
- 시간복잡도: O(1)
- Top (탑)
- 스택의 가장 위에 있는 데이터를 조회하는 연산이지만, 제거하지는 않는다.
- 시간복잡도: O(1)
특징
- 장점
- 데이터의 삽입, 삭제가 빠르다.
- 후입선출 방식으로 최근에 추가된 데이터에 쉽게 접근할 수 있다.
- 단점
- 고정 크기 스택에서는 크기를 초과하는 데이터를 삽입하려고 할 때 스택 오버플로우(Overflow)가 발생할 수 있다. 동적 크기의 스택도 크기 증가 시 메모리 재할당이 필요하여 성능이 저하될 수 있다.
- 스택은 후입선출(LIFO) 원칙에 따라 동작하기 때문에, 스택에 저장된 데이터 중간에 접근하거나 수정하는 것이 불가능하다.
- 사용 사례
- 함수 호출과 반환
- 함수가 호출될 때마다 호출 스택에 현재 상태를 저장하고, 함수가 반환되면 해당 상태를 스택에서 제거하는데 스택이 사용된다.
- 뒤로 가기 (Undo) 기능
- 편집기나 그래픽 소프트웨어에서 사용자의 작업을 스택에 기록하여 되돌리기(undo)를 구현할 때 활용된다.
- 깊이 우선 탐색
- DFS(깊이 우선 탐색) 알고리즘에서 스택을 활용하여 재귀 호출을 대체할 수 있다.
- 역순 출력
- 데이터를 역순으로 출력하거나 처리해야 하는 경우 스택을 사용할 수 있다.
- 함수 호출과 반환
구현
# Stack 생성
stack = []
# Stack에 데이터 삽입
stack.append('A')
stack.append('B')
stack.append('C')
# Stack에서 데이터 제거 (후입선출)
last_element = stack.pop()
print(f'Removed Element: {last_element}') # 출력: Removed Element: C
# Stack 상태 출력
print(f'Stack: {stack}') # 출력: Stack: ['A', 'B']
- 스택은 파이썬의 리스트를 사용하여 간단히 구현할 수 있다.
append()
메서드로 데이터를 추가하고,pop()
메서드로 데이터를 제거한다.
시간복잡도
연산 | 시간 복잡도 |
---|---|
삽입(Push) | O(1) |
삭제(Pop) | O(1) |
접근(Access) | O(n) |
탐색(Search) | O(n) |