一、线性表
线性表是数据结构中最基本、最常用的一种线性结构,它是由n 个具有相同特性的数据元素组成的有限序列。其核心特征是:除第一个元素外,每个元素有且仅有一个直接前驱;除最后一个元素外,每个元素有且仅有一个直接后继,即元素之间呈一对一的线性关系。
线性表在逻辑上是线性结构 (人为想象的结构),也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连续的 , 线性表在物理上存储时,通常以数组和链式结构的形式存储。
常⻅的线性表:顺序表、链表、栈、队列、字符串...
二、顺序表
1.概念与结构
概念:顺序表是线性表的顺序存储结构 ,它通过一段连续的存储空间依次存储元素,使得元素的逻辑顺序与物理存储顺序完全一致 (逻辑结构和物理结构都是线性的)。这种结构的核心特点是 "地址连续、依次存放 ",通常借助数组实现。
2.顺序表与数组的区别
- 本质与定义
数组:编程语言原生的基础存储结构,是连续内存块, 仅用于存储同类型元素,无长度管理功能(需用户手动记录有效元素数)。
顺序表:基于数组实现的数据结构,封装了数组 + 长度信息(length),用于实现线性表的逻辑结构(一对一关系)。 - 功能差异
数组:仅提供存储和下标访问,无插入、删除等逻辑操作,需用户手动处理元素移动、边界检查。
顺序表:封装了线性表完整操作(插入、删除、查找、判空等),自动处理边界和元素移动,屏蔽底层细节。 - 长度管理
数组:容量固定,无法直接区分 "总容量" 和 "有效元素数"。
顺序表:明确区分 "容量"(底层数组大小)和 "当前长度"(length 记录有效元素数)。
核心关系:数组是顺序表的底层存储工具,顺序表是对数组的功能扩展与逻辑封装。
3.分类
3.1静态顺序表
概念:底层数组的容量在定义时固定,编译期即确定,运行过程中无法动态调整
结构:
c
// 静态顺序表:底层基于固定大小数组实现,容量编译时确定,不可动态扩容
// 核心是封装数组+有效元素个数,实现线性表的基础逻辑结构(一对一关系)
// 1. 数据类型重定义:将int重命名为SLDataType
// 作用:统一管理存储类型,后续需修改元素类型(如float、char),仅改此处即可,提升代码可维护性
typedef int SLDataType;
// 2. 宏定义静态顺序表的最大容量:固定为7
// 数组arr的大小由N决定,编译期确定,静态顺序表的容量不可修改
#define N 7
// 3. 定义静态顺序表的结构体类型,最终重命名为SL(简化使用)
typedef struct Seqlist
{
SLDataType arr[N]; // 存储数据的底层数组,物理连续,最大可存N(7)个SLDataType类型元素
int size; // 记录当前顺序表中有效元素的个数(0 ≤ size ≤ N)
// 关键:区分数组容量(N)和实际数据量,用于判断表空(size=0)或表满(size=N)
}SL; // 结构体重命名为SL,后续定义变量可直接写 SL list; 无需重复写struct Seqlist
静态顺序表缺陷:空间给少了不够⽤,给多了造成空间浪费
3.2动态顺序表
概念:底层数组的容量可在运行时动态调整
结构:
c
运行
// 动态顺序表:底层基于动态分配的数组实现,容量可在运行时动态调整,
// 解决静态顺序表容量固定的问题,适用于元素数量不确定的场景
// 1. 数据类型重定义:将int命名为SLDataType
// 作用:统一管理存储类型,后续若需存储其他类型(如float、结构体等),仅修改此处即可,提升代码可维护性
typedef int SLDataType;
// 2. 定义动态顺序表的结构体类型,最终重命名为SL(简化变量定义)
typedef struct SqeList // 注:结构体名应为SeqList(顺序表的规范拼写,此处SqeList可能为笔误)
{
SLDataType* arr; // 指向动态分配的数组的指针,用于实际存储元素
// 数组大小可通过动态内存函数(如realloc)调整,实现容量动态变化
int size; // 记录当前顺序表中有效元素的个数(0 ≤ size ≤ capacity)
// 是顺序表的核心属性,反映实际数据量
int capacity; // 记录当前底层数组的容量(最大可容纳的元素数)
// 用于判断是否需要扩容(当size == capacity时,插入元素前需扩容)
}SL; // 结构体重命名为SL,后续定义变量可直接写 SL list; 无需重复写struct SqeList
三、动态顺序表的实现
1. 创建SeqList.h SeqList.c test.c文件
-
SeqList.h:声明结构体类型、宏定义、函数原型,作为模块的接口,供其他文件(SeqList.c和test.c)包含。 -
SeqList.c: 实现 SeqList.h 中声明的所有函数,是顺序表功能的具体实现。 -
test.c: 调用 SeqList.h 中声明的函数,测试顺序表的各种操作是否正确,验证功能完整性。
2.定义动态顺序表的结构
c
SeqList.h
#pragma once
#include <stdio.h>
//定义动态顺序表的结构
// 1. 数据类型重定义:将int命名为SLDataType
// 作用:统一管理存储类型,后续若需存储其他类型(如float、结构体等),仅修改此处即可,提升代码可维护性
typedef int SLDataType;
// 2. 定义动态顺序表的结构体类型,最终重命名为SL(简化变量定义)
typedef struct SqeList // 注:结构体名应为SeqList(顺序表的规范拼写,此处SqeList可能为笔误)
{
SLDataType* arr; // 指向动态分配的数组的指针,用于实际存储元素
// 数组大小可通过动态内存函数(如realloc)调整,实现容量动态变化
int size; // 记录当前顺序表中有效元素的个数(0 ≤ size ≤ capacity)
// 是顺序表的核心属性,反映实际数据量
int capacity; // 记录当前底层数组的容量(最大可容纳的元素数)
// 用于判断是否需要扩容(当size == capacity时,插入元素前需扩容)
}SL; // 结构体重命名为SL,后续定义变量可直接写 SL list; 无需重复写struct SqeList
3. 初始化顺序表
我们需要写一个 void SLInit() 函数将 结构体SL 里的 SLDataType* arr 置为空,即NULL ,size 和 capacity 置为0
void SLInit()该函数的参数必须是指针形式,原因是需要通过指针修改外部结构体变量的内部状态。
如果参数定义为结构体本身(void SLInit(SL ps)),函数会对传入的结构体进行值拷贝------ 即创建一个与外部 结构体完全相同的临时副本,函数内部所有操作都只作用于这个临时副本,而不会影响外部原始的结构体变量。
当参数为指针(SL* ps)时,函数接收的是外部结构体变量的地址。通过这个地址(指针),函数可以直接访问并修改外部结构体的内部成员(ps->arr、ps->size 等),确保初始化操作能作用于原始变量
c
SeqList.h
#pragma once
#include <stdio.h>
//顺序表的初始化
void SLInit(SL* ps);
c
SeqList.c
#include "SeqList.h"
// 初始化顺序表
// 功能:将顺序表初始化为空表状态,为后续操作做准备
// 参数:ps - 指向顺序表结构体(SL)的指针,用于操作具体的顺序表实例
void SLInit(SL* ps)
{
ps->arr = NULL; // 将指向底层数组的指针置空(初始无动态分配的内存)
ps->size = ps->capacity = 0; // 初始化有效元素个数(size)和容量(capacity)均为0
// size=0表示当前无有效元素;capacity=0表示初始无存储容量
}
接着我们需要在test.c文件测试函数是否可以正常运行
c
test.c
// 测试顺序表初始化函数(SLInit)的测试用例函数
// 功能:创建一个顺序表实例并调用初始化函数,验证初始化操作是否可正常执行
void SLInit_test()
{
SL SeqList; // 定义一个顺序表结构体变量(未初始化,成员为随机值)
SLInit(&SeqList); // 调用初始化函数,传入结构体的地址
// 目的:通过SLInit函数将SeqList初始化为空表状态(arr=NULL, size=0, capacity=0)
}
// 程序入口函数
int main()
{
SLInit_test(); // 调用初始化测试函数,执行顺序表的初始化测试
return 0; // 程序正常结束
}
可以看见初始化成功

