顺序表(SqList)完整解析与实现(数据结构专栏版)

顺序表(SqList)完整解析与实现(数据结构专栏版)

编写的这份代码完美实现了顺序表(静态顺序存储线性表)的核心操作,包括初始化、清空、查找、插入、删除、取值、打印等功能,符合数据结构中线性表的 "顺序存储" 规范,遵循 "逻辑结构与物理结构一致" 的特点,是一份优秀的顺序表入门实现。下面结合数据结构专栏知识点,从核心结构、功能拆解、原理解析、优化点、扩展知识点五个方面进行全面讲解。

一、前置知识:顺序表的核心定义(补充完整结构)

首先补充这份代码依赖的2.List.h头文件核心内容,帮助理解整体结构(数据结构专栏标准定义):

c

运行

复制代码
#ifndef _LIST_H_
#define _LIST_H_

// 顺序表最大容量(静态数组长度)
#define MAXSIZE 100
// 顺序表数据类型别名(便于修改)
typedef int ElemType;

// 顺序表结构体定义(静态顺序存储)
typedef struct {
    // 存储元素的静态数组,物理上连续存储
    ElemType elem[MAXSIZE];
    // 记录顺序表现有元素个数(有效长度),区别于数组长度MAXSIZE
    int length;
} SqList;

// 函数声明(与实现文件对应)
void InitList(SqList *L);
void clearList(SqList *L);
int Locate(SqList *L, int e);
int Insert(SqList *L, int e, int pos);
int Delete(SqList *L, int pos);
int GetElem(SqList *L, int pos);
void PrintList(SqList *L);

#endif
  • 数据结构知识点:顺序表采用静态数组实现顺序存储,元素在物理内存上连续排列,逻辑上的相邻元素在物理上也相邻;
  • 核心字段说明:
  1. elem[MAXSIZE]:静态数组,用于存放顺序表元素,容量固定为MAXSIZE,是静态顺序表的特征;
  2. length:记录顺序表当前有效元素个数,取值范围0 ~ MAXSIZElength=0表示空表,length=MAXSIZE表示满表;
  • 设计亮点:采用 "1-based(基于 1)" 的逻辑位置对外提供接口(用户感知的位置从 1 开始),符合人类使用习惯,内部通过pos-1转换为 "0-based(基于 0)" 的数组下标,适配 C 语言数组特性,兼顾易用性和底层兼容性。

二、核心功能函数拆解(顺序表操作实现)

