33_顺序表(待完善)

核心摘要: 本文使用C语言从零实现动态顺序表,重点讲解内存分配策略、增删改查的核心算法以及边界条件处理,解决"数组越界"和"扩容无效"两大常见问题。


一、顺序表的基本概念与结构设计

1. 逻辑结构与物理结构对比

   (1) 逻辑结构:线性表(元素之间具有一对一的前后关系)。

   (2) 物理结构:连续的内存地址(与数组一致,但支持动态扩展)。

2. 静态数组 vs 动态顺序表

   (1) 静态数组的致命缺陷:容量固定,编译时确定,易溢出。

   (2) 动态顺序表的优势:运行时扩容,内存利用率高。

3. 结构体定义(C语言描述)

   采用 SeqList 结构体封装三个必要属性:

c 复制代码
typedef int SLDataType;  // 方便后续修改数据类型(如改为char或double)

typedef struct SeqList
{
    SLDataType* a;      // 指向动态开辟数组的指针
    int size;           // 有效数据个数(当前长度)
    int capacity;       // 当前最大容量(总空间大小)
} SeqList;

    a. 为什么需要 capacity ------ 区分"已用空间"和"总空间",是扩容判断的依据。

    b. 为什么用 typedef 定义数据类型? ------ 提高代码复用性,一行修改即可适配int/float/struct。


二、核心操作的实现(增删改查)

1. 初始化与销毁(内存管理)

   (1) 初始化 :将指针置 NULLsizecapacity 置 0。

c 复制代码
void SLInit(SeqList* psl);

   (2) 销毁 :释放 psl->a,重新调用初始化(防止野指针)。

c 复制代码
void SLDestroy(SeqList* psl);

2. 扩容机制(核心难点)

   (1) 触发条件size == capacity

   (2) 扩容策略

    a. 小容量场景:capacity 为 0 时,开辟 4 个元素大小。

    b. 大容量场景:新容量 = 原容量 × 2(指数增长,减少 realloc 次数)。

   (3) 扩容函数实现

c 复制代码
void SLCheckCapacity(SeqList* psl)
{
   if (psl->size == psl->capacity)
   {
       int newCapacity = (psl->capacity == 0 ? 4 : psl->capacity * 2);
       SLDataType* tmp = (SLDataType*)realloc(psl->a, newCapacity * sizeof(SLDataType));
       if (tmp == NULL)
       {
           perror("realloc fail");
           return;
       }
       psl->a = tmp;
       psl->capacity = newCapacity;
   }

3. 增(头插 / 尾插 / 任意位置插入)

   (1) 尾插 :先检查容量,再在 psl->a[psl->size] 处赋值,最后 size++。时间复杂度 O(1)。

   (2) 头插 :从后往前移动元素(for 循环从 size1),腾出 [0] 位置。时间复杂度 O(n)。

   (3) 任意位置插入 :将 [pos, size-1] 全部后移一位,再插入。必须判断 pos 合法性(0 ≤ pos ≤ size)。

4. 删(头删 / 尾删 / 任意位置删除)

   (1) 尾删 :直接 size--(注意:不需要真正清除内存,下次插入会覆盖)。

   (2) 头删 :从前往后移动元素(for 循环从 1size-1),最后 size--

   (3) 任意位置删除 :将 [pos+1, size-1] 前移覆盖 pos 位置。

    ⚠️ 删除操作的三重检查:

    ⓵ 断言 psl 不为空。

    ⓶ 断言 psl->size > 0(空表不能删)。

    ⓷ 检查 pos 范围:0 ≤ pos < size。

5. 查与改

   (1) 按值查找:遍历数组,返回第一个匹配的下标(找不到返回 -1)。

   (2) 按位置修改 :直接赋值 psl->a[pos] = x前提是 pos < size

   (3) 按值修改:先查找位置,再修改(查找逻辑复用)。


三、打印与调试辅助函数

1. 打印顺序表

   遍历 size 次,输出 psl->a[i],格式统一为 [1, 2, 3, 4]

2. 菜单交互示例(用于控制台测试)

   提供 0-7 的数字菜单:

   ⓵ 尾插   ⓶ 头插   ⓷ 尾删   ⓸ 头删

   ⓹ 任意插入   ⓺ 任意删除   ⓻ 查找   ⓼ 打印   ⓿ 退出


四、常见错误与避坑指南(实用性重点)

1. 扩容时的野指针问题

   (1) 错误写法 :直接用 realloc(psl->a, newSize) 且不判断返回值。

   (2) 正确写法 :用临时指针 tmp 接收 realloc 返回值,成功后再赋值给 psl->a

    原因realloc 失败返回 NULL,直接赋值会丢失原有内存地址,导致内存泄漏且无法恢复。

2. 删除/插入时的越界访问

   (1) 头插移动元素时,必须从后往前移动for (i = size; i > pos; i--))。

   (2) 头删移动元素时,必须从前往后移动for (i = pos; i < size-1; i++))。

    口诀:插入向后挪,删除向前盖;反向操作必越界。

