
본 시리즈는 프로그래밍 자료구조의 개념을 정리하고 실습을 진행해보는 시리즈입니다.
Deque(Double-Ended Queue, 데크, 덱)은 양방향에서 데이터를 삽입하고 제거할 수 있는 자료구조입니다.

Deque는 양쪽 끝에서 삽입과 삭제가 가능한 구조로, 다음과 같은 주요 연산을 지원합니다:
addFirst(item): 덱의 앞쪽에 데이터를 추가합니다.addLast(item): 덱의 뒤쪽에 데이터를 추가합니다.removeFirst(): 덱의 앞쪽에서 데이터를 제거합니다.removeLast(): 덱의 뒤쪽에서 데이터를 제거합니다.peekFirst(): 덱의 앞쪽에 있는 데이터를 확인합니다.peekLast(): 덱의 뒤쪽에 있는 데이터를 확인합니다.isEmpty(): 덱이 비어 있는지 확인합니다.이처럼 Deque는 양쪽 끝에서 데이터를 처리할 수 있기 때문에, 스택이나 큐보다 유연한 자료구조로, 양방향에서 데이터를 처리해야 하는 다양한 알고리즘에서 중요한 역할을 합니다.
Deque는 다음과 같은 상황에서 매우 유용하게 사용됩니다:
이처럼 Deque는 큐와 스택의 기능을 모두 지원하면서도 더 유연한 사용이 가능하다는 점에서, 다양한 프로그래밍 문제에서 강력한 도구로 사용될 수 있습니다.

