69. 데크/원형 큐 (Deque / Circular Queue)

핵심 인사이트 (3줄 요약)

  1. 본질: 데크(Deque)는 양쪽 끝에서 삽입/삭제가 모두 O(1)에 가능한 자료구조이고, 원형 큐(Circular Queue)는 큐를 배열로 구현할 때 front와 rear가 순환하는 구조이다.
  2. 가치: 데크는 스택과 큐의 장점을 결합하여 유연한 연산을 제공하고, 원형 큐는 배열 기반 큐에서 공간 낭비 문제를 해결한다.
  3. 융합: 작업 스케줄링, 버퍼 관리, 스케줄러 구현, 양방향 대기열이 필요한 시스템 등에 활용된다.

Ⅰ. 개요 및 필요성 (Context & Necessity)

데크(Deque, Double-Ended Queue)와 원형 큐(Circular Queue)는 기본 큐의 제한을 극복하기 위해 발전한 자료구조이다.

**데크(Deque)**는 양쪽 끝(앞과 뒤)에서 모두 O(1) 시간에 삽입과 삭제가 가능한 자료구조이다. 일반 큐는 한쪽 끝에서만 삽입(enqueue), 다른 쪽 끝에서만 삭제(dequeue)가 가능하지만, 데크는 양쪽 모두에서 가능하다.

**원형 큐(Circular Queue)**는 배열 기반 큐에서 발생하는 공간 낭비 문제를 해결한다. 일반 배열 큐에서 dequeue를 반복하면 앞쪽 공간이浪费되지만, 원형 구조에서는 front와 rear가 배열의 끝에 도달하면 다시 처음으로 되돌아가게 하여 공간을 효율적으로 활용한다.

이 자료구조들이 중요한 이유는 효율성유연성 때문이다. 데크는 스택과 큐의 연산을 모두 제공하면서도 O(1) 시간 복잡도를 유지한다. 원형 큐는 배열 기반 큐의缺点인 "사용하지 않는 공간" 문제를 해결한다.

[데크와 원형 큐 구조]

┌──────────────────────────────────────────────────────────────┐
│                                                              │
│  [데크 (Deque) 구조]                                         │
│  ────────────────────────────────────────────                │
│                                                              │
│      Front ◄─────────────────────────────► Rear              │
│                                                              │
│      ┌───┬───┬───┬───┬───┬───┬───┬───┐                     │
│      │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │                     │
│      └───┴───┴───┴───┴───┴───┴───┴───┘                     │
│                                                              │
│      양쪽 끝에서 삽입/삭제 가능:                              │
│      - insert_front(x): 앞에 삽입                             │
│      - insert_rear(x): 뒤에 삽입                             │
│      - delete_front(): 앞에서 삭제                            │
│      - delete_rear(): 뒤에서 삭제                            │
│                                                              │
│  [원형 큐 (Circular Queue) 구조]                              │
│  ────────────────────────────────────────────                │
│                                                              │
│      초기 상태:                                               │
│      ┌───┬───┬───┬───┬───┐                                 │
│      │   │   │   │   │   │  front = rear = 0               │
│      └───┴───┴───┴───┴───┘                                 │
│                                                              │
│      3개 삽입 후:                                            │
│      ┌───┬───┬───┬───┬───┐                                 │
│      │ A │ B │ C │   │   │  front=0, rear=3                │
│      └───┴───┴───┴───┴───┘                                 │
│                                                              │
│      2개 삭제 후:                                             │
│      ┌───┬───┬───┬───┬───┐                                 │
│      │   │   │ C │   │   │  front=2, rear=3                │
│      └───┴───┴───┴───┴───┘                                 │
│                                                              │
│      2개 삽입 후 (원형으로 순환):                              │
│      ┌───┬───┬───┬───┬───┐                                 │
│      │ E │ D │ C │   │   │  front=2, rear=0 (순환!)        │
│      └───┴───┴───┴───┴───┘                                 │
│                                                              │
└──────────────────────────────────────────────────────────────┘
  • 관찰: 원형 큐에서 rear가 배열 끝에 도달하면 다시 처음으로 돌아간다.
  • 원인: 원형으로 인덱스를 계산하기 때문에 (rear + 1) % capacity로 modulo 연산을 사용한다.
  • 결과: 배열의 공간을 모두 활용할 수 있다.
  • 판단: 배열 기반 큐에서는 항상 원형 큐 구현이 효율적이다.

