当前位置: 首页 > news >正文

数据结构中的列表:深度解析数组与链表的实现与抉择

1. 基于数组的列表

这种列表在底层使用数组作为其存储结构。数组是一段连续的内存空间,可以直接通过索引(下标)访问任意位置的元素。

核心思想:在内存中分配一块连续的空间,按顺序存放元素。

特点

  • 随机访问能力强:因为内存是连续的,可以通过 基地址 + 索引 * 元素大小 的公式直接计算出任何一个元素的地址,因此访问第 i 个元素的时间复杂度是 O(1)。这是它最大的优势。

  • 内存利用率高:数据本身之外几乎没有额外的内存开销(除了记录容量和当前长度等元信息)。

  • 插入和删除效率低:在列表中间插入或删除一个元素时,需要将该元素之后的所有元素都向后或向前移动一位,以保持连续性。平均时间复杂度为 O(n)

  • 大小固定:传统数组的大小在创建时就已确定。为了解决这个问题,现代编程语言通常实现了动态数组(如 Python 的 list、Java 的 ArrayList、C++ 的 std::vector)。当当前容量不足时,动态数组会自动分配一个更大的新数组(通常是原来的1.5或2倍),并将旧数据拷贝过去。这个扩容操作的时间复杂度是 O(n),但由于其不是频繁发生,均摊下来,追加操作的时间复杂度仍然是 O(1)

常见操作的时间复杂度

操作时间复杂度说明
按索引访问O(1)随机访问
在末尾追加O(1)均摊成本
在开头或中间插入O(n)需要移动后续元素
在开头或中间删除O(n)需要移动后续元素
查找元素O(n)如果无序,需要遍历

2. 链表

这种列表在底层使用一组节点来存储数据,每个节点不仅包含数据本身,还包含一个或多个指向其他节点的指针

核心思想:通过指针将一组零散的内存块串联起来。

常见的链表类型

  • 单向链表:每个节点包含 数据下一个节点的指针 (next)

  • 双向链表:每个节点包含 数据前一个节点的指针 (prev)下一个节点的指针 (next)。这牺牲了少量空间,但换来了更高效的前向遍历和节点删除能力。

  • 循环链表:尾节点的 next 指针指向头节点,形成一个环。

特点

  • 内存不连续:节点可以分散在内存的各个地方,充分利用零散内存。

  • 随机访问效率低:要访问第 i 个元素,必须从头节点开始逐个遍历,时间复杂度为 O(n)

  • 插入和删除效率高只要拿到了要操作位置的节点指针,插入和删除操作只需要修改几个指针的指向,无需移动大量数据。时间复杂度为 O(1)。这是它最大的优势。

  • 大小动态:天生动态,每次添加新元素时才申请内存,没有扩容和拷贝的成本。

常见操作的时间复杂度

操作时间复杂度说明
按索引访问O(n)需要从头遍历
在头部插入/删除O(1)修改头指针和少量节点指针
在尾部插入/删除O(1)对于双向链表;单向链表需要O(n)来找到尾节点
在已知节点后插入O(1)修改指针
删除已知节点O(1)对于双向链表;单向链表需要O(n)来找到前驱节点
查找元素O(n)需要遍历

对比总结:数组列表 vs. 链表

特性基于数组的列表链表
内存布局连续分散(非连续)
随机访问快 (O(1))慢 (O(n))
头部插入/删除慢 (O(n))快 (O(1))
中间插入/删除慢 (O(n))快 (O(1))***
内存开销小(仅需存储数据)大(需额外存储指针)
容量扩展需要扩容和拷贝天然动态,无需预先分配
缓存友好性(局部性原理)低(内存不连续)

*:链表的“快”的前提是你已经拥有了要插入/删除位置的节点指针。如果你是通过索引来定位,那么定位过程本身就是 O(n) 的,整体操作也就变成了 O(n)。


在不同编程语言中的具体实现

  • Python: list 是一种动态数组,不是链表。

  • Java: ArrayList 是基于数组的,LinkedList 是双向链表。

  • C++: std::vector 是动态数组,std::list 是双向链表。

  • JavaScript: Array 在底层实现上更接近于动态数组(虽然规范不限定具体实现)。

如何选择?

  • 选择基于数组的列表,如果:

    • 你需要频繁地按索引随机访问元素。

    • 你的操作大多是在列表的末尾进行添加和删除。

    • 你追求更高的内存效率和缓存性能。

  • 选择链表,如果:

    • 你需要频繁在列表的任意位置(尤其是头部和中间)进行插入和删除。

    • 列表的大小变化非常频繁且不可预测,你不想处理扩容问题。

    • 你不需要频繁的随机访问。

希望这个详细的梳理能帮助你彻底理解“列表”这个核心数据结构!

http://www.dtcms.com/a/341101.html

相关文章:

  • PyTorch API 3 - distributed
  • 前后端联合实现文件上传,实现 SQL Server image 类型文件上传
  • 51单片机-驱动LED点阵模块教程
  • SQL-leetcode—3374. 首字母大写 II
  • Docker--安装MySQL、Redis
  • 面试常考的 SQL 窗口函数汇总
  • 【Tech Arch】Apache Pig大数据处理的高效利器
  • 深入理解数据结构:从数组、链表到B树家族
  • 数据结构:利用旋转在AVL树中维持平衡(Inserting in AVL with Rotation)
  • FastAPI初学
  • PyTorch API 1
  • 新能源知识库(81)新能源半实物仿真平台介绍
  • C/C++ Linux系统编程:详解常见的系统调用函数,文件I/O核心:open, close, read, write
  • 【C++】基础:C++11-14-17常用新特性介绍
  • 计算机网络技术-局域网配置(Day.4)
  • 微信小程序授权登录+JWT
  • shell间接引用
  • CVE-2018-12613 漏洞复现
  • 为什么我的UI界面会突然卡顿,失去响应
  • FLASK项目快速构建
  • 用TestComplete打造高效CI/CD测试流程
  • nodejs mongodb基础
  • 【论文阅读】-《SIGN-OPT: A QUERY-EFFICIENT HARD-LABEL ADVERSARIAL ATTACK》
  • Gitea Webhook教程:实现git push后自动部署更新网站 (CI/CD入门)
  • 7.2 Linux:驱动开发——模块机制
  • 7.3 Linux:驱动开发——应用程序和驱动程序的交互
  • imx6ull-驱动开发篇30——Linux 非阻塞IO实验
  • 电商平台商品详情数据爬取教程​
  • JS原型链
  • 第10课_Rust网络编程