Stack, Queue, Deque는 데이터의 삽입과 삭제 순서가 정해진 선형 자료구조이다. 각각의 특성을 이해하고 적절히 활용하는 것이 중요하다.
나중에 들어간 것이 먼저 나온다
삽입(push): 제거(pop):
↓ ↑
[ C ] ← top [ C ] ← top
[ B ] [ B ]
[ A ] [ A ]
------- -------
실생활 예시:
import java.util.Stack;
public class StackExample {
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
// 삽입 (push) - O(1)
stack.push(1);
stack.push(2);
stack.push(3);
System.out.println(stack); // [1, 2, 3]
// 조회 (peek) - 제거하지 않고 맨 위 확인 - O(1)
System.out.println(stack.peek()); // 3
// 제거 (pop) - O(1)
System.out.println(stack.pop()); // 3
System.out.println(stack.pop()); // 2
System.out.println(stack); // [1]
// 비어있는지 확인
System.out.println(stack.isEmpty()); // false
// 크기
System.out.println(stack.size()); // 1
// 요소 검색 (1-based index, top부터)
stack.push(10);
stack.push(20);
System.out.println(stack.search(20)); // 1 (top)
System.out.println(stack.search(10)); // 2
System.out.println(stack.search(1)); // 3
}
}
| 메서드 | 설명 | 시간복잡도 |
|---|---|---|
push(E item) |
요소 삽입 | O(1) |
pop() |
맨 위 요소 제거 및 반환 | O(1) |
peek() |
맨 위 요소 조회 (제거 X) | O(1) |
isEmpty() |
비어있는지 확인 | O(1) |
search(Object o) |
요소 위치 검색 (1-based) | O(n) |
size() |
크기 반환 | O(1) |
Java의 Stack 클래스는 레거시!
// ✘ 권장하지 않음 (Vector 기반, 동기화 오버헤드)
Stack<Integer> stack = new Stack<>();
// ✔ 권장: Deque 인터페이스 사용
Deque<Integer> stack = new ArrayDeque<>();
stack.push(1);
stack.push(2);
stack.pop();
이유:
Stack은 Vector를 상속받아 불필요한 동기화 오버헤드Deque가 더 일관된 API 제공ArrayDeque가 더 좋음public boolean isValidParentheses(String s) {
Deque<Character> stack = new ArrayDeque<>();
for (char c : s.toCharArray()) {
if (c == '(' || c == '{' || c == '[') {
stack.push(c);
} else {
if (stack.isEmpty()) return false;
char top = stack.pop();
if (c == ')' && top != '(') return false;
if (c == '}' && top != '{') return false;
if (c == ']' && top != '[') return false;
}
}
return stack.isEmpty();
}
// 테스트
System.out.println(isValidParentheses("()[]{}")); // true
System.out.println(isValidParentheses("([)]")); // false
System.out.println(isValidParentheses("{[]}")); // true
public int evalRPN(String[] tokens) {
Deque<Integer> stack = new ArrayDeque<>();
for (String token : tokens) {
if (token.equals("+")) {
stack.push(stack.pop() + stack.pop());
} else if (token.equals("-")) {
int b = stack.pop();
int a = stack.pop();
stack.push(a - b);
} else if (token.equals("*")) {
stack.push(stack.pop() * stack.pop());
} else if (token.equals("/")) {
int b = stack.pop();
int a = stack.pop();
stack.push(a / b);
} else {
stack.push(Integer.parseInt(token));
}
}
return stack.pop();
}
// 테스트: ["2", "1", "+", "3", "*"] → (2 + 1) * 3 = 9
String[] tokens = {"2", "1", "+", "3", "*"};
System.out.println(evalRPN(tokens)); // 9
public int[] dailyTemperatures(int[] temperatures) {
int n = temperatures.length;
int[] answer = new int[n];
Deque<Integer> stack = new ArrayDeque<>(); // 인덱스 저장
for (int i = 0; i < n; i++) {
// 현재 온도가 스택의 온도보다 높으면
while (!stack.isEmpty() &&
temperatures[i] > temperatures[stack.peek()]) {
int idx = stack.pop();
answer[idx] = i - idx; // 날짜 차이
}
stack.push(i);
}
return answer;
}
// 테스트
int[] temps = {73, 74, 75, 71, 69, 72, 76, 73};
int[] result = dailyTemperatures(temps);
// [1, 1, 4, 2, 1, 1, 0, 0]
먼저 들어간 것이 먼저 나온다
삽입(offer): 제거(poll):
↓ ↑
front → [A][B][C] ← rear front → [A][B][C] ← rear
실생활 예시:
import java.util.*;
public class QueueExample {
public static void main(String[] args) {
// LinkedList로 구현
Queue<Integer> queue = new LinkedList<>();
// 삽입 (offer) - O(1)
queue.offer(1);
queue.offer(2);
queue.offer(3);
System.out.println(queue); // [1, 2, 3]
// 조회 (peek) - 제거하지 않고 맨 앞 확인 - O(1)
System.out.println(queue.peek()); // 1
// 제거 (poll) - O(1)
System.out.println(queue.poll()); // 1
System.out.println(queue.poll()); // 2
System.out.println(queue); // [3]
// 비어있는지 확인
System.out.println(queue.isEmpty()); // false
// 크기
System.out.println(queue.size()); // 1
}
}
| 메서드 | 설명 | 예외 발생 | 시간복잡도 |
|---|---|---|---|
| offer(E e) | 요소 삽입 | false 반환 | O(1) |
| add(E e) | 요소 삽입 | 예외 발생 | O(1) |
| poll() | 맨 앞 요소 제거 및 반환 | null 반환 | O(1) |
| remove() | 맨 앞 요소 제거 및 반환 | 예외 발생 | O(1) |
| peek() | 맨 앞 요소 조회 | null 반환 | O(1) |
| element() | 맨 앞 요소 조회 | 예외 발생 | O(1) |
권장: 예외 대신 null을 반환하는 offer(), poll(), peek() 사용
// 1. LinkedList - 일반적인 Queue
Queue<Integer> queue1 = new LinkedList<>();
// 2. ArrayDeque - 더 빠름 (추천!)
Queue<Integer> queue2 = new ArrayDeque<>();
// 3. PriorityQueue - 우선순위 큐
Queue<Integer> queue3 = new PriorityQueue<>();
public void bfs(int[][] graph, int start) {
Queue<Integer> queue = new LinkedList<>();
boolean[] visited = new boolean[graph.length];
queue.offer(start);
visited[start] = true;
while (!queue.isEmpty()) {
int node = queue.poll();
System.out.print(node + " ");
for (int neighbor : graph[node]) {
if (!visited[neighbor]) {
queue.offer(neighbor);
visited[neighbor] = true;
}
}
}
}
class RecentCounter {
private Queue<Integer> queue;
public RecentCounter() {
queue = new LinkedList<>();
}
public int ping(int t) {
queue.offer(t);
// 3000ms 이전 요청은 제거
while (queue.peek() < t - 3000) {
queue.poll();
}
return queue.size();
}
}
// 사용
RecentCounter counter = new RecentCounter();
System.out.println(counter.ping(1)); // 1
System.out.println(counter.ping(100)); // 2
System.out.println(counter.ping(3001)); // 3
System.out.println(counter.ping(3002)); // 3
class MyStack {
private Queue<Integer> queue;
public MyStack() {
queue = new LinkedList<>();
}
public void push(int x) {
queue.offer(x);
// 새로 추가한 요소를 맨 앞으로
int size = queue.size();
for (int i = 1; i < size; i++) {
queue.offer(queue.poll());
}
}
public int pop() {
return queue.poll();
}
public int top() {
return queue.peek();
}
public boolean empty() {
return queue.isEmpty();
}
}
addFirst() addLast()
↓ ↓
front → [A][B][C][D] ← rear
↑ ↑
removeFirst() removeLast()
특징:
import java.util.*;
public class DequeExample {
public static void main(String[] args) {
Deque<Integer> deque = new ArrayDeque<>();
// 앞에 추가
deque.addFirst(1);
deque.addFirst(2);
System.out.println(deque); // [2, 1]
// 뒤에 추가
deque.addLast(3);
deque.addLast(4);
System.out.println(deque); // [2, 1, 3, 4]
// 앞에서 제거
System.out.println(deque.removeFirst()); // 2
// 뒤에서 제거
System.out.println(deque.removeLast()); // 4
System.out.println(deque); // [1, 3]
// 조회
System.out.println(deque.peekFirst()); // 1
System.out.println(deque.peekLast()); // 3
}
}
| 기능 | 앞(First) | 뒤(Last) |
|---|---|---|
| 삽입 | addFirst(e) / offerFirst(e) |
addLast(e) / offerLast(e) |
| 제거 | removeFirst() / pollFirst() |
removeLast() / pollLast() |
| 조회 | getFirst() / peekFirst() |
getLast() / peekLast() |
Stack으로 사용:
Deque<Integer> stack = new ArrayDeque<>();
stack.push(1); // addFirst()
stack.push(2);
stack.pop(); // removeFirst()
stack.peek(); // peekFirst()
Queue로 사용:
Deque<Integer> queue = new ArrayDeque<>();
queue.offer(1); // addLast()
queue.offer(2);
queue.poll(); // removeFirst()
queue.peek(); // peekFirst()
// 1. ArrayDeque - 배열 기반 (가장 빠름, 추천!)
Deque<Integer> deque1 = new ArrayDeque<>();
// 2. LinkedList - 연결 리스트 기반
Deque<Integer> deque2 = new LinkedList<>();
ArrayDeque vs LinkedList:
public int[] maxSlidingWindow(int[] nums, int k) {
Deque<Integer> deque = new ArrayDeque<>(); // 인덱스 저장
int[] result = new int[nums.length - k + 1];
int idx = 0;
for (int i = 0; i < nums.length; i++) {
// 윈도우 범위를 벗어난 인덱스 제거
while (!deque.isEmpty() && deque.peekFirst() < i - k + 1) {
deque.pollFirst();
}
// 현재 값보다 작은 값들 제거 (단조 감소 유지)
while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
deque.offerLast(i);
// 윈도우가 완성되면 최댓값 추가
if (i >= k - 1) {
result[idx++] = nums[deque.peekFirst()];
}
}
return result;
}
// 테스트
int[] nums = {1, 3, -1, -3, 5, 3, 6, 7};
int[] result = maxSlidingWindow(nums, 3);
// [3, 3, 5, 5, 6, 7]
public boolean isPalindrome(String s) {
Deque<Character> deque = new ArrayDeque<>();
// 알파벳과 숫자만 추가
for (char c : s.toLowerCase().toCharArray()) {
if (Character.isLetterOrDigit(c)) {
deque.offerLast(c);
}
}
// 양끝에서 비교
while (deque.size() > 1) {
if (deque.pollFirst() != deque.pollLast()) {
return false;
}
}
return true;
}
// 테스트
System.out.println(isPalindrome("A man, a plan, a canal: Panama")); // true
System.out.println(isPalindrome("race a car")); // false
class RecentItems<T> {
private Deque<T> deque;
private int maxSize;
public RecentItems(int k) {
this.deque = new ArrayDeque<>();
this.maxSize = k;
}
public void add(T item) {
if (deque.size() == maxSize) {
deque.pollFirst(); // 가장 오래된 항목 제거
}
deque.offerLast(item); // 새 항목 추가
}
public List<T> getRecent() {
return new ArrayList<>(deque);
}
}
// 사용
RecentItems<String> recent = new RecentItems<>(3);
recent.add("A");
recent.add("B");
recent.add("C");
recent.add("D"); // A 제거됨
System.out.println(recent.getRecent()); // [B, C, D]
| 연산 | Stack | Queue | Deque |
|---|---|---|---|
| 삽입 (앞) | - | - | O(1) |
| 삽입 (뒤) | O(1) | O(1) | O(1) |
| 제거 (앞) | - | O(1) | O(1) |
| 제거 (뒤) | O(1) | - | O(1) |
| 조회 (앞) | - | O(1) | O(1) |
| 조회 (뒤) | O(1) | - | O(1) |
// ArrayDeque (추천)
// 내부 배열 크기는 2의 거듭제곱으로 자동 조정
// 초기 크기: 16
Deque<Integer> deque = new ArrayDeque<>();
// 메모리: 16 * 4 bytes (int) = 64 bytes
Deque<Integer> deque2 = new ArrayDeque<>(100);
// 메모리: 128 * 4 bytes = 512 bytes (다음 2의 거듭제곱)
// LinkedList
// 각 노드: 데이터 + prev + next + 객체 오버헤드
// 약 40 bytes per element
LinkedList<Integer> list = new LinkedList<>();
// 100개 저장 시: ~4KB
| 상황 | 선택 | 이유 |
|---|---|---|
| 후입선출(LIFO) 필요 | ArrayDeque (Stack으로) |
Stack 클래스는 레거시 |
| 선입선출(FIFO) 필요 | ArrayDeque (Queue로) |
가장 빠르고 효율적 |
| 양쪽 삽입/삭제 | ArrayDeque |
Deque의 기본 용도 |
| 우선순위가 필요 | PriorityQueue |
최소/최대 힙 |
| 중간 삽입/삭제 | LinkedList |
하지만 비추천 |
| BFS 구현 | LinkedList 또는 ArrayDeque |
둘 다 OK |
| DFS 구현 | ArrayDeque (Stack으로) |
재귀 또는 명시적 스택 |
// ✘ 비추천
Stack<Integer> stack = new Stack<>();
// ✔ 추천
Deque<Integer> stack = new ArrayDeque<>();
Queue<Integer> queue = new LinkedList<>();
// 예외 발생 메서드
try {
queue.remove(); // NoSuchElementException
} catch (Exception e) {
// 처리
}
// null 반환 메서드 (추천)
Integer value = queue.poll();
if (value == null) {
// 비어있음
}
// 크기를 알고 있다면 초기 용량 지정
Deque<Integer> deque = new ArrayDeque<>(1000);
// 재할당 횟수 감소 → 성능 향상
ArrayDeque 사용 권장ArrayDeque 또는 LinkedList 사용ArrayDeque가 가장 빠름필요한 기능 → 선택할 자료구조
─────────────────────────────────────
후입선출(LIFO) → ArrayDeque (Stack으로)
선입선출(FIFO) → ArrayDeque (Queue로)
양쪽 삽입/삭제 → ArrayDeque
우선순위 → PriorityQueue
다음 상황에 적합한 자료구조를 선택하세요:
정답:
// 두 개의 Stack으로 Queue 구현하기
class MyQueue {
private Deque<Integer> stack1;
private Deque<Integer> stack2;
public MyQueue() {
stack1 = new ArrayDeque<>();
stack2 = new ArrayDeque<>();
}
public void push(int x) {
stack1.push(x);
}
public int pop() {
if (stack2.isEmpty()) {
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
public int peek() {
if (stack2.isEmpty()) {
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
return stack2.peek();
}
public boolean empty() {
return stack1.isEmpty() && stack2.isEmpty();
}
}
#Java #자료구조 #Stack #Queue #Deque #LIFO #FIFO #알고리즘 #코딩테스트
#BFS #DFS #ArrayDeque