본문 바로가기
Study/코딩테스트(JS)

07. 큐

by dailycoding777 2025. 2. 25.

‘줄을 서다’라는 뜻 (FIFO)

이런 큐의 특징을 선입선출 또는 FIFO라고 한다.

스택과 마찬가지로 큐도 삽입하는 연산을 푸쉬(Push), 꺼내는 연산을 팝(Pop)이라 한다.

큐를 사용하는 곳.

  • 작업 대기열 : 네트워크 통신 할 때 서버에 작업 요청하면 서버는 들어온 순서대로 작업을 처리
  • 이벤트 처리 : 어떤 application & 시스템에서 사용자의 이벤트 (키보드 입력 , 마우스 움직임)

큐의 ADT(추상구문트리)

  • enqueue: 큐의 뒤쪽(리어)에 데이터를 추가하는 연산.
  • dequeue: 큐의 앞쪽(프론트)에서 데이터를 제거하고 반환하는 연산.
  • isEmpty: 큐가 비어있는지 확인하는 연산.
  • isFull: 정적 배열 기반의 큐 구현 시 큐가 가득 찼는지 확인하는 연산.
  • front : 앞에 데이터가 있는 위치
  • rear : 뒤에 데이터가 있는 위치

자바스크립트로 치면 push() ,shift()입니다.

  • 추가할 때 뒤에서 추가 push() | O(1)
  • 삭제할 때 앞에서 삭제 shift() | O(n)
class Queue {
  constructor() {
    this.items = [];
  }

  // 큐의 뒤쪽에 데이터를 추가하는 메서드 (enqueue: push() 사용)
  enqueue(item) {
    this.items.push(item);
  }

  // 큐의 앞쪽에서 데이터를 제거하고 반환하는 메서드 (dequeue: shift() 사용)
  dequeue() {
    if (this.isEmpty()) {
      throw new Error("빈 큐에서 dequeue 불가여여");
    }
    return this.items.shift();
  }

  // 큐의 맨 앞 데이터를 확인하는 메서드 (삭제 없이)
  front() {
    if (this.isEmpty()) {
      throw new Error("빈 큐에서 front 불가여여");
    }
    return this.items[0];
  }

  // 큐가 비어있는지 확인하는 메서드
  isEmpty() {
    return this.items.length === 0;
  }

  // 큐 내 데이터의 개수를 반환하는 메서드
  size() {
    return this.items.length;
  }
}

// 사용 예제
const q = new Queue();
q.enqueue(10);
q.enqueue(20);
q.enqueue(30);

console.log("큐의 크기:", q.size());       // 출력: 큐의 크기: 3
console.log("큐의 앞 데이터:", q.front());   // 출력: 큐의 앞 데이터: 10
console.log("dequeue 결과:", q.dequeue());  // 출력: dequeue 결과: 10
console.log("큐의 크기:", q.size());       // 출력: 큐의 크기: 2

근데 이 둘을 아무리 최적화 시켜도 진짜 큐의 성능을 따라갈 순 없음.


큐를 사용한 3가지 방법

1. 대중적인 방법 (배열의 push()와 shift() 사용)

class QueuePopular {
  constructor() {
    this.items = [];
  }

  // 뒤쪽에 데이터 추가 (push 사용)
  enqueue(item) {
    this.items.push(item);
  }

  // 앞쪽에서 데이터 삭제 (shift 사용)
  dequeue() {
    if (this.isEmpty()) {
      throw new Error("빈 큐에서 dequeue 불가");
    }
    return this.items.shift();
  }

  // 맨 앞 데이터 확인 (삭제 없이)
  front() {
    if (this.isEmpty()) {
      throw new Error("빈 큐에서 front 불가");
    }
    return this.items[0];
  }

  // 큐가 비었는지 확인
  isEmpty() {
    return this.items.length === 0;
  }

  // 큐 크기 반환
  size() {
    return this.items.length;
  }
}

// 사용 예제
const queuePopular = new QueuePopular();
queuePopular.enqueue(1);
queuePopular.enqueue(2);
queuePopular.enqueue(3);
console.log("대중적 큐의 크기:", queuePopular.size()); // 출력: 3
console.log("대중적 큐의 front:", queuePopular.front()); // 출력: 1
console.log("dequeue 결과:", queuePopular.dequeue());   // 출력: 1

