数据结构---顺序表

一、线性表

线性表是数据结构中最基本、最常用的一种线性结构,它是由n 个具有相同特性的数据元素组成的有限序列。其核心特征是:除第一个元素外,每个元素有且仅有一个直接前驱;除最后一个元素外,每个元素有且仅有一个直接后继,即元素之间呈一对一的线性关系。

线性表在逻辑上是线性结构 (人为想象的结构),也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连续的 , 线性表在物理上存储时,通常以数组和链式结构的形式存储

常⻅的线性表:顺序表、链表、栈、队列、字符串...

二、顺序表

1.概念与结构

概念:顺序表是线性表的顺序存储结构 ,它通过一段连续的存储空间依次存储元素,使得元素的逻辑顺序与物理存储顺序完全一致 (逻辑结构和物理结构都是线性的)。这种结构的核心特点是 "地址连续、依次存放 ",通常借助数组实现。

2.顺序表与数组的区别

  1. 本质与定义
    数组:编程语言原生的基础存储结构,是连续内存块, 仅用于存储同类型元素,无长度管理功能(需用户手动记录有效元素数)。
    顺序表:基于数组实现的数据结构,封装了数组 + 长度信息(length),用于实现线性表的逻辑结构(一对一关系)。
  2. 功能差异
    数组:仅提供存储和下标访问,无插入、删除等逻辑操作,需用户手动处理元素移动、边界检查。
    顺序表:封装了线性表完整操作(插入、删除、查找、判空等),自动处理边界和元素移动,屏蔽底层细节。
  3. 长度管理
    数组:容量固定,无法直接区分 "总容量" 和 "有效元素数"。
    顺序表:明确区分 "容量"(底层数组大小)和 "当前长度"(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文件

  1. SeqList.h :声明结构体类型、宏定义、函数原型,作为模块的接口,供其他文件(SeqList.ctest.c)包含。

  2. SeqList.c : 实现 SeqList.h 中声明的所有函数,是顺序表功能的具体实现。

  3. 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 置为空,即NULLsizecapacity 置为0

void SLInit()该函数的参数必须是指针形式,原因是需要通过指针修改外部结构体变量的内部状态

如果参数定义为结构体本身(void SLInit(SL ps)),函数会对传入的结构体进行值拷贝------ 即创建一个与外部 结构体完全相同的临时副本,函数内部所有操作都只作用于这个临时副本,而不会影响外部原始的结构体变量

当参数为指针(SL* ps)时,函数接收的是外部结构体变量的地址。通过这个地址(指针),函数可以直接访问并修改外部结构体的内部成员(ps->arrps->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. 检查容量并扩容(核心逻辑)

  1. 创建函数void SLCheckCapacity(SL* ps),函数需要一个参数,ps 指向顺序表的指针
c 复制代码
// 检查容量并扩容
void SLCheckCapacity(SL* ps)
{

}
  1. 在检查容量之前,需要判断传来的顺序表指针SL* ps,如果为空,程序停止运行,可以用assert(ps != NULL)语句实现,其所要包含的头文件是 <assert.h>
c 复制代码
assert(ps != NULL)
  1. 检查容量是否充足,运用if (ps->size == ps->capacity)语句来检测容量是否充足。如果容量不足,用realloc()进行扩容。
  2. 使用realloc需要包含头文件stdlib.hrealloc第一个参数是扩容的对象,第二个参数是扩容的大小(单位是字节) ,并且需要把realloc数据类型强转为扩容对象的数据类型
  3. 先使用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));
}
  1. 使用if (tmp == NULL)判断tmp是否为空,为空说明扩容失败。
c 复制代码
if (tmp == NULL)
{
    perror("realloc failed");  // 打印内存分配失败原因
    exit(EXIT_FAILURE);        // 退出程序
}
  1. 如果tmp不为空,说明扩容成功,将tmp的值赋给ps ->arr,newCapacity的值赋给capacity
c 复制代码
ps->arr = tmp;
ps->capacity = newCapacity;
  1. 该函数完整代码
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.打印顺序表中的有效元素

  1. 创建函数SLPrint(SL* ps),函数需要一个参数,ps 指向顺序表的指针
c 复制代码
void SLPrint(SL* ps)
{

}
  1. 需要判断传来的顺序表指针SL* ps,如果为空,程序停止运行
c 复制代码
assert(ps != NULL);
  1. 遍历顺序表的有效元素并逐个打印,所有元素打印完毕后换行
c 复制代码
for (int i = 0; i < ps->size; i++)
{
	printf("%d ", ps->arr[i]);
}
printf("\n");
  1. 该函数完整代码
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.尾插

  1. 创建函数void SLPushBack(SL* ps, SLDataType x),函数需要两个参数,ps 指向顺序表的指针,x 要插入的元素值
