理解和应用JavaScript中的队列数据结构
在JavaScript中,队列(Queue)是一种常见的数据结构,遵循先进先出(FIFO)的原则。在JavaScript中,可以使用数组来实现队列的基本功能
队列的基础
- 队列是一种线性数据结构,遵循先进先出(FIFO)的原则,即最先入队的元素最先出队。
- 队列通常包括入队(enqueue)、出队(dequeue)、查看队首元素(peek)、检查队列是否为空(isEmpty)等基本操作。
队列的主要特性
- 先进先出(FIFO):队列中元素的处理顺序严格按照入队的顺序进行,最先入队的元素最先被处理。
- 队列为空时无法进行出队操作:当队列为空时,无法执行出队操作,只能等待队列中有新元素入队。
- 简单高效:使用数组实现队列操作简单高效,可以直接利用数组的push和shift方法实现入队和出队操作。
js中数组作为队列的优势
- 简单易用:使用数组实现队列操作简单易懂,不需要额外的数据结构或复杂的算法。
- 内置方法支持:数组提供了丰富的内置方法,如push、shift等,方便实现队列的基本操作。
简单示例
js
// 队列的实现
class Queue {
constructor() {
this.queue = [];
}
enqueue(item) {
this.queue.push(item);
}
dequeue() {
return this.queue.shift();
}
peek() {
return this.queue[0];
}
isEmpty() {
return this.queue.length === 0;
}
size() {
return this.queue.length;
}
}
// 使用示例
let myQueue = new Queue();
myQueue.enqueue(1);
myQueue.enqueue(2);
console.log(myQueue.peek()); // 输出 1
console.log(myQueue.dequeue()); // 输出 1
console.log(myQueue.size()); // 输出 1
console.log(myQueue.isEmpty()); // 输出 false
队列的操作图示
图示说明
以上代码示例展示了一个基于数组实现的队列类Queue,具有入队、出队、查看队首元素、检查队列是否为空以及获取队列大小等基本操作。通过调用这些方法,可以实现队列的基本功能并进行相关操作。
在这个示例中,我们首先创建了一个空队列myQueue,然后依次向队列中添加元素1和2。通过调用peek方法,我们可以查看队首元素,即输出1。接着我们执行出队操作,将队首元素1移出队列并输出,然后通过调用size方法获取队列大小,输出1。最后,我们调用isEmpty方法检查队列是否为空,输出false。
队列的算法
合并 K 个升序链表
- 给你一个链表数组,每个链表都已经按升序排列。 请你将所有链表合并到一个升序链表中,返回合并后的链表。
- 示例 1:
rust
输入: lists = [[1,4,5],[1,3,4],[2,6]]
输出: [1,1,2,3,4,4,5,6]
解释: 链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
- 提示: k == lists.length 0 <= k <= 10^4 0 <= lists[i].length <= 500 -10^4 <= lists[i][j] <= 10^4 lists[i] 按 升序 排列 lists[i].length 的总和不超过 10^4
- 算法步骤:
-
创建一个虚拟头节点
dummyHead
和一个当前节点指针current
,用于构建合并后的有序链表。 -
创建一个最小堆
minHeap
,比较函数为比较ListNode
对象的val
属性。 -
将K个有序链表的头节点依次加入到最小堆
minHeap
中。 -
循环处理最小堆,直到堆为空:
- 取出堆顶元素
node
(最小节点),将其接入到结果链表中(current.next = node
)。 - 更新当前节点指针
current
为当前节点node
(current = current.next
)。 - 如果最小节点
node
还有下一个节点(node.next
),将其加入到最小堆minHeap
中。
- 取出堆顶元素
-
返回虚拟头节点
dummyHead
的下一个节点,即合并后的有序链表的头节点。
- 完整代码:
js
class ListNode {
constructor(val = 0, next = null) {
this.val = val; // 节点的值
this.next = next; // 指向下一个节点的指针
}
}
class MinHeap {
constructor(comparator = (a, b) => a - b, data = []) {
this.data = data; // 堆的数据存储
this.comparator = comparator; // 比较函数,默认为升序
this.heapify(); // 堆化操作,将data数组转化为堆
}
heapify() {
if (this.size() < 2) return;
for (let i = Math.floor(this.size() / 2) - 1; i >= 0; i--) {
this.bubbleDown(i); // 从非叶子节点开始进行下沉操作,保持堆的性质
}
}
peek() {
if (this.size() === 0) return null;
return this.data[0]; // 返回堆顶元素(最小元素)
}
offer(value) {
this.data.push(value); // 将新元素推入堆的末尾
this.bubbleUp(this.size() - 1); // 对新元素进行上浮操作,维护堆的性质
}
poll() {
if (this.size() === 0) {
return null;
}
const result = this.data[0]; // 取出堆顶元素
const last = this.data.pop(); // 取出堆的最后一个元素
if (this.size() !== 0) {
this.data[0] = last; // 将最后一个元素放到堆顶
this.bubbleDown(0); // 对堆顶元素进行下沉操作,维护堆的性质
}
return result; // 返回原堆顶元素(最小元素)
}
bubbleUp(index) {
while (index > 0) {
const parentIndex = (index - 1) >> 1; // 计算父节点的索引
if (this.comparator(this.data[index], this.data[parentIndex]) < 0) {
this.swap(index, parentIndex); // 如果当前节点比父节点小,交换它们
index = parentIndex; // 更新当前节点的索引为父节点索引,继续向上比较
} else {
break; // 如果当前节点大于等于父节点,则堆的性质已经满足,退出循环
}
}
}
bubbleDown(index) {
const lastIndex = this.size() - 1; // 堆的最后一个元素索引
while (true) {
const leftIndex = index * 2 + 1; // 左子节点索引
const rightIndex = index * 2 + 2; // 右子节点索引
let findIndex = index; // 待交换的节点索引,默认为当前节点
// 找出当前节点、左子节点、右子节点中的最小值
if (leftIndex <= lastIndex && this.comparator(this.data[leftIndex], this.data[findIndex]) < 0) {
findIndex = leftIndex;
}
if (rightIndex <= lastIndex && this.comparator(this.data[rightIndex], this.data[findIndex]) < 0) {
findIndex = rightIndex;
}
// 如果当前节点就是最小值,则堆的性质已经满足,退出循环
if (index !== findIndex) {
this.swap(index, findIndex); // 否则交换当前节点与最小节点
index = findIndex; // 更新当前节点索引为最小节点索引,继续向下比较
} else {
break;
}
}
}
swap(index1, index2) {
[this.data[index1], this.data[index2]] = [this.data[index2], this.data[index1]]; // 交换两个元素的位置
}
size() {
return this.data.length; // 返回堆的大小
}
}
var mergeKLists = function(lists) {
const dummyHead = new ListNode(0); // 创建虚拟头节点
let current = dummyHead; // 初始化当前节点指针
// 创建一个最小堆,比较函数为比较ListNode对象的val属性
const minHeap = new MinHeap((a, b) => a.val - b.val);
// 将每个链表的头节点加入到堆中
lists.forEach(list => {
if (list) {
minHeap.offer(list);
}
});
// 循环处理堆,直到堆为空
while (minHeap.size() > 0) {
const node = minHeap.poll(); // 取出堆顶元素(最小节点)
current.next = node; // 将最小节点接入到结果链表中
current = current.next; // 更新当前节点指针
// 如果最小节点还有下一个节点,将其加入到堆中
if (node.next) {
minHeap.offer(node.next);
}
}
return dummyHead.next; // 返回合并后的有序链表的头节点
};
小结
这篇文章深入探讨了JavaScript中队列数据结构的基础知识和实现示例。内容涵盖了队列的定义、特性,以及使用数组实现队列的优势。文章还展示了队列的常见操作示例,并通过合并K个升序链表的算法演示了队列在算法中的应用。