知蛛网
  • 首页
  • 会员
  • 活动
  • 工作台

常见问题
活动
关于我们
联系我们
隐私协议
用户协议
会员协议
© 2026 知蛛网. 保留所有权利。
首页会员工作台我的
flynn@flynn
主页wiki简历作品集
目录
全部书籍
00-路线
  • 学习路线
  • 命名与写作规范
01-计算机基础
01-计算机网络
  • 互联网是如何工作的
  • HTTP协议
  • TCP/IP协议
  • 浏览器直接打开本地html文件,请求时的协议问题
02-浏览器原理
  • 浏览器是如何渲染页面的
  • 浏览器是如何渲染页面的
  • 面试之浏览器
03-操作系统
  • 进程与线程
  • 内存管理与虚拟内存
  • IO 模型与多路复用
  • 文件系统与页缓存
04-数据结构与算法
  • 数组与链表
  • 栈与队列
  • 哈希表
  • 树与堆
  • 基础数据结构
01-基础数据结构
02-高级算法
  • 排序算法
  • 二分查找
  • 递归与回溯
  • 动态规划
  • 双指针与滑动窗口
03-leetcode刷题
02-前端核心
01-web开发
  • Web 和 Web 标准
  • web 入门
  • web 开发
  • URL
  • 域名
  • Web服务器
  • 命令行
  • 工作流程和流程
  • 超链接
  • 浏览器指纹
02-html
  • HTML基础语法
  • HTML 概述
  • HTML 发展史
  • HTML调试
  • HTML标题与段落
  • 文本格式化
  • 强调与重要性
  • 块级元素和内联元素
  • 列表
  • 区块
  • 语义元素
  • 页面结构化和语义化
  • 超链接
  • 相对路径和绝对路径
  • 图片
  • 表格
  • 表单和按钮
  • 多媒体
  • 视频/音频
  • HTML 头部
  • HTML 字符集
  • HTML颜色
  • HTML 语言代码
  • HTML 国家/地区
  • Emoji (表情符号)
  • HTML 标签简写及全称 |
  • SVG
  • Canvas 2D
  • Canvas 3D
  • 框架 `<iframe>`
  • HTML 拖放
  • HTML5 Geolocation(地理定位)
  • Web 存储
  • HTML URL 编码
  • MathML
  • 远程图片资源在自己网站中无法展示问题
03-css
  • CSS简介
  • CSS 语法
  • CSS 创建
  • CSS 发展史
  • CSS 浏览器支持
  • CSS 选择器
  • CSS `!important` 规则
  • css颜色
  • CSS 文本格式
  • CSS 盒子模型
  • CSS 尺寸
  • CSS 内边距
  • CSS 外边距
  • CSS 边框
  • CSS 轮廓
  • CSS 背景(background)
  • CSS 溢出
  • CSS 文档流
  • CSS 显示和可见性
  • CSS 浮动
  • CSS 定位
  • CSS 对齐方式
  • CSS 网页布局
  • CSS 多列布局(CSS Multi-column Layout)
  • CSS 透明度
  • CSS 响应式设计(Responsive Design)
  • CSS 媒体查询
  • CSS 列表
  • CSS 表格
  • CSS 表单
  • CSS 链接
  • CSS 用户界面
  • CSS 计数器
  • CSS 过渡
  • CSS 动画
  • CSS 2D 转换
  • CSS 3D 转换
  • CSS 变量(CSS Custom Properties)
  • CSS 原生嵌套(CSS Nesting)
  • CSS 函数(CSS Functions)
04-javascript
  • 什么是 JavaScript?
  • 编程范式(Programming Paradigm)
  • 什么是范式?
  • ECMAScript 规范
  • 什么是语法糖
  • 开发环境搭建
  • 什么是堆和栈?
  • 什么是执行上下文
  • JavaScript 垃圾回收机制
  • 作业队列与事件循环
  • 深拷贝和浅拷贝
  • 语法与类型
  • 字面量
  • 控制流与错误处理
  • 循环与迭代
  • 函数
  • 表达式与运算符
  • 数字和字符串
  • 表达日期与时间
  • 正则表达式
  • 索引集合类
  • 带键的集合
  • 对象
  • 类
  • Promise
  • async/await
  • 迭代器和生成器
  • 国际化
  • 模块
  • 资源管理
  • 属性的可枚举性和所有权
  • 闭包
  • 继承与原型链
  • 元编程
  • 内存管理
  • 内置对象
  • arguments 对象
  • BOM 对象(浏览器对象模型)
  • DOM 对象(文档对象模型)
  • bind、apply、call的区别
  • new 操作符
  • typeof 和 instanceof 的区别
  • HTML5 Web Workers