4. 检查容量并扩容(核心逻辑)
- 创建函数
void SLCheckCapacity(SL* ps),函数需要一个参数,ps指向顺序表的指针
c
// 检查容量并扩容
void SLCheckCapacity(SL* ps)
{
}
- 在检查容量之前,需要判断传来的顺序表指针
SL* ps,如果为空,程序停止运行,可以用assert(ps != NULL)语句实现,其所要包含的头文件是<assert.h>
c
assert(ps != NULL)
- 检查容量是否充足,运用
if (ps->size == ps->capacity)语句来检测容量是否充足。如果容量不足,用realloc()进行扩容。 - 使用
realloc需要包含头文件stdlib.h,realloc第一个参数是扩容的对象,第二个参数是扩容的大小(单位是字节) ,并且需要把realloc的数据类型强转为扩容对象的数据类型 - 先使用
tmp临时存储扩容后的ps->arr,是为了防止扩容失败导致数据丢失
c
if (ps->size == ps->capacity)
{
// 扩容策略:初始容量为0则设为4,否则扩为原来的2倍
int newCapacity = (ps->capacity == 0) ? 4 : ps->capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
}
- 使用
if (tmp == NULL)判断tmp是否为空,为空说明扩容失败。
c
if (tmp == NULL)
{
perror("realloc failed"); // 打印内存分配失败原因
exit(EXIT_FAILURE); // 退出程序
}
- 如果
tmp不为空,说明扩容成功,将tmp的值赋给ps ->arr,newCapacity的值赋给capacity
c
ps->arr = tmp;
ps->capacity = newCapacity;
- 该函数完整代码
c
SeqList.c
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//检查容量并扩容
void SLCheckCapacity(SL* ps);
c
SeqList.h
#include "SeqList.h"
// 检查并扩容顺序表
// 功能:当顺序表现有元素个数达到容量上限时,动态扩容以容纳更多元素
// 参数:ps - 指向顺序表结构体(SL)的指针,用于访问和修改顺序表的容量与数组
void SLCheckCapacity(SL* ps)
{
assert(ps != NULL); // 断言:确保传入的指针ps不为NULL(避免对空指针操作导致崩溃)
// 判断当前有效元素个数是否等于容量(即表是否已满)
if (ps->size == ps->capacity)
{
// 计算新容量:若初始容量为0(空表),则初始化为4;否则扩容为原容量的2倍
// 2倍扩容是常用策略,平衡扩容频率和内存利用率
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
// 重新分配内存:使用realloc调整动态数组的大小
// realloc会尝试在原内存地址扩展,若失败则分配新地址并复制原数据
SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
// 检查内存分配是否成功(注:原代码此处判断条件有误,应为tmp == NULL,以下已修正逻辑说明)
// 若tmp为NULL,表示realloc失败(如内存不足)
if (tmp == NULL)
{
perror("realloc failed"); // 打印错误信息(如"realloc failed: Cannot allocate memory")
exit(EXIT_FAILURE); // 异常退出程序(EXIT_FAILURE表示程序执行失败)
}
ps->arr = tmp; // 更新数组指针为新分配的内存地址
ps->capacity = newCapacity; // 更新顺序表的容量为新容量
}
}
5.打印顺序表中的有效元素
- 创建函数
SLPrint(SL* ps),函数需要一个参数,ps指向顺序表的指针
c
void SLPrint(SL* ps)
{
}
- 需要判断传来的顺序表指针
SL* ps,如果为空,程序停止运行
c
assert(ps != NULL);
- 遍历顺序表的有效元素并逐个打印,所有元素打印完毕后换行
c
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
- 该函数完整代码
c
SeqList.c
// 打印顺序表中的所有有效元素
// 功能:遍历顺序表的有效元素并逐个打印,方便查看当前顺序表的内容
// 参数:ps - 指向顺序表结构体(SL)的指针,用于访问顺序表中的元素和有效元素个数
void SLPrint(SL* ps)
{
assert(ps != NULL); // 断言:确保传入的顺序表指针不为NULL,避免对空指针操作导致错误
// 遍历顺序表中所有有效元素(下标从0到size-1)
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]); // 打印当前下标对应的元素,以空格分隔
}
printf("\n"); // 所有元素打印完毕后换行,使输出格式更清晰
}
6.尾插
- 创建函数
void SLPushBack(SL* ps, SLDataType x),函数需要两个参数,ps指向顺序表的指针,x要插入的元素值
c
void SLPushBack(SL* ps, SLDataType x)
{
}
- 需要判断传来的顺序表指针
SL* ps,如果为空,程序停止运行
c
assert(ps != NULL);
- 用
void SLCheckCapacity(SL* ps)函数检查容量是否足够,空间容量不够该函数会进行扩容。调用SLCheckCapacity函数后,顺序表一定有足够空间插入新元素。接着插入元素:将x存入数组的末尾(下标为当前size的位置),然后size++ - 该函数完整代码
c
SeqList.c
// 尾插操作:在顺序表的末尾插入一个新元素
// 功能:将元素x添加到顺序表的最后一个有效元素之后,更新有效元素个数
// 参数:
// ps - 指向顺序表结构体(SL)的指针,用于操作目标顺序表
// x - 要插入的元素值(类型为SLDataType,与顺序表存储类型一致)
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps != NULL); // 断言:确保传入的顺序表指针不为NULL,避免对空指针操作
// 检查容量:若当前元素个数已达容量上限,自动扩容以容纳新元素
// 调用SLCheckCapacity函数后,顺序表一定有足够空间插入新元素
SLCheckCapacity(ps);
// 插入元素:将x存入数组的末尾(下标为当前size的位置)
// 同时通过后置++操作,将有效元素个数size加1(先使用size的值作为下标,再自增)
ps->arr[ps->size++] = x;
}
- 在test.c中测试
c
void SLPushBack_test()
{
SL SeqList;
SLInit(&SeqList);
SLPushBack(&SeqList, 1);
SLPushBack(&SeqList, 2);
SLPushBack(&SeqList, 3);
SLPushBack(&SeqList, 4);
SLPushBack(&SeqList, 5);
SLPrint(&SeqList);
}
int main()
{
//SLInit_test();
SLPushBack_test();
return 0;
}
运行成功

7.头插
原理:把所有元素都往后移动一位,在开头插入所需数据
1.创建函数void SLPushFront(SL* ps, SLDataType x),函数需要两个参数,ps 指向顺序表的指针,x 要插入的元素值
c
void SLPushFront(SL* ps, SLDataType x)
{
}
2.需要判断传来的顺序表指针SL* ps,如果为空,程序停止运行
c
assert(ps != NULL);
-
用
void SLCheckCapacity(SL* ps)函数检查容量是否足够,空间容量不够该函数会进行扩容。调用SLCheckCapacity函数后,顺序表一定有足够空间插入新元素。 -
把所有元素都往后移动一位,在开头插入所需数据 ,顺序表有效个数
size+1 -
该函数完整代码
c
SeqList.c
// 头插操作:在顺序表的头部插入一个元素
// 参数:ps 指向顺序表的指针,x 要插入的元素值
void SLPushFront(SL* ps, SLDataType x)
{
// 断言检查:确保传入的顺序表指针不为空,避免对空指针操作导致未定义行为
assert(ps != NULL);
// 检查并扩容:若当前顺序表容量不足,自动扩容以容纳新元素
SLCheckCapacity(ps);
// 数据后移:从最后一个元素开始,将所有元素依次向后移动一个位置
// 目的是为头部腾出第一个位置(索引0),避免插入时覆盖原有数据
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i - 1]; // 第i个位置的值更新为第i-1个位置的值
}
// 插入新元素:在头部(索引0)放入要插入的值x
ps->arr[0] = x;
// 更新长度:顺序表元素个数加1
ps->size++;
}
- 在test.c中测试
c
// 测试顺序表头插功能(SLPushFront函数)的测试函数
void SLPushFront_test()
{
// 定义一个顺序表变量
SL SeqList;
// 初始化顺序表(完成初始空间分配、size置0等初始化操作)
SLInit(&SeqList);
// 多次调用头插函数,向顺序表头部插入元素
SLPushFront(&SeqList, 1); // 第一次头插:表中元素为 [1]
SLPushFront(&SeqList, 2); // 第二次头插:新元素2插入头部,表中元素为 [2, 1]
SLPushFront(&SeqList, 3); // 第三次头插:新元素3插入头部,表中元素为 [3, 2, 1]
SLPushFront(&SeqList, 4); // 第四次头插:新元素4插入头部,表中元素为 [4, 3, 2, 1]
SLPushFront(&SeqList, 5); // 第五次头插:新元素5插入头部,表中元素为 [5, 4, 3, 2, 1]
// 打印顺序表中的所有元素,验证头插操作的正确性
SLPrint(&SeqList);
}
运行成功

