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. 不适用场景(什么时候该换链表)

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

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

相关推荐
晓蛋2 天前
c语言指的是什么意思
c语言·编译器·编程开发·集成开发环境·程序实例
一隅论数智2 天前
给AI一张“业务概念地图“:本体如何从哲学走向企业智能
大数据·人工智能·经验分享·笔记·学习·学习方法·政务
倒头就睡的小比特2 天前
算法竞赛C++常用的STL
c++·算法
小羊没烦恼!2 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
未济2 天前
linux 配置环境变量
linux
傲世仙尊2 天前
目录即文件-Ext文件系统收尾篇
linux·c语言
虎头金猫2 天前
4K 视频总卡在公网带宽?用 N1 + OpenList 把网盘播放链路重新理顺
运维·服务器·网络·python·容器·beautifulsoup·pandas
牵猫散步的鱼儿2 天前
重载、重写(覆盖)、重定义区别
c语言
猎头南楼2 天前
知识社区推荐系统实践:新用户冷启动与长短期兴趣建模的挑战 资深推荐算法工程师
人工智能·深度学习·算法·机器学习
phltxy2 天前
C 语言指针:从内存地址到灵活的数据访问
c语言