1. 初始化(InitList)与清空(clearList

c

运行

复制代码
void InitList(SqList *L) // 初始化
{
    L->length = 0; 
}

void clearList(SqList *L) // 清空
{
    L->length = 0;
}
  • 核心逻辑:顺序表的初始化和清空均只需将length置为 0,无需修改数组elem中的数据;
  • 数据结构知识点:顺序表的length是 "有效元素标识",length=0仅表示当前无有效元素,数组中的旧数据不会被清除(也无需清除),后续插入元素会直接覆盖旧数据,这种设计高效且简洁(时间复杂度 O (1));
  • 区别与联系:
  1. 初始化:针对未使用过的空顺序表 ,建立初始状态(length=0);
  2. 清空:针对已存有元素的顺序表 ,重置为初始空状态(length=0),二者操作相同,场景不同;
  • 健壮性补充:可添加if(L == NULL)的空指针判断,避免传入野指针导致程序崩溃。

2. 查找元素(Locate):按值查找,返回逻辑位置

c

运行

复制代码
int Locate(SqList *L, int e) // 查找元素 e
{
    if (L->length == 0) 
    {
        return -1;
    }
    for (int i = 0; i < L->length; i++) 
    {
        if (L->elem[i] == e) 
        {
            return i + 1;  // 转换为1-based逻辑位置返回
        }
    }
    printf("未找到该元素\n");
    return -1;  
}
  • 操作类型:按值查找(顺序查找),适用于无序顺序表(这是无序线性表的唯一查找方式);
  • 核心流程:判空→遍历有效元素→匹配成功返回 1-based 位置→匹配失败返回 - 1;
  • 时间复杂度:O (n)(n 为顺序表长度L->length),最坏情况下需要遍历所有有效元素;
  • 关键细节:返回i+1而非i,将数组的 0-based 下标转换为用户感知的 1-based 逻辑位置,符合接口设计的易用性要求;
  • 注意点:若存在多个相同元素e,仅返回第一个匹配元素的逻辑位置,这是顺序查找的默认行为。

3. 插入元素(Insert):按逻辑位置插入,保持元素有序性

c

运行

复制代码
int Insert(SqList *L, int e, int pos) // 在逻辑位置 pos(1-based)插入元素 e
{
    int index = pos - 1; // 转换为0-based数组下标
    // 步骤1:校验插入位置合法性
    if (index < 0 || index > L->length) 
    {
        printf("插入位置不合法\n");
        return 0;
    }
    // 步骤2:校验顺序表是否已满
    if (L->length >= MAXSIZE) 
    {
        printf("表已满,无法插入\n");
        return 0;
    }
    // 步骤3:元素后移,为插入位置腾出空间(从后往前移,避免数据覆盖)
    for (int i = L->length; i > index; i--) 
    {
        L->elem[i] = L->elem[i - 1];
    }
    // 步骤4:在目标位置插入新元素
    L->elem[index] = e;
    // 步骤5:更新有效元素个数
    L->length++;
    return 1; 
}
  • 核心规则:顺序表插入操作需保证元素的物理连续性,插入位置后的所有元素需向后移动一位,腾出空间存放新元素;
  • 关键细节解析:
  1. 位置合法性:index > L->length是允许的(对应在表尾插入,pos = L->length + 1),此时无需移动任何元素,直接插入表尾,效率最高(O (1));
  2. 元素后移:采用 "从后往前" 遍历移动(iL->length递减到index+1),若从前往后移动,会导致前面的元素覆盖后面的元素,丢失数据;
  3. 时间复杂度:最好情况 O (1)(表尾插入),最坏情况 O (n)(表头插入),平均时间复杂度 O (n);
  • 返回值说明:返回 1 表示插入成功,返回 0 表示插入失败,便于调用者判断操作结果。

4. 删除元素(Delete):按逻辑位置删除,返回被删元素

c

运行

复制代码
int Delete(SqList *L, int pos) //删除
{
    int index = pos - 1; // 转换为0-based数组下标
    // 步骤1:校验删除位置合法性
    if (index < 0 || index >= L->length) 
    {
        printf("删除位置不合法\n");
        return 0; 
    }
    // 步骤2:保存被删除元素的值(用于返回)
    int e = L->elem[index];
    // 步骤3:元素前移,填补被删除元素的空位(从前往后移)
    for (int i = index; i < L->length - 1; i++) 
    {
        L->elem[i] = L->elem[i + 1];
    }
    // 步骤4:更新有效元素个数
    L->length--;
    // 步骤5:返回被删除的元素
    return e;
}
  • 核心规则:与插入操作相反,删除元素后,后续元素需向前移动一位,填补空位,保证元素的物理连续性;
  • 关键细节解析:
  1. 位置合法性:index >= L->length是不允许的(删除位置不能超过有效元素的最后一个位置),与插入操作的位置校验有区别;
  2. 元素前移:采用 "从前往后" 遍历移动(iindex递增到L->length-2),直接用后面的元素覆盖前面的元素,高效且无数据丢失;
  3. 时间复杂度:最好情况 O (1)(表尾删除),最坏情况 O (n)(表头删除),平均时间复杂度 O (n);
  • 注意点:空表返回 0,但 0 也可能是合法的被删元素,可优化为 "通过指针参数传出被删元素,返回值仅标识操作成功 / 失败",避免歧义。

5. 取值操作(GetElem)与打印操作(PrintList

c

运行

复制代码
int GetElem(SqList *L, int pos) // 获取逻辑位置 pos 的元素
{
    int index = pos - 1;
    // 步骤1:校验位置合法性
    if (index < 0 || index >= L->length) 
    {
        printf("位置不合法\n");
    }
    // 步骤2:返回目标位置的元素值
    return L->elem[index];
}

void PrintList(SqList *L) // 打印
{
    for (int i = 0; i < L->length; i++) 
    {
        printf("%d ", L->elem[i]);
    }   
    printf("\n"); 
}
  • 取值操作(GetElem):
  1. 核心逻辑:直接通过转换后的数组下标访问元素,时间复杂度 O (1)(顺序表的随机访问特性);
  2. 数据结构知识点:顺序表支持随机访问,即通过下标可以直接访问任意位置的元素,无需遍历,这是顺序表相比链表的核心优势;
  • 打印操作(PrintList):
  1. 核心逻辑:遍历顺序表的所有有效元素(0 ~ L->length-1),避免遍历整个数组(0 ~ MAXSIZE-1),防止打印无效的垃圾数据;
  2. 设计亮点:打印后换行,提升输出可读性,符合控制台输出的使用习惯。

三、顺序表核心原理与特性(数据结构专栏知识点)

1. 核心原理:逻辑结构与物理结构一致

顺序表的线性逻辑结构(元素之间一对一的相邻关系),通过数组的连续物理存储结构来实现,即:

  • 逻辑上第i个元素(1-based),对应物理上数组下标为i-1的元素;
  • 逻辑上相邻的元素(如第i个和第i+1个),在物理内存中也处于相邻的数组位置(下标i-1i)。

这种一致性赋予了顺序表 "随机访问" 的特性,也是顺序表与链表的核心区别。

2. 顺序表(静态)的优缺点

优点
  1. 支持随机访问,访问效率高(O (1)),适合频繁按位置取值的场景;
  2. 元素物理连续,无需额外存储指针域,空间利用率较高(无冗余开销);
  3. 实现简单,操作直观,易于理解和编码。
缺点
  1. 容量固定(静态数组),无法动态扩展,若MAXSIZE设置过小,容易出现表满溢出;若设置过大,容易造成内存浪费;
  2. 插入、删除操作需要移动大量元素,时间效率较低(O (n)),适合元素个数相对稳定、少增删的场景;
  3. 内存分配一次性完成,且需要连续的内存空间,若内存中无足够大的连续空间,无法创建顺序表。

3. 关键特性总结

特性 具体描述 时间复杂度
随机访问 支持通过下标直接访问任意元素 O(1)
插入操作 表尾插入高效,表头 / 中间插入需移动元素 最好 O (1),最坏 O (n)
删除操作 表尾删除高效,表头 / 中间删除需移动元素 最好 O (1),最坏 O (n)
按值查找 无序顺序表采用顺序查找,需遍历元素 O(n)

四、优化点与改进方案(提升代码完善度)

1. 优化点 1:添加空指针判断,提升健壮性

所有操作函数均接收SqList *L作为参数,需先判断指针是否为NULL,避免野指针导致程序崩溃,示例如下(以Insert函数为例):

c

运行

复制代码
int Insert(SqList *L, int e, int pos)
{
    // 新增:空指针判断
    if (L == NULL)
    {
        printf("顺序表指针为空,操作失败!\n");
        return 0;
    }
    int index = pos - 1;
    // 后续原有逻辑...
}

2. 优化点 2:解决Delete函数的返回值歧义

原函数中空队列返回 0,0 也可能是合法元素,优化方案:通过指针参数传出被删元素,返回值仅标识操作成功 / 失败:

c

运行

复制代码
int Delete(SqList *L, int pos, int *e)
{
    if (L == NULL || e == NULL)
    {
        printf("指针为空,操作失败!\n");
        return 0;
    }
    int index = pos - 1;
    if (index < 0 || index >= L->length) 
    {
        printf("删除位置不合法\n");
        return 0; 
    }
    // 通过指针传出被删元素
    *e = L->elem[index];
    for (int i = index; i < L->length - 1; i++) 
    {
        L->elem[i] = L->elem[i + 1];
    }
    L->length--;
    return 1; // 1表示删除成功,0表示失败
}

3. 优化点 3:实现动态顺序表,解决容量固定问题

静态顺序表的容量限制是核心缺陷,可通过 ** 动态数组(堆内存分配)** 实现动态顺序表,修改结构体如下:

c

运行

复制代码
typedef struct {
    ElemType *elem; // 动态数组指针,指向堆内存
    int length;     // 有效元素个数
    int listsize;   // 当前分配的内存容量(可扩展)
} SqList;
  • 初始化时动态分配内存:L->elem = (ElemType *)malloc(MAXSIZE * sizeof(ElemType));
  • 容量不足时扩展内存:L->elem = (ElemType *)realloc(L->elem, new_size * sizeof(ElemType));
  • 优势:可根据实际需求动态扩展容量,避免内存浪费或溢出,更贴近实际开发场景。

五、总结

  1. 顺序表是线性表的顺序存储实现,核心特点是 "逻辑结构与物理结构一致",支持随机访问,访问效率高;
  2. 核心操作中,插入和删除的时间复杂度为 O (n)(需移动元素),取值和随机访问的时间复杂度为 O (1),适合少增删、多查询的场景;
  3. 对外提供 1-based 逻辑位置接口,内部转换为 0-based 数组下标,是兼顾易用性和底层兼容性的优秀设计;
  4. 静态顺序表实现简单但容量固定,动态顺序表可解决容量问题,是实际开发中的首选。

这份代码完美契合数据结构专栏的核心知识点,是理解线性表的绝佳案例,吃透顺序表的实现逻辑,能够为后续学习链表、栈、队列等线性数据结构打下坚实基础。

相关推荐
格林威2 小时前
多光源条件下图像一致性校正:消除阴影与高光干扰的 6 个核心策略,附 OpenCV+Halcon 实战代码!
人工智能·数码相机·opencv·算法·计算机视觉·分类·视觉检测
iAkuya2 小时前
(leetcode)力扣100 40二叉树的直径(迭代递归)
java·算法·leetcode
橘颂TA2 小时前
【剑斩OFFER】算法的暴力美学——leetCode 103 题:二叉树的锯齿形层序遍历
算法·leetcode·结构与算法
2501_901147832 小时前
高性能计算笔记:灯泡开关问题的数学优化与常数级解法
笔记·算法·求职招聘
C_心欲无痕2 小时前
JavaScript 常见算法与手写函数实现
开发语言·javascript·算法
CoovallyAIHub2 小时前
YOLO26正式亮相!极致速度优化,为落地而生!
深度学习·算法·计算机视觉
我就想睡到自然醒2 小时前
【C++基础STL1】数组和vector
c++
玖釉-2 小时前
[Vulkan 学习之路] 04 - 选妃环节:挑选物理设备与队列族
c++·windows·图形渲染
三万棵雪松2 小时前
【AI小智硬件程序(九)】
c++·人工智能·嵌入式·esp32·ai小智