8.在任意位置插入
原理:把pos位置后面的元素都往后移动一位,在pos位置插入所需数据
- 创建函数
void SLInsert(SL* ps, int pos, SLDataType x),函数需要三个参数,ps指向顺序表的指针,pos要插入的位置(从1开始计数),x要插入的元素值
c
void SLInsert(SL* ps, int pos, SLDataType x)
{
}
- 需要判断传来的顺序表指针
SL* ps,如果为空,程序停止运行
c
assert(ps != NULL);
- 确保插入位置合法,合法范围是
[1, ps->size + 1],即可以插入到第一个位置、中间位置或末尾(表尾插入)
pos=1表示插在头部,pos=ps->size+1表示插在尾部
c
assert(pos >= 1 && pos <= ps->size + 1);
-
用
void SLCheckCapacity(SL* ps)函数检查容量是否足够,空间容量不够该函数会进行扩容。调用SLCheckCapacity函数后,顺序表一定有足够空间插入新元素 -
把
pos后所有元素都往后移动一位,在pos位置插入所需数据 ,顺序表有效个数size+1 -
该函数完整代码
c
SeqList.c
void SLInsert(SL* ps, int pos, SLDataType x)
{
// 断言检查:确保顺序表指针不为空,避免对空指针操作
assert(ps != NULL);
// 断言检查:确保插入位置合法
// 合法范围是 [1, ps->size + 1],即可以插入到第一个位置、中间位置或末尾(表尾插入)
// 若pos=1表示插在头部,pos=ps->size+1表示插在尾部
assert(pos >= 1 && pos <= ps->size + 1);
// 检查并扩容:若当前容量不足,自动扩容以容纳新元素
SLCheckCapacity(ps);
// 元素后移:从最后一个元素开始,到pos位置为止,依次向后移动一个位置
// 目的是为pos位置腾出空间,避免插入时覆盖原有数据
// 注意:i从ps->size(原最后一个元素的下一位)开始,直到i >= pos时停止
for (int i = ps->size; i >= pos; i--)
{
ps->arr[i] = ps->arr[i - 1]; // 将第i-1个元素的值赋给第i个位置
}
// 插入新元素:在pos对应的索引位置(pos-1,因为数组从0开始)放入元素x
ps->arr[pos - 1] = x;
// 更新顺序表长度:元素个数加1
ps->size++;
}
- 在test.c中测试
c
// 测试顺序表的插入功能(SLInsert函数)的测试函数
void SLInsert_test()
{
// 定义一个顺序表变量
SL SeqList;
// 初始化顺序表(分配初始空间、设置初始长度等)
SLInit(&SeqList);
// 先通过头插法向顺序表中插入一些测试数据
SLPushFront(&SeqList, 1); // 头插1,此时表中元素:[1]
SLPushFront(&SeqList, 2); // 头插2,此时表中元素:[2, 1]
SLPushFront(&SeqList, 3); // 头插3,此时表中元素:[3, 2, 1]
SLPushFront(&SeqList, 4); // 头插4,此时表中元素:[4, 3, 2, 1]
SLPushFront(&SeqList, 5); // 头插5,此时表中元素:[5, 4, 3, 2, 1]
// 打印当前顺序表的元素,验证插入结果
SLPrint(&SeqList);
// 在位置4插入元素10(位置从1开始计数,即插入到第4个元素后面)
// 插入前元素:[5, 4, 3, 2, 1],插入后预期:[5, 4, 3, 10, 2, 1]
SLInsert(&SeqList, 4, 10);
// 打印插入后的顺序表,验证SLInsert函数的正确性
SLPrint(&SeqList);
}
运行成功

9.尾删
原理:直接减少有效元素个数(无需真正删除,后续插入会覆盖)
- 创建函数
SLPopBack(SL* ps),函数需要一个参数,ps指向顺序表的指针
c
void SLPopBack(SL* ps)
{
}
- 需要判断传来的顺序表指针
SL* ps,如果为空,程序停止运行
c
assert(ps != NULL);
- 避免对空表 执行删除操作,即
ps->size != 0
c
assert(ps->size != 0);
- 将顺序表的元素个数减1,此处不实际清除原末尾元素的内存值,而是通过减小size,使该元素在逻辑上"被删除"
- 该函数完整代码
c
SeqList.c
// 尾删操作:删除顺序表中最后一个元素
// 参数:ps 指向顺序表的指针(通过指针操作原顺序表)
void SLPopBack(SL* ps)
{
// 断言检查:确保传入的顺序表指针不为空
// 防止对空指针进行解引用操作,避免程序崩溃或未定义行为
assert(ps != NULL);
// 断言检查:确保顺序表不为空(元素个数大于0)
// 避免对空表执行删除操作(无元素可删,逻辑错误)
assert(ps->size != 0);
// 尾删核心逻辑:将顺序表的元素个数减1
// 说明:顺序表底层为数组,此处不实际清除原末尾元素的内存值,
// 而是通过减小size,使该元素在逻辑上"被删除"(不再被顺序表的操作访问),
// 后续插入新元素时会覆盖该位置的旧值,节省操作成本
ps->size--;
}
- 在test.c测试
c
// 测试顺序表尾删功能(SLPopBack函数)的测试函数
void SLPopBack_test()
{
// 定义一个顺序表变量
SL SeqList;
// 初始化顺序表(完成初始空间分配、size置0等初始化操作)
SLInit(&SeqList);
// 先通过头插法向顺序表中插入一些测试数据
SLPushFront(&SeqList, 1); // 头插1,表中元素:[1]
SLPushFront(&SeqList, 2); // 头插2,表中元素:[2, 1]
SLPushFront(&SeqList, 3); // 头插3,表中元素:[3, 2, 1]
SLPushFront(&SeqList, 4); // 头插4,表中元素:[4, 3, 2, 1]
SLPushFront(&SeqList, 5); // 头插5,表中元素:[5, 4, 3, 2, 1]
// 打印初始插入后的顺序表,作为尾删操作的参照
SLPrint(&SeqList);
// 多次调用尾删函数,逐步删除顺序表的最后一个元素,并打印结果验证
SLPopBack(&SeqList); // 删除最后一个元素1,预期结果:[5, 4, 3, 2]
SLPrint(&SeqList); // 打印验证
SLPopBack(&SeqList); // 再删除最后一个元素2,预期结果:[5, 4, 3]
SLPrint(&SeqList); // 打印验证
SLPopBack(&SeqList); // 再删除最后一个元素3,预期结果:[5, 4]
SLPrint(&SeqList); // 打印验证
SLPopBack(&SeqList); // 再删除最后一个元素4,预期结果:[5]
SLPrint(&SeqList); // 打印验证
SLPopBack(&SeqList); // 再删除最后一个元素5,预期结果:空表(无元素)
SLPrint(&SeqList); // 打印验证(通常为空输出或提示信息)
}
运行成功

