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

秋招准备-数据结构

数据结构秋招准备记录

线性表

线性表的顺序存储与链式存储详解​
线性表作为数据结构中最基础、最常用的结构之一,其存储方式主要分为顺序存储和链式存储两大类。这两种存储方式各有特点,适用于不同的应用场景。​
一、顺序存储结构:顺序表​
顺序存储结构下的线性表被称为顺序表。在这种结构中,线性表中逻辑上相邻的两个节点,在物理地址上也是相邻的。顺序表通过数组来实现存储,而数组既支持静态分配,也支持动态分配。​

  1. 静态分配​
    在静态分配时,数组的大小和空间在程序编译阶段就已固定。例如,在 C 语言中定义int arr[10];,就创建了一个大小为 10 的整型数组。这种方式的优点是访问元素速度极快,可通过下标直接访问,时间复杂度为 O(1) 。然而,它存在明显的局限性:一旦数组空间被占满,再尝试插入新数据就会发生溢出错误,无法灵活适应数据量的动态变化。​
  2. 动态分配​
    动态分配的数组则在程序执行过程中通过动态内存分配函数(如 C 语言中的malloc和free,C++ 中的new和delete)来管理内存。当现有数据空间被占满时,程序会开辟一块更大的内存空间,将原数据复制到新空间中,然后释放旧空间,以此拓展存储空间。以 Python 语言为例,其列表本质上就是动态数组,在插入元素导致空间不足时,Python 解释器会自动进行扩容操作。这种方式避免了为线性表一次性分配过多内存造成的浪费,但频繁的内存分配和数据复制操作会带来一定的性能开销。​
  3. 顺序表的性能分析​
    顺序表在查找操作上具有天然优势,可直接通过下标定位元素,时间复杂度为 O(1) 。但在插入和删除操作中,平均需要移动约 2/n 个元素(n为线性表长度),时间复杂度达到 O(n)。例如,在长度为 100 的顺序表头部插入一个元素,需要将后面的 99 个元素依次向后移动一位。​
    二、链式存储结构​
    当线性表存储不需要连续的物理位置时,链式存储结构应运而生。链式存储通过指针将各个节点串联起来,根据链表结构的不同,可分为单链表、双链表、循环链表和静态链表。​
  4. 单链表​
    单链表的每个节点除了存储数据元素外,还包含一个指向下一个节点的指针(next指针)。这种结构在插入和删除操作上具有高效性,仅需修改相关节点的指针,时间复杂度为 O(1)。但在查找操作中,需要从表头开始逐个遍历节点,平均时间复杂度为 O(n) 。例如,要查找链表中第 5 个节点,必须依次经过前 4 个节点。此外,单链表因指针的存在会额外占用一定的存储空间,存在空间浪费的问题。​
  5. 双链表​
    双链表在单链表的基础上进行了扩展,每个节点除了next指针外,还增加了一个指向前驱节点的指针(prev指针)。这使得双链表在删除和插入操作时更加灵活,能够快速找到前驱节点,无需像单链表那样可能需要从头遍历。双链表的插入和删除操作时间复杂度同样为 O(1),但由于每个节点需要存储两个指针,相比单链表占用更多的存储空间。​
  6. 循环链表​
    循环链表分为单循环链表和双循环链表。在单循环链表中,尾节点的next指针指向头节点,形成一个环形结构;双循环链表则是头节点的prev指针指向尾节点,尾节点的next指针指向头节点。循环链表的优势在于可以从任意节点开始遍历整个链表,常用于实现队列、循环缓冲区等数据结构。​
  7. 静态链表​
    静态链表借助数组来模拟链表结构,通过数组元素中的游标(类似于指针)来表示节点间的逻辑关系。与传统链表不同,静态链表在物理上使用连续的数组空间,但在逻辑上实现了链表的插入和删除操作。这种方式适用于不支持指针操作的编程语言环境,或对内存管理有特殊要求的场景。​
  8. 链表结构的性能对比​
    与顺序表相比,链表在插入和删除操作上具有明显优势,时间复杂度为 O(1) ;但在随机访问元素时,链表需要顺序遍历,时间复杂度为 O(n),远不及顺序表的 O(1) 。因此,链表更适合频繁进行插入和删除操作,而对随机访问需求较低的场景。

栈 (Stack)

