A deque, short for double-ended queue, is a versatile data structure that allows efficient insertion and deletion operations at both ends. Unlike a standard queue, which only supports operations from one end, a deque provides flexibility in managing elements from both the front and back. This makes deques particularly useful in scenarios where you need to perform frequent insertions and deletions on either side of the collection.
One common way to implement a deque is using an array that dynamically resizes as needed. This approach provides constant time complexity for most operations but may require additional overhead for resizing.
class ArrayDeque {
constructor() {
this.items = [];
}
addFront(element) {
this.items.unshift(element);
}
removeFront() {
if (this.isEmpty()) return undefined;
return this.items.shift();
}
peekFront() {
if (this.isEmpty()) return undefined;
return this.items[0];
}
addBack(element) {
this.items.push(element);
}
removeBack() {
if (this.isEmpty()) return undefined;
return this.items.pop();
}
peekBack() {
if (this.isEmpty()) return undefined;
return this.items[this.items.length - 1];
}
isEmpty() {
return this.items.length === 0;
}
size() {
return this.items.length;
}
}
Another efficient way to implement a deque is using a doubly linked list. This approach ensures that all operations are performed in constant time without the overhead of resizing.
class Node {
constructor(element) {
this.element = element;
this.next = null;
this.prev = null;
}
}
class LinkedListDeque {
constructor() {
this.front = null;
this.back = null;
this.size = 0;
}
addFront(element) {
const newNode = new Node(element);
if (this.isEmpty()) {
this.front = newNode;
this.back = newNode;
} else {
newNode.next = this.front;
this.front.prev = newNode;
this.front = newNode;
}
this.size++;
}
removeFront() {
if (this.isEmpty()) return undefined;
const element = this.front.element;
this.front = this.front.next;
if (this.front) this.front.prev = null;
else this.back = null;
this.size--;
return element;
}
peekFront() {
if (this.isEmpty()) return undefined;
return this.front.element;
}
addBack(element) {
const newNode = new Node(element);
if (this.isEmpty()) {
this.front = newNode;
this.back = newNode;
} else {
newNode.prev = this.back;
this.back.next = newNode;
this.back = newNode;
}
this.size++;
}
removeBack() {
if (this.isEmpty()) return undefined;
const element = this.back.element;
this.back = this.back.prev;
if (this.back) this.back.next = null;
else this.front = null;
this.size--;
return element;
}
peekBack() {
if (this.isEmpty()) return undefined;
return this.back.element;
}
isEmpty() {
return this.size === 0;
}
size() {
return this.size;
}
}
Deques are powerful data structures that offer flexibility and efficiency in managing collections of elements from both ends. By understanding their characteristics, use cases, and implementation details, you can leverage deques to solve complex problems in various domains. Whether you're implementing algorithms, designing systems, or working on software projects, mastering deques will undoubtedly enhance your ability to handle data efficiently.