10.头删
原理:所有元素向前移动一位,覆盖头元素
- 创建函数
SLPopFront(SL* ps),函数需要一个参数,ps指向顺序表的指针
c
void SLPopFront(SL* ps)
{
}
- 需要判断传来的顺序表指针
SL* ps,如果为空,程序停止运行
c
assert(ps != NULL);
- 从第二个元素开始,将所有元素依次向前移动一个位置,目的是覆盖第一个元素(逻辑上删除头部元素)
c
for (int i = 0; i < ps->size - 1; i++)
{
// 循环范围:i从0到size-2(因为i+1最大为size-1,即最后一个元素)
ps->arr[i] = ps->arr[i + 1];
}
- 顺序表有效个数size减1
c
ps->size--;
- 该函数完整代码
c
SeqList.c
// 头删操作:删除顺序表的第一个元素
// 参数:ps 指向顺序表的指针(通过指针修改原顺序表)
void SLPopFront(SL* ps)
{
// 断言检查:确保传入的顺序表指针不为空
// 防止对空指针解引用,避免程序崩溃或未定义行为
assert(ps != NULL);
// 元素前移:从第二个元素开始,将所有元素依次向前移动一个位置
// 目的是覆盖第一个元素(逻辑上删除头部元素),保持后续元素的连续性
// 循环范围:i从0到size-2(因为i+1最大为size-1,即最后一个元素)
for (int i = 0; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1]; // 第i个位置的值更新为第i+1个位置的值
}
// 更新长度:顺序表元素个数减1,完成头删的逻辑收尾
ps->size--;
}
- 在test.c测试
c
// 测试顺序表头删功能(SLPopFront函数)的测试函数
void SLPopFront_test()
{
// 定义一个顺序表变量
SL SeqList;
// 初始化顺序表(完成初始空间分配、size置0等初始化操作)
SLInit(&SeqList);
// 先通过头插法向顺序表中插入一些测试数据
SLPushFront(&SeqList, 1); // 头插1,表中元素:[1]
SLPushFront(&SeqList, 2); // 头插2,表中元素:[2, 1]
SLPushFront(&SeqList, 3); // 头插3,表中元素:[3, 2, 1]
SLPushFront(&SeqList, 4); // 头插4,表中元素:[4, 3, 2, 1]
SLPushFront(&SeqList, 5); // 头插5,表中元素:[5, 4, 3, 2, 1]
// 打印初始插入后的顺序表,作为头删操作的参照
SLPrint(&SeqList);
// 多次调用头删函数,逐步删除顺序表的第一个元素,并打印结果验证
SLPopFront(&SeqList); // 删除第一个元素5,预期结果:[4, 3, 2, 1]
SLPrint(&SeqList); // 打印验证
SLPopFront(&SeqList); // 再删除第一个元素4,预期结果:[3, 2, 1]
SLPrint(&SeqList); // 打印验证
SLPopFront(&SeqList); // 再删除第一个元素3,预期结果:[2, 1]
SLPrint(&SeqList); // 打印验证
SLPopFront(&SeqList); // 再删除第一个元素2,预期结果:[1]
SLPrint(&SeqList); // 打印验证
SLPopFront(&SeqList); // 再删除第一个元素1,预期结果:空表(无元素)
SLPrint(&SeqList); // 打印验证(通常为空输出或提示信息)
}
成功运行

11.指定位置删除
原理:从要删除位置的下一个元素开始,依次向前移动一个位置
- 创建函数
SLErase(SL* ps, int pos),函数需要一个参数,ps指向顺序表的指针
c
void SLErase(SL* ps, int pos)
{
}
- 需要判断传来的顺序表指针
SL* ps,如果为空,程序停止运行
c
assert(ps != NULL);
- 确保删除位置合法,合法范围是
[1, ps->size],即可以删除第一个位置、中间位置或末尾
pos=1表示删除头部,pos=ps->size表示删除尾部
c
assert(pos >= 1 && pos <= ps->size);
- 从要删除位置的下一个元素开始,依次向前移动一个位置,覆盖要删除的元素,实现逻辑删除
c
for (int i = pos - 1; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
- 顺序表有效个数size减1
c
ps->size--;
- 该函数完整代码
c
SeqList.h
// 指定位置删除:删除顺序表中指定位置的元素
// 参数:ps 指向顺序表的指针,pos 要删除的位置(从1开始计数)
void SLErase(SL* ps, int pos)
{
// 断言检查:确保顺序表指针不为空,避免对空指针操作
assert(ps != NULL);
// 断言检查:确保删除位置合法
// 合法范围是 [1, ps->size],即必须是顺序表中已存在的元素位置
assert(pos >= 1 && pos <= ps->size);
// 元素前移:从要删除位置的下一个元素开始,依次向前移动一个位置
// 目的是覆盖要删除的元素,实现逻辑删除,并保持后续元素的连续性
// 循环变量i起始于pos对应的数组索引(pos-1),终止于倒数第二个元素(size-2)
for (int i = pos - 1; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1]; // 用后一个元素的值覆盖当前元素
}
// 更新长度:顺序表元素个数减1,完成删除操作
ps->size--;
}
- 在test.c测试
c
运行
// 测试顺序表指定位置删除功能(SLErase函数)的测试函数
void SLErase_test()
{
// 定义一个顺序表变量
SL SeqList;
// 初始化顺序表(完成初始空间分配、size置0等初始化操作)
SLInit(&SeqList);
// 先通过头插法向顺序表中插入一些测试数据
SLPushFront(&SeqList, 1); // 头插1,表中元素:[1]
SLPushFront(&SeqList, 2); // 头插2,表中元素:[2, 1]
SLPushFront(&SeqList, 3); // 头插3,表中元素:[3, 2, 1]
SLPushFront(&SeqList, 4); // 头插4,表中元素:[4, 3, 2, 1]
SLPushFront(&SeqList, 5); // 头插5,表中元素:[5, 4, 3, 2, 1]
// 打印初始插入后的顺序表,作为删除操作的参照
SLPrint(&SeqList);
// 调用指定位置删除函数,删除第1个位置的元素(从1开始计数,即头部元素5)
SLErase(&SeqList, 1);
// 打印删除后的顺序表,验证删除结果(预期:[4, 3, 2, 1])
SLPrint(&SeqList);
}
运行成功

12.修改指定位置元素
原理:直接在所要修改的位置修改即可
- 创建函数
SLModify(SL* ps, int pos, SLDataType x),函数需要三个参数,ps指向顺序表的指针,pos要修改的位置(从1开始计数),x要修改的元素值
c
void SLModify(SL* ps, int pos, SLDataType x)
{
}
- 需要判断传来的顺序表指针
SL* ps,如果为空,程序停止运行
c
assert(ps != NULL);
- 确保修改的位置合法,合法范围是
[1, ps->size],即可以修改第一个位置、中间位置或末尾
pos=1表示修改头部,pos=ps->size表示修改尾部
c
assert(pos >= 1 && pos <= ps->size);
- 指定的位置修改元素
c
ps->arr[pos - 1] = x;
- 该函数完整代码
c
SeqList.c
// 修改顺序表中指定位置的元素值
// 参数:ps 指向顺序表的指针,pos 要修改的位置(从1开始计数),x 新的元素值
void SLModify(SL* ps, int pos, SLDataType x)
{
// 断言检查:确保传入的顺序表指针不为空,避免对空指针操作
assert(ps != NULL);
// 断言检查:确保修改位置合法
// 合法范围是 [1, ps->size],即必须是顺序表中已存在的元素位置
assert(pos >= 1 && pos <= ps->size);
// 修改元素值:将pos对应的数组索引位置(pos-1,因数组从0开始)的元素更新为x
ps->arr[pos - 1] = x;
}
- 在test.c测试
c
// 测试顺序表修改指定位置元素功能(SLModify函数)的测试函数
void SLModify_test()
{
// 定义一个顺序表变量
SL SeqList;
// 初始化顺序表(完成初始空间分配、size置0等初始化操作)
SLInit(&SeqList);
// 先通过头插法向顺序表中插入一些测试数据
SLPushFront(&SeqList, 1); // 头插1,表中元素:[1]
SLPushFront(&SeqList, 2); // 头插2,表中元素:[2, 1]
SLPushFront(&SeqList, 3); // 头插3,表中元素:[3, 2, 1]
SLPushFront(&SeqList, 4); // 头插4,表中元素:[4, 3, 2, 1]
SLPushFront(&SeqList, 5); // 头插5,表中元素:[5, 4, 3, 2, 1]
// 打印初始插入后的顺序表,作为修改操作的参照
SLPrint(&SeqList);
// 调用修改函数,将第5个位置的元素(值为1)修改为0
SLModify(&SeqList, 5, 0);
SLPrint(&SeqList); // 打印验证,预期结果:[5, 4, 3, 2, 0]
// 调用修改函数,将第3个位置的元素(值为3)修改为0
SLModify(&SeqList, 3, 0);
SLPrint(&SeqList); // 打印验证,预期结果:[5, 4, 0, 2, 0]
// 调用修改函数,将第1个位置的元素(值为5)修改为0
SLModify(&SeqList, 1, 0);
SLPrint(&SeqList); // 打印验证,预期结果:[0, 4, 0, 2, 0]
}
运行成功