定义与特性: 栈是一种操作受限的线性表,遵循后进先出 (LIFO) 原则。元素的插入(称为 入栈/Push)和删除(称为 出栈/Pop)操作只能在表的同一端进行,这一端被称为 栈顶 (Top)。不允许进行插入删除操作的另一端称为 栈底 (Bottom)。

存储方式: 栈可以采用顺序存储(使用一组地址连续的存储单元,称为 顺序栈)或链式存储(链栈)来实现。

核心操作: Push(入栈)、Pop(出栈)、Peek/Top(获取栈顶元素,不删除)。

应用场景:

函数调用栈: 存储函数调用时的返回地址、局部变量、参数等。

表达式求值: 处理运算符优先级和括号匹配(如中缀转后缀表达式,后缀表达式计算)。

括号匹配: 检查代码或表达式中的括号((), [], {})是否成对且嵌套正确。

浏览器的前进/后退: 利用两个栈实现。

深度优先搜索 (DFS): 递归实现的本质就是利用系统栈,也可以用显式栈实现非递归DFS。

撤销 (Undo) 操作: 将操作记录压入栈。

队列 (Queue)

定义与特性: 队列也是一种操作受限的线性表,遵循先进先出 (FIFO) 原则。元素的插入(称为 入队/Enqueue)操作只能在表的一端(称为 队尾/Rear)进行,而元素的删除(称为 出队/Dequeue)操作只能在表的另一端(称为 队头/Front)进行。

存储方式: 队列同样可采用顺序存储(顺序队列)或链式存储(链队列)实现。

循环队列 (Circular Queue): 为了解决顺序队列的“假溢出”问题(数组前端有空位但无法入队),将顺序队列视为一个环状空间。关键点在于:

队头指针 front 指向队头元素。

队尾指针 rear 指向队尾元素的下一个位置(约定)。

队满条件: (rear + 1) % MAX_SIZE == front (牺牲一个存储单元区分队空队满)。

队空条件: front == rear。

入队/出队操作需要对指针进行取模运算:rear = (rear + 1) % MAX_SIZE, front = (front + 1) % MAX_SIZE。

应用场景:

任务调度:

CPU 调度: 操作系统使用就绪队列(如FCFS调度算法)管理等待CPU的进程,按请求到达的先后顺序分配CPU资源。

打印机缓冲区: 将打印任务排成队列,主机快速发送数据到缓冲区,打印机按顺序从中取出任务打印,解决主机与外部设备速度不匹配问题。

消息队列: 系统间或组件间异步通信。

广度优先搜索 (BFS): 按层次遍历图或树。

缓存区: 解决数据生产者(如用户请求、I/O设备)和消费者(如CPU、磁盘)速度不匹配,提供缓冲。

资源池管理: 如线程池、数据库连接池,管理可用资源。

数组 (Array) - 通用概念

定义与特性: 数组是由相同类型的元素构成的有限序列。它是一种基础且重要的线性数据结构。

核心特性:

顺序存储: 元素在内存中占据连续的地址空间(顺序存储结构)。

固定长度 (静态性): 在大多数编程语言(如C, C++, Java)中,数组一旦被定义,其维数和长度(容量)通常是固定的,不能改变。

随机访问: 通过下标(索引) 可以直接访问或修改数组中的任何元素,时间复杂度为 O(1)。这是数组的核心优势。

元素类型一致: 所有元素必须是相同的数据类型(如整型、浮点型、字符型等)。

基本操作: 主要支持按索引存取 (Access) 和修改 (Update) 元素。插入和删除操作通常效率较低(O(n)),因为可能需要移动大量元素以保持连续性。

特殊矩阵压缩存储: 对于包含大量重复值(如全零)或具有特定规律(如对称、三角、对角、稀疏矩阵)的矩阵,为了节省空间,会使用特殊的压缩存储算法:

对称矩阵: 只存储主对角线及其一侧的元素(如上三角或下三角),通过计算确定对称位置元素。

稀疏矩阵: 只存储非零元素及其位置(行号、列号)。

  1. JavaScript 数组的特殊性

与通用数组概念的显著差异: JavaScript 数组 (Array) 的行为与上述传统数组有本质区别。

核心特性:

动态大小: 长度可变,可随时添加或移除元素(使用 push, pop, shift, unshift, splice 等方法)。

异构元素: 同一个数组中可以存储不同类型的数据(数字、字符串、布尔值、null, undefined, 对象、函数,甚至其他数组)。

