[Java] ArrayDeque
정의
java.util.ArrayDeque<E> 는 원형 배열 (circular buffer) 기반의 Deque 구현. JDK 1.6 도입.
Stack 으로 쓰면 java.util.Stack 보다 빠르고, Queue 로 쓰면 LinkedList 보다 빠르다. Stack / Queue / Deque 가 필요할 때 거의 항상 첫 번째 선택.
사용 상황
| 상황 | 권장 |
|---|---|
| LIFO 스택 (재귀 대신 반복 DFS) | push / pop |
| FIFO 큐 (BFS, 작업 처리) | offer / poll |
| 양방향 삽입/삭제 (슬라이딩 윈도우) | offerFirst / pollLast |
| 괄호/수식 파싱 | 스택으로 활용 |
| 단조 덱 (Monotone Deque) | 슬라이딩 윈도우 최댓값 |
null 이 필요하거나 인덱스 기반 접근이 필요하면 LinkedList 또는 ArrayList.
시각화
원형 배열 동작
flowchart LR
INIT["초기: head=0, tail=0"]
ADD["addLast(a,b,c): tail=3"]
FIRST["addFirst(x): head=7, tail=3"]
GROW["grow: 용량 초과 시 2배 확장 + 복사"]
INIT --> ADD --> FIRST
FIRST -->|"가득 찼을 때"| GROW
head 와 tail 은 같은 방향으로 이동하되, 끝에 도달하면 0 으로 되돌아온다.
인덱스 계산: (head - 1 + length) % length (addFirst), tail % length (addLast).
내부 구조
public class ArrayDeque<E> extends AbstractCollection<E>
implements Deque<E>, Cloneable, Serializable {
transient Object[] elements; // 백킹 배열
transient int head; // head 인덱스
transient int tail; // tail 인덱스 (next slot)
}
초기 (size 8): [_ _ _ _ _ _ _ _]
↑head=tail=0
addLast: a, b, c
[a b c _ _ _ _ _]
↑head ↑tail=3
addFirst: x
[a b c _ _ _ _ x]
↑head=7 tail=3
수정된 인덱스 = (i + length) % length
가득 차면 배열 크기를 2 배로 grow 하고 원소를 새 배열로 복사.
복잡도
| 작업 | 시간 |
|---|---|
addFirst, addLast | amortized O(1) |
removeFirst, removeLast | O(1) |
peek*, getFirst, getLast | O(1) |
contains, remove(Object) | O(n) |
| 임의 인덱스 접근 | 불가 (List 가 아니므로) |
amortized 인 이유는 가득 찼을 때 2 배 grow + copy 비용 때문. 평균은 O(1).
LinkedList vs ArrayDeque
| 항목 | LinkedList | ArrayDeque |
|---|---|---|
| 백킹 | linked nodes | 원형 배열 |
| 메모리 / 원소 | 노드 헤더 + 두 포인터 (~32B) | 슬롯 1 개 (4-8B) |
| 캐시 친화 | ✗ | ✓ |
| addFirst / addLast | O(1) | amortized O(1) |
| null 허용 | ✓ | ✗ |
| List 가능 | ✓ (인덱스 O(n)) | ✗ |
| Deque 가능 | ✓ | ✓ |
| 실측 속도 | 느림 | 빠름 |
null 거부와 List 미구현이 단점이지만, 그 외에는 모든 면에서 LinkedList 보다 우월.
Stack 사용 예
Deque<Integer> stack = new ArrayDeque<>();
stack.push(1); // head 쪽에 push
stack.push(2);
stack.push(3);
stack.peek(); // 3
stack.pop(); // 3
// DFS: 재귀 대신 반복으로
Deque<Integer> dfs = new ArrayDeque<>();
dfs.push(startNode);
while (!dfs.isEmpty()) {
int node = dfs.pop();
for (int next : graph[node]) {
if (!visited[next]) {
visited[next] = true;
dfs.push(next);
}
}
}
Deque<E> stack = new ArrayDeque<>() 패턴이 Vector 기반 Stack<E> 보다 두세 배 빠르다.
Queue 사용 예 (BFS)
Queue<Integer> bfs = new ArrayDeque<>();
bfs.offer(startNode);
visited[startNode] = true;
while (!bfs.isEmpty()) {
int node = bfs.poll();
for (int next : graph[node]) {
if (!visited[next]) {
visited[next] = true;
bfs.offer(next);
}
}
}
Deque 사용 예: 슬라이딩 윈도우 최댓값
// 크기 k 의 슬라이딩 윈도우에서 최댓값 배열 구하기 (단조 감소 덱)
int[] nums = {1, 3, -1, -3, 5, 3, 6, 7};
int k = 3;
int[] result = new int[nums.length - k + 1];
Deque<Integer> dq = new ArrayDeque<>(); // 인덱스 저장 (단조 감소)
for (int i = 0; i < nums.length; i++) {
// 윈도우 범위 벗어난 front 제거
if (!dq.isEmpty() && dq.peekFirst() < i - k + 1) {
dq.pollFirst();
}
// 현재 원소보다 작은 rear 제거 (단조 유지)
while (!dq.isEmpty() && nums[dq.peekLast()] <= nums[i]) {
dq.pollLast();
}
dq.offerLast(i);
if (i >= k - 1) {
result[i - k + 1] = nums[dq.peekFirst()];
}
}
// result: [3, 3, 5, 5, 6, 7] O(n) 시간
Deque 인터페이스 주요 API
| 조작 | head (앞) | tail (뒤) |
|---|---|---|
| 추가 | addFirst(e) / offerFirst(e) | addLast(e) / offerLast(e) |
| 제거 (예외) | removeFirst() | removeLast() |
| 제거 (null) | pollFirst() | pollLast() |
| 조회 (예외) | getFirst() | getLast() |
| 조회 (null) | peekFirst() | peekLast() |
스택 메서드 push / pop / peek 은 각각 addFirst / removeFirst / peekFirst 의 alias.
큐 메서드 offer / poll / peek 은 addLast / removeFirst / peekFirst 의 alias.
괄호 검사 (스택 활용 예)
boolean isBalanced(String s) {
Deque<Character> stack = new ArrayDeque<>();
for (char c : s.toCharArray()) {
if (c == '(' || c == '[' || c == '{') {
stack.push(c);
} else if (c == ')' || c == ']' || c == '}') {
if (stack.isEmpty()) return false;
char top = stack.pop();
if ((c == ')' && top != '(') ||
(c == ']' && top != '[') ||
(c == '}' && top != '{')) return false;
}
}
return stack.isEmpty();
}
함정
1. null 비허용
Deque<String> dq = new ArrayDeque<>();
dq.add(null); // NullPointerException
이유: poll / peek 이 null 을 “비어 있음” 신호로 쓰는 의미와 충돌.
2. thread-safe 가 아님
ConcurrentLinkedDeque 또는 LinkedBlockingDeque 사용.
3. iterator 는 fail-fast iterator
순회 중 수정 시 CME.
4. push/pop vs offer/poll 혼용 주의
Deque<Integer> dq = new ArrayDeque<>();
dq.push(1); dq.push(2); dq.push(3); // 스택: [3, 2, 1] (head 쪽)
dq.poll(); // 3 (스택 top)
dq.offer(4); // tail 쪽에 추가: [2, 1, 4]
WARNING
push 는 head 쪽에 넣고 (addFirst), offer 는 tail 쪽에 넣는다 (addLast). 스택과 큐를 혼용하면 원소 순서가 뒤섞인다. 한 Deque 를 스택 또는 큐 하나의 역할 로만 쓴다.
참고
이 글의 용어 (7개)
- [Java] ArrayListjava
- 정의 는 동적 배열 (dynamic array) 로 구현된 . 내부적으로 를 들고 다니며, 용량이 부족해지면 새 배열로 옮긴다. Java 의 가장 흔한 컬렉션 구현체. 인덱스 기…
- [Java] ConcurrentLinkedDequejava
- 정의 는 lock-free . 의 양방향 버전. JDK 1.7 추가. 양 끝 모두에서 add/remove 가 가능하며, 모든 연산이 CAS (Compare-And-Swap) 기반…
- [Java] Fail-Fast Iteratorjava
- 정의 Fail-Fast Iterator 는 순회 도중 컬렉션의 구조가 변경되면 즉시 을 던지는 iterator. , , 등 대부분의 컬렉션의 iterator 가 이 방식. 목적은…
- [Java] LinkedListjava
- 정의 는 양방향 연결 리스트 (doubly linked list) 로 구현된 이자 . 각 원소는 와 포인터를 가진 노드로 보관된다. 와 같은 List 이지만, 메모리 레이아웃과 …
- 덱 (Deque)algorithm
- 정의 Deque (덱, double-ended queue) 는 양쪽 끝에서 O(1) push/pop 이 가능한 선형 자료구조. 큐 + 스택의 일반화. 내부 구현은 대개 chunk…
- 큐 (Queue)algorithm
- 정의 큐 (Queue) 는 FIFO (First In, First Out) 순서로 원소를 관리하는 추상 자료구조입니다. (또는 ), (또는 ), 세 연산만 제공하며, 가장 먼저 …
- Geometry Basic: 벡터, CCW, 외적, 내적algorithm
- 정의 기하 알고리즘의 밑바탕. 대부분의 2D/3D 기하 문제는 벡터 연산, 내적/외적, CCW 판정 세 가지의 조합으로 풀립니다. 문제 상황 PS 기하에서 자주 마주치는 질문들:…
💬 댓글