13.释放内存
原理:运用free释放内存
- 创建函数
SLDestroy(SL* ps),函数需要一个参数,ps指向顺序表的指针
c
void SLDestroy(SL* ps)
{
}
- 需要判断传来的顺序表指针
SL* ps,如果为空,程序停止运行
c
assert(ps != NULL);
- 若顺序表的动态数组已分配内存(非空指针),则释放该内存
c
// 若顺序表的动态数组已分配内存(非空指针),则释放该内存
if (ps->arr != NULL)
{
free(ps->arr); // 释放动态数组占用的堆内存,防止内存泄漏
ps->arr = NULL; // 将数组指针置空,避免成为野指针(后续误操作已释放内存)
}
// 重置元素个数为0,表明顺序表已无有效元素
ps->size = 0;
// 重置容量为0,与空数组状态保持一致(无可用内存空间)
ps->capacity = 0;
- 完整代码
c
// 释放顺序表(SL类型)所占用的动态内存并重置状态
// 参数:ps 指向待释放的顺序表的指针
void SLDestroy(SL* ps)
{
// 断言检查:确保传入的顺序表指针不为空,避免对空指针操作
assert(ps != NULL);
// 若顺序表的动态数组已分配内存(非空指针),则释放该内存
if (ps->arr != NULL)
{
free(ps->arr); // 释放动态数组占用的堆内存,防止内存泄漏
ps->arr = NULL; // 将数组指针置空,避免成为野指针(后续误操作已释放内存)
}
// 重置元素个数为0,表明顺序表已无有效元素
ps->size = 0;
// 重置容量为0,与空数组状态保持一致(无可用内存空间)
ps->capacity = 0;
}
- 在test.c中运行
c
// 测试顺序表销毁功能(SLDestroy函数)的测试函数
void SLDestroy_test()
{
// 定义一个顺序表变量
SL SeqList;
// 初始化顺序表(分配初始空间、重置size和capacity等)
SLInit(&SeqList);
// 通过头插法向顺序表中插入测试数据
SLPushFront(&SeqList, 1); // 头插1,表中元素:[1]
SLPushFront(&SeqList, 2); // 头插2,表中元素:[2, 1]
SLPushFront(&SeqList, 3); // 头插3,表中元素:[3, 2, 1]
SLPushFront(&SeqList, 4); // 头插4,表中元素:[4, 3, 2, 1]
SLPushFront(&SeqList, 5); // 头插5,表中元素:[5, 4, 3, 2, 1]
// 打印插入数据后的顺序表,作为销毁操作的参照
SLPrint(&SeqList);
// 调用顺序表销毁函数,释放动态分配的内存并重置状态
// 预期:释放SeqList.arr指向的堆内存,将其置空,同时size和capacity重置为0
SLDestroy(&SeqList);
}
运行成功

14.查找所有相同元素,并且打印下标
原理:创建了一个新的的结构体struct IND来存储下标(与顺序表结构体相近,只是改了arr的类型,因为下标是整数,所以arr的类型固定用int ),运用了尾插函数,和打印函数 (这里因为传参的数据类型不一样,没有直接用顺序表的函数),也添加了struct IND的初始化函数(与struct SeqList相近)
c
//方便后续修改SLDataType类型的占位符
#define SL_PRINTF_FORMAT "%d"
c
// 定义一个用于存储下标(整数类型)的动态数组结构体
// 用途:通常用于收集顺序表中符合条件的元素的下标,支持动态扩容
typedef struct index
{
int* arr; // 指向动态分配的数组,用于存储具体的下标值(整数类型)
int size; // 记录当前已存储的下标个数(有效元素数量)
int capacity; // 记录当前数组的容量(最多可存储的下标个数)
}IND; // 结构体别名,后续可通过IND直接定义该类型的变量
c
// 初始化用于存储下标的顺序表(IND类型)
// 参数:index 指向待初始化的IND结构体的指针
void INInit(IND* index)
{
// 将存储下标的动态数组指针置为空(初始无分配内存)
index->arr = NULL;
// 初始化元素个数为0(初始无存储任何下标)
index->size = 0;
// 初始化容量为0(初始未分配内存空间)
index->capacity = 0;
}
- 创建函数
SLFindAll(SL* ps, SLDataType x, IND* index),函数需要三个参数,ps指向顺序表的指针,x要查找的元素,index储存查找元素下标的顺序表
c
void SLFindAll(SL* ps, SLDataType x, IND* index)
{
}
- 需要判断传来的顺序表指针
SL* ps、IND* index,如果为空,程序停止运行
c
assert(ps != NULL && index != NULL);
- 遍历原顺序表的所有元素,逐个比对是否与目标元素
x相等,将相等的元素的下标存储到检查存储下标的数组(这里的原理与尾插一样)
c
// 遍历原顺序表的所有元素,逐个比对是否与目标元素x相等
for (int i = 0; i < ps->size; i++)
{
// 当找到与x相等的元素时,记录其下标i
if (ps->arr[i] == x)
{
// 检查存储下标的数组是否已满(元素个数等于当前容量)
if (index->size == index->capacity)
{
// 计算新容量:初始容量为0时设为4,否则扩容为原来的2倍(减少扩容次数)
int newCapacity = index->capacity == 0 ? 4 : index->capacity * 2;
// 为存储下标的数组重新分配内存(每个元素为int类型,大小为newCapacity)
int* tmp = (int*)realloc(index->arr, newCapacity * sizeof(int));
if (tmp == NULL) // 检查内存分配是否失败
{
perror("realloc failed"); // 打印内存分配失败的错误信息
exit(EXIT_FAILURE); // 分配失败时终止程序,避免后续错误
}
index->arr = tmp; // 更新数组指针为新分配的内存地址
index->capacity = newCapacity; // 更新容量为新计算的容量值
}
// 将当前找到的元素下标i存入数组,并将元素个数加1
index->arr[index->size++] = i;
}
}
- 打印查找结果,目标元素x的总个数,以及所有匹配元素的下标
c
// 打印查找结果:目标元素x的总个数,以及所有匹配元素的下标
printf("元素 " SL_PRINTF_FORMAT " 共有%d个,下标为:", x, index->size);
// 遍历存储下标的数组,逐个打印所有找到的下标
for (int i = 0; i < index->size; i++)
{
printf("%d ", index->arr[i]);
}
printf("\n"); // 打印换行,使输出格式更整洁
}
- 释放存储下标的动态数组所占用的内存,并重置结构体状态
c
// 释放存储下标的动态数组所占用的内存,并重置结构体状态
free(index->arr); // 释放index中动态分配的数组内存,避免内存泄漏
index->arr = NULL; // 将数组指针置空,防止后续对已释放内存的非法访问(野指针问题)
index->size = 0; // 重置元素个数为0,表明当前无存储任何下标
index->capacity = 0; // 重置容量为0,与空数组状态保持一致
- 该函数完整代码
c
SeqList.c
// 查找顺序表中所有与目标元素x相等的元素,并将它们的下标存储到index结构体中
// 参数:
// ps:指向待查找的原顺序表的指针(存储SLDataType类型元素)
// x:要查找的目标元素(类型为SLDataType)
// index:指向用于存储结果下标的结构体(需提前初始化,包含动态数组arr、元素个数size、容量capacity)
void SLFindAll(SL* ps, SLDataType x, index* index)
{
// 断言检查:确保原顺序表指针和存储结果的index指针均不为空,避免对空指针操作
assert(ps != NULL && index != NULL);
// 遍历原顺序表的所有元素,逐个比对是否与目标元素x相等
for (int i = 0; i < ps->size; i++)
{
// 当找到与x相等的元素时,记录其下标i
if (ps->arr[i] == x)
{
// 检查存储下标的数组是否已满(元素个数等于当前容量)
if (index->size == index->capacity)
{
// 计算新容量:初始容量为0时设为4,否则扩容为原来的2倍(减少扩容次数)
int newCapacity = index->capacity == 0 ? 4 : index->capacity * 2;
// 为存储下标的数组重新分配内存(每个元素为int类型,大小为newCapacity)
int* tmp = (int*)realloc(index->arr, newCapacity * sizeof(int));
if (tmp == NULL) // 检查内存分配是否失败
{
perror("realloc failed"); // 打印内存分配失败的错误信息
exit(EXIT_FAILURE); // 分配失败时终止程序,避免后续错误
}
index->arr = tmp; // 更新数组指针为新分配的内存地址
index->capacity = newCapacity; // 更新容量为新计算的容量值
}
// 将当前找到的元素下标i存入数组,并将元素个数加1
index->arr[index->size++] = i;
}
}
// 打印查找结果:目标元素x的总个数,以及所有匹配元素的下标
//SL_PRINTF_FORMAT = %d
printf("元素 " SL_PRINTF_FORMAT " 共有%d个,下标为:", x, index->size);
// 遍历存储下标的数组,逐个打印所有找到的下标
for (int i = 0; i < index->size; i++)
{
printf("%d ", index->arr[i]);
}
printf("\n"); // 打印换行,使输出格式更整洁
// 释放存储下标的动态数组所占用的内存,并重置结构体状态
free(index->arr); // 释放index中动态分配的数组内存,避免内存泄漏
index->arr = NULL; // 将数组指针置空,防止后续对已释放内存的非法访问(野指针问题)
index->size = 0; // 重置元素个数为0,表明当前无存储任何下标
index->capacity = 0; // 重置容量为0,与空数组状态保持一致
}
7.在test.c中测试
c
// 测试查找顺序表中所有相同元素下标功能(SLFindAll函数)的测试函数
void SLFindAll_test()
{
// 定义原顺序表变量并初始化
SL SeqList;
SLInit(&SeqList); // 初始化原顺序表(存储待查找的元素)
// 定义用于存储下标的IND结构体变量并初始化
IND index;
INInit(&index); // 初始化下标存储结构(确保初始状态合法)
// 向原顺序表中插入测试数据(通过头插法)
SLPushFront(&SeqList, 1); // 头插1,表中元素:[1]
SLPushFront(&SeqList, 2); // 头插2,表中元素:[2, 1]
SLPushFront(&SeqList, 1); // 头插1,表中元素:[1, 2, 1]
SLPushFront(&SeqList, 4); // 头插4,表中元素:[4, 1, 2, 1]
SLPushFront(&SeqList, 1); // 头插1,表中元素:[1, 4, 1, 2, 1]
SLPushFront(&SeqList, 1); // 头插1,表中元素:[1, 1, 4, 1, 2, 1]
SLPushFront(&SeqList, 5); // 头插5,表中元素:[5, 1, 1, 4, 1, 2, 1]
SLPushFront(&SeqList, 1); // 头插1,表中元素:[1, 5, 1, 1, 4, 1, 2, 1]
SLPushFront(&SeqList, 1); // 头插1,表中元素:[1, 1, 5, 1, 1, 4, 1, 2, 1]
// 打印原顺序表的所有元素,作为查找操作的参照
SLPrint(&SeqList);
// 调用SLFindAll函数,查找原顺序表中所有值为1的元素,并将下标存入index
// 预期结果:统计值为1的元素个数,并输出它们在原顺序表中的下标(从0开始)
SLFindAll(&SeqList, 1, &index);
}
//预期结果:元素 1 共有6个,下标为:0 1 3 4 6 8
运行成功

