从栈到队列,再到链表:前端开发者必知的线性数据结构

掌握这些基础,你的代码能力会上一个台阶

作为前端开发者,我们每天都在和数组打交道。pushpopshiftunshiftsplice 这些方法用得行云流水。但你是否想过,这些操作背后隐藏着怎样的数据结构原理?

今天,我们就来聊聊线性数据结构中的"三剑客"------栈、队列和链表。理解了它们,你写出的代码会更加优雅高效。

一、栈(Stack):后进先出的"冰柜"

栈就像冰柜里的雪糕------后放进去的先拿出来 。这是一种典型的 LIFO(Last In First Out) 数据结构。

栈的特性

栈可以看成是操作受限的数组 ,一个特殊的数组

  • 只能在栈顶(数组尾部)进行操作
  • 入栈:push
  • 出栈:pop
  • 查看栈顶元素:stack[stack.length - 1](即 peek 操作)

代码实现

javascript

arduino 复制代码
const stack = []; // 空栈

// 入栈
stack.push("东北大板");
stack.push("可爱多");
stack.push("冰工厂");
stack.push("巧乐兹");

// 出栈
while(stack.length) {
    const top = stack[stack.length - 1];
    console.log('取出来的是:', top);
    stack.pop();
}

console.log(stack); // []

栈的应用场景

  • 函数调用栈
  • 浏览器的后退功能
  • 括号匹配校验
  • 撤销操作(Ctrl+Z)

二、队列(Queue):先进先出的"排队"

队列就像在奶茶店排队------先来的先服务 。这是典型的 FIFO(First In First Out) 数据结构。

队列的特性

队列也是一种操作受限的数组

  • 只能在队尾入队
  • 只能在队首出队
  • 入队:push
  • 出队:shift

代码实现

javascript

arduino 复制代码
const queue = []; // 空队列

// 入队
queue.push("许");
queue.push("叶");
queue.push("戴");

// 出队
while(queue.length) {
    const top = queue[0];
    console.log('取出来的是:', top);
    queue.shift();
}

console.log(queue); // []

队列的应用场景

  • 任务队列(宏任务、微任务)
  • 消息队列
  • 广度优先搜索(BFS)

三、数组的增删改查:不得不说的复杂度

splice 方法详解

splice 是数组增删改的"瑞士军刀",它的本质是 slice + replace

javascript

scss 复制代码
// 语法:splice(start_index, del_count, ...addedItems)

const arr = [1, 2];
console.log(arr.splice(1, 0, 3)); // [] (删除0个,返回空数组)
console.log(arr); // [1, 3, 2]

arr.splice(1, 1); // 删除索引1处的元素
console.log(arr); // [1, 2]

⚠️ 注意 :数组的增删改方法都不是纯函数,它们会直接修改原数组!

线性关系:O(n) 的代价

假设数组的长度是 n,当我们在数组中间进行增加或删除操作时:

  • 需要移动的元素数量随着 n 的增加而增加
  • 呈一个线性关系
  • 时间复杂度为 O(n)

这就是数组的"痛点"------插入和删除的效率会随着数据量增长而线性下降。

四、链表(LinkedList):灵活连接的"链条"

为了解决数组增删效率低的问题,链表应运而生。链表通过指针连接各个节点,实现了高效的增删操作。

链表节点的定义

javascript

ini 复制代码
function ListNode(val) {
    this.val = val;
    this.next = null;
}

const node = new ListNode(1);
node.next = new ListNode(2);
console.log(node); // {val: 1, next: {val: 2, next: null}}

链表的添加与删除

添加元素 :前驱节点的 next 指向新节点,新节点的 next 指向后继节点。

删除元素 :前驱节点的 next 直接指向后继节点。

本质都是对 next 指针的操作!

链表的复杂度分析

操作 时间复杂度 说明
访问 O(n) 需要从头遍历,麻烦
插入(已知位置) O(1) 只需改变指针指向
删除(已知位置) O(1) 只需改变指针指向
查找目标位置 O(n) 这是插入/删除的前置成本

💡 关键理解 :链表虽然有高效的增删操作(O(1)),但前提是你已经明确了要插入或删除的目标位置 。找到这个位置本身需要 O(n) 的遍历时间。也就是说,链表的总操作是 O(n)(查找)+ O(1)(操作)= O(n)

数组 vs 链表 完整对比