📢 섹션 요약 비유: 데크는 양쪽 문이 있는 터널과 같습니다.左側から進入して右側から退出することも、右側から進入して左側から退出することもできる。원형 큐는 원형 교차로와 같습니다. 차가 교차로에 들어가서左회전하면 다시 같은 교차로로 돌아올 수 있듯이, 원형 구조 덕분에 차량(데이터)이 배열의 끝에 도달해도 다시 처음으로될 수 있다.


Ⅱ. 아키텍처 및 핵심 원리 (Deep Dive)

데크와 원형 큐의 핵심 구현 원리를 살펴보자.

데크 구현: 연결 리스트 기반 - 양쪽 끝에 포인터를 유지하여 O(1) 삽입/삭제 가능. 배열 기반 - 양쪽 끝 접근을 위해 양쪽에 포인터를 유지. 단, 배열 기반은 크기 제한이 있고 확장 시 복사 비용이 발생한다.

원형 큐 구현: 핵심은 modulo 연산을 사용한 인덱스 계산이다. rear = (rear + 1) % capacity, front = (front + 1) % capacity. empty 조건: front == rear. full 조건: (rear + 1) % capacity == front.

원형 큐 full vs empty 구분: 한 가지 문제는 front == rear가 empty와 full 모두에서 발생한다는 것이다. 이를 해결하기 위한 방법: 하나의 공간을 항상 비워두기(가장 일반적인 방법), 또는 사이즈 카운터를 유지하기.

[데크/원형 큐 구현]

┌──────────────────────────────────────────────────────────────┐
│                                                              │
│  [원형 큐 구현]                                               │
│  ────────────────────────────────────────────                │
│                                                              │
│  class CircularQueue:                                        │
│      def __init__(self, capacity):                          │
│          self.capacity = capacity                            │
│          self.array = [None] * capacity                     │
│          self.front = 0                                      │
│          self.rear = 0                                      │
│                                                              │
│      def is_empty(self):                                    │
│          return self.front == self.rear                      │
│                                                              │
│      def is_full(self):                                     │
│          return (self.rear + 1) % self.capacity == self.front │
│                                                              │
│      def enqueue(self, item):                                │
│          if self.is_full():                                  │
│              raise Exception("Queue is full")                 │
│          self.array[self.rear] = item                        │
│          self.rear = (self.rear + 1) % self.capacity         │
│                                                              │
│      def dequeue(self):                                      │
│          if self.is_empty():                                 │
│              raise Exception("Queue is empty")               │
│          item = self.array[self.front]                        │
│          self.front = (self.front + 1) % self.capacity        │
│          return item                                         │
│                                                              │
│  [데크 구현 (연결 리스트 기반)]                                │
│  ────────────────────────────────────────────                │
│                                                              │
│  class DequeNode:                                           │
│      def __init__(self, data):                              │
│          self.data = data                                   │
│          self.prev = None                                   │
│          self.next = None                                   │
│                                                              │
│  class Deque:                                               │
│      def __init__(self):                                    │
│          self.front = None                                  │
│          self.rear = None                                   │
│          self.size = 0                                      │
│                                                              │
│      def insert_front(self, item):                          │
│          new_node = DequeNode(item)                         │
│          if self.is_empty():                                │
│              self.front = self.rear = new_node               │
│          else:                                              │
│              new_node.next = self.front                       │
│              self.front.prev = new_node                       │
│              self.front = new_node                           │
│          self.size += 1                                      │
│                                                              │
│      def insert_rear(self, item):                           │
│          new_node = DequeNode(item)                         │
│          if self.is_empty():                                │
│              self.front = self.rear = new_node               │
│          else:                                              │
│              new_node.prev = self.rear                        │
│              self.rear.next = new_node                       │
│              self.rear = new_node                           │
│          self.size += 1                                      │
│                                                              │
│      def delete_front(self):                                │
│          if self.is_empty():                                │
│              raise Exception("Deque is empty")               │
│          item = self.front.data                             │
│          self.front = self.front.next                        │
│          if self.front:                                     │
│              self.front.prev = None                          │
│          self.size -= 1                                      │
│          return item                                        │
│                                                              │
│      def delete_rear(self):                                 │
│          if self.is_empty():                                │
│              raise Exception("Deque is empty")               │
│          item = self.rear.data                             │
│          self.rear = self.rear.prev                          │
│          if self.rear:                                     │
│              self.rear.next = None                          │
│          self.size -= 1                                      │
│          return item                                        │
│                                                              │
└──────────────────────────────────────────────────────────────┘
  • 관찰: 원형 큐는 modulo 연산으로 인덱스를 계산하여 순환 구조를实现한다.
  • 원인: 일반 배열 인덱스 연산은 끝에 도달하면 더 이상 증가할 수 없지만, modulo로 capacity范围内的 값으로 순환시킬 수 있다.
  • 결과: 배열의 공간을 모두 활용할 수 있다.
  • 판단: 배열 기반 큐에서는 항상 원형 구조를 사용하는 것이 효율적이다.

