🧩

数据结构

数据结构研究数据的组织、存储和操作方法,是程序设计的核心基础。本章介绍线性表、栈、队列、 串与数组、树、图等经典结构,以及查找与排序算法,并分析它们的特点与适用场景。

📚 9 个小节🌳 树与图🔍 查找🔢 六大排序

1数据结构概述

计算机解决问题时,首先要把现实中的信息抽象为数据,并选择合适的方式组织这些数据。 数据结构就是研究数据之间的相互关系(逻辑结构)、数据在计算机中的存储方式(存储结构)以及在其上可执行的操作。

1.1 基本术语

  • 数据(Data):能输入计算机并被处理的符号的总称,如数、字符、图像、声音。
  • 数据元素:数据的基本单位(如一条学生记录),一个元素可由若干数据项组成(学号、姓名——数据项是最小不可分割单位)。
  • 数据对象:性质相同的数据元素的集合,如全部整数、全班学生记录。

1.2 逻辑结构

逻辑结构描述数据元素之间的逻辑关系,与存储无关,分为四类:

▦

集合

元素同属一个集合,元素间除"同属"外无其他关系

━

线性结构

一对一:元素排成序列,有唯一前驱和后继

🌳

树形结构

一对多:层次关系,如家族谱、目录

🕸️

图状结构

多对多:任意两元素都可能相关,如交通网

1.3 存储结构(物理结构)

  • 顺序存储:元素存放在地址连续的存储单元里,借助相对位置表示逻辑关系(数组实现)。
  • 链式存储:元素存放在任意地址,每个节点附带指针指向相关节点。
  • 索引存储:在建数据的同时建立附加的索引表以加快查找。
  • 散列存储(哈希):根据元素关键字直接计算出存储地址。
🔑

逻辑结构是面向问题的抽象,存储结构是逻辑结构在计算机中的实现。同一种逻辑结构可以采用不同的存储结构(如线性表既可顺序存储也可链式存储)。

2线性表

线性表(Linear List):n(n≥0)个数据元素的有限序列。除第一个元素外每个元素有且仅有一个直接前驱; 除最后一个元素外每个元素有且仅有一个直接后继。如:26 个英文字母表、学生成绩单。

2.1 顺序表(顺序存储)

用一组地址连续的存储单元依次存放线性表元素,高级语言中借助数组实现。

下标:   [0]   [1]   [2]   [3]   [4]
数据:   12    25    38    47    60      ← 逻辑相邻,物理也相邻
  • 优点:可随机存取(按下标直接定位,O(1));存储密度高,无需额外指针。
  • 缺点:插入和删除需移动大量元素(平均移动一半,O(n));需要连续空间,扩容不便、可能浪费。

2.2 链表(链式存储)

节点由数据域 + 指针域构成,节点在内存中可以不连续。

头指针 head
  ↓
[12|●]→[25|●]→[38|●]→[47|∧]     (∧ 表示空指针 NULL)
数据 指针
  • 单链表:每个节点只有一个指向后继的指针。
  • 双链表:节点同时有指向前驱和后继的两个指针,可双向遍历。
  • 循环链表:尾节点指向头节点,形成环,从任意节点都可遍历全表。
  • 优点:插入删除只需修改指针、不需移动元素(O(1),若已定位);按需动态分配空间。
  • 缺点:只能顺序存取,找第 i 个元素要从头遍历(O(n));指针占用额外空间。

2.3 顺序表与链表对比

对比项顺序表(数组)链表
存储空间连续,需预分配不连续,动态申请
按位置访问随机存取 O(1)顺序存取 O(n)
插入/删除平均移动 n/2 个元素 O(n)改指针即可 O(1)(不计查找)
额外开销无每个节点有指针开销
适用场景查找多、增删少、长度稳定增删频繁、长度变化大

3栈