c 复制代码
void SLPushBack(SL* ps, SLDataType x)
{

}
  1. 需要判断传来的顺序表指针SL* ps,如果为空,程序停止运行
c 复制代码
assert(ps != NULL);
  1. void SLCheckCapacity(SL* ps)函数检查容量是否足够,空间容量不够该函数会进行扩容。调用SLCheckCapacity函数后,顺序表一定有足够空间插入新元素。接着插入元素:将x存入数组的末尾(下标为当前size的位置),然后size++
  2. 该函数完整代码
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;
}
  1. 在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);
  1. void SLCheckCapacity(SL* ps)函数检查容量是否足够,空间容量不够该函数会进行扩容。调用SLCheckCapacity函数后,顺序表一定有足够空间插入新元素。

  2. 把所有元素都往后移动一位,在开头插入所需数据 ,顺序表有效个数size+1

  3. 该函数完整代码

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++;
}
  1. 在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位置插入所需数据

  1. 创建函数void SLInsert(SL* ps, int pos, SLDataType x),函数需要三个参数,ps 指向顺序表的指针,pos 要插入的位置(从1开始计数),x 要插入的元素值
c 复制代码
void SLInsert(SL* ps, int pos, SLDataType x)
{

}
  1. 需要判断传来的顺序表指针SL* ps,如果为空,程序停止运行
c 复制代码
assert(ps != NULL);
  1. 确保插入位置合法,合法范围是 [1, ps->size + 1],即可以插入到第一个位置、中间位置或末尾(表尾插入)
    pos=1表示插在头部,pos=ps->size+1表示插在尾部
c 复制代码
  assert(pos >= 1 && pos <= ps->size + 1);
  1. void SLCheckCapacity(SL* ps)函数检查容量是否足够,空间容量不够该函数会进行扩容。调用SLCheckCapacity函数后,顺序表一定有足够空间插入新元素

  2. pos后所有元素都往后移动一位,在pos位置插入所需数据 ,顺序表有效个数size+1

  3. 该函数完整代码

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++;
}
  1. 在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.尾删

原理:直接减少有效元素个数(无需真正删除,后续插入会覆盖)

  1. 创建函数SLPopBack(SL* ps),函数需要一个参数,ps 指向顺序表的指针
c 复制代码
void SLPopBack(SL* ps)
{

}
  1. 需要判断传来的顺序表指针SL* ps,如果为空,程序停止运行
c 复制代码
assert(ps != NULL);
  1. 避免对空表 执行删除操作,即ps->size != 0
c 复制代码
 assert(ps->size != 0);
  1. 将顺序表的元素个数减1,此处不实际清除原末尾元素的内存值,而是通过减小size,使该元素在逻辑上"被删除"
  2. 该函数完整代码
c 复制代码
SeqList.c
// 尾删操作:删除顺序表中最后一个元素
// 参数:ps 指向顺序表的指针(通过指针操作原顺序表)
void SLPopBack(SL* ps)
{
    // 断言检查:确保传入的顺序表指针不为空
    // 防止对空指针进行解引用操作,避免程序崩溃或未定义行为
    assert(ps != NULL);
    
    // 断言检查:确保顺序表不为空(元素个数大于0)
    // 避免对空表执行删除操作(无元素可删,逻辑错误)
    assert(ps->size != 0);
    
    // 尾删核心逻辑:将顺序表的元素个数减1
    // 说明:顺序表底层为数组,此处不实际清除原末尾元素的内存值,
    // 而是通过减小size,使该元素在逻辑上"被删除"(不再被顺序表的操作访问),
    // 后续插入新元素时会覆盖该位置的旧值,节省操作成本
    ps->size--;
}
  1. 在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.头删

原理:所有元素向前移动一位,覆盖头元素

  1. 创建函数SLPopFront(SL* ps),函数需要一个参数,ps 指向顺序表的指针
c 复制代码
void SLPopFront(SL* ps)
{

}
  1. 需要判断传来的顺序表指针SL* ps,如果为空,程序停止运行
c 复制代码
assert(ps != NULL);
  1. 从第二个元素开始,将所有元素依次向前移动一个位置,目的是覆盖第一个元素(逻辑上删除头部元素)
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];
}
  1. 顺序表有效个数size减1
c 复制代码
ps->size--;
  1. 该函数完整代码
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--;
}
  1. 在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.指定位置删除

原理:从要删除位置的下一个元素开始,依次向前移动一个位置

  1. 创建函数SLErase(SL* ps, int pos),函数需要一个参数,ps 指向顺序表的指针
c 复制代码
void SLErase(SL* ps, int pos)
{

}
  1. 需要判断传来的顺序表指针SL* ps,如果为空,程序停止运行