四、动态顺序表完整代码
1.test.c
c
test.c
#include "SeqList.h" // 包含顺序表相关函数和结构体的声明
// 测试顺序表初始化功能(SLInit函数)的测试函数
void SLInit_test()
{
SL SeqList; // 定义一个顺序表变量
SLInit(&SeqList); // 调用初始化函数,初始化顺序表
}
// 测试顺序表尾插功能(SLPushBack函数)的测试函数
void SLPushBack_test()
{
SL SeqList; // 定义一个顺序表变量
SLInit(&SeqList); // 初始化顺序表
// 通过尾插法向顺序表中插入元素
SLPushBack(&SeqList, 1); // 尾插1,表中元素:[1]
SLPushBack(&SeqList, 2); // 尾插2,表中元素:[1, 2]
SLPushBack(&SeqList, 3); // 尾插3,表中元素:[1, 2, 3]
SLPushBack(&SeqList, 4); // 尾插4,表中元素:[1, 2, 3, 4]
SLPushBack(&SeqList, 5); // 尾插5,表中元素:[1, 2, 3, 4, 5]
SLPrint(&SeqList); // 打印尾插后的顺序表,验证结果
}
// 测试顺序表头插功能(SLPushFront函数)的测试函数
void SLPushFront_test()
{
// 初始化顺序表
SL SeqList; // 定义一个顺序表变量
SLInit(&SeqList); // 初始化顺序表
// 通过头插法向顺序表中插入元素
SLPushFront(&SeqList, 1); // 头插1,表中元素:[1]
SLPushFront(&SeqList, 2); // 头插2,表中元素:[2, 1]
SLPushFront(&SeqList, 3); // 头插3,表中元素:[3, 2, 1]
SLPushFront(&SeqList, 4); // 头插4,表中元素:[4, 3, 2, 1]
SLPushFront(&SeqList, 5); // 头插5,表中元素:[5, 4, 3, 2, 1]
SLPrint(&SeqList); // 打印头插后的顺序表,验证结果
}
// 测试顺序表指定位置插入功能(SLInsert函数)的测试函数
void SLInsert_test()
{
// 初始化顺序表
SL SeqList; // 定义一个顺序表变量
SLInit(&SeqList); // 初始化顺序表
// 先通过头插法插入一些测试数据
SLPushFront(&SeqList, 1); // 头插1,表中元素:[1]
SLPushFront(&SeqList, 2); // 头插2,表中元素:[2, 1]
SLPushFront(&SeqList, 3); // 头插3,表中元素:[3, 2, 1]
SLPushFront(&SeqList, 4); // 头插4,表中元素:[4, 3, 2, 1]
SLPushFront(&SeqList, 5); // 头插5,表中元素:[5, 4, 3, 2, 1]
SLPrint(&SeqList); // 打印插入数据后的顺序表,作为插入操作的参照
// 在第4个位置插入元素10(从1开始计数,原第4个元素为2)
SLInsert(&SeqList, 4, 10);
SLPrint(&SeqList); // 打印插入后的顺序表,验证结果(预期:[5, 4, 3, 10, 2, 1])
}
// 测试顺序表尾删功能(SLPopBack函数)的测试函数
void SLPopBack_test()
{
// 初始化顺序表
SL SeqList; // 定义一个顺序表变量
SLInit(&SeqList); // 初始化顺序表
// 先通过头插法插入一些测试数据
SLPushFront(&SeqList, 1); // 头插1,表中元素:[1]
SLPushFront(&SeqList, 2); // 头插2,表中元素:[2, 1]
SLPushFront(&SeqList, 3); // 头插3,表中元素:[3, 2, 1]
SLPushFront(&SeqList, 4); // 头插4,表中元素:[4, 3, 2, 1]
SLPushFront(&SeqList, 5); // 头插5,表中元素:[5, 4, 3, 2, 1]
SLPrint(&SeqList); // 打印插入数据后的顺序表,作为删除操作的参照
// 多次执行尾删操作,验证尾删功能及边界情况(删至空表)
SLPopBack(&SeqList); // 删除尾部元素1,表中元素:[5, 4, 3, 2]
SLPrint(&SeqList);
SLPopBack(&SeqList); // 删除尾部元素2,表中元素:[5, 4, 3]
SLPrint(&SeqList);
SLPopBack(&SeqList); // 删除尾部元素3,表中元素:[5, 4]
SLPrint(&SeqList);
SLPopBack(&SeqList); // 删除尾部元素4,表中元素:[5]
SLPrint(&SeqList);
SLPopBack(&SeqList); // 删除尾部元素5,表中元素:[](空表)
SLPrint(&SeqList);
}
// 测试顺序表头删功能(SLPopFront函数)的测试函数
void SLPopFront_test()
{
// 初始化顺序表
SL SeqList; // 定义一个顺序表变量
SLInit(&SeqList); // 初始化顺序表
// 先通过头插法插入一些测试数据
SLPushFront(&SeqList, 1); // 头插1,表中元素:[1]
SLPushFront(&SeqList, 2); // 头插2,表中元素:[2, 1]
SLPushFront(&SeqList, 3); // 头插3,表中元素:[3, 2, 1]
SLPushFront(&SeqList, 4); // 头插4,表中元素:[4, 3, 2, 1]
SLPushFront(&SeqList, 5); // 头插5,表中元素:[5, 4, 3, 2, 1]
SLPrint(&SeqList); // 打印插入数据后的顺序表,作为删除操作的参照
// 多次执行头删操作,验证头删功能及边界情况(删至空表)
SLPopFront(&SeqList); // 删除头部元素5,表中元素:[4, 3, 2, 1]
SLPrint(&SeqList);
SLPopFront(&SeqList); // 删除头部元素4,表中元素:[3, 2, 1]
SLPrint(&SeqList);
SLPopFront(&SeqList); // 删除头部元素3,表中元素:[2, 1]
SLPrint(&SeqList);
SLPopFront(&SeqList); // 删除头部元素2,表中元素:[1]
SLPrint(&SeqList);
SLPopFront(&SeqList); // 删除头部元素1,表中元素:[](空表)
SLPrint(&SeqList);
}
// 测试顺序表指定位置删除功能(SLErase函数)的测试函数
void SLErase_test()
{
// 初始化顺序表
SL SeqList; // 定义一个顺序表变量
SLInit(&SeqList); // 初始化顺序表
// 先通过头插法插入一些测试数据
SLPushFront(&SeqList, 1); // 头插1,表中元素:[1]
SLPushFront(&SeqList, 2); // 头插2,表中元素:[2, 1]
SLPushFront(&SeqList, 3); // 头插3,表中元素:[3, 2, 1]
SLPushFront(&SeqList, 4); // 头插4,表中元素:[4, 3, 2, 1]
SLPushFront(&SeqList, 5); // 头插5,表中元素:[5, 4, 3, 2, 1]
SLPrint(&SeqList); // 打印插入数据后的顺序表,作为删除操作的参照
// 删除第1个位置的元素(从1开始计数,即头部元素5)
SLErase(&SeqList, 1);
SLPrint(&SeqList); // 打印删除后的顺序表,验证结果(预期:[4, 3, 2, 1])
}
// 测试顺序表修改指定位置元素功能(SLModify函数)的测试函数
void SLModify_test()
{
// 初始化顺序表
SL SeqList; // 定义一个顺序表变量
SLInit(&SeqList); // 初始化顺序表
// 先通过头插法插入一些测试数据
SLPushFront(&SeqList, 1); // 头插1,表中元素:[1]
SLPushFront(&SeqList, 2); // 头插2,表中元素:[2, 1]
SLPushFront(&SeqList, 3); // 头插3,表中元素:[3, 2, 1]
SLPushFront(&SeqList, 4); // 头插4,表中元素:[4, 3, 2, 1]
SLPushFront(&SeqList, 5); // 头插5,表中元素:[5, 4, 3, 2, 1]
SLPrint(&SeqList); // 打印插入数据后的顺序表,作为修改操作的参照
// 修改第5个位置的元素(值为1)为0
SLModify(&SeqList, 5, 0);
SLPrint(&SeqList); // 打印验证,预期结果:[5, 4, 3, 2, 0]
// 修改第3个位置的元素(值为3)为0
SLModify(&SeqList, 3, 0);
SLPrint(&SeqList); // 打印验证,预期结果:[5, 4, 0, 2, 0]
// 修改第1个位置的元素(值为5)为0
SLModify(&SeqList, 1, 0);
SLPrint(&SeqList); // 打印验证,预期结果:[0, 4, 0, 2, 0]
}
// 测试查找所有相同元素下标功能(SLFindAll函数)的测试函数
void SLFindAll_test()
{
// 初始化原顺序表
SL SeqList; // 定义原顺序表变量
SLInit(&SeqList); // 初始化原顺序表(存储待查找元素)
// 初始化用于存储下标的IND结构体
IND index; // 定义存储下标的结构体变量
INInit(&index); // 初始化下标存储结构
// 先通过头插法向原顺序表插入测试数据
SLPushFront(&SeqList, 1); // 头插1,表中元素:[1]
SLPushFront(&SeqList, 2); // 头插2,表中元素:[2, 1]
SLPushFront(&SeqList, 1); // 头插1,表中元素:[1, 2, 1]
SLPushFront(&SeqList, 4); // 头插4,表中元素:[4, 1, 2, 1]
SLPushFront(&SeqList, 1); // 头插1,表中元素:[1, 4, 1, 2, 1]
SLPushFront(&SeqList, 1); // 头插1,表中元素:[1, 1, 4, 1, 2, 1]
SLPushFront(&SeqList, 5); // 头插5,表中元素:[5, 1, 1, 4, 1, 2, 1]
SLPushFront(&SeqList, 1); // 头插1,表中元素:[1, 5, 1, 1, 4, 1, 2, 1]
SLPushFront(&SeqList, 1); // 头插1,表中元素:[1, 1, 5, 1, 1, 4, 1, 2, 1]
SLPrint(&SeqList); // 打印原顺序表,作为查找操作的参照
// 查找原顺序表中所有值为1的元素,并将下标存入index
SLFindAll(&SeqList, 1, &index);
}
// 测试顺序表销毁功能(SLDestroy函数)的测试函数
void SLDestroy_test()
{
// 初始化顺序表
SL SeqList; // 定义一个顺序表变量
SLInit(&SeqList); // 初始化顺序表
// 先通过头插法插入一些测试数据
SLPushFront(&SeqList, 1); // 头插1,表中元素:[1]
SLPushFront(&SeqList, 2); // 头插2,表中元素:[2, 1]
SLPushFront(&SeqList, 3); // 头插3,表中元素:[3, 2, 1]
SLPushFront(&SeqList, 4); // 头插4,表中元素:[4, 3, 2, 1]
SLPushFront(&SeqList, 5); // 头插5,表中元素:[5, 4, 3, 2, 1]
SLPrint(&SeqList); // 打印插入数据后的顺序表,作为销毁操作的参照
// 调用销毁函数,释放顺序表的动态内存并重置状态
// 打印原顺序表的所有元素,作为查找操作的参照
//预期结果:元素 1 共有6个,下标为:0 1 3 4 6 8
SLDestroy(&SeqList);
}
int main()
{
// 测试函数调用入口,通过注释/取消注释选择要测试的功能
// SLInit_test(); // 测试初始化功能
// SLPushBack_test(); // 测试尾插功能
// SLPushFront_test(); // 测试头插功能
// SLInsert_test(); // 测试指定位置插入功能
// SLPopBack_test(); // 测试尾删功能
// SLPopFront_test(); // 测试头删功能
// SLErase_test(); // 测试指定位置删除功能
// SLModify_test(); // 测试修改指定位置元素功能
// SLFindAll_test(); // 测试查找所有相同元素下标功能
// SLDestroy_test(); // 测试销毁功能
return 0;
}
2.SeqList.c
c
SeqList.c
#include "SeqList.h" // 包含顺序表相关结构体定义和函数声明
// 顺序表的初始化函数
// 功能:将顺序表初始化为空表状态
// 参数:ps 指向待初始化的顺序表的指针
void SLInit(SL* ps)
{
ps->arr = NULL; // 动态数组指针置空(初始无内存分配)
ps->size = ps->capacity = 0; // 元素个数和容量均初始化为0
}
// 用于存储下标的顺序表(IND类型)的初始化函数
// 功能:将存储下标的结构体初始化为空状态
// 参数:index 指向待初始化的IND结构体的指针
void INInit(IND* index)
{
index->arr = NULL; // 存储下标的动态数组指针置空
index->size = index->capacity = 0; // 元素个数和容量均初始化为0
}
// 检查顺序表容量并在需要时进行扩容
// 功能:确保顺序表有足够空间存储新元素,空间不足时自动扩容
// 参数:ps 指向顺序表的指针
void SLCheckCapacity(SL* ps)
{
assert(ps != NULL); // 确保顺序表指针不为空
// 当元素个数等于容量时,需要扩容
if (ps->size == ps->capacity)
{
// 计算新容量:初始容量为0则设为4,否则扩容为原来的2倍
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
// 重新分配内存,大小为新容量乘以单个元素的字节数
SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
// 检查内存分配是否成功
if (tmp == NULL)
{
perror("realloc failed"); // 打印内存分配失败信息
exit(EXIT_FAILURE); // 分配失败时终止程序
}
ps->arr = tmp; // 更新顺序表的数组指针为新分配的内存
ps->capacity = newCapacity; // 更新顺序表的容量为新容量
}
}
// 打印顺序表中的所有元素
// 功能:遍历顺序表并打印每个元素,元素间用空格分隔,最后换行
// 参数:ps 指向待打印的顺序表的指针
void SLPrint(SL* ps)
{
assert(ps != NULL); // 确保顺序表指针不为空
// 遍历顺序表的有效元素并打印
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n"); // 打印换行,使输出格式整洁
}
// 顺序表的尾插操作
// 功能:在顺序表的尾部插入一个新元素
// 参数:ps 指向顺序表的指针,x 要插入的元素值
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps != NULL); // 确保顺序表指针不为空
SLCheckCapacity(ps); // 检查容量,不足则扩容
// 将新元素插入到尾部(下标为size的位置),并更新元素个数
ps->arr[ps->size++] = x;
}
// 顺序表的头插操作
// 功能:在顺序表的头部插入一个新元素
// 参数:ps 指向顺序表的指针,x 要插入的元素值
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps != NULL); // 确保顺序表指针不为空
SLCheckCapacity(ps); // 检查容量,不足则扩容
// 将现有元素从后往前依次后移一位,为头部腾出位置
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
// 在头部(下标0)插入新元素,并更新元素个数
ps->arr[0] = x;
ps->size++;
}
// 顺序表的指定位置插入操作
// 功能:在顺序表的指定位置(从1开始计数)插入一个新元素
// 参数:ps 指向顺序表的指针,pos 插入位置(1-based),x 要插入的元素值
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps != NULL); // 确保顺序表指针不为空
// 确保插入位置合法:pos必须在[1, size+1]范围内(可插入到尾部)
assert(pos >= 1 && pos <= ps->size + 1);
SLCheckCapacity(ps); // 检查容量,不足则扩容
// 将pos位置及之后的元素从后往前依次后移一位
for (int i = ps->size; i >= pos; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
// 在pos对应的下标(pos-1)插入新元素,并更新元素个数
ps->arr[pos - 1] = x;
ps->size++;
}
// 顺序表的尾删操作
// 功能:删除顺序表尾部的最后一个元素
// 参数:ps 指向顺序表的指针
void SLPopBack(SL* ps)
{
assert(ps != NULL); // 确保顺序表指针不为空
assert(ps->size != 0); // 确保顺序表不为空(有元素可删)
// 通过减少元素个数实现尾删(逻辑删除,不实际释放内存)
ps->size--;
}
// 顺序表的头删操作
// 功能:删除顺序表头部的第一个元素
// 参数:ps 指向顺序表的指针
void SLPopFront(SL* ps)
{
assert(ps != NULL); // 确保顺序表指针不为空
assert(ps->size != 0); // 确保顺序表不为空(有元素可删)
// 将头部之后的元素从前往后依次前移一位,覆盖头部元素
for (int i = 0; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
// 更新元素个数(删除后减少1)
ps->size--;
}
// 顺序表的指定位置删除操作
// 功能:删除顺序表中指定位置(从1开始计数)的元素
// 参数:ps 指向顺序表的指针,pos 要删除的位置(1-based)
void SLErase(SL* ps, int pos)
{
assert(ps != NULL); // 确保顺序表指针不为空
// 确保删除位置合法:pos必须在[1, size]范围内(删除已存在的元素)
assert(pos >= 1 && pos <= ps->size);
// 将pos位置之后的元素从前往后依次前移一位,覆盖pos位置的元素
for (int i = pos - 1; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
// 更新元素个数(删除后减少1)
ps->size--;
}
// 顺序表的指定位置元素修改操作
// 功能:修改顺序表中指定位置(从1开始计数)的元素值
// 参数:ps 指向顺序表的指针,pos 要修改的位置(1-based),x 新的元素值
void SLModify(SL* ps, int pos, SLDataType x)
{
assert(ps != NULL); // 确保顺序表指针不为空
// 确保修改位置合法:pos必须在[1, size]范围内(修改已存在的元素)
assert(pos >= 1 && pos <= ps->size);
// 将pos对应的下标(pos-1)的元素值更新为x
ps->arr[pos - 1] = x;
}
// 查找顺序表中所有与目标元素x相等的元素,并记录它们的下标
// 功能:遍历顺序表,收集所有值为x的元素的下标,存储到index结构体中并打印结果
// 参数:ps 指向原顺序表的指针,x 目标元素,index 用于存储下标的IND结构体指针
void SLFindAll(SL* ps, SLDataType x, IND* index)
{
assert(ps != NULL && index != NULL); // 确保指针均不为空
// 遍历原顺序表,查找所有值为x的元素
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x) // 找到与x相等的元素
{
// 检查存储下标的数组是否已满,满则扩容
if (index->size == index->capacity)
{
// 计算新容量:初始容量为0则设为4,否则扩容为原来的2倍
int newCapacity = index->capacity == 0 ? 4 : index->capacity * 2;
// 重新分配内存,存储下标(int类型)
int* tmp = (int*)realloc(index->arr, newCapacity * sizeof(int));
if (tmp == NULL) // 检查内存分配是否失败
{
perror("realloc failed"); // 打印错误信息
exit(EXIT_FAILURE); // 分配失败时终止程序
}
index->arr = tmp; // 更新下标数组指针
index->capacity = newCapacity; // 更新容量
}
// 将当前元素的下标i存入数组,并更新元素个数
index->arr[index->size++] = i;
}
}
// 打印查找结果:目标元素x的总个数及所有下标
printf("元素 " SL_PRINTF_FORMAT " 共有%d个,下标为:", x, index->size);
for (int i = 0; i < index->size; i++)
{
printf("%d ", index->arr[i]);
}
printf("\n");
// 释放存储下标的动态数组内存,避免泄漏
free(index->arr);
index->arr = NULL; // 置空指针,防止野指针
index->size = 0; // 重置元素个数
index->capacity = 0; // 重置容量
}
// 顺序表的销毁函数
// 功能:释放顺序表动态分配的内存,并重置状态为初始空表
// 参数:ps 指向待销毁的顺序表的指针
void SLDestroy(SL* ps)
{
assert(ps != NULL); // 确保顺序表指针不为空
// 若顺序表已分配内存,则释放
if (ps->arr != NULL)
{
free(ps->arr); // 释放动态数组内存
ps->arr = NULL; // 置空指针,防止野指针
}
// 重置元素个数和容量为0,回到初始空表状态
ps->size = 0;
ps->capacity = 0;
}
3.SeqList.h
c
SeqList.h
#pragma once // 防止头文件被重复包含(仅在当前编译单元中包含一次)
// 包含必要的标准库头文件
#include <stdio.h> // 提供输入输出函数(如printf)
#include <stdlib.h> // 提供动态内存分配函数(如realloc、exit)
#include <assert.h> // 提供断言函数(assert),用于参数合法性检查
// 定义顺序表数据类型(SLDataType)对应的printf占位符宏
// 用途:打印SLDataType类型元素时统一使用,方便后续修改类型时同步调整占位符
#define SL_PRINTF_FORMAT "%d" // 当前SLDataType为int,对应占位符为%d
// 定义动态顺序表中存储的数据类型(可根据需要修改为其他类型,如float、long等)
typedef int SLDataType;
// 定义动态顺序表的结构体
typedef struct SeqList
{
SLDataType* arr; // 指向动态分配的数组,用于存储顺序表的元素
int size; // 记录顺序表中当前有效元素的个数(当前长度)
int capacity; // 记录顺序表当前的容量(最多可存储的元素个数)
}SL; // 结构体别名,简化变量定义(如SL list;)
// 定义用于存储下标(整数类型)的动态数组结构体
typedef struct index
{
int* arr; // 指向动态分配的数组,用于存储具体的下标值(整数)
int size; // 记录当前已存储的下标个数
int capacity; // 记录当前数组的容量(最多可存储的下标个数)
}IND; // 结构体别名,简化变量定义(如IND idx;)
// 以下为函数声明(函数原型),对应顺序表的各种操作
// 顺序表的初始化
// 功能:将顺序表初始化为空表(arr为NULL,size和capacity为0)
void SLInit(SL* ps);
// 检查容量并扩容
// 功能:若顺序表已满(size == capacity),则自动扩容(初始容量为4,之后翻倍)
void SLCheckCapacity(SL* ps);
// 打印顺序表元素
// 功能:遍历顺序表,打印所有有效元素(元素间用空格分隔,最后换行)
void SLPrint(SL* pa);
// 尾插操作
// 功能:在顺序表的尾部插入一个新元素x
void SLPushBack(SL* ps, SLDataType x);
// 头插操作
// 功能:在顺序表的头部插入一个新元素x
void SLPushFront(SL* ps, SLDataType x);
// 任意位置插入操作
// 功能:在顺序表的指定位置pos(1-based)插入新元素x
void SLInsert(SL* ps, int pos, SLDataType x);
// 尾删操作
// 功能:删除顺序表尾部的最后一个元素
void SLPopBack(SL* ps);
// 头删操作
// 功能:删除顺序表头部的第一个元素
void SLPopFront(SL* ps);
// 指定位置删除操作
// 功能:删除顺序表中指定位置pos(1-based)的元素
void SLErase(SL* ps, int pos);
// 修改指定位置元素
// 功能:将顺序表中指定位置pos(1-based)的元素修改为x
void SLModify(SL* ps, int pos, SLDataType x);
// 查找所有相同元素的下标
// 功能:查找顺序表中所有值为x的元素,将其下标存储到IND结构体中并打印结果
void SLFindAll(SL* ps, SLDataType x, IND* index);
// 释放顺序表内存
// 功能:释放顺序表动态分配的数组内存,重置size和capacity为0
void SLDestroy(SL* ps);