栈(Stack):只允许在一端(栈顶 top)进行插入和删除的特殊线性表,另一端称栈底。 入栈(push)与出栈(pop)遵循 LIFO(后进先出)原则,像一摞盘子:后放上去的先拿走。
  push →  ┌────┐
          │ 30 │ ← top(栈顶,最后入栈,最先出栈)
          ├────┤
          │ 20 │
          ├────┤
          │ 10 │ ← bottom(栈底)
          └────┘
  pop 顺序:30 → 20 → 10

3.1 栈的实现

可用顺序栈(数组+栈顶指针)或链栈实现。入栈栈顶指针上移,出栈下移;要防止上溢(栈满还压入)和下溢(栈空还弹出)。

3.2 栈的典型应用

  1. 函数调用与返回:系统用"函数调用栈"保存返回地址、参数和局部变量;递归本质上就是借助栈实现的。
  2. 括号匹配检查:左括号入栈,遇右括号弹出匹配;栈最终为空且不发生失配则表达式合法。
  3. 表达式求值:中缀转后缀(逆波兰式),再借助栈计算;运算符有优先级。
  4. 撤销操作(Undo)、浏览器后退、深度优先搜索(DFS)等。
📝

入栈序列为 1,2,3 时,合法出栈序列不只有 3,2,1:例如 1 入 1 出、2 入 2 出、3 入 3 出 可得到 1,2,3——这类"出栈序列合法性"判断题的关键是:任意元素出栈时,栈中比它先入栈的元素必须按逆序出现。

4队列

队列(Queue):只允许在表的一端(队尾 rear)插入、在另一端(队头 front)删除的线性表, 遵循 FIFO(先进先出)原则,如同排队买票。
  出队 ← [ 10 ][ 20 ][ 30 ] ← 入队
          ↑front                ↑rear

4.1 循环队列

用数组实现队列时,队头删除会造成前面空间闲置("假溢出")。解决办法是把数组想象成首尾相接的环, 队头、队尾指针用取模运算循环移动,称为循环队列。通常牺牲一个单元来区分队空与队满: 队空:front == rear;队满:(rear+1) % n == front。

4.2 队列的应用与变种

  • 操作系统中的任务排队:打印队列、进程就绪队列、消息缓冲。
  • 广度优先搜索(BFS):按层次遍历图或树。
  • 双端队列 Deque:两端都可进出;优先队列:按优先级高低出队(通常用堆实现)。
  • 实际系统中的消息队列(Kafka 等)、秒杀请求削峰也是队列思想的延伸。
💡

栈与队列的记忆:栈像弹夹/水桶(后进先出 LIFO),队列像排队/管道(先进先出 FIFO),二者都是"操作受限的线性表"。

5串、数组与广义表

5.1 串(字符串)

串是由零个或多个字符组成的有限序列,如 "hello"。串中字符个数为串的长度,长度为 0 的串称空串。 常见操作有求长度、连接、比较、求子串、插入删除,其中最重要的是模式匹配:在主串中查找子串(模式串)出现的位置。 朴素匹配法最坏 O(nm),KMP 算法利用已匹配信息使时间复杂度降为 O(n+m)。

5.2 数组

数组是类型相同、下标有序的一组元素的集合。一维数组可视为顺序表;二维数组在内存中按行优先(C、Java)或列优先(Fortran)次序展开为一维序列存放。

二维数组 A[2][3] 按行优先存放:
A[0][0], A[0][1], A[0][2], A[1][0], A[1][1], A[1][2]

5.3 特殊矩阵的压缩存储

  • 对称矩阵:a[i][j] = a[j][i],只存下三角(含对角线)。
  • 三角矩阵:上(下)三角元素相同,只存一半再加一个常数单元。
  • 稀疏矩阵:绝大多数元素为零,只记录非零元的"行、列、值"三元组。

5.4 广义表

广义表是线性表的推广,其元素既可以是单个数据(原子),也可以又是一个广义表(子表),如 L = (a, (b, c), d), 可表示层次嵌套数据,广泛用于人工智能的符号表达式和函数式语言。