05-typescript
  • TypeScript 类型系统基础
  • 类型体操
03-框架与跨端
01-react
  • 开发环境搭建
  • JSX
  • 组件
  • 生命周期
  • Hooks
  • 组件通信
  • 状态管理
  • React 19 新特性
  • useCallback 闭包陷阱
  • 常用的性能优化操作
  • Firbe 架构
  • React 18 新特性
  • useRef
  • 基于umijs 4.0 搭建官网
  • react+vite本地开发,如何手机联调
  • React 实现谷歌登录
  • React 项目调起 WhatsApp
02-vue
  • vue 解决跨域请求
  • 在vue2使用TinyMCE富文本编辑器
  • 使用jszip 导出新闻
  • Vue 本地开发启用 https
  • 基于vue3+vite+ts+elementplus+pinia+axios搭建后台管理系统
03-react-native
  • iOS 开发环境(RN 0.72)
  • RN 原生模块(Native Module)
  • 查看Maven包的Java包名
  • 优化 APP 启动速度
  • 安全键盘
  • 启动图与 Icon 尺寸规范
  • RN如何生成/配置iOS的开发者证书和生产证书
  • 邓白氏编码
  • 微信开放平台配置Universal Link
  • 上架至Apple App Store
04-android
  • 开发环境搭建
  • Android项目目录结构
  • 认识Gradle
04-工程化
01-git
  • 如何解决已被git跟踪文件不在跟踪
  • git标签
  • Git报错: Failed to connect to github.com port 443 解决方案
  • Git 分支操作速查
02-构建工具
  • 构建工具原理与选型
02-webpack
  • webpack 核心概念与构建流程
  • webpack 性能优化
03-vite
  • vite 为什么快
  • vite 插件与生产构建
03-测试
  • 前端测试体系
04-部署
  • Nginx 反向代理解决跨域
  • SPA 刷新非首页报 404
05-docker
  • Docker 常用命令
  • docker-compose nginx配置ssl证书
  • Docker Desktop 升级后 docker-compose 命令找不到
06-性能优化
  • 前端性能优化体系
  • 背景 GIF 优化方案
07-微前端
  • 微前端实战(qiankun)
  • 微前端 · qiankun
01-qiankun
05-后端与数据
01-nodejs
  • Node 版本与依赖的 engines 冲突
  • Express 实现 Gmail 发信(邮箱验证码)
02-php
  • 浏览器保存新闻,发布到自有网站
  • [分布式] 浏览器保存新闻,发布到自有网站
  • 邮件发送,多账号轮询
  • nginx和php-fpm通信问题导致网站页面空白
03-python
  • macOS M1 芯片 如何安装 py 和 tkinter
  • tkinter 编写的程序如何打包成桌面软件
04-架构设计
  • 后台管理系统中角色管理、管理员管理、权限设置他们三者之间的关系
05-运维
  • redis启动失败
  • 实现自动备份宝塔项目及数据库到本地
  • 宝塔面板
01-宝塔
06-实战项目
  • 实战准备工作
02-项目记录
  • AI-Chat 模块技术文档
  • CRMChat 部署
07-面试专项
01-面试
  • 面试之自我介绍
  • CSS 面试
  • JS面试
  • React面试
  • Vue面试
  • RN 面试 · 题目清单
  • RN 面试 · 答案详解
  • RN 物流公司 · 题目清单
  • RN 物流公司 · 答案详解
  • Uniapp 面试
  • 带团队经验
12-react专项
  • 一个表格,某一行的某一列数据变动了,如何优化性能?
13-reactnative专项
  • RN 开发过程中,有遇到什么棘手的问题?你是如何解决的?
