一、什么是顺序表?
顺序表(Sequential List)是用一段地址连续的存储单元依次存储数据元素的线性结构。换句话说,就是把数据元素一个接一个地放在连续的内存空间里。
听起来是不是很熟悉?没错,这和我们日常使用的数组(Array)几乎是一模一样的概念。
💡 一句话总结:顺序表是数据结构中的"学术名称",数组是编程语言中的"实现工具"。在大多数场景下,它们描述的是同一种存储方式。
二、数组与顺序表的关系
| 对比维度 | 数组(Array) | 顺序表(Sequential List) |
|---|---|---|
| 本质 | 编程语言提供的数据类型 | 数据结构中的逻辑概念 |
| 存储方式 | 连续的内存空间 | 连续的存储单元 |
| 元素访问 | 通过下标 arr[i] |
通过位置索引 |
| 元素类型 | 通常要求相同类型 | 通常要求相同类型 |
| 大小 | 定义时确定(静态)或动态扩展 | 可静态可动态 |
核心关系:数组是顺序表最常见的实现方式。当我们用数组来存储一组逻辑上相邻的数据时,这个数组就是一个顺序表。
三、顺序表的特点
✅ 优点
- 随机访问快:通过下标可以直接定位到任意元素,时间复杂度为 O(1)。
- 存储密度高:只存储数据本身,不需要额外的空间存储关系。
- 实现简单:利用数组即可实现,代码直观易懂。
❌ 缺点
- 插入和删除效率低:中间插入或删除元素时,需要移动大量元素,时间复杂度为 O(n)。
- 容量固定:静态顺序表的大小在创建时就确定了,难以灵活扩展。
四、用 C 语言实现静态顺序表
由于我们目前还没接触指针和动态内存,这里先用静态数组来实现一个完整的顺序表。这种方式虽然容量固定,但非常适合理解顺序表的核心思想。
4.1 定义顺序表
c
#include <stdio.h>
#define MAXSIZE 100 // 顺序表的最大容量
// 定义顺序表结构
typedef struct {
int data[MAXSIZE]; // 存储数据的数组
int length; // 当前顺序表的长度(元素个数)
} SeqList;
这里我们用结构体 SeqList 来封装数组和当前长度,这样操作起来更加清晰。
4.2 初始化顺序表
c
// 初始化顺序表
void initList(SeqList *list) {
list->length = 0; // 初始时没有任何元素
}
4.3 在末尾添加元素(插入)
c
// 在顺序表末尾添加一个元素
int append(SeqList *list, int value) {
if (list->length >= MAXSIZE) {
printf("顺序表已满,无法添加!\n");
return 0; // 添加失败
}
list->data[list->length] = value; // 放到当前末尾位置
list->length++; // 长度加 1
return 1; // 添加成功
}
4.4 在指定位置插入元素
c
// 在位置 pos(从 0 开始)插入元素 value
int insert(SeqList *list, int pos, int value) {
// 检查合法性
if (pos < 0 || pos > list->length) {
printf("插入位置不合法!\n");
return 0;
}
if (list->length >= MAXSIZE) {
printf("顺序表已满,无法插入!\n");
return 0;
}
// 从后往前,把 pos 位置及之后的元素都后移一位
for (int i = list->length; i > pos; i--) {
list->data[i] = list->data[i - 1];
}
list->data[pos] = value; // 放入新元素
list->length++; // 长度加 1
return 1;
}
🔍 思考一下:为什么循环要从后往前移动?如果从前往后移动会发生什么?
4.5 删除指定位置的元素
c
// 删除位置 pos 的元素
int delete(SeqList *list, int pos) {
if (pos < 0 || pos >= list->length) {
printf("删除位置不合法!\n");
return 0;
}
// 从 pos 位置开始,后面的元素依次前移
for (int i = pos; i < list->length - 1; i++) {
list->data[i] = list->data[i + 1];
}
list->length--; // 长度减 1
return 1;
}
4.6 查找元素
c
// 按值查找,返回元素位置,找不到返回 -1
int find(SeqList list, int value) {
for (int i = 0; i < list.length; i++) {
if (list.data[i] == value) {
return i; // 找到了,返回下标
}
}
return -1; // 没找到
}
// 按位置获取元素
int get(SeqList list, int pos) {
if (pos < 0 || pos >= list.length) {
printf("位置不合法!\n");
return -1;
}
return list.data[pos];
}
4.7 遍历输出
c
// 打印顺序表中的所有元素
void printList(SeqList list) {
printf("顺序表内容:");
for (int i = 0; i < list.length; i++) {
printf("%d ", list.data[i]);
}
printf("\n当前长度:%d\n", list.length);
}
五、完整测试代码
把上面的代码组合起来,写一个简单的 main 函数测试一下:
c
#include <stdio.h>
#define MAXSIZE 100
typedef struct {
int data[MAXSIZE];
int length;
} SeqList;
void initList(SeqList *list) {
list->length = 0;
}
int append(SeqList *list, int value) {
if (list->length >= MAXSIZE) return 0;
list->data[list->length++] = value;
return 1;
}
int insert(SeqList *list, int pos, int value) {
if (pos < 0 || pos > list->length || list->length >= MAXSIZE) return 0;
for (int i = list->length; i > pos; i--) {
list->data[i] = list->data[i - 1];
}
list->data[pos] = value;
list->length++;
return 1;
}
int delete(SeqList *list, int pos) {
if (pos < 0 || pos >= list->length) return 0;
for (int i = pos; i < list->length - 1; i++) {
list->data[i] = list->data[i + 1];
}
list->length--;
return 1;
}
void printList(SeqList list) {
printf("顺序表:");
for (int i = 0; i < list.length; i++) {
printf("%d ", list.data[i]);
}
printf("(长度=%d)\n", list.length);
}
int main() {
SeqList list;
initList(&list);
// 添加元素
append(&list, 10);
append(&list, 20);
append(&list, 30);
printList(list); // 输出:10 20 30
// 在位置 1 插入 15
insert(&list, 1, 15);
printList(list); // 输出:10 15 20 30
// 删除位置 2 的元素(即 20)
delete(&list, 2);
printList(list); // 输出:10 15 30
return 0;
}
六、数组 vs 顺序表:一张图看懂
内存中的存储方式(两者完全相同):
地址: 0x1000 0x1004 0x1008 0x100C 0x1010
┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
│ 10 │ │ 20 │ │ 30 │ │ 40 │ │ │
└─────┘ └─────┘ └─────┘ └─────┘ └─────┘
↑ ↑ ↑ ↑
arr[0] arr[1] arr[2] arr[3]
这既是"数组",也是"顺序表"
关键区别在于视角:
- 数组视角:关注内存中的一块连续区域,强调物理存储。
- 顺序表视角:关注数据之间的逻辑关系(线性、有序),强调数据结构层面的抽象。
七、小结
| 要点 | 内容 |
|---|---|
| 核心概念 | 顺序表 = 用连续空间存储的线性表,数组是其最常见的实现 |
| 访问特性 | 随机访问 O(1),插入删除 O(n) |
| 实现方式 | 静态数组(容量固定)/ 动态数组(容量可变,需用到指针和动态内存) |
| 学习建议 | 先掌握静态实现,理解核心操作逻辑,后续再学习动态扩展 |
📌 下节预告:当我们学习了指针和动态内存管理后,就可以实现动态顺序表,让表的大小随需扩展,解决静态顺序表容量固定的问题。
希望这篇博客能帮助你理清数组和顺序表的关系!如果有任何疑问,欢迎随时交流。😊