2. 배열을 이용한 방식 (앞 포인터 사용하여 shift() 없이)

class QueueArray {
  constructor() {
    this.items = [];
    this.frontIndex = 0; // 앞쪽 인덱스 관리
  }

  // 뒤쪽에 데이터 추가
  enqueue(item) {
    this.items.push(item);
  }

  // 앞쪽에서 데이터 삭제 (인덱스 이동)
  dequeue() {
    if (this.isEmpty()) {
      throw new Error("빈 큐에서 dequeue 불가여여");
    }
    const item = this.items[this.frontIndex];
    this.frontIndex++;
    // 불필요한 공간이 많으면 배열 재설정
    if (this.frontIndex > 50 && this.frontIndex * 2 >= this.items.length) {
      this.items = this.items.slice(this.frontIndex);
      this.frontIndex = 0;
    }
    return item;
  }

  // 맨 앞 데이터 확인 (삭제 없이)
  front() {
    if (this.isEmpty()) {
      throw new Error("빈 큐에서 front 불가여여");
    }
    return this.items[this.frontIndex];
  }

  // 큐가 비었는지 확인
  isEmpty() {
    return this.size() === 0;
  }

  // 현재 큐 크기 반환
  size() {
    return this.items.length - this.frontIndex;
  }
}

// 사용 예제
const queueArray = new QueueArray();
queueArray.enqueue(10);
queueArray.enqueue(20);
queueArray.enqueue(30);
console.log("배열 큐의 크기:", queueArray.size());       // 출력: 3
console.log("배열 큐의 front:", queueArray.front());       // 출력: 10
console.log("dequeue 결과:", queueArray.dequeue());        // 출력: 10

3. 연결리스트를 이용한 방식

// 노드 클래스 정의
class Node {
  constructor(value) {
    this.value = value;
    this.next = null;
  }
}

class QueueLinkedList {
  constructor() {
    this.head = null; // 큐의 앞쪽
    this.tail = null; // 큐의 뒤쪽
    this.length = 0;
  }

  // 뒤쪽에 데이터 추가
  enqueue(item) {
    const newNode = new Node(item);
    if (this.isEmpty()) {
      this.head = newNode;
      this.tail = newNode;
    } else {
      this.tail.next = newNode;
      this.tail = newNode;
    }
    this.length++;
  }

  // 앞쪽에서 데이터 삭제
  dequeue() {
    if (this.isEmpty()) {
      throw new Error("빈 큐에서 dequeue 불가");
    }
    const item = this.head.value;
    this.head = this.head.next;
    this.length--;
    if (this.isEmpty()) {
      this.tail = null;
    }
    return item;
  }

  // 맨 앞 데이터 확인 (삭제 없이)
  front() {
    if (this.isEmpty()) {
      throw new Error("빈 큐에서 front 불가");
    }
    return this.head.value;
  }

  // 큐가 비었는지 확인
  isEmpty() {
    return this.length === 0;
  }

  // 큐 크기 반환
  size() {
    return this.length;
  }
}

// 사용 예제
const queueLinkedList = new QueueLinkedList();
queueLinkedList.enqueue("A");
queueLinkedList.enqueue("B");
queueLinkedList.enqueue("C");
console.log("연결리스트 큐의 크기:", queueLinkedList.size()); // 출력: 3
console.log("연결리스트 큐의 front:", queueLinkedList.front()); // 출력: A
console.log("dequeue 결과:", queueLinkedList.dequeue());        // 출력: A

 


  • 대중적인 방법은 코드가 간단하지만, shift()로 인해 성능 이슈 있을 수 있음.
  • 배열 방식은 인덱스 관리로 효율성을 높인 방법이고,
  • 연결리스트 방식은 메모리 재할당 없이 깔끔하게 큐를 관리할 수 있는 방법
 

'Study > 코딩테스트(JS)' 카테고리의 다른 글

08.해시  (0) 2025.02.25
06. 스택  (0) 2025.02.25
05.배열  (0) 2025.02.18
04.코딩테스트 필수 문법  (1) 2025.02.11
03.알고리즘의 효율 분석(BigO,시간&공간 복잡도)  (0) 2025.01.21