第二章 线性表

2.1 线性表的基本概念

2.1.1 线性表的定义

  • 线性表是具有相同数据类型的 (n) 个数据元素组成的有限序列。

记作:

java 复制代码
L=(a_1,a_2,。。。。,a_n)  

其中:

  • (a_1) 是第一个元素。
  • (a_n) 是最后一个元素。
  • 当 (n=0) 时,线性表为空表。

2.1.2 线性表的逻辑特征

  1. 存在唯一的第一个元素。
  2. 存在唯一的最后一个元素。
  3. 除第一个元素外,每个元素有且仅有一个直接前驱。
  4. 除最后一个元素外,每个元素有且仅有一个直接后继。

2.1.3 线性表的基本操作

操作 含义
InitList 初始化线性表
Length 获取线性表长度
GetElem 获取指定位置的元素
LocateElem 查找满足条件的元素
ListInsert 在指定位置插入元素
ListDelete 删除指定位置的元素
PrintList 遍历或输出线性表
Empty 判断线性表是否为空
DestroyList 销毁线性表

本节总结: 线性表是一种线性逻辑结构。其核心特征是元素之间存在唯一的前后关系,常见实现方式为顺序存储和链式存储。


2.2 顺序表

2.2.1 顺序表的定义

  • 顺序表是使用一组地址连续的存储单元依次存储线性表元素的存储结构。

2.2.2 顺序表的特点

  1. 逻辑相邻的元素在物理存储位置上也相邻。
  2. 支持通过下标随机访问元素。
  3. 插入和删除可能需要移动大量元素。
  4. 静态数组的容量通常固定;动态数组可以根据需要扩容。

2.2.3 顺序表的结构示例

java 复制代码
//数组通过下标访问元素,通常可以在常数时间内完成。
int[] arr = new int[10];
arr[0] = 10;
arr[1] = 20;
arr[2] = 30;
System.out.println(arr[1]);

2.2.4 顺序表的插入

在长度为 (n) 的顺序表中,于第 (i) 个位置插入元素,需要将原来第 (i) 个及其之后的元素向后移动。
基本步骤:

  1. 判断插入位置是否合法。
  2. 判断顺序表是否有剩余容量。
  3. 将第 (n) 个元素至第 (i) 个元素依次向后移动。
  4. 将新元素放入第 (i) 个位置。
  5. 更新表长。
  • 示例:
java 复制代码
int[] arr = {10, 20, 30, 40, 0};
int length = 4;
int position = 2;
int value = 15;
for (int i = length; i > = position; i--) {
    arr[i] = arr[i - 1];
}
arr[position - 1] = value;
length++;

插入后有效元素为:

java 复制代码
10, 15, 20, 30, 40

2.2.5 顺序表的删除

删除第 (i) 个元素时,需要将该元素之后的所有元素依次向前移动。
基本步骤:

  1. 判断删除位置是否合法。
  2. 删除目标位置的元素。
  3. 将后续元素依次向前移动。
  4. 更新表长。
  • 示例:
java 复制代码
int[] arr = {10, 20, 30, 40, 50};
int length = 5;
int position = 3;
for (int i = position - 1; i < length - 1; i++) {
    arr[i] = arr[i + 1];
}
length--;
  • 删除后有效元素为:
java 复制代码
10, 20, 40, 50

2.2.6 顺序表的时间复杂度

操作 时间复杂度 原因
按下标访问 (O(1)) 可以直接计算元素地址
查找指定元素 (O(n)) 最坏情况下需要遍历全部元素
在表尾插入 (O(1)) 假设容量充足
在表头插入 (O(n)) 需要移动原有元素
在表中插入 (O(n)) 可能需要移动后续元素
删除表尾元素 (O(1)) 只需修改表长
删除表头元素 (O(n)) 需要移动后续元素

本节总结: 顺序表最大的优势是随机访问效率高;主要缺点是插入、删除可能引起元素移动,且需要管理存储容量。


2.3 单链表

2.3.1 单链表的定义

单链表通过结点之间的指针连接数据元素,每个结点包含数据域和指针域。

java 复制代码
class Node {
    int data;
    Node next;
    Node(int data) {
        this.data = data;
    }
}

其中:

  • data:保存结点数据。
  • next:指向下一个结点。
  • 最后一个结点的 next 通常为 null。

2.3.2 单链表的特点

  1. 结点在内存中不要求连续存储。
  2. 通过指针建立逻辑上的前后关系。
  3. 插入、删除操作通常不需要移动其他结点。
  4. 不支持像数组那样直接通过下标随机访问。
  5. 查找某个位置的结点通常需要从头遍历。

2.3.3 单链表的遍历

java 复制代码
Node current = head;
while (current ! = null) {
    System.out.println(current.data);
    current = current.next;
}
  • 从头结点开始,依次访问结点,直到指针为 null。
    时间复杂度:

    O(n)

2.3.4 单链表的插入

在结点 p 后面插入新结点 s:

java 复制代码
s.next = p.next;
p.next = s;
  • 必须先让新结点指向原来的后继,再修改前驱结点的指针。
  • 若顺序写反,可能丢失后续链表的访问路径。

