javascript:队列详解

266 阅读3分钟

在计算机科学中,队列(Queue)是一种常见的数据结构,它遵循先进先出(First-In-First-Out,FIFO)的原则。队列通常用于需要按照顺序处理的任务,比如任务调度、缓冲、广度优先搜索等场景。本文将介绍队列的概念、基本操作以及通过一个经典算法问题——"二叉树的层序遍历"来解释队列的应用。

什么是队列?

队列是一种线性数据结构,其特点是只允许在队列的一端(通常称为队尾)插入元素,在另一端(通常称为队首)删除元素。这种特性使得队列中的元素按照先进先出的顺序进行处理。在生活中像我们去食堂排队打饭一样。

image.png

队列的基本操作

队列的基本操作包括:

  1. 入队(Enqueue) :将元素添加到队列的末尾。
  2. 出队(Dequeue) :从队列的头部删除一个元素,并返回被删除的元素。
  3. 获取队首元素(Front) :获取队列头部的元素,但不对队列做任何修改。
  4. 获取队列长度(Size) :获取队列中元素的个数。
  5. 判空(isEmpty) :判断队列是否为空。

队列的实现

队列可以通过数组或链表来实现。在本文中,我们将使用数组来实现一个简单的队列。

class Queue {
    constructor() {
        this.items = [];
    }

    enqueue(element) {
        this.items.push(element);
    }

    dequeue() {
        if (this.isEmpty()) {
            return "Underflow";
        }
        return this.items.shift();
    }

    front() {
        if (this.isEmpty()) {
            return "No elements in Queue";
        }
        return this.items[0];
    }

    isEmpty() {
        return this.items.length === 0;
    }

    size() {
        return this.items.length;
    }
}

算法题

力扣239题

image.png

我们最容易想到的方法是暴力解法:

var maxSlidingWindow = function(nums, k) {
    const result = [];
    const n = nums.length;

    // 边界情况处理
    if (n <= k) {
        return [Math.max(...nums)];
    }

    // 找出每个窗口的最大值
    for (let i = 0; i <= n - k; i++) {
        let max = nums[i];
        for (let j = i + 1; j < i + k; j++) {
            max = Math.max(max, nums[j]);
        }
        result.push(max);
    }

    return result;
};

尽管这段代码可以正确地找到滑动窗口中的最大值,但它的时间复杂度较高。在每个窗口内部使用了一个嵌套的循环来寻找最大值,导致时间复杂度为 O(kn),其中 n 是数组的长度。在 k 比较大的情况下,算法的性能会受到影响。在力扣中有n,k值很大的测试用例,所以会超出时间限制。

image.png

为了提高算法的效率,我们可以使用双端队列(deque)来优化。Deque 可以在 O(1) 的时间复杂度内完成插入和删除操作,从而降低整体算法的时间复杂度。

var maxSlidingWindow = function(nums, k) {
    let result = [];
    const deque = [];

    for (let i = 0; i < nums.length; i++) {
        // 移除 deque 中超出窗口范围的元素
        while (deque.length && deque[0] < i - k + 1) {
            deque.shift();
        }

        // 移除 deque 中小于当前元素的元素
        while (deque.length && nums[deque[deque.length - 1]] <= nums[i]) {
            deque.pop();
        }

        // 将当前元素加入 deque
        deque.push(i);

        // 如果当前索引大于等于 k - 1,则将 deque 的首位元素加入结果数组
        if (i >= k - 1) {
            result.push(nums[deque[0]]);
        }
    }

    return result;
};

在重构后的代码中,我们使用了一个双端队列 deque 来保存滑动窗口中可能成为最大值的元素的索引。通过调整窗口和维护 deque,我们可以在每次移动窗口时,以常数时间内获取当前窗口的最大值。

这个优化的算法时间复杂度为 O(n),其中 n 是数组的长度,因为在每个元素只会被操作一次。相比于原来的算法,这个算法在窗口滑动过程中无需遍历窗口内的所有元素,因此性能更高。