자바스크립트로 자료구조에 대해 공부하다 스택(Stack)을 배열로 구현하고 큐(Queue)도 똑같이 배열로 구현했을 때 큐의 시간복잡도가 늘어나는 것을 보고 이유를 찾게 되었다.
자바스크립트엔 스택과 큐가 따로 내장되어 있지 않기 때문에 배열과 내장함수들을 이용하여 스택과 큐를 흉내내어 만드는데 배열로 스택과 큐를 구현하였을 때 element가 제거되는 방식에서 사용되는 두 내장함수 Array.prototype.pop() 과 Array.prototype.shift()의 차이에 의해 시간 복잡도가 달라진다.
그래서 시간 복잡도를 이해하고 스택과 큐에 적용하기 위해서는 스택과 큐의 구현 방식인 배열(Array) 방식과 연결리스트(Linked List) 방식의 차이를 이해해야 한다.
속성
메소드
스택 (Stack) 사용 사례
처음 문제를 풀 때는 무작정 if문을 사용하여 구현하였는데 스택을 이해하고 클래스로 구현하니 코드가 더 쉽게 이해가 된다.
class Stack {
constructor() {
this.items = [];
}
push(item) {
this.items.push(item);
}
pop() {
return this.items.pop();
}
// pop과 달리 아이템은 유지한 채 값만 받아온다.
peek() {
if (this.items.length === 0) {
return null;
}
return this.items[this.items.length - 1];
}
getSize() {
return this.items.length;
}
isEmpty() {
return this.getSize() === 0;
}
}
연결리스트로 구현하니 size를 어떻게 가져올지 고민이었다. 결국 stack에 index 변수를 담아 관리하기로 했다. 연결리스트에 대한 개념은 이해했으나 구상이 제대로 되지 않았는데 스택을 이해하고 이를 연결리스트로 구현하니 머릿속에서 좀 더 구체화가 된 것 같다.
class Node {
constructor(data) {
this.data = data;
this.next = null;
}
}
class Stack {
constructor() {
this.top = null
this.index = 0;
}
isEmpty() {
return this.top === null;
}
push(data) {
// ex) data = 13
const newNode = new Node(data);
// newNode.data = 13, newNode.next = null
newNode.next = this.top;
// stack.top = newNode {data : 13, next = null}
this.top = newNode;
this.index++ ;
}
pop() {
if (this.isEmpty()) return;
let re = this.top
this.top = this.top.next;
this.index-- ;
return re.data
}
peek() {
if (this.isEmpty()) return false;
return this.top.data;
}
size(){
return this.index;
}
}
스택에서는 배열과 연결리스트에서 시간 복잡도 차이는 크게 생기지 않는다. 그 이유는 둘 다 최상단의 데이터를 삽입, 삭제하며 관리하기 때문에 모두 O(1)의 시간 복잡도를 갖는다. 하지만 큐에서는 두 방식에서 시간 복잡도의 차이가 발생한다.
속성
메소드
큐 (Queue) 사용 사례
데이터가 입력된 시간 순서대로 처리해야 할 필요가 있는 상황에 이용한다.
스택과 다른점은 가장 앞에 있는 element를 추출하기 위해 pop() 대신 shift() 메서드를 사용한다는 것이다. shift()는 가장 앞에 있는 요소를 추출하고 나머지 요소들을 앞으로 당겨줘야하기 때문에 O(n)의 시간복잡도를 갖게 된다. 그래서 시간복잡도를 O(1)로 하기 위해 연결리스트 방식을 사용해야 정석적인 큐(Queue)를 만들 수 있다.
export default class Queue {
constructor() {
this.items = [];
}
enqueue(item) {
this.items.push(item);
}
dequeue() {
return this.items.shift();
}
peek() {
return this.items[0];
}
getSize() {
return this.items.length;
}
isEmpty() {
return this.getSize() === 0;
}
}
연결리스트로 큐를 구현하여 element를 추출하는 과정에서 shift() 메서드를 사용하지 않아 시간복잡도 O(1)을 구현할 수 있다.
class Node {
constructor(data) {
this.data = data;
this.next = null;
}
}
class Queue {
constructor() {
this.front = null;
this.rear = null;
this.index = 0;
}
isEmpty() {
return this.front == null && this.rear === null;
}
insert(data) {
const newNode = new Node(data);
if (this.isEmpty()) this.front = newNode;
else this.rear.next = newNode;
this.rear = newNode;
this.index++;
}
remove() {
if (this.isEmpty()) return;
this.front = this.front.next;
if (!this.front) this.rear = null;
this.index--;
}
peekFront() {
if (this.isEmpty()) return false;
return this.front.data;
}
size(){
return this.index;
}
}
위에서 계속 언급했던 것처럼 shift() 메서드를 사용하면 뒤 요소들을 앞으로 당겨줘야 하기 때문에 시간복잡도가 O(n)이 되지만 연결리스트로 큐를 구현하면 가장 앞에 있는 요소만 제거하면 되기 때문에 O(1)의 시간복잡도를 갖는다.
여러 가지 자료구조 중 스택과 큐에 대하여 알아보았는데 처음 개념 공부를 하고 문제를 풀 때는 어떻게 문제에 적용할지 어려웠는데 문제를 먼저 풀어서도 있겠지만 개념을 클래스로 구현하여 다시 정리해보니 확실히 이해가 더 된 느낌이다.
오 이해가 쏙쏙 되네요 !!