2.3.5 单链表的删除

  • 删除结点 p 后面的结点:
java 复制代码
if (p ! = null && p.next ! = null) {
    p.next = p.next.next;
}
  • 通过修改指针,跳过需要删除的结点。

2.3.6 头指针、头结点和首元结点

概念 含义
头指针 指向链表第一个结点的指针
头结点 为操作方便设置的附加结点,通常不存放有效数据
首元结点 链表中第一个真正存放数据的结点
  • 设置头结点可以统一空表与非空表的操作,减少特殊情况的处理。

2.3.7 单链表的时间复杂度

操作 时间复杂度 说明
按位置查找 (O(n)) 需要逐个遍历
按值查找 (O(n)) 最坏情况下需要遍历全表
已知前驱后的插入 (O(1)) 只需要修改指针
已知前驱后的删除 (O(1)) 只需要修改指针
遍历链表 (O(n)) 每个结点访问一次

本节总结: 单链表通过指针连接结点。它的插入、删除在已知前驱结点时效率较高,但访问指定位置需要遍历。


2.4 双链表

2.4.1 双链表的定义

  • 双链表的每个结点包含数据域、前驱指针和后继指针。
java 复制代码
class DoubleNode {
    int data;
    DoubleNode prev;
    DoubleNode next;
    DoubleNode(int data) {
        this.data = data;
    }
}

2.4.2 双链表的特点

  1. 可以沿 next 方向向后遍历。
  2. 可以沿 prev 方向向前遍历。
  3. 插入和删除操作可以直接修改前驱和后继关系。
  4. 每个结点需要额外存储一个指针。

2.4.3 双链表插入

  • 在结点 p 后插入结点 s,基本指针关系如下:
java 复制代码
s.prev = p;
s.next = p.next;
if (p.next != null) {
    p.next.prev = s;
}
p.next = s;
  • 需要同时维护前驱和后继指针。

2.4.4 双链表删除

  • 删除结点 p 时:
java 复制代码
if (p.prev ! = null) {
    p.prev.next = p.next;
}
if (p.next ! = null) {
    p.next.prev = p.prev;
}

实际应用中还需要根据是否存在头结点、尾结点等情况维护链表的首尾引用。

本节总结: 双链表支持双向遍历,删除已定位的结点较方便,但需要额外的指针空间,且插入、删除时必须维护前驱和后继关系。


2.5 循环链表

2.5.1 循环单链表

1.循环单链表的最后一个结点不指向 null,而是指向链表中的指定结点,通常指向头结点或第一个数据结点。
2.若采用头结点形式,则空循环单链表可以让头结点的 next 指向自身。

2.5.2 循环双链表

循环双链表同时具有前驱和后继指针,并且首尾结点相互连接。
常见形式:

  • 尾结点的 next 指向头结点。
  • 头结点的 prev 指向尾结点。

2.5.3 循环链表的特点

  1. 可以从指定结点继续遍历。
  2. 遍历时需要判断是否回到起始位置,避免无限循环。
  3. 适合循环调度、轮流访问等场景。

本节总结: 循环链表通过首尾连接形成循环结构,适合循环访问问题,但遍历终止条件必须设计正确。


2.6 顺序表与链表的比较

比较项目 顺序表 单链表
存储方式 连续存储 结点分散存储
随机访问 (O(1)) (O(n))
按值查找 (O(n)) (O(n))
已知位置插入 可能需要移动元素 定位结点后修改指针
已知位置删除 可能需要移动元素 定位前驱后修改指针
空间开销 可能存在预留容量 每个结点需要指针空间
适合场景 频繁访问、按下标读取 插入删除较多、结点数量变化较大

本节总结: 顺序表适合随机访问,链表适合在已定位结点后进行灵活的插入和删除。具体选择应根据实际操作频率决定。


相关推荐
w_zero_one41 分钟前
递归算法(一)
java·开发语言·算法
SL_staff1 小时前
私有化低代码不是减配版开发:JVS双模式建模如何重建IT交付信任
java·低代码·spring cloud
墨着染霜华1 小时前
Redocly 生成 OpenAPI 接口文档踩坑实录|Node 版本兼容 + 接口地址错误问题解决
java
文丑颜不良啊1 小时前
LeetCode1881~1990题目解析
java·leetcode
风影同志783242 小时前
20 个比特能装下多少行情?——实测拆解 Kronos 的 K 线分词器
算法
十碗饭吃不饱2 小时前
后端运维:网络联通、进程查询、Java服务启动脚本
java·运维·网络
T_Wizard2 小时前
后端转 AI 应用开发,我先把这 12 个名词理清了:一张图看懂架构、RAG、Tool Calling 与 MCP
java·ai编程
杨航 AI3 小时前
JEV的用法
java·前端·数据库
灿飞3 小时前
Kotlin与Java虚拟机七:空安全:把十亿美元的错误消灭在类型系统里
java·kotlin
写的都是BUG3 小时前
APK 里的钥匙,一直就放在门口:聊聊加固真正该做什么
前端·算法