JavaScript를 이용해 원형 큐 구현해보기

치와와견주·2024년 11월 1일

안녕하세요. 이전 포스팅에이어서 오늘은 원형 큐에 대해서 공부한 내용을 공유해보고자 합니다.

원형 큐는 일반적인 선형큐의 단점을 보완한 확장 개념입니다. 우선, 선형큐에 대해 간단히 복습해보겠습니다. 선형큐는 배열이나 링크드 리스트로 구현할 수 있지만, 배열로 구현할때 다음과 같은 문제가 발생할 수 있습니다.

  • 비효율적인 요소이동 : 요소를 삽입하거나 삭제 할때마다 모든 요소를 앞으로 혹은 뒤로 밀어야 합니다.
  • 공간 낭비 : 삭제된 요소의 자리를 재활용하지 않으면, 앞 공간이 계속 비어있게 되어 큐가 비어있지 않더라도, 새요소를 삽입할 수 없게 됩니다.

이러한 문제를 해결하기 위해서 원형큐가 도입되었습니다. 원형 큐는 큐의 마지막 포인터(rear)를 첫 번째 포인터(front)와 연결하여 끝과 끝이 이어진 원형 구조를 만들어 공간을 효율적으로 활용합니다.

원형 큐의 특징은 다음과 같습니다.

  • 원형 구조 : rear기 배열의 끝에 도달하여도 front가 가리키는 요소로 되돌하가서 비어있는 공간을 더 사용할 수 있습니다.
  • 공간 낭비 : 삭제된 요소의 공간을 재활용하기 때문에 메모리를 최적화 할 수 있습니다.

enQueue 연산

  1. Queue가 이미 가득 차 있는지 확인 합니다.
  2. 큐가 비어있다면 front와 rear을 0으로 초기화 합니다.
  3. 그렇지 않다면, 한칸 이동 시키되, 원형으로 처리하기 위해 다음 수식을 rear에 대입합니다. rear = (rear +1) % MaxSize
  4. rear의 위치에 queue에 값을 넣습니다.

deQueue 연산

  1. Queue가 비어있는지 확인합니다.
  2. 큐가 비어있지 않다면, 요소를 제거할 수 있다는 의미이므로, front가 가르키는 index 값을 null로 변경 합니다.
  3. 만약, front와 rear의 값이 같다면, 요소가 하나만 존재한다는 의미이므로 두 값 모두 -1로 초기화 합니다.
  4. 그렇지 않다면, 다음 연산을 이용해 front의 위치를 이동 시킵니다. front = (front +1) % MaxSize

Array를 이용한 원형 큐 구현

class CircularQueue {
  constructor(MAX_SIZE) {
    this.queue = [];
    this.MAX_SIZE = MAX_SIZE;
    this.front = -1;
    this.rear = -1;
  }

  isFull() {
    return (this.rear + 1) % this.MAX_SIZE === this.front;
  }
  isEmpty() {
    return this.front === -1 && this.rear === -1;
  }

  enQueue(item) {
    if (this.isFull()) {
      throw Error("Queue is full");
    }

    if (this.isEmpty()) {
      this.front = this.rear = 0;
    } else {
      // rear 포인터를 한 칸 이동시키되, 원형으로 처리
      this.rear = (this.rear + 1) % this.MAX_SIZE;
    }

    this.queue[this.rear] = item;
  }

  deQueue() {
    if (this.isEmpty()) {
      throw Error("Queue is empty");
    }
    this.queue[this.front] = null;

    if (this.front === this.rear) {
      this.front = this.rear = -1;
    } else {
      this.front = (this.front + 1) % this.MAX_SIZE;
    }
  }
}

원형 큐는 대부분의 로직이 기존 선형 큐와 유사하지만, 주요 차이점은 삽입과 삭제 시 rear와 front를 연결하여 순환 구조를 유지하는 방식입니다. 이때, (rear + 1) % maxSize를 통해 rear 포인터가 front로 돌아가도록 설정합니다. 이 방식으로 배열의 끝에 도달해도 다시 처음으로 이동할 수 있게 되어 효율적인 메모리 활용이 가능합니다.

배열을 사용해 구현된 원형 큐의 연산별 시간 복잡도는 다음과 같습니다:

삽입 (enQueue): Queue가 가득 찼는지 확인한 후, 올바른 위치에 요소를 삽입합니다. 요소를 삽입하는 작업은 상수 시간이므로, 시간 복잡도는 𝑂(1) 입니다.

삭제 (deQueue): Queue가 비었는지 확인하고, 요소를 삭제한 후 front 포인터를 이동시키거나, 큐가 비었을 경우 front와 rear를 초기화합니다. 이 역시 상수 시간에 수행되므로, 시간 복잡도는 O(1)입니다.

특정 요소 찾기: 특정 요소를 찾기 위해서는 모든 요소를 순회해야 하므로, 최악의 경우 O(n)의 시간 복잡도가 발생합니다. 이 부분은 일반적인 선형 큐와 동일합니다.

따라서, 원형 큐는 효율적인 메모리 활용과 간단한 삽입/삭제 연산을 제공하며, 삽입과 삭제는 O(1), 특정 요소를 찾는 연산은 O(n)의 시간 복잡도를 가집니다.

LinkedList를 이용한 원형 큐

다음은 자바스크립트와 LinkedList로 구현한 원형 큐입니다.

class NewNode {
  constructor(value, priority) {
    this.value = value;
    this.priority = priority;
    this.next = null;
  }
}

class PriorityLinkedListQueue {
  constructor() {
    this.head = null;
    this.tail = null;
  }

  printQueue() {
    let current = this.head;
    while (current) {
      current = current.next;
    }
  }

  enQueue(value, priority) {
    const newNode = new NewNode(value, priority);
    // 리스트가 비어있을 경우
    if (!this.head && !this.tail) {
      this.head = newNode;
      this.tail = newNode;
      return;
    }
    // 새 노드가 head보다 우선순위가 높을 경우
    if (this.head.priority < newNode.priority) {
      newNode.next = this.head;
      this.head = newNode;
      return;
    }

    let current = this.head;
    let previous = null;

    while (current) {
      if (current.priority <= newNode.priority) {
        previous = current;
        current = current.next;
      }
    }

    if (!current) {
      this.tail.next = newNode;
      this.tail = newNode;
    } else {
      previous.next = newNode;
      newNode.next = current;
    }
  }

  deQueue() {
    this.head = this.head.next;

    if (!this.head.next) {
      this.tail = null;
    }
  }
}

원형 큐는 요소를 삽입할 때 마지막 요소의 next를 front와 연결해 순환 구조를 유지하는 것 외에는 일반적인 선형 큐와 거의 동일한 로직을 사용합니다.

삽입 (enQueue): 마지막 위치에 요소를 추가하고, rear.next를 front와 연결해 원형 구조를 만듭니다. 삽입은 큐의 끝에서 이루어지므로 상수 시간에 수행되며, 시간 복잡도는 O(1)입니다.

삭제 (deQueue): 큐의 맨 앞 요소를 제거하고, 새로운 front와 rear.next를 연결해 순환 구조를 유지합니다. 이 역시 큐의 앞부분에서 한 번의 연산으로 처리되므로, 시간 복잡도는 O(1)입니다.

따라서 원형 큐는 삽입과 삭제 모두 O(1)의 시간 복잡도를 가지며, 효율적인 메모리 사용과 간단한 연산을 제공하는 구조입니다.

결론

배열로 선형 큐를 구현할 때 발생하는 한계를 해결하기 위해 원형 큐에 대해 공부해 보았습니다.

선형 큐를 배열로 구현하면 삽입과 삭제 시 앞쪽 공간이 비더라도 재사용할 수 없거나, 모든 요소를 이동시켜야 하는 문제가 있습니다. 이때, 배열을 사용해야 한다면 원형 큐 구조를 활용해 끝과 끝을 연결하여 이러한 문제를 효과적으로 해결할 수 있습니다.

반면, 연결 리스트(Linked List)로 큐를 구현할 경우, 선형 큐와 원형 큐 사이에 큰 차이가 없습니다. 연결 리스트는 삭제된 요소의 자리를 자연스럽게 활용할 수 있으므로, 굳이 원형 큐 구조를 사용할 필요가 없다는 생각이 들었습니다.

따라서, 배열로 큐를 구현해야 하는 상황이라면 원형 큐를 이용하는게 더 좋은 선택이 될 것 같습니다.

profile
건들면 물어요

0개의 댓글