6树与二叉树

6.1 树的基本概念

树是 n(n≥0)个节点的有限集合,有且仅有一个根节点,其余节点分成若干互不相交的子树,每个子树本身又是一棵树。 它适合表达具有层次关系的数据(组织架构、文件目录、菜单)。

  • 度:节点拥有的子树数;树的度是各节点度的最大值。
  • 层次/深度:根为第 1 层,逐层加一;树的最大层数即树的深度。
  • 叶子是度为 0 的节点;父节点、子节点、兄弟节点、森林(若干棵互不相交的树)。

6.2 二叉树

二叉树:每个节点最多有两个子节点(左子树、右子树,次序不能颠倒)的树。

重要性质:

  • 第 i 层最多有 2^(i-1) 个节点;深度为 k 的二叉树最多有 2^k - 1 个节点。
  • 对任何二叉树,若叶子数为 n₀、度为 2 的节点数为 n₂,则 n₀ = n₂ + 1。

两种特殊形态:满二叉树每层节点数都达到最大(无空缺);完全二叉树除最后一层外全满,最后一层节点从左到右连续排列——它可用数组高效存储。 一般二叉树常用链式存储(节点含 left、right 两个指针)。

6.3 二叉树的遍历

遍历指按某种次序访问每个节点且仅访问一次。根据根的访问时机分为(以左→右为序):

遍历方式访问顺序用途
前序遍历(先根)根 → 左 → 右复制树、显示目录结构、前缀表达式
中序遍历(中根)左 → 根 → 右二叉排序树中得到有序序列、中缀表达式
后序遍历(后根)左 → 右 → 根释放/计算目录大小、后缀表达式
层序遍历自上而下、自左向右逐层访问借助队列实现

6.4 哈夫曼树(最优二叉树)

路径长度是路径上的分支数。给定 n 个带权叶子构造一棵二叉树,使带权路径长度之和 WPL 最小的树称哈夫曼树。 构造方法:每次取权值最小的两棵树合并、新树根权值为两者之和,重复直到只剩一棵树。 其应用哈夫曼编码是一种不定长前缀编码:出现频率高的字符用短编码,广泛用于数据压缩(如 ZIP、JPEG)。

7图

图是比树更复杂的多对多非线性结构。现实中的交通线路、社交关系、课程先修关系、网络拓扑都可以用图建模。

7.1 图的定义与术语

图 G 由顶点集 V 和边集 E 组成,记 G=(V, E)。

  • 有向图 / 无向图:边有方向(弧:尾→头)或无方向。
  • 度、入度、出度:无向图中与顶点相连的边数为度;有向图分入度(以它为终点)和出度。
  • 权与网:边上标注距离、费用等数值(权),带权图也称网。
  • 路径与连通:两顶点间的顶点序列;无向图任意两顶点都有路径则为连通图。

7.2 图的存储

邻接矩阵

用二维数组 A[i][j] 表示顶点 i 到 j 是否有边(带权则存权值,无边存 ∞)。判断两顶点是否相邻极快 O(1),但稀疏图浪费空间,适合稠密图。

邻接表

为每个顶点建立一个链表,链接它的所有邻接点。节省空间、便于遍历邻接点,适合稀疏图,是实际中最常用的存储方式。

7.3 图的遍历

  • 深度优先搜索 DFS:从某顶点出发,沿一条路走到尽头再回溯,借助栈/递归实现——"不撞南墙不回头"。
  • 广度优先搜索 BFS:先访问起点,再依次访问其所有邻接点,层层外扩,借助队列实现——"水波式扩散",可求无权图最短路径。

7.4 经典图算法

问题算法思想
最小生成树Prim、Kruskal以最小总权值选出连通所有顶点的 n-1 条边(架光缆、修路)
单源最短路径Dijkstra(迪杰斯特拉)贪心逐步确定起点到各顶点的最短距离(导航)
各点对最短路径Floyd动态规划思想,用中间点松弛
工程活动排序拓扑排序、关键路径对有向无环图排序,判断课程/工序先后与工期

