/ ALGORITHM, JAVASCRIPT, TYPESCRIPT

JS TS 로 Queue 구현

Object(or Map) 을 활용한 Queue

javascript
class ObjectQueue {
    item = {};
    start = 0;
    end = 0;

    constructor() {}

    enqueue(value){
        this.item[this.end] = value;
        this.end += 1;
        return this.end - 1;
    }
    
     // 큐가 비어 있을 때는 undefined, 그렇지 않은 경우 0 번째 원소를 반환
    dequeue(){
        if(this.isEmpty()) return undefined;
        const value = this.item[this.start];
        delete this.item[this.start];
        this.start += 1;
        return value;
    }

    isEmpty(){
        if(this.start === this.end) {
            this.start = 0;
            this.end = 0;
            return true;
        }
        return false;
    }
}

Object 나 Map 을 활용해서 key-value 형식으로 저장한다.

다만 Object, Map 이 사용하는 메모리가 다른 방법에 비해 많아, 메모리 효율은 다소 떨어질 수 있다.

LinkedList 를 활용한 Queue

javascript
class Node {
    value = null;
    next = null
    constructor(value) {
        this.value = value;
    }
}

class LinkedListQueue {
    front = null;
    rear = null;
    length = 0;

    enqueue(value){
        const newNode = new Node(value);
        // rear 가 null 인, 초기 상황일 때
        if(!this.rear) {
            this.front = this.rear = newNode;
        } else {
            // 현재 맨 마지막 노드의 다음 노드를 newNode 로 지정하고, newNode 를 마지막 노드로 지정
            this.rear.next = newNode;
            this.rear = newNode;
        }
        this.length += 1;
    }

    dequeue() {
        if(!this.front) {
            return undefined;
        }
        const value = this.front.value;
        this.front = this.front.next;

        // 하나를 dequeue 하고 Queue 에 아무것도 없을 때
        if(!this.front) {
            this.rear = null;
        }
        this.length -= 1;

        return value;
    }
}

원형 큐

typescript
class CircularQueue<T> {
    private readonly items: (T | null)[];
    private readonly capacity: number;
    private front: number;
    private rear: number;
    private size: number;

    constructor(capacity: number = 8) {
        this.capacity = capacity;
        this.items = new Array(capacity).fill(null);
        this.front = 0;
        this.rear = 0;
        this.size = 0;
    }

    enqueue(value: T) {
        if (this.isFull()) return false;
        this.items[this.rear] = value;

        // 여기서 만약 capacity 를 넘어가면, 0부터 시작해서 front 까지 다시 사용
        this.rear = (this.rear + 1) % this.capacity;
        this.size++;
        return true;
    }

    dequeue() {
        if (this.isEmpty()) return undefined;
        const value = this.items[this.front];
        this.items[this.front] = null;
        
         // 여기서 만약 capacity 를 넘어가면, 0부터 시작해서 rear 까지 다시 사용
        this.front = (this.front + 1) % this.capacity; 
        this.size--;
        return value;
    }

    isEmpty() {
        return this.size === 0;
    }

    isFull() {
        return this.size === this.capacity;
    }
}


원형 큐는 고정 사이즈를 정하고, 고정 사이즈만큼의 배열을 순환하면서 사용할 수 있다.

여기서 만약 배열이 꽉 찼는데도 enqueue 가 일어나면 문제가 된다. 배열에 이미 값이 존재하는데 그 위에 덮어쓰게 되기 때문이다.

따라서 원형큐가 가득 찼으면 배열을 그 순간에 늘리거나 해야한다.

이 과정에서 기존 배열을 복사해야해서 시간이 많이 소요된다.

이렇게 보면 원형큐는 dequeue 보다 enqueue 가 월등히 많은 경우, 썩 좋지 못할 것 같다.

원형 덱(deque, Double Ended Queue)

typescript
class CircularDeque<T> {
    private items: (T | null)[];
    private capacity: number;
    private front: number;
    private rear: number;
    private size: number;

    constructor(capacity: number = 8) {
        this.capacity = capacity;
        this.items = new Array(capacity).fill(null);
        this.front = 0;
        this.rear = 0;
        this.size = 0;
    }

