우리는 미진이를 통해서 스택과 큐를 알아보았어요. 나름 재미있었다고 생각을 했는데... 읽으시는 분들은 아니었을까요? 흑흑.
스택과 큐를 공부하면서 많은 도움을 받았던 유튜브 링크까지 드립니다. 요기에 큐까지 있으니까 관심 있으신 분들은 함 봐 보세요!
우리가 배웠던 큐, 그러니까, 일직선으로 있는 선형 큐 같은 경우에는 단점이 좀 있는데요.
자바스크립트 같은 경우엔.. 배열로 큐를 구현한다고 했을 때, 인덱스 0인 요소를 빼 버리면 바로 뒤에 있는 요소가 0이 되잖아요? 그래서 큐를 돌릴 때 딱히 문제가 되지 않아요.
그런데, 이제, JS가 아닌 다른 언어들은 배열의 크기가 정해져 있기 때문에, 큐가 다 차게 되면 데이터를 더 추가할 수 없고(할 수는 있지만 시간이 너무 오래 걸리기도 해요), 빼낸 메모리들의 빈 공간을 사용할 수가 없어서 메모리가 쓸데없이 사용이 되기도 합니다. 그리고 계속 뒤로 밀리는 시스템이기 때문에, 할 때마다 데이터들을 앞으로 한 칸씩 당기게 되면 우리는 해피 코딩을 할 수 없게 될 거예요.
그래서 이를 보완한 점이 환형 큐, Circular Queue입니다.
선형 큐의 문제점을 보완하기 위한 자료 구조로, 큐의 처음(front)과 끝(rear)가 붙어 있는 모습인데요. 그 모습이, 음, 저는 그냥 도넛 같았습니다.
요롷게 생긴 친구인데, 이왕 도넛 같다고 한 거, 도라에몽의 암기 빵처럼 만들어 봅니다.
7과 0이 붙어 있는 거 보이시죠? 이렇게 큐를 동그랗게 말았는데요, 보시는 것처럼 원형 큐는 크기의 제한을 두어야 됩니다.
흐름을 설명하기 전에 알아두어야 할 게 있습니다.
원형 큐 중 인덱스 하나는 공백으로 남겨야 한다.
제일 처음 시작하는 인덱스를 공백으로 남겨 놓고 그 다음 인덱스부터 데이터 삽입이 이뤄지는데요, 우선삽입 후 데이터를 빼내는 것이기 때문에 그런 것도 있고, 큐의 포화 상태 여부를 판단하기 위해서도 필요합니다.
'front와 rear가 같이 있다면 공백으로 하자'라는 약속을 해 놓았기 때문에 항상 dequeue로 공백을 유지해야 하는 front의 자리는 empty로 남겨 놓습니다.
enqueue(이하 인큐)를 사용하여 데이터를 넣었습니다.
rear를 단순히 +1을 해 준 게 아니라, 약간 특별한 수식으로 넣어 줬는데요.
rear = (rear + 1) % size
입니다. 큐의 사이즈를 나누었죠? 이는 원형 큐이기 때문이에요. 큐 사이즈의 나머지로 연산을 함으로써 계속 빙빙 돌 수 있게 만드는 원리입니다.
계속 데이터를 넣어 볼게요.
딸기잼(data)을 7번까지 발랐고, 이제는 0번까지 다 채우고 싶은데 그게 안 되네요. 왜일까요? rear = (rear + 1) % size
이 수식을 썼을 때도 0이 나와서, 당연히 들어가야 되는데...
앞서 말했지만, front는 큐의 포화 상태를 위해서라도 항상 공백이어야 합니다. 큐의 공백이 딱 한 개 남았기 때문에 포화 상태로 인식하고, 데이터를 더 넣지 않습니다.
자, dequeue(이하 데큐)를 사용해서 데이터를 빼 봅시다.
(혹시 진짜 식빵에 바른 토핑만 긁어서 드시는 분 계신가요? 제가 식당 일 할 때 진짜 이런 분 계셔서, 아직도 뇌리에 남네요.)
데큐를 4번 실행한 결과입니다. front는 4에 가 있구요. 물론 front도 rear와 마찬가지로 front = (front + 1) % size
를 작성해서 큐의 사이즈 안에서 놀아나게 합니다. 공백이 많이 늘었네요? 데이터를 더 넣을 수 있겠어요.
이런 식으로 데이터는 빠져나가고, 들어오는 과정을 거칩니다.
환형 큐로 오면서 몇 가지 추가가 된 게 있어 보이네요!
enqueue
dequeue
size
front
rear
까지는 같은데,
isempty : 큐가 비어 있는지 확인
isfull : 큐가 꽉 찼는지 확인
이게 꼭 필요해 보입니다.
rear = (rear + 1) % size
의 인덱스에 데이터 넣음front = (front + 1) % size
인덱스에 담긴 데이터를 반환함.rear = (rear + 1) % size
를 했을 때 front 값이 나온다면 true를 반환 아니라면 false를 반환class Oqueue {
constructor () {
this.storage = {};
this.front = 0;
this.rear = 0;
this.size = 7; //임의로 작게 설정
}
isFull() {
return (this.rear + 1) % this.size === this.front;
}
isEmpty() {
return this.rear === this.front
}
enqueue(element) {
if(this.isFull()) return 'storage에 공간이 없습니다.'
else {
this.storage[this.rear = (this.rear + 1) % this.size] = element;
return this.storage;
}
}
dequeue() {
if(this.isEmpty()) return 'storage에 데이터가 없습니다.'
else {
let curData = this.storage[this.front = (this.front + 1) % this.size];
delete this.storage[this.front % this.size]
return curData;
}
}
}
사실 우선순위 큐까지 작성하려고 했으나 트리 구조에 대해 아직 공부하지 않아서, Tree를 공부한 후에 우선순위 큐는 따로 정리를 해야겠네요
혹시 잘못된 정보가 있다면 알려 주세요! 정확한 정보는 나와 우리 모두를 살립니다