8查找

查找(检索):在数据集合中寻找关键字等于给定值的元素。衡量查找效率的主要指标是平均查找长度 ASL(查找成功时平均比较次数)。

8.1 线性表查找

  • 顺序查找:从头到尾逐个比较。对数据无任何要求,简单但慢,平均 ASL ≈ (n+1)/2,O(n)。
  • 二分查找(折半查找):要求数据已排序且顺序存储。每次与中间元素比较,排除一半:mid = (low+high)/2。 效率 O(log₂n),如在 100 万个有序数中找一个数最多约 20 次比较。
  • 分块查找(索引顺序查找):把表分块,块间有序、块内可无序,先查索引块再块内顺序找,是二者的折中。

8.2 树表查找

二叉排序树(二叉搜索树 BST):左子树值 < 根 < 右子树值。中序遍历得到升序序列,查找类似二分,平均 O(log n); 若插入顺序不当退化成链表则降为 O(n),为此有平衡二叉树(AVL)、红黑树通过旋转保持平衡(数据库索引、map 底层常用)。

8.3 哈希查找(散列)

思想:不经过比较,由关键字通过哈希函数 H(key) 直接计算存储地址,理想情况下查找 O(1)。如除留余数法 H(key) = key % p。 不同关键字可能算出同一地址,即冲突(碰撞),解决方法:

  • 开放定址法:冲突后按线性探测、二次探测等寻找下一个空位。
  • 链地址法:同一地址的所有同义词存入一个链表。
  • 再哈希法、建立公共溢出区等。装填因子(已存元素/表长)越大冲突越多,需适时扩容。

9排序

排序:把一组无序记录按关键字递增(或递减)排列起来,是最基础、使用最频繁的算法。 若多个记录关键字相等,排序后相对次序保持不变,则称算法稳定。

9.1 三大简单排序

  • 冒泡排序:相邻元素两两比较,逆序则交换,每轮把当前最大者"冒泡"到末尾。稳定,O(n²)。
  • 简单选择排序:每轮在未排序部分选出最小元素放到已排序序列末尾。比较次数与初始顺序无关,O(n²),不稳定。
  • 直接插入排序:把每个元素插入前面已排好序的序列中的正确位置(类似整理扑克牌)。稳定,O(n²),对基本有序的数据很快。

9.2 三种高效排序

  • 快速排序:选一个基准,把比它小的放左边、大的放右边,再对两边递归。平均 O(n log n),实践中最快的内排序之一,不稳定。
  • 归并排序:把序列不断对半拆成单元素(自然有序),再两两合并成有序序列。稳定、O(n log n),但需 O(n) 额外空间,适合外部排序。
  • 堆排序:利用完全二叉树性质的"堆"反复取堆顶极值。O(n log n)、原地、不稳定。

9.3 排序算法对比

算法平均时间最坏时间额外空间稳定性
冒泡排序O(n²)O(n²)O(1)稳定
选择排序O(n²)O(n²)O(1)不稳定
插入排序O(n²)O(n²)O(1)稳定
快速排序O(n log n)O(n²)O(log n)不稳定
归并排序O(n log n)O(n log n)O(n)稳定
堆排序O(n log n)O(n log n)O(1)不稳定

数据量小用简单排序;量大追求速度用快排;要求稳定用归并;语言标准库常采用混合排序(如内省排序 Timsort)。

🔑

本章小结:数据结构 = 逻辑结构(集合、线性、树、图)+ 存储结构(顺序、链式、索引、散列)+ 运算。 线性结构中:顺序表随机存取快、链表增删灵活、栈 LIFO 管"回溯"、队列 FIFO 管"排队";树和图表达层次与网络关系;查找从顺序、二分、BST 到哈希逐级提速;排序分 O(n²) 简单算法与 O(n log n) 高效算法。下一章学习如何在计算机中持久化管理大规模数据——数据库技术。