    enqueueFront(value: T) {
        if (this.isFull()) this.resize();
        this.front = (this.front - 1 + this.capacity) % this.capacity;
        this.items[this.front] = value;
        this.size++;
    }

    enqueueBack(value: T) {
        if (this.isFull()) this.resize();
        this.items[this.rear] = value;
        this.rear = (this.rear + 1) % this.capacity;
        this.size++;
    }

    dequeueFront() {
        if (this.isEmpty()) return undefined;
        const value = this.items[this.front];
        this.items[this.front] = null;
        this.front = (this.front + 1) % this.capacity;
        this.size--;
        return value as T;
    }

    dequeueBack() {
        if (this.isEmpty()) return undefined;
        this.rear = (this.rear - 1 + this.capacity) % this.capacity;
        const value = this.items[this.rear];
        this.items[this.rear] = null;
        this.size--;
        return value as T;
    }

    peekFront() {
        return this.isEmpty() ? undefined : (this.items[this.front]);
    }


    peekBack() {
        return this.isEmpty() ? undefined : (this.items[(this.rear - 1 + this.capacity) % this.capacity]);
    }

    isEmpty() {
        return this.size === 0;
    }

    isFull() {
        return this.size === this.capacity;
    }

    getSize() {
        return this.size;
    }

    private resize() {
        const newCapacity = this.capacity * 2;
        const newItems = new Array(newCapacity).fill(null);

        for (let i = 0; i < this.size; i++) {
            newItems[i] = this.items[(this.front + i) % this.capacity];
        }

        this.items = newItems;
        this.capacity = newCapacity;
        this.front = 0;
        this.rear = this.size;
    }
}


원형 큐와 큰 차이는 없다.

여기서 원형 큐나, 원형 데크나 resize 부분을 알아둬야한다.

Resize

typescript
    private resize() {
        const newCapacity = this.capacity * 2;
        const newItems = new Array(newCapacity).fill(null);

        for (let i = 0; i < this.size; i++) {
            newItems[i] = this.items[(this.front + i) % this.capacity];
        }

        this.items = newItems;
        this.capacity = newCapacity;
        this.front = 0;
        this.rear = this.size;
    }

resize 부분을 보면 capacity 를 2배(혹은 원하는 만큼) 늘리고나서

for 문 내부에서 아이템을 복제 및 정렬을 한다.

언뜻 생각해보면 capacity 만 2배로 복제해도 되지 않을까 싶지만 기존 배열의 순서를 정렬해서 새로운 배열에 적용해야만 한다.

예시를 들어서 살펴보면,

기존 상태 (1,2,3,4 덱에 삽입)

typescript
Index:      0      1      2      3
Items:    [ 1  ] [ 2  ] [ 3  ] [ 4  ]  
Front → index 0
Rear  → index 0 (다음 삽입 위치)

1, 2 dequeue

typescript
Index:      0      1      2      3
Items:    [ X  ] [ X  ] [ 3  ] [ 4  ]  
Front → index 2
Rear  → index 0 (다음 삽입 위치)

5, 6 enqueue

typescript
Index:      0      1      2      3
Items:    [ 5  ] [ 6  ] [ 3  ] [ 4  ]  
Front → index 2
Rear  → index 2 (가득 참)

7 enqueue (Resize 실행)

단순히 Capacity 만 늘리는 경우(잘못된 경우)

typescript
	Index:      0      1      2      3      4      5      6      7
	Items:    [ 5  ] [ 6  ] [ 3  ] [ 4  ] [ X  ] [ X  ] [ X  ] [ X  ]
	Front → index 2
	Rear  → index 4
	

3 → 4→ 5→ 6 순서가 아니라, 5 → 6 → 3→ 4 가 되어버린다.

따라서 3,4,5,6 순서로 정렬시켜주는 과정이 필요

resize 및 정렬 과정(올바른 경우)

typescript
Index:      0      1      2      3      4      5      6      7
Items:    [ 3  ] [ 4  ] [ 5  ] [ 6  ] [ X  ] [ X  ] [ X  ] [ X  ]
Front → index 0
Rear  → index 4