理解和应用JavaScript中的队列数据结构

理解和应用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
  • 算法步骤
  1. 创建一个虚拟头节点dummyHead和一个当前节点指针current,用于构建合并后的有序链表。

  2. 创建一个最小堆minHeap,比较函数为比较ListNode对象的val属性。

  3. 将K个有序链表的头节点依次加入到最小堆minHeap中。

  4. 循环处理最小堆,直到堆为空:

    • 取出堆顶元素node(最小节点),将其接入到结果链表中(current.next = node)。
    • 更新当前节点指针current为当前节点nodecurrent = current.next)。
    • 如果最小节点node还有下一个节点(node.next),将其加入到最小堆minHeap中。
  5. 返回虚拟头节点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个升序链表的算法演示了队列在算法中的应用。

相关推荐
WeiShuai11 分钟前
vue-cli3使用DllPlugin优化webpack打包性能
前端·javascript
forwardMyLife15 分钟前
element-plus的面包屑组件el-breadcrumb
javascript·vue.js·ecmascript
ice___Cpu17 分钟前
Linux 基本使用和 web 程序部署 ( 8000 字 Linux 入门 )
linux·运维·前端
JYbill19 分钟前
nestjs使用ESM模块化
前端
加油吧x青年38 分钟前
Web端开启直播技术方案分享
前端·webrtc·直播
吕彬-前端1 小时前
使用vite+react+ts+Ant Design开发后台管理项目(二)
前端·react.js·前端框架
小白小白从不日白1 小时前
react hooks--useCallback
前端·react.js·前端框架
恩婧2 小时前
React项目中使用发布订阅模式
前端·react.js·前端框架·发布订阅模式
mez_Blog2 小时前
个人小结(2.0)
前端·javascript·vue.js·学习·typescript
珊珊而川2 小时前
【浏览器面试真题】sessionStorage和localStorage
前端·javascript·面试