Java에서 Deque는 Queue와 Stack의 특성을 모두 가지며, 양쪽 끝에서 삽입 및 삭제가 가능한 자료구조입니다.
Deque 인터페이스를 제공하며, 이를 구현한 대표적인 클래스들이 ArrayDeque와 LinkedList입니다.Deque 인터페이스는 양방향 큐 연산을 위한 메서드들을 제공합니다.
Deque는 큐(Queue)와 스택(Stack)의 기능을 모두 구현할 수 있습니다.Queue 인터페이스를 상속 받는 인터페이스이기 때문에 Queue의 메서드들도 함께 구현되어있어서 동일한 메서드 명으로 사용 가능합니다.push()와 pop()도 구현되어 있습니다.Deque 자료구조입니다.ArrayDeque는 스레드 안전하지 않지만, 대부분의 상황에서 빠르고 효율적이기 때문에 Deque 구현체로 자주 사용됩니다.ArrayDeque는 기본적으로 큐와 스택으로 모두 사용될 수 있습니다.Deque 자료구조입니다.ArrayDeque보다 더 큽니다.ArrayDeque는 배열 기반이므로 연속된 메모리를 사용해 매우 빠른 인덱싱을 지원하며, 메모리 오버헤드가 적습니다. 하지만 배열의 크기가 가득 차면 크기 재조정이 발생할 수 있고 이때만 성능이 약간 저하됩니다.LinkedList는 동적 크기 조절이 자연스럽게 가능하지만, 각 노드마다 포인터가 필요하므로 메모리 오버헤드가 큽니다.성능 면에서는 ArrayDeque가 일반적으로 더 빠르고 효율적입니다. 그렇기 때문에, ArrayDeque는 Java에서 Deque로 구현할 때 가장 널리 사용됩니다.
1. 데이터 삽입 메서드
Deque는 데이터를 앞쪽 또는 뒤쪽에 삽입할 수 있는 다양한 메서드를 제공합니다. 이 메서드들은 큐와 스택의 모든 삽입 방식들을 지원합니다.
addFirst(E e): 덱의 앞쪽에 요소를 추가합니다. 삽입에 실패할 경우 예외를 던집니다.addLast(E e): 덱의 뒤쪽에 요소를 추가합니다. 삽입에 실패할 경우 예외를 던집니다.offerFirst(E e): 덱의 앞쪽에 요소를 삽입하고, 성공 여부를 true/false로 반환합니다. 예외를 던지지 않습니다.offerLast(E e): 덱의 뒤쪽에 요소를 삽입하고, 성공 여부를 true/false로 반환합니다. 예외를 던지지 않습니다.2. 데이터 삭제 메서드
Deque는 양방향에서 데이터를 삭제하는 여러 메서드를 제공합니다. 이를 통해 큐의 dequeue 및 스택의 pop 기능을 지원합니다.
removeFirst(): 덱의 앞쪽에서 요소를 제거하고 반환합니다. 제거할 요소가 없으면 예외를 던집니다.removeLast(): 덱의 뒤쪽에서 요소를 제거하고 반환합니다. 제거할 요소가 없으면 예외를 던집니다.pollFirst(): 덱의 앞쪽에서 요소를 제거하고 반환합니다. 덱이 비어있을 경우 null을 반환합니다.pollLast(): 덱의 뒤쪽에서 요소를 제거하고 반환합니다. 덱이 비어있을 경우 null을 반환합니다.3. 데이터 조회 메서드
데이터를 삭제하지 않고 현재 덱의 앞쪽 또는 뒤쪽 요소를 확인하는 메서드입니다.
peekFirst(): 덱의 앞쪽에 있는 요소를 반환하지만, 제거하지는 않습니다. 덱이 비어 있으면 null을 반환합니다.peekLast(): 덱의 뒤쪽에 있는 요소를 반환하지만, 제거하지는 않습니다. 덱이 비어 있으면 null을 반환합니다.4. 스택 연산을 지원하는 메서드
Deque는 스택과 유사하게 동작하는 메서드도 제공합니다.
push(E e): 덱의 앞쪽에 데이터를 삽입하는 스택 연산입니다. pop(): 덱의 앞쪽에서 데이터를 제거하고 반환하는 스택 연산입니다. 5. 기타 메서드
isEmpty(): 덱이 비어있는지 여부를 확인하는 메서드로, 비어 있으면 true를 반환하고, 요소가 있으면 false를 반환합니다.size(): 덱에 있는 요소의 개수를 반환합니다.iterator(): 덱의 요소들을 앞쪽부터 차례대로 순회하는 반복자를 반환합니다.descendingIterator(): 덱의 요소들을 뒤쪽부터 차례대로 순회하는 반복자를 반환합니다.Deque는 Queue 인터페이스를 상속받으면서 큐의 기본 연산인 offer, poll, peek 등을 모두 지원합니다. 또한 스택의 push()와 pop() 메서드도 제공하여, 큐와 스택의 장점을 모두 통합한 유연한 자료구조입니다.
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.LinkedList;
public class DequeExample {
public static void main(String[] args) {
// ArrayDeque 사용
Deque<Integer> arrayDeque = new ArrayDeque<>();
// 데이터 삽입
arrayDeque.addFirst(10);
arrayDeque.addLast(20);
arrayDeque.addLast(30);
// 데이터 확인
System.out.println(arrayDeque.peekFirst()); // 10 출력
System.out.println(arrayDeque.peekLast()); // 30 출력
// 데이터 제거
System.out.println(arrayDeque.removeFirst()); // 10 출력
System.out.println(arrayDeque.removeLast()); // 30 출력
// LinkedList 사용
Deque<Integer> linkedListDeque = new LinkedList<>();
// 데이터 삽입
linkedListDeque.addFirst(40);
linkedListDeque.addLast(50);
// 데이터 확인
System.out.println(linkedListDeque.peekFirst()); // 40 출력
System.out.println(linkedListDeque.peekLast()); // 50 출력
}
}
Python에서는 collections.deque 모듈을 사용하여 Deque를 구현할 수 있습니다.
deque는 양방향 삽입과 삭제를 O(1)의 시간 복잡도로 처리할 수 있습니다.list)와 달리 양쪽 끝에서의 삽입과 삭제가 매우 효율적으로 처리되며, 이는 스택과 큐의 기능을 모두 제공하기 위해 설계되었습니다.deque는 원형 버퍼로 구현되어 있어 양방향 큐 연산이 모두 O(1) 시간 복잡도를 보장합니다.append(x): 덱의 뒤쪽에 데이터를 추가합니다.appendleft(x): 덱의 앞쪽에 데이터를 추가합니다.pop(): 덱의 뒤쪽에서 데이터를 제거하고 반환합니다.popleft(): 덱의 앞쪽에서 데이터를 제거하고 반환합니다.extend(iterable): 뒤쪽에 여러 데이터를 한 번에 추가합니다.extendleft(iterable): 앞쪽에 여러 데이터를 한 번에 추가합니다.rotate(n): 덱을 n만큼 회전시킵니다. 양수는 오른쪽으로, 음수는 왼쪽으로 회전합니다.from collections import deque
# deque 생성
d = deque()
# 데이터 삽입
d.append(10) # 뒤쪽에 삽입
d.appendleft(20) # 앞쪽에 삽입
d.append(30) # 뒤쪽에 삽입
# 데이터 확인
print(d[0]) # 20 출력 (덱의 맨 앞 요소)
print(d[-1]) # 30 출력 (덱의 맨 뒤 요소)
# 데이터 제거
print(d.popleft()) # 20 출력 (앞쪽에서 제거)
print(d.pop()) # 30 출력 (뒤쪽에서 제거)
# 덱 상태 출력
print(d) # deque([10])
deque는 양쪽 끝에서의 삽입과 삭제가 O(1)로 매우 효율적입니다.deque가 훨씬 더 적합한 자료구조입니다.deque는 인덱스를 사용한 접근이 O(n)이기 때문입니다.Deque는 큐와 스택의 모든 연산을 효율적으로 지원하기 때문에 다양한 알고리즘과 문제에서 유용하게 활용됩니다. 아래는 대표적인 활용 사례들입니다:
Deque는 이 두 가지 모두를 지원하므로 양방향 탐색이 필요한 알고리즘에서 매우 효과적으로 사용할 수 있습니다.Deque를 사용하면 효율적으로 양쪽 끝에서 데이터를 처리할 수 있습니다.Deque가 자주 사용됩니다.addFirst()와 removeLast() 메서드를 통해 캐시의 앞쪽에서 데이터를 추가하고, 뒤쪽에서 오래된 데이터를 제거하는 방식으로 동작합니다.Deque가 활용됩니다.Deque가 유용하게 사용됩니다.Deque를 사용하여 문자열이 회문(앞뒤로 같은 문자열)인지 확인할 수 있습니다.Deque의 양방향 접근이 매우 효율적입니다.from collections import deque
def is_palindrome(s):
d = deque(s)
while len(d) > 1:
if d.popleft() != d.pop():
return False
return True
print(is_palindrome("racecar")) # True
print(is_palindrome("hello")) # False
Deque를 사용하여 윈도우 안의 최댓값을 구하는 문제를 효율적으로 해결할 수 있습니다.Deque를 사용하면 현재 윈도우 내에서 가장 큰 값을 빠르게 찾아낼 수 있습니다.from collections import deque
def sliding_window_max(nums, k):
d = deque()
result = []
for i, n in enumerate(nums):
while d and nums[d[-1]] <= n:
d.pop()
d.append(i)
if d[0] == i - k:
d.popleft()
if i >= k - 1:
result.append(nums[d[0]])
return result
nums = [1, 3, -1, -3, 5, 3, 6, 7]
k = 3
print(sliding_window_max(nums, k)) # [3, 3, 5, 5, 6, 7]
Deque는 연속된 배열 또는 연결 리스트를 기반으로 구현되어, 대부분의 연산들이 상수 시간에 처리됩니다.
Deque의 연산들은 양방향 큐의 특성 덕분에 삽입/삭제/조회 모두 상수 시간 복잡도를 가집니다. Deque는 큐와 스택의 기능을 모두 제공하는 자료구조로, 그 연산들은 모두 상수 시간 복잡도를 보장합니다.
큐와 스택의 주요 연산과 Deque의 시간 복잡도를 비교해 보면 다음과 같습니다:
| 자료구조 | 삽입 연산 | 삭제 연산 | 조회 연산 |
|---|---|---|---|
| Queue (FIFO) | O(1) | O(1) | O(1) |
| Stack (LIFO) | O(1) | O(1) | O(1) |
| Deque | O(1) | O(1) | O(1) |
Deque의 공간 복잡도는 저장하는 데이터의 개수에 비례하여 O(n)입니다.
이번 포스팅에서는 Deque(더블 엔디드 큐) 자료구조의 개념과 구현 방법을 Java와 Python에서 각각 살펴보았습니다.
Deque는 큐와 스택의 기능을 모두 제공하면서도 더 유연한 자료구조로, 다양한 프로그래밍 문제에서 중요한 역할을 합니다.ArrayDeque와 LinkedList를 사용하여 Deque를 구현할 수 있으며, 성능 면에서는 ArrayDeque가 더 효율적이기 때문에 더 많이 사용됩니다.collections.deque를 사용하여 양방향 큐 연산을 매우 효율적으로 처리할 수 있으며, 스레드 안전성도 지원합니다.다음 포스팅에서는 선형 자료구조 중 Hash를 기반으로 동작하는 Hashtable에 대해서 다뤄보려 합니다.