web前端算法简介之队列

发布时间:2024年01月13日

队列

队列(Queue)是一种常见的数据结构,它类似于排队的形式,遵循 先进先出(FIFO) 的原则。在队列中,元素在尾部添加,而在头部移除。这就类似于排队时,先来的人先服务,后来的人排在后面等待服务。

队列,和栈一样,也是一种对数据的"存"和"取"有严格要求的线性存储结构。

与栈结构不同的是,队列的两端都"开口",要求数据 只能从一端进,从另一端出 ,如图 1 所示:

通常,称进数据的一端为 "队尾",出数据的一端为 "队头",数据元素进队列的过程称为 "入队",出队列的过程称为 "出队"。

不仅如此,队列中数据的进出要遵循 “先进先出” 的原则,即最先进队列的数据元素,同样要最先出队列。

拿图 1 中的队列来说,从数据在队列中的存储状态可以分析出,元素 1 最先进队,其次是元素 2,最后是元素 3。

此时如果将元素 3 出队,根据队列 “先进先出” 的特点,元素 1 要先出队列,元素 2 再出队列,最后才轮到元素 3 出队列。

栈和队列不要混淆,栈结构是一端封口,特点是"先进后出";而队列的两端全是开口,特点是"先进先出"。

因此,数据从表的一端进,从另一端出,且遵循 “先进先出” 原则的线性存储结构就是队列。

队列基本操作

队列通常包括以下几种基本操作:

入队(enqueue):将元素添加到队列的尾部。
出队(dequeue):从队列的头部移除元素。
队首(front):获取队列头部的元素,但不移除它。
队尾(rear):获取队列尾部的元素,但不移除它。
判空(isEmpty):判断队列是否为空。
大小(size):获取队列中元素的数量。

队列常常用于实现广度优先搜索(BFS)、线程池、缓存等应用场景,是计算机科学中非常重要的数据结构之一。

更多详细内容,请微信搜索“前端爱好者戳我 查看

JvaScript 任务队列

为什么 JavaScript 是单线程?

JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事

那么,为什么JavaScript 不能有多个线程呢 ?这样能提高效率啊。

JavaScript 的单线程,与它的用途有关。

作为浏览器脚本语言,JavaScript 的主要用途是与用户互动,以及操作 DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。

比如,假定JavaScript 同时有两个线程,一个线程在某个 DOM 节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

所以,为了避免复杂性,从一诞生,JavaScript 就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。

一种非阻塞的事件循环机制就产生了:

  • 消息队列:消息队列是一个先进先出的队列,它里面存放着各种消息。
  • 事件循环:事件循环是指主线程重复从消息队列中取消息、执行的过程。

在JavaScript中,消息队列是事件驱动架构的核心部分,用于管理异步任务的执行顺序。

由于JavaScript在浏览器环境中被设计为单线程(即同一时间只能执行一个任务),为了处理异步操作(如网络请求、定时器、DOM事件等),它采用了一种非阻塞的事件循环机制。

事件循环(Event Loop)与消息队列的关系:

宏任务队列(Macrotask Queue):
  • 也称为Task Queue或Job Queue,包含那些需要在 主线程空闲时执行的任务
  • 宏任务包括:
    • 脚本整体代码、
    • setTimeout、
    • setInterval、
    • I/O、
    • UI渲染、
    • AJAX回调函数等。
微任务队列(Microtask Queue):
  • 又称Promise Job Queue,优先级高于宏任务队列
  • 微任务包括:
    • Promise.then/catch/finally、
    • MutationObserver、
    • process.nextTick(Node.js环境)、
    • async/await(内部基于Promise)等。

事件循环:

事件循环的基本流程如下:
  1. 初始阶段:JS引擎开始执行全局脚本(这也是一个宏任务)。

  2. 同步执行:从上到下逐行执行代码,遇到异步操作(如fetch请求或setTimeout调用)则不会阻塞执行流,而是将回调函数放入相应的消息队列。

  3. 检查微任务队列:当前宏任务执行完毕后,立即查看微任务队列是否有待执行的任务。如果有,则按先进先出(FIFO)原则执行所有微任务。

  4. 渲染阶段:在执行完所有的微任务之后,浏览器会进行一次渲染更新(如果有必要的话)。

  5. 检查宏任务队列:然后轮询宏任务队列,取出第一个宏任务来执行。

  6. 重复循环:以上步骤不断循环,形成了所谓的“事件循环”。

因此,在JavaScript中,消息队列不是单一的一个队列,而是至少包含两个层次的任务队列(宏任务队列和微任务队列)。

这种结构确保了即使在单线程环境下,也能实现异步编程和高效的并发处理能力。

这种机制就叫做事件循环机制,取一个消息并执行的过程叫做一次循环。

关于栈的前端算法题

最近的请求次数

写一个 RecentCounter 类来计算特定时间范围内最近的请求。

请你实现 RecentCounter 类:

  • RecentCounter() 初始化计数器,请求数为 0 。
  • int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。
    保证 每次对 ping 的调用都使用比之前更大的 t 值。

示例 1:

输入:
["RecentCounter", "ping", "ping", "ping", "ping"]
[[], [1], [100], [3001], [3002]]
输出:
[null, 1, 2, 3, 3]

解释:
RecentCounter recentCounter = new RecentCounter();
recentCounter.ping(1);     // requests = [1],范围是 [-2999,1],返回 1
recentCounter.ping(100);   // requests = [1, 100],范围是 [-2900,100],返回 2
recentCounter.ping(3001);  // requests = [1, 100, 3001],范围是 [1,3001],返回 3
recentCounter.ping(3002);  // requests = [1, 100, 3001, 3002],范围是 [2,3002],返回 3

提示:

1 <= t <= 109
保证每次对 ping 调用所使用的 t 值都 严格递增
至多调用 ping 方法 10^4 次

实现代码


var RecentCounter = function () {
    // 新建一个队列,用于存储数据
    this.stack = []
}


/** 
 * @param {number} t
 * @return {number}
 */
RecentCounter.prototype.ping = function (t) {
    // 把数据存储在队列中
    this.stack.push(t)
    while (this.stack[0] < t - 3000) {
        // 如果满足条件,则出队
        this.stack.shift(t)
    }

    return this.stack.length
};

/**
 * Your RecentCounter object will be instantiated and called as such:
 * var obj = new RecentCounter()
 * var param_1 = obj.ping(t)
 */

参考文档

  • https://c.biancheng.net/view/3352.html
文章来源:https://blog.csdn.net/BradenHan/article/details/135541278
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。