【C语言数据结构】第2章:线性表(2)--线性表的顺序存储结构
每日一句
乾坤未定,你我皆是黑马,
乾坤已定,我定扭转乾坤。
1.顺序存储的定义与本质
顺序表是线性表的物理实现方式,其核心是用连续的存储单元依次存储线性表的元素,使得逻辑上相邻的元素在物理存储上也相邻。这种存储方式的本质是通过 “物理位置的连续性” 保证逻辑关系的线性性。
(1)核心特征
随机访问
通过元素的 “位置” 可直接计算其存储地址(如第 i 个元素的地址 = 首元素地址 + (i-1)× 单个元素占用的存储单元数);
存储密度高
无需额外存储元素间的关系(如指针),空间利用率高;
插入 / 删除效率低
插入或删除元素时,需移动大量后续元素以保持存储的连续性。
(2)与逻辑结构的关联
顺序表的物理结构完全匹配线性表的逻辑结构(一对一的序偶关系),是线性表最直观的实现方式。
2.顺序表的存储表示
顺序表的存储可分为静态存储和动态存储两种方式:
静态顺序表
定义
使用固定大小的数组存储元素,表的最大长度在编译时确定。
代码示例(C 语言)
#define MaxSize 100 // 预定义表的最大长度
typedef int DataType; // 假设元素类型为int
typedef struct {DataType data[MaxSize]; // 存储元素的数组int length; // 当前表的长度(元素个数)
} SeqList;
特点
实现简单,但最大长度固定,易出现 “空间浪费”(表元素少但数组大)或 “溢出”(表元素超过数组大小)。
动态顺序表
定义
使用指针和动态内存分配,表的最大长度在运行时确定,可根据需要扩展。
代码示例(C 语言)
#define InitSize 10 // 初始分配的空间大小
typedef int DataType;
typedef struct {DataType *data; // 指向动态分配数组的指针int length; // 当前表的长度int maxSize; // 当前分配的最大空间
} DynSeqList;
特点
灵活度高,可动态扩展空间,但需手动管理内存(如扩容、释放),实现相对复杂。
3.顺序表的地址计算
顺序表支持随机访问的核心是 “地址可通过公式直接计算”。假设:
首元素的存储地址为 Loc(a1);
每个元素占用 c 个存储单元(如int
占 4 字节,double
占 8 字节);则第 i 个元素的地址为:Loc(ai)=Loc(a1)+(i−1)×c
示例:若首元素地址为 1000(十六进制),元素类型为int
(c=4),则第 5 个元素的地址为:1000+(5−1)×4=1016
这种地址计算方式使得顺序表的 “按位置查找” 操作时间复杂度为 O(1),是其核心优势。
4.顺序表的基本操作实现
顺序表的核心操作(初始化、查找、插入、删除)需结合 “连续存储” 的特点设计:
初始化操作:
1.静态顺序表:只需将length
置 0。
void InitSeqList(SeqList *L) {L->length = 0; // 表初始化为空
}
2.动态顺序表:需分配初始内存,并设置length
和maxSize
。
void InitDynSeqList(DynSeqList *L) {L->data = (DataType *)malloc(InitSize * sizeof(DataType));if (L->data == NULL) exit(1); // 内存分配失败则终止L->length = 0;L->maxSize = InitSize;
}
查找操作:
1.按位置查找(GetElem
):直接通过地址公式访问元素,时间复杂度 O(1)。
DataType GetElem(SeqList L, int i) {if (i < 1 || i > L.length) {printf("位置越界");exit(1);}return L.data[i-1]; // 数组下标从0开始,位置i对应下标i-1
}
2.按值查找(LocateElem
):遍历数组,逐个比较值,时间复杂度 O(n)。
int LocateElem(SeqList L, DataType e) {for (int i = 0; i < L.length; i++) {if (L.data[i] == e) {return i + 1; // 返回位置(从1开始)}}return 0; // 查找失败
}
插入操作:
步骤:①判断位置是否越界;②将第 i 至第 n 个元素后移一位;③插入新元素;④表长度加 1。
代码示例(静态顺序表):
void InsertElem(SeqList *L, int i, DataType e) {if (i < 1 || i > L->length + 1) { // 插入位置范围是1~length+1printf("位置越界");exit(1);}if (L->length >= MaxSize) { // 表已满则无法插入printf("表已满");exit(1);}// 元素后移:从最后一个元素开始,直到第i个元素for (int j = L->length; j >= i; j--) {L->data[j] = L->data[j-1];}L->data[i-1] = e; // 插入新元素L->length++; // 表长度加1
}
时间复杂度:平均移动 n/2 个元素,最坏情况 O(n)(如在表头插入)。
删除操作:
- 步骤:①判断位置是否越界;②保存被删元素的值;③将第 i+1 至第 n 个元素前移一位;④表长度减 1。
- 代码示例(静态顺序表):
DataType DeleteElem(SeqList *L, int i) {if (i < 1 || i > L->length) { // 删除位置范围是1~lengthprintf("位置越界");exit(1);}DataType temp = L->data[i-1]; // 保存被删元素// 元素前移:从第i个元素开始,直到最后一个元素for (int j = i; j < L->length; j++) {L->data[j-1] = L->data[j];}L->length--; // 表长度减1return temp; // 返回被删元素 }
- 时间复杂度:平均移动 (n−1)/2 个元素,最坏情况 O(n)(如删除表头元素)。
5.顺序表的优缺点总结
优点:
- 支持随机访问,按位置查找效率高(O(1));
- 存储密度高,无需额外空间存储元素间关系;
- 实现简单,代码易读。
缺点:
- 插入 / 删除操作效率低(需移动大量元素,O(n));
- 静态顺序表空间固定,易浪费或溢出;
- 动态顺序表需手动管理内存,实现复杂且扩容有开销。
6.顺序表的适用场景
顺序表适用于“查找频繁,插入删除少”的场景:
- 如 “学生信息管理系统”(频繁按学号查询,插入删除操作少);
- 如 “考试成绩统计系统”(按考号查找成绩,偶尔添加 / 删除考生)。
若场景中插入删除操作频繁(如 “消息队列”“撤销操作栈”),则更适合选择链表。
总结
顺序表是线性表的 “连续存储” 实现,其核心优势是随机访问效率高,核心劣势是插入删除效率低。理解顺序表的存储方式、地址计算和操作实现,是掌握 “数据结构与存储方式关联” 的关键。后续学习链表时,可通过对比二者的优缺点,更深入理解 “数据结构的选择需结合场景” 的原则。