3. 结构体传值 vs 传址

   (1) 错误 :函数参数写 void func(SeqList sl),修改的是临时副本。

   (2) 正确 :一律传指针 void func(SeqList* psl),用 -> 访问成员。

4. 缩容的误区

   不建议在删除元素时立即缩容(频繁缩容会导致性能抖动)。业界通用做法:只扩不缩 ,或仅在剩余空间占比极低时(如 size < capacity/4)考虑缩容。


五、完整代码文件结构(项目组织)

1. 分文件编写(工业级规范)

   (1) SeqList.h ------ 头文件:结构体定义、函数声明、宏定义。

   (2) SeqList.c ------ 源文件:所有函数的具体实现。

   (3) test.c ------ 测试文件:main 函数和菜单交互。

2. 头文件中的防重复包含

c 复制代码
#ifndef _SEQLIST_H_
#define _SEQLIST_H_

// 所有声明放在这里

#endif

3. 编译命令示例(gcc)

bash 复制代码
gcc -o test SeqList.c test.c -std=c99 -Wall

六、顺序表的性能总结与适用场景

1. 时间复杂度汇总

操作 时间复杂度 备注
尾插 / 尾删 O(1) 平均情况(考虑扩容均摊后仍为 O(1))
头插 / 头删 / 任意位置插入删除 O(n) 需要移动大量元素
按位置访问 O(1) 随机访问是顺序表的最大优势
按值查找 O(n) 无序情况下必须遍历

2. 适用场景(什么时候用顺序表)

   ⓵ 需要频繁随机访问(如通过下标取第 i 个元素)。

   ⓶ 插入/删除操作只在尾部进行(栈结构)。

   ⓷ 元素个数可预估,或对内存连续有硬性要求(如 DMA 传输)。

3. 不适用场景(什么时候该换链表)

   ⓵ 频繁在头部或中间插入/删除。

   ⓶ 元素个数极其不稳定,且单个元素体积很大(扩容拷贝成本高)。

相关推荐
hujinyuan201606 分钟前
2025年12月中国电子学会青少年机器人技术等级考试试卷(二级) 真题+答案
人工智能·算法·机器人
映翰通朱工11 分钟前
工业4G网关无公网IP远程运维实战(内网终端异地访问方案)
运维·服务器·网络·安全·智能路由器
玖玥拾18 分钟前
C/C++ 基础笔记(十三)继承
c语言·c++·继承
洪晓露28 分钟前
将 rke2 集群证书延长至 10 年
运维·服务器·数据库
谢平康1 小时前
解决用 rm 报bash: /usr/bin/rm: Argument list too long错
linux·运维·运维开发
IP老炮不瞎唠1 小时前
Python 价格监控如何实现?思路与实用方法分享
运维·服务器·网络
bIo7lyA8v1 小时前
算法复杂度评估的实验统计方法与可视化的技术8
算法
李老师讲编程1 小时前
中国电子学会图形化2020.12月Scratch三级考级题
算法·scratch·信息学奥赛·图形化编程·scratch素材
睡不醒男孩0308232 小时前
CLup 6.x 版本中针对StarRocks 存算一体集群的完整操作手册
java·服务器·网络·clup
退休倒计时2 小时前
【每日一题】LeetCode 53. 最大子数组和 TypeScript
数据结构·算法·leetcode·typescript