덱 (Deque) 자료 구조
덱(Deque, Double-Ended Queue)은 큐와 스택의 특성을 모두 가지고 있으며, 양쪽 끝에서 삽입과 삭제가 모두 가능한 자료 구조이다. 덱은 다음과 같은 두 가지 유형이 있다.
- 입력 제한 덱 (Input-Restricted Deque): 한쪽 끝에서만 삽입이 가능하고, 양쪽 끝에서 삭제가 가능하다.
- 출력 제한 덱 (Output-Restricted Deque): 양쪽 끝에서 삽입이 가능하고, 한쪽 끝에서만 삭제가 가능하다.
덱의 주요 연산
덱은 다음과 같은 주요 연산을 지원한다.
- Add to Front (addFront): 덱의 앞쪽 끝에 요소를 추가한다.
- Add to Rear (addRear): 덱의 뒤쪽 끝에 요소를 추가한다.
- Remove from Front (removeFront): 덱의 앞쪽 끝에서 요소를 제거하고 반환한다.
- Remove from Rear (removeRear): 덱의 뒤쪽 끝에서 요소를 제거하고 반환한다.
- Peek Front (peekFront): 덱의 앞쪽 끝에 있는 요소를 제거하지 않고 반환한다.
- Peek Rear (peekRear): 덱의 뒤쪽 끝에 있는 요소를 제거하지 않고 반환한다.
- isEmpty: 덱이 비어 있는지 확인한다.
- isFull: 덱이 가득 찼는지 확인한다 (고정 크기의 덱인 경우).
덱의 활용 예
덱은 다양한 문제 해결에 유용하게 사용될 수 있다.
- 슬라이딩 윈도우 문제 (Sliding Window Problems): 고정 크기의 창을 사용하여 배열이나 리스트의 부분 집합을 반복적으로 처리할 때 유용하다. 예를 들어, 최대 슬라이딩 윈도우 문제에서 덱을 사용하여 윈도우 내에서 최대값을 효율적으로 찾을 수 있다.
- 대기열 관리 (Queue Management): 덱은 양쪽 끝에서 삽입과 삭제가 가능하기 때문에 대기열 관리에 유연성을 제공한다. 예를 들어, 고객 서비스 대기열에서 새로 온 고객을 뒤에 추가하고, VIP 고객을 앞에 추가할 수 있다.
- 이중 스택 (Double-Ended Stack): 덱은 큐와 스택의 기능을 모두 제공하기 때문에 이중 스택으로 사용할 수 있다. 예를 들어, 스택의 두 끝에서 데이터를 삽입하고 삭제하는 경우에 유용하다.
- 캐시 구현 (Cache Implementation): 덱은 LRU (Least Recently Used) 캐시와 같은 캐시 알고리즘을 구현하는 데 사용될 수 있다. 가장 최근에 사용된 항목을 앞쪽에 추가하고, 가장 오래된 항목을 뒤쪽에서 제거할 수 있다.
- 문자열 조작 (String Manipulation): 덱을 사용하여 문자열의 회문(팰린드롬) 여부를 확인하거나, 문자열을 뒤집는 등의 작업을 효율적으로 수행할 수 있다.
덱의 이론적 특성
- 시간 복잡도: 덱의 주요 연산은 모두 평균적으로 O(1) 시간 복잡도를 가진다. 즉, 요소의 삽입과 삭제가 덱의 양쪽 끝에서 상수 시간 내에 이루어진다.
- 공간 복잡도: 덱은 사용하는 데이터 구조에 따라 공간 복잡도가 결정된다. 배열 기반 덱은 고정된 크기의 배열을 사용하며, 연결 리스트 기반 덱은 동적 메모리 할당을 사용한다.
- 데이터 순서: 덱은 요소를 양쪽 끝에서 삽입하고 삭제할 수 있기 때문에, 요소의 순서를 유연하게 관리할 수 있다. 이는 다양한 알고리즘과 문제 해결에 유용하게 사용될 수 있다.
덱의 구현 방식
배열 기반 덱:
- 장점: 인덱스를 통해 요소에 빠르게 접근할 수 있습니다.
- 단점: 크기가 고정되어 있으며, 크기를 초과하면 재할당이 필요합니다. 또한, 배열의 앞쪽 끝에서 요소를 삽입하거나 삭제할 때 요소들을 이동시켜야 합니다.
덱의 활용 예시: 슬라이딩 윈도우 최대값 문제
덱을 사용하여 슬라이딩 윈도우 최대값 문제를 해결하는 방법을 예로 들어보겠다. 주어진 배열과 윈도우 크기 k에 대해, 윈도우 내에서 최대값을 찾는 문제이다.
- 배열의 처음부터 끝까지 순회하면서, 덱을 사용하여 현재 윈도우의 최대값을 유지한다.
- 덱의 앞쪽에는 현재 윈도우 내의 최대값을 유지하고, 뒤쪽에는 나머지 값들을 유지한다.
- 새로운 요소가 추가될 때마다 덱의 뒤쪽에서 현재 값보다 작은 값들을 제거한다.
- 덱의 앞쪽에서 최대값을 유지한다.
이러한 방식으로 덱을 사용하여 슬라이딩 윈도우 내의 최대값을 효율적으로 찾을 수 있다.
구현 코드
class Deque:
def __init__(self):
self._values = []
def isEmpty(self) -> bool:
return len(self._values) == 0
def addFront(self, value):
self._values.insert(0, value)
def addRear(self, value):
self._values.append(value)
def removeFront(self):
if self.isEmpty():
return None
return self._values.pop(0)
def removeRear(self):
if self.isEmpty():
return None
return self._values.pop()
def peekFront(self):
if self.isEmpty():
return None
return self._values[0]
def peekRear(self):
if self.isEmpty():
return None
return self._values[-1]
def __str__(self) -> str:
return f"Deque: {self._values}"
deque = Deque()
# Add elements to the rear
deque.addRear(10)
deque.addRear(20)
deque.addRear(30)
print(deque) # Deque: [10, 20, 30]
# Add elements to the front
deque.addFront(5)
deque.addFront(1)
print(deque) # Deque: [1, 5, 10, 20, 30]
# Peek front and rear
print(deque.peekFront()) # 1
print(deque.peekRear()) # 30
# Remove elements from the front
print(deque.removeFront()) # 1
print(deque.removeFront()) # 5
print(deque) # Deque: [10, 20, 30]
# Remove elements from the rear
print(deque.removeRear()) # 30
print(deque) # Deque: [10, 20]
# Check if deque is empty
print(deque.isEmpty()) # False
# Remove remaining elements
deque.removeFront()
deque.removeFront()
print(deque) # Deque: []
print(deque.isEmpty()) # Output: True
모든 코드는 github에 저장되어 있습니다.
https://github.com/SeongUk18/Algorithm/tree/main/Data_structure_algorithms
728x90
'Data structure & Algorithms study' 카테고리의 다른 글
이중 연결 리스트 (Doubly Linked List) (0) | 2024.06.11 |
---|---|
단일 연결 리스트 (Singly Linked List) (0) | 2024.05.27 |
원형 큐(Circular Queue) (0) | 2024.05.26 |
큐 (Queue) (0) | 2024.05.26 |
스택(Stack) (0) | 2024.05.23 |