C语言-数据结构中顺序表详解:从原理到实战应用

一、什么是顺序表?

顺序表是线性表的一种实现方式,它采用连续的内存空间来存储数据元素,同时保持元素之间的逻辑顺序关系。

直观理解

想象一下排队买票的人群:每个人都按照先后顺序站着,前面的人买完票离开后,后面的人依次向前移动填补空位。顺序表就是这种"排队"结构在计算机内存中的实现。

基本特征

  • 连续存储:所有元素存储在连续的内存空间中
  • 随机访问:可以通过下标直接访问任意位置的元素
  • 大小固定:创建时需要指定容量,后期难以动态调整

二、顺序表的设计与实现

1. 管理结构体设计

顺序表需要一个管理结构来记录其状态信息:

// 顺序表管理结构体
typedef struct sq_list
{
    int capacity;   // 顺序表的总容量
    int index;      // 当前最后一个数据的下标位置(-1表示空表)
    int *data_p;    // 指向顺序表数据存储空间的指针
} sq_list_t, *sq_list_p;

内存布局示意图

+-------------------+      +------------------------+
| 管理结构体         |      | 数据存储空间           |
| +-------------+   |      | +---+---+---+---+---+  |
| | capacity: 5 |   |      | |   |   |   |   |   |  |
| | index: 2    |   +----->| +---+---+---+---+---+  |
| | data_p: ----|----------|  0   1   2   3   4     |
| +-------------+   |      |                        |
+-------------------+      +------------------------+

2. 顺序表初始化

/**
 * @brief   初始化顺序表
 * @param   cap_size: 顺序表的容量
 * @return  成功: 返回顺序表管理结构体指针
 *          失败: 返回NULL
 */
sq_list_p SequentialList_Init(unsigned int cap_size)
{
    // 1. 申请管理结构体内存
    sq_list_p p = malloc(sizeof(sq_list_t));
    if (p == NULL) return NULL;
    
    // 初始化管理结构体
    memset(p, 0, sizeof(sq_list_t));
    p->capacity = cap_size;
    p->index = -1;  // -1表示空表
    
    // 2. 申请数据存储空间
    p->data_p = malloc(sizeof(int) * cap_size);
    if (p->data_p == NULL) {
        free(p);
        return NULL;
    }
    
    return p;
}

3. 顺序表销毁

/**
 * @brief   销毁顺序表,释放所有内存
 * @param   p: 顺序表管理结构体指针
 */
void SequentialList_UnInit(sq_list_p p)
{
    if (p == NULL) return;
    
    // 先释放数据空间,再释放管理结构
    free(p->data_p);
    free(p);
}

三、核心操作实现

1. 判断状态

// 判断顺序表是否已满
bool SequentialList_IsFull(sq_list_p p)
{
    return p->index == p->capacity - 1;
}

// 判断顺序表是否为空
bool SequentialList_IsEmpty(sq_list_p p)
{
    return p->index == -1;
}

2. 插入数据(表头插入)

/**
 * @brief   在顺序表表头插入数据
 * @param   p: 顺序表管理结构体指针
 * @param   new_data: 要插入的新数据
 * @return  成功: 0, 失败: -1
 */
int SequentialList_InsertData(sq_list_p p, int new_data)
{
    // 检查顺序表是否已满
    if (SequentialList_IsFull(p)) {
        printf("顺序表已满,无法插入新数据!\n");
        return -1;
    }
    
    // 将所有数据向后移动一位
    for (int i = p->index; i >= 0; i--) {
        p->data_p[i + 1] = p->data_p[i];
    }
    
    // 在表头插入新数据
    p->data_p[0] = new_data;
    p->index++;  // 更新末尾索引
    
    return 0;
}

插入过程图示

插入前: [10, 20, 30, _, _]  (index=2)
向后移动: [10, 20, 30, 30, _] → [10, 20, 20, 30, _] → [10, 10, 20, 30, _]
插入新数据: [5, 10, 20, 30, _]  (index=3)

3. 删除数据(按位置删除)

/**
 * @brief   删除指定位置的数据
 * @param   p: 顺序表管理结构体指针
 * @param   pos: 要删除的位置
 * @return  成功: 0, 失败: -1
 */