📢 섹션 요약 비유: 원형 큐의 modulo 연산은 시계와 같습니다. 12시를 지나면 1시가 되듯이, 원형 큐도 배열의끝(capacity-1)을 지나면 처음으로(0) 돌아간다. 이로 인해 시계처럼 끝없이 순환하며 공간을 효율적으로 활용한다.


Ⅲ. 구현 및 실무 응용 (Implementation & Practice)

데크와 원형 큐의 실무 적용은 다음과 같다.

데크의 실무 적용: 작업 스케줄링: 양방향으로 작업 추가/제거가 필요할 때 활용된다. ** undo/redo 시스템**: 양쪽에서 자유롭게 추가/삭제해야 하는 상황에 활용된다. 滑动窗口: 일정 범위의 데이터를 양방향으로 조정하며 처리해야 하는 경우 활용된다.

원형 큐의 실무 적용: 버퍼 관리: 네트워크 패킷 버퍼, 디스크 버퍼 등에서 활용된다. CPU 스케줄링: 준비 큐를 원형으로 관리하여 공정하게 프로세스를 스케줄링한다. 生产者-消费者问题: 생산자와 소비자가 각각 한쪽 끝에서만 접근하는 동기화 문제에 활용된다.

[데크/원형 큐 실무 활용]

┌──────────────────────────────────────────────────────────────┐
│                                                              │
│  [滑动窗口平均 - 데크 활용]                                   │
│  ────────────────────────────────────────────                │
│                                                              │
│  시간序列 데이터에서 일정한 창 크기의 평균을 계산               │
│                                                              │
│  데이터: [1, 3, -1, -3, 5, 3, 6, 7], 창 크기 = 3           │
│                                                              │
│  데크를 이용해 현재 창 내의 원소 관리:                        │
│  - 인덱스 저장, 값 비교 가능                                 │
│  - 새로운 원소가 들어올 때 기존 원소들 중 더 큰 것들 제거      │
│  - 창 크기를 벗어나는 원소 인덱스 제거                        │
│                                                              │
│  결과: [1, 1, -1, -3, 3, 5, 5]                              │
│                                                              │
│  [生产者-消费者 버퍼 - 원형 큐 활용]                          │
│  ────────────────────────────────────────────                │
│                                                              │
│  生产者: 데이터를 버퍼에 삽입 (rear에서)                       │
│  消费者: 데이터를 버퍼에서 제거 (front에서)                    │
│                                                              │
│  버퍼가 가득 차면 생산자 대기                                 │
│  버퍼가 비어있으면 소비자 대기                                 │
│  원형 큐로 구현하여 공간 낭비 없이 관리                        │
│                                                              │
└──────────────────────────────────────────────────────────────┘