对比维度 数组 链表
访问元素 O(1) ✅ O(n) ❌
头部插入 O(n) ❌ O(1) ✅
头部删除 O(n) ❌ O(1) ✅
中间插入 O(n) O(n)(查找+指针)
内存结构 连续(或哈希) 离散
内存申请 扩容时一次性申请 每次新增都申请

什么时候用数组?

  • 数据量较小
  • 频繁的遍历和索引访问
  • 对内存连续性有要求

什么时候用链表?

  • 数据量较大
  • 频繁的头部的插入和删除操作
  • 对内存连续性不敏感

五、JS 数组的"特殊身份"(重要!)

需要注意的是,JS 数组未必是真正的数组!这一点经常被前端开发者忽略。

javascript

ini 复制代码
// 情况一:每一项类型一致 → 连续内存(真正的数组)
const arr1 = [1, 2, 3, 4, 5];

// 情况二:每一项类型不一致 → 离散内存
const arr2 = ['haha', 1, {a: 1}];

原理解析

  • 当数组元素类型一致 时,JS 会分配连续内存,此时它具备传统数组的特征
  • 当数组元素类型不一致 时,JS 底层使用哈希分配内存空间,数据存储在离散的位置,通过哈希表映射访问

⚠️ 在后一种情况下,JS 数组不再具有数组的连续内存特征,但仍然可以通过下标访问 ------ 这就是哈希表的功劳!

这也是为什么 JS 数组可以这么"灵活"的原因 ------ 它底层做了很多透明处理。

六、排序小贴士

使用 sort 方法时,一定要传比较函数,否则会按 ASCII 码排序:

javascript

css 复制代码
let arr = [10, 2, 5];
arr.sort((a, b) => a - b); // [2, 5, 10]

// 不传比较函数的后果:
let arr2 = [10, 2, 5];
arr2.sort(); // [10, 2, 5] ❌ 按字符串排序了!

总结:一张图看懂三种数据结构

text

perl 复制代码
┌─────────────────────────────────────────────────────────┐
│                    线性数据结构                          │
├─────────────┬─────────────┬─────────────────────────────┤
│     栈      │    队列     │           链表              │
├─────────────┼─────────────┼─────────────────────────────┤
│   LIFO      │   FIFO      │   离散存储 + 指针连接        │
│   push/pop  │   push/shift│   增删 O(1)* 访问 O(n)      │
│   栈顶操作  │   两端操作   │   需要手动实现节点           │
└─────────────┴─────────────┴─────────────────────────────┘

核心要点回顾

  • 栈和队列:都是操作受限的数组,通过限制操作方式保证了数据的有序性
  • 数组增删:O(n) 的线性关系,数据量越大代价越高
  • splice:增删改一体,但会修改原数组(非纯函数)
  • 链表:离散存储,增删高效(O(1)),但访问低效(O(n))
  • JS 数组:类型一致才是真数组,类型不一致时底层是哈希表

一句话总结

数组用空间换时间(连续内存换快速访问),链表用时间换空间(遍历访问换灵活增删)------ 根据场景选择合适的数据结构,才是聪明的开发者。

相关推荐
寅时码9 小时前
React 之死·终章:一个 useRef,把闭包陷阱、依赖数组、漫天 rerender 全送走
前端·react.js·ai编程
不好听6139 小时前
HTML 事件监听机制:从 DOM Level 0 到 React 合成事件
前端·react.js·html
wordbaby10 小时前
为什么刷新页面就 404?聊聊 SPA 路由与 Nginx 的那点事
前端
daols8810 小时前
vxe-table 渲染器教程一:实现金额输入控件
javascript·vue.js·vxe-table
不好听61310 小时前
React 核心概念:JSX、组件与数据驱动
前端·react.js
触底反弹10 小时前
🔥 React 零基础入门(中):500 行屎山代码到组件化的蜕变
前端·javascript·react.js
不好听61310 小时前
React vs Vue:两大前端框架技术选型深度对比
前端·vue.js·react.js
GuWenyue10 小时前
Cursor黑盒拆解!1套LangChain.js手写Mini编程Agent,自动生成React项目,效率提升60%
前端·数据库·人工智能
GuWenyue10 小时前
传统Agent工具两大痛点!300行代码落地MCP跨语言工具,彻底解耦LLM与工具
前端·人工智能·算法
小林ixn11 小时前
从 onclick 到 React 合成事件,再到完整应用逻辑:一次前端架构的深度剖析
前端·react.js·前端框架