c 复制代码
assert(ps != NULL);
  1. 确保删除位置合法,合法范围是 [1, ps->size],即可以删除第一个位置、中间位置或末尾
    pos=1表示删除头部,pos=ps->size表示删除尾部
c 复制代码
  assert(pos >= 1 && pos <= ps->size);
  1. 从要删除位置的下一个元素开始,依次向前移动一个位置,覆盖要删除的元素,实现逻辑删除
c 复制代码
for (int i = pos - 1; i < ps->size - 1; i++)
    {
        ps->arr[i] = ps->arr[i + 1]; 
    }
  1. 顺序表有效个数size减1
c 复制代码
ps->size--;
  1. 该函数完整代码
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--;
}
  1. 在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.修改指定位置元素

原理:直接在所要修改的位置修改即可

  1. 创建函数SLModify(SL* ps, int pos, SLDataType x),函数需要三个参数,ps 指向顺序表的指针,pos 要修改的位置(从1开始计数),x 要修改的元素值
c 复制代码
void SLModify(SL* ps, int pos, SLDataType x)
{

}
  1. 需要判断传来的顺序表指针SL* ps,如果为空,程序停止运行
c 复制代码
assert(ps != NULL);
  1. 确保修改的位置合法,合法范围是 [1, ps->size],即可以修改第一个位置、中间位置或末尾
    pos=1表示修改头部,pos=ps->size表示修改尾部
c 复制代码
assert(pos >= 1 && pos <= ps->size);
  1. 指定的位置修改元素
c 复制代码
	ps->arr[pos - 1] = x;
  1. 该函数完整代码
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;
}
  1. 在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释放内存

  1. 创建函数SLDestroy(SL* ps),函数需要一个参数,ps 指向顺序表的指针
c 复制代码
void SLDestroy(SL* ps)
{

}
  1. 需要判断传来的顺序表指针SL* ps,如果为空,程序停止运行
c 复制代码
assert(ps != NULL);
  1. 若顺序表的动态数组已分配内存(非空指针),则释放该内存
c 复制代码
// 若顺序表的动态数组已分配内存(非空指针),则释放该内存
    if (ps->arr != NULL)
    {
        free(ps->arr);     // 释放动态数组占用的堆内存,防止内存泄漏
        ps->arr = NULL;    // 将数组指针置空,避免成为野指针(后续误操作已释放内存)
    }
    
    // 重置元素个数为0,表明顺序表已无有效元素
    ps->size = 0;
    // 重置容量为0,与空数组状态保持一致(无可用内存空间)
    ps->capacity = 0;
  1. 完整代码
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;
}
  1. 在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;
}
  1. 创建函数SLFindAll(SL* ps, SLDataType x, IND* index),函数需要三个参数,ps 指向顺序表的指针,x 要查找的元素,index 储存查找元素下标的顺序表
c 复制代码
void SLFindAll(SL* ps, SLDataType x, IND* index)
{

}
  1. 需要判断传来的顺序表指针SL* psIND* index,如果为空,程序停止运行
c 复制代码
assert(ps != NULL && index != NULL);
  1. 遍历原顺序表的所有元素,逐个比对是否与目标元素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;
        }
    }
    
  1. 打印查找结果,目标元素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");  // 打印换行,使输出格式更整洁
}
  1. 释放存储下标的动态数组所占用的内存,并重置结构体状态
c 复制代码
// 释放存储下标的动态数组所占用的内存,并重置结构体状态
free(index->arr);       // 释放index中动态分配的数组内存,避免内存泄漏
index->arr = NULL;      // 将数组指针置空,防止后续对已释放内存的非法访问(野指针问题)
index->size = 0;        // 重置元素个数为0,表明当前无存储任何下标
index->capacity = 0;    // 重置容量为0,与空数组状态保持一致
  1. 该函数完整代码
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);
相关推荐
x_xbx3 小时前
LeetCode:215. 数组中的第K个最大元素
数据结构·算法·leetcode
西野.xuan3 小时前
内存布局(堆vs栈)一篇详解!!
java·数据结构·算法
j_xxx404_3 小时前
蓝桥杯基础--时间复杂度
数据结构·c++·算法·蓝桥杯·排序算法
进击的荆棘4 小时前
优选算法——分治
数据结构·算法·leetcode·分治
Yupureki4 小时前
《实战项目-个人在线OJ平台》1.项目简介和演示
c语言·数据结构·c++·sql·算法·性能优化·html5
不染尘.5 小时前
欧拉路径算法
开发语言·数据结构·c++·算法·图论
小王不爱笑1325 小时前
Java HashSet
数据结构
ulias2126 小时前
函数栈帧的创建和销毁
开发语言·数据结构·c++·windows·算法
代码探秘者6 小时前
【算法篇】3.位运算
java·数据结构·后端·python·算法·spring