一、什么是顺序表?
顺序表是线性表的一种实现方式,它采用连续的内存空间来存储数据元素,同时保持元素之间的逻辑顺序关系。
直观理解
想象一下排队买票的人群:每个人都按照先后顺序站着,前面的人买完票离开后,后面的人依次向前移动填补空位。顺序表就是这种"排队"结构在计算机内存中的实现。
基本特征
- 连续存储:所有元素存储在连续的内存空间中
- 随机访问:可以通过下标直接访问任意位置的元素
- 大小固定:创建时需要指定容量,后期难以动态调整
二、顺序表的设计与实现
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;
}
四、顺序表的优缺点分析
优点:
- 随机访问高效:通过下标可直接访问任意元素,时间复杂度为O(1)
- 内存利用率高:只需要存储数据本身,不需要额外指针空间
- 缓存友好:连续存储有利于CPU缓存命中
缺点:
- 插入删除效率低:平均需要移动一半元素,时间复杂度为O(n)
- 容量固定:创建后难以动态调整大小
- 内存要求高:需要大块连续内存空间
五、完整使用示例
#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. 变种顺序表
- 动态顺序表:支持自动扩容
- 多维顺序表:如矩阵、二维数组等
- 环形缓冲区:首尾相连的顺序表
914

被折叠的 条评论
为什么被折叠?