本质是对象: 在 JavaScript 中,数组是继承自 Object 的一种特殊对象。其索引实际上被解释为字符串形式的属性名(如 ‘0’, ‘1’)。这解释了它为何能存储不同类型和动态改变大小。

V8引擎的内部实现 (核心优化): 为了平衡性能和内存,V8引擎(Chrome/Node.js的JS引擎)对数组采用两种主要内部表示:

快数组 (Fast Elements / Packed Elements):

存储方式: 使用连续的内存空间(线性存储),类似于传统数组。

特点: 访问速度快(O(1)随机访问),是新建数组或密集数组(无空洞)的默认模式。

动态调整: 内部有扩容(如容量翻倍)和收缩机制,以适应元素数量的变化。

空洞 (Holes): 当数组中存在某些索引位置未被赋值(如 arr[10] = 100; 但 arr[0] 到 arr[9] 未定义),这些位置称为“空洞”,访问它们会得到 undefined。少量空洞时仍可能是快数组。

慢数组 (Dictionary Elements / Hash Map):

存储方式: 使用哈希表 (Hash Table) 来存储元素及其索引(键值对)。不需要连续内存。

特点: 访问速度相对较慢(接近 O(1),但常数因子比快数组大),插入删除特定位置元素可能更快(不涉及移动)。内存使用更灵活。

转换触发: 当数组变得非常稀疏(空洞比例很高)或者执行了不适合快数组的操作(如给超大索引赋值、delete 操作等)时,V8 可能会将其从快数组转换为慢数组,以节省内存空间(空间换时间)。反之,当数组变得足够密集时,可能转换回快数组(时间换空间)。

模拟栈和队列: JavaScript 数组提供了方便的方法来模拟栈和队列:

栈: push() (入栈) + pop() (出栈) - 操作栈顶。

队列: push() (入队到队尾) + shift() (出队从队头) 或 unshift() (入队到队头) + pop() (出队从队尾) - 注意 shift/unshift 操作整个数组效率较低。

处理二进制数据 - TypedArray 和 ArrayBuffer:

ArrayBuffer: 表示原始的二进制数据缓冲区。它分配一段固定长度的连续内存字节,但不能直接操作。

视图 (TypedArray / DataView): 为了读写 ArrayBuffer 的内容,需要创建视图。

TypedArray (类型化数组): 如 Int8Array, Uint32Array, Float64Array 等。它们提供对 ArrayBuffer 的特定数据类型的视图(如 8 位有符号整数、32 位无符号整数、64 位浮点数)。元素类型必须一致,长度固定。这更接近传统数组的概念,用于高效处理二进制数据(如图像、音频、网络协议)。

DataView: 提供更底层、更灵活的读写 ArrayBuffer 的方法,可以在同一个 ArrayBuffer 上读写多种数据类型。

树和森林

图的存储

图的遍历

图应用

查找

顺序/折半

树形查找

散列表

排序

相关文章:

  • 如何安装huaweicloud-sdk-core-3.1.142.jar到本地仓库?
  • Linux 命令全讲解:从基础操作到高级运维的实战指南
  • 滚动部署详解
  • 复杂系统仿真的具体意义
  • 2.3 关于async/await的原理介绍
  • Playwright定位器详解:自动化测试的核心工具
  • 多线程1(Thread)
  • C++语法系列之类型转换
  • 『React』组件副作用,useEffect讲解
  • 12:点云处理—调平,角度,平面度,高度,体积
  • Oracle双平面适用场景讨论会议
  • 【MATLAB代码】制导——三点法,二维平面下的例程|运动目标制导,附完整源代码
  • 【Typst】6.布局函数
  • .Net Framework 4/C# 初识 C#
  • 由docker引入架构简单展开说说技术栈学习之路
  • 基于 NXP + FPGA+Debian 高可靠性工业控制器解决方案
  • Dify知识库下载小程序
  • Jpom:Java开发者的一站式自动化运维平台详解
  • RabbitMQ在SpringBoot中的应用
  • RNN结构扩展与改进:从简单循环网络到时间间隔网络的技术演进
  • html5微网站开发教程/阿里云建站
  • 武汉最好的网站建设前十/电脑优化工具
  • 做海报赚钱的网站/官网制作公司
  • 政府网站集约化建设征求意见/百度竞价点击神器奔奔
  • 网站的轮播图一般是做多大/免费推广引流平台推荐
  • 做恐怖网站/千锋教育官网