14-过往面试总结
  • 冷哥传媒 · 面试题
  • 沐腾科技 · 面试题
  • 字符频率排序
02-面试八股
  • 网络八股
  • 浏览器八股
  • 工程化八股
03-算法面试题
  • 前端高频手写题
  • 场景设计题
04-真实面试经历
  • 20251027 盾蓝科技 面试
  • 20251028 芝麻甜心 面试
08-工具
01-n8n
  • 初始n8n
  • 快速入门
  • 在宝塔中搭建n8n环境
  • n8n中常用节点
  • n8n和代码的区别
02-Trae
  • Figma MCP 配置指南
03-UI_UX_pro_max
  • UI_UX_pro_max的使用
04-开发环境
  • VMWare虚拟机安装
  • PowerShell 执行策略与编码设置
98-杂项
  • A股股票交易相关费用
99-手册
  • iOS 开发者账号申请流程及费用
  • 邓白氏编码申请及费用
  • App 推送通道申请及费用
  • App 开发收费项目清单
  • 注册AWS账号
  • 使用Amazon Polly实现文本转语音
  • Google 翻译 Key 申请与前端调用
  • 使用SmsForwarder转发短信

栈与队列

04-数据结构与算法2026/11/14

栈和队列的差别只有一条出队规则,但这条规则决定了调用栈会溢出、事件循环分微宏任务、撤销重做必须倒着来。

翻浏览器的前进后退、编辑器里连按 Ctrl+Z、Promise 回调比 setTimeout 先执行,背后是同一个判断:这个东西该后进先出,还是先进先出。选错一条出队规则,一万条数据就能把页面卡住。

LIFO 与 FIFO

维度栈(LIFO)队列(FIFO)
出队位置同一端进出,栈顶一端进、另一端出
语义最近的优先处理最早的优先处理
典型实现数组 push / pop数组 + 头指针、链表
真实用途调用栈、撤销重做、括号匹配、DFS任务队列、BFS、消息队列、缓冲区
前端对应函数调用、路由历史宏任务队列、微任务队列、事件派发

用数组实现栈

栈是数组最贴合的场景,push 和 pop 都在尾部,均摊 O(1):

class Stack {
    constructor() { this.items = []; }
    push(x) { this.items.push(x); }
    pop() { return this.items.pop(); }
    peek() { return this.items[this.items.length - 1]; }
    get size() { return this.items.length; }
}

队列的 O(n) 陷阱

用 push 入队、shift 出队写起来最顺手,但 shift 要删掉下标 0 并让后面所有元素前移一位,是实打实的 O(n)。循环里对一万条数据做 shift,就是五千万次搬移:

const q = [];
for (let i = 0; i < 10000; i++) q.push(i);
while (q.length) q.shift(); // 每次 O(n),整体 O(n²)

正确做法是用头指针记录出队位置,或者用 Map 以自增下标为键。头指针版不搬移,但数组本身不会收缩,需要定期截断:

class Queue {
    constructor() { this.arr = []; this.head = 0; }
    enqueue(v) { this.arr.push(v); }
    dequeue() {
        if (this.head === this.arr.length) return undefined;
        const v = this.arr[this.head++];
        // 头指针越过一半且长度可观时压缩一次,避免数组无限增长
        if (this.head > 1024 && this.head * 2 > this.arr.length) {
            this.arr = this.arr.slice(this.head);
            this.head = 0;
        }
        return v;
    }
    get size() { return this.arr.length - this.head; }
}

Map 版把下标当键,delete(this.head++) 不影响其它元素,代价是每次操作多一次哈希查找。

调用栈就是栈

JS 引擎为每次函数调用压一个栈帧(局部变量、返回地址),返回时弹出。递归太深,栈帧堆到上限就抛 RangeError:

function f(n) {
    return f(n + 1);
}
f(0); // RangeError: Maximum call stack size exceeded

V8 默认栈约 1MB,普通递归深度上限一万层以内。改成显式栈就能绕开限制,这也是树遍历优先写迭代版的原因。

队列在事件循环里

浏览器把待执行的回调分成两个队列:

上一篇数组与链表
下一篇哈希表
本页目录
LIFO 与 FIFO用数组实现栈队列的 O(n) 陷阱调用栈就是栈队列在事件循环里优先队列不是队列常见误区常考题型1. 有效括号2. 最小栈3. 用两个栈实现队列4. 滑动窗口最大值关联阅读
队列存放执行时机
微任务队列(microtask)Promise 回调、queueMicrotask、MutationObserver当前宏任务结束后立刻清空
宏任务队列(macrotask)setTimeout、setInterval、I/O、UI 事件每轮事件循环取一个

微任务队列清空之前不会让出主线程,在微任务里无限 then 会饿死渲染。

优先队列不是队列

优先队列按优先级出队,不是先进先出,标准实现是二叉堆(见 树与堆),入队出队都是 O(log n)。任务调度器、Top K、Dijkstra 都用它。

常见误区

很多人以为 shift 只是"取第一个",和 pop 一样快。 实际上 pop 是 O(1),shift 是 O(n),它要维护数组的连续性;在循环里用它做 BFS 队列,复杂度会从 O(n) 掉到 O(n²)。

很多人以为"栈和队列是两种完全不同的结构"。 实际上它们只差一个出口方向,用两个栈可以拼出队列,用一个栈加递归也能模拟队列;判断该用哪个,看的是"谁应该先被处理"。

常考题型

1. 有效括号

思路:遇到左括号压栈,遇到右括号就看栈顶是不是配对的那一个,最后栈必须为空。

function isValid(s) {
    const pair = { ')': '(', ']': '[', '}': '{' };
    const stack = [];
    for (const ch of s) {
        if (!pair[ch]) {
            stack.push(ch);
        } else if (stack.pop() !== pair[ch]) {
            return false;
        }
    }
    return stack.length === 0;
}

时间 O(n),空间 O(n)。易错点:只有右括号时 pop() 返回 undefined,比较自然失败;结尾必须检查栈空,否则 "(" 会被判成合法。

2. 最小栈

思路:辅助栈同步记录"压入当前元素后的最小值",取最小值 O(1),代价是多一份空间。

class MinStack {
    constructor() { this.stack = []; this.min = []; }
    push(x) {
        this.stack.push(x);
        const cur = this.min.length ? this.min[this.min.length - 1] : Infinity;
        this.min.push(Math.min(x, cur));
    }
    pop() { this.min.pop(); return this.stack.pop(); }
    top() { return this.stack[this.stack.length - 1]; }
    getMin() { return this.min[this.min.length - 1]; }
}

各操作 O(1),空间 O(n)。易错点:pop 时两个栈必须同步弹,否则最小值会错位。

3. 用两个栈实现队列

思路:in 栈负责入队,out 栈负责出队;out 空了才把 in 整体倒过去,每个元素最多倒一次,均摊 O(1)。

class MyQueue {
    constructor() { this.in = []; this.out = []; }
    push(x) { this.in.push(x); }
    peek() {
        if (!this.out.length) while (this.in.length) this.out.push(this.in.pop());
        return this.out[this.out.length - 1];
    }
    pop() { const v = this.peek(); this.out.pop(); return v; }
    empty() { return !this.in.length && !this.out.length; }
}

均摊时间 O(1),最坏单次 O(n),空间 O(n)。易错点:out 非空时不能往里倒,否则顺序会乱。

4. 滑动窗口最大值

思路:单调递减队列存下标,队头永远是当前窗口最大值;新元素入队前把比它小的全部弹掉。

function maxSlidingWindow(nums, k) {
    const dq = []; // 存下标,对应值单调递减
    const res = [];
    for (let i = 0; i < nums.length; i++) {
        while (dq.length && dq[0] <= i - k) dq.shift(); // 移出窗口
        while (dq.length && nums[dq[dq.length - 1]] <= nums[i]) dq.pop();
        dq.push(i);
        if (i >= k - 1) res.push(nums[dq[0]]);
    }
    return res;
}

时间 O(n)(每个下标最多进出队一次),空间 O(k)。易错点:队列里存值而不是下标就没法判断过期;窗口未满时不能往结果里写。

关联阅读

  • 数组与链表:shift 的代价来自数组的连续内存
  • 哈希表:LRU 用哈希表 + 双向链表,本质是队列的变体
  • 树与堆:优先队列的标准实现是二叉堆