📢 섹션 요약 비유: 데크/원형 큐 실무 활용은 지하철 플랫폼과 같습니다. 양방향으로 출입이 가능한 데크는 플랫폼 양쪽에서 열차가 출발하고 도착할 수 있음을 의미한다. 원형 큐는 순환 플랫폼으로, 열차가 플랫폼을一圈하고 다시 같은 위치로 돌아올 수 있음을 의미한다. 이를 통해 지하철 시스템이より 효율적으로運営できる。


Ⅳ. 품질 관리 및 테스트 (Quality & Testing)

데크와 원형 큐의 품질 관리에서 가장 중요한 것은 경계 조건 처리동기화이다.

품질 관리 체크리스트: empty 상태에서 삭제/꺼내기를 시도하면 예외가 발생하는지 확인해야 한다. full 상태에서 삽입을 시도하면 예외가 발생하는지 확인해야 한다. 원형 큐의 modulo 연산에서 오버플로우가 발생하지 않는지 확인해야 한다.

📢 섹션 요약 비유: 데크/원형 큐 품질 관리는 교차로 신호 제어와 같습니다. 차가 아무 것도 없는 상태(비어있음)에서 출차하려고 하면 안 되고, 차로 가득 찬 상태(가득 참)에서 진입하려고 하면 충돌이 발생한다. 올바른 신호 체계(empty/full 확인)를 통해 이러한 상황이 발생하지 않도록 해야 한다.


데크와 원형 큐의 최신 동향은 병렬 처리고성능 버퍼와 관련되어 있다. Lock-free 원형 버퍼: 멀티스레드 환경에서 안전하게 사용 가능한 무잠금 구현. 메모리 매핑 버퍼: 대용량 데이터 처리를 위한 파일 매핑 기반 원형 버퍼.

데크와 원형 큐는 "기본적이지만 필수적인 자료구조"로서, 더 복잡한 시스템의构建에 항상 활용된다.

📢 섹션 요약 비유: 데크와 원형 큐의 발전은 스마트시티 교통 시스템과 같습니다. 단순한 일방향 도로에서 양방향 도로(데크)로 발전하고, 단순한 십자로가 아닌 원형 교차로(원형 큐)로 발전하여 차량 흐름을より効率的に했다. 이러한 기본 시설의 발전이 전체 시스템의 효율성을 끌어올린다.


핵심 인사이트 ASCII 다이어그램 (Concept Map)

[데크/원형 큐 (Deque / Circular Queue) 핵심 개념 맵]

         ┌─────────────────────────────────┐
         │       데크/원형 큐 (Deque/Circular Queue)      │
         └────────────────┬────────────────┘
                          │
      ┌───────────────────┼───────────────────┐
      │                   │                    │
      ▼                   ▼                    ▼
┌──────────────┐  ┌──────────────┐  ┌──────────────┐
│  데크 (Deque) │  │  원형 큐     │  │   실무 활용   │
│ 양방향 삽입/  │  │  Circular   │  │  Use Cases  │
│ 삭제 O(1)   │  │  Queue      │  │              │
├──────────────┤  ├──────────────┤  ├──────────────┤
│ 연결 리스트   │  │ modulo 연산  │  │ 버퍼 관리   │
│ 기반/배열    │  │ 공간 효율적  │  │ 스케줄링    │
│ 기반        │  │              │  │ sliding win │
└──────────────┘  └──────────────┘  └──────────────┘

참고

  • 모든 약어는 반드시 전체 명칭과 함께 표기
  • 일어/중국어 절대 사용 금지
  • 각 섹션 끝에 📢 요약 비유 반드시 추가
  • 최소 800자/파일
  • 파일명: 01_, 02_... 형식