int SequentialList_DeleteAt(sq_list_p p, unsigned int pos)
{
    // 检查位置是否有效
    if (SequentialList_IsEmpty(p) || pos > p->index) {
        printf("无效的删除位置!\n");
        return -1;
    }
    
    // 将后面的数据向前移动
    for (int i = pos; i < p->index; i++) {
        p->data_p[i] = p->data_p[i + 1];
    }
    
    p->index--;  // 更新末尾索引
    
    return 0;
}

4. 遍历显示

/**
 * @brief   显示顺序表中的所有数据
 * @param   p: 顺序表管理结构体指针
 */
void SequentialList_Display(sq_list_p p)
{
    if (SequentialList_IsEmpty(p)) {
        printf("顺序表为空!\n");
        return;
    }
    
    printf("============== 顺序表内容 ==============\n");
    for (int i = 0; i <= p->index; i++) {
        printf("位置 %d: %d\n", i, p->data_p[i]);
    }
    printf("========================================\n");
}

5. 修改数据

/**
 * @brief   修改指定位置的数据
 * @param   p: 顺序表管理结构体指针
 * @param   pos: 要修改的位置
 * @param   new_data: 新的数据值
 * @return  成功: 0, 失败: -1
 */
int SequentialList_UpdateAt(sq_list_p p, unsigned int pos, int new_data)
{
    if (SequentialList_IsEmpty(p) || pos > p->index) {
        printf("无效的修改位置!\n");
        return -1;
    }
    
    p->data_p[pos] = new_data;
    return 0;
}

四、顺序表的优缺点分析

优点:

  1. 随机访问高效:通过下标可直接访问任意元素,时间复杂度为O(1)
  2. 内存利用率高:只需要存储数据本身,不需要额外指针空间
  3. 缓存友好:连续存储有利于CPU缓存命中

缺点:

  1. 插入删除效率低:平均需要移动一半元素,时间复杂度为O(n)
  2. 容量固定:创建后难以动态调整大小
  3. 内存要求高:需要大块连续内存空间

五、完整使用示例

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>

// 包含顺序表相关定义和函数声明
// ...

int main()
{
    printf("========== 顺序表演示程序 ==========\n");
    
    // 1. 初始化顺序表(容量为5)
    sq_list_p list = SequentialList_Init(5);
    if (list == NULL) 
    {
        printf("顺序表初始化失败!\n");
        return -1;
    }
    printf("顺序表初始化成功,容量:%d\n", list->capacity);
    
    // 2. 插入数据
    SequentialList_InsertData(list, 30);
    SequentialList_InsertData(list, 20);
    SequentialList_InsertData(list, 10);
    printf("插入了3个数据后的顺序表:\n");
    SequentialList_Display(list);
    
    // 3. 继续插入
    SequentialList_InsertData(list, 5);
    SequentialList_InsertData(list, 0);
    printf("插入了5个数据后的顺序表:\n");
    SequentialList_Display(list);
    
    // 4. 尝试插入第6个数据(应该失败)
    if (SequentialList_InsertData(list, -5) == -1) 
    {
        printf("顺序表已满,插入失败(符合预期)\n");
    }
    
    // 5. 删除第二个数据(位置1)
    SequentialList_DeleteAt(list, 1);
    printf("删除位置1的数据后的顺序表:\n");
    SequentialList_Display(list);
    
    // 6. 修改第一个数据
    SequentialList_UpdateAt(list, 0, 100);
    printf("修改位置0的数据后的顺序表:\n");
    SequentialList_Display(list);
    
    // 7. 销毁顺序表
    SequentialList_UnInit(list);
    printf("顺序表已销毁,程序结束。\n");
    
    return 0;
}

六、顺序表的应用场景

适合使用顺序表的场景:

  1. 数据量相对固定的场景
  2. 需要频繁随机访问元素的场景
  3. 对内存使用效率要求高的场景
  4. 数据插入删除操作较少的场景

实际应用示例:

  • 学生成绩管理系统(学生数量相对固定)
  • 游戏中的物品栏系统
  • 操作系统中的进程控制块管理
  • 嵌入式系统中的传感器数据缓存

七、扩展思考

1. 如何实现动态扩容?

可以通过重新分配更大的内存空间,然后复制原有数据来实现动态扩容,但这会带来性能开销。

2. 顺序表与链表的比较

  • 顺序表:适合查询多、增删少的场景
  • 链表:适合增删多、查询少的场景

3. 变种顺序表

  • 动态顺序表:支持自动扩容
  • 多维顺序表:如矩阵、二维数组等
  • 环形缓冲区:首尾相连的顺序表
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值