「 每日一句 · Daily Quote 」
"我这个人走得很慢,但是我从不后退。"
--- 亚伯拉罕·林肯
文章目录
- 前言
- 一、线性表
- 二、顺序表
-
- [2.1 概念与结构](#2.1 概念与结构)
- [2.2 分类](#2.2 分类)
-
- [2.2.1 静态顺序表](#2.2.1 静态顺序表)
- [2.2.2 动态顺序表](#2.2.2 动态顺序表)
- 三、动态顺序表的实现
- [3.1 定义动态顺序表的结构](#3.1 定义动态顺序表的结构)
-
- [3.2 初始化顺序表](#3.2 初始化顺序表)
- [3.3 检查空间容量是否足够](#3.3 检查空间容量是否足够)
- [3.4 打印顺序表](#3.4 打印顺序表)
- [3.5 销毁顺序表](#3.5 销毁顺序表)
- [3.6 尾插](#3.6 尾插)
- [3.7 头插](#3.7 头插)
- [3.8 尾删](#3.8 尾删)
- [3.9 头删](#3.9 头删)
- [3.10 在指定位置之前插入数据](#3.10 在指定位置之前插入数据)
- [3.11 删除pos位置的数据](#3.11 删除pos位置的数据)
- [3.12 查找](#3.12 查找)
- 总结
前言
数据结构是程序员的必修内功,而顺序表是最基础、最常用的线性结构之一。本文将从线性表的概念入手,介绍顺序表的定义及其与数组的区别,对比静态与动态顺序表的优劣,重点讲解动态顺序表的完整实现,包括初始化、扩容、头尾插入删除、指定位置插入删除、查找等核心接口,并逐段分析代码逻辑。掌握顺序表,是学习链表、栈、队列等复杂数据结构的第一步。
一、线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。线性表是⼀种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不⼀定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
二、顺序表
2.1 概念与结构
概念:顺序表是用⼀段物理地址连续 的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。

顺序表和数组的区别?
顺序表的底层结构是数组,对数组的封装,实现了常用的增删改查等接口
打个比方
数组相当于未经雕琢的"基础食材/菜品",仅提供最底层的连续物理存储能力;
而顺序表则是以此为核心原料进行高级封装后的"完整料理",其底层结构依然是原生数组,却拥有增、删、改、查等一系列标准数据结构接口

2.2 分类
2.2.1 静态顺序表
概念:使用定长数组存储元素

静态顺序表缺陷:空间给少了不够用,给多了造成空间浪费
2.2.2 动态顺序表

三、动态顺序表的实现
3.1 定义动态顺序表的结构
c
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* arr;
int size; //有效数据个数
int capacity; //空间容量
}SL;
typedef int SLDataType- 对类型重命名: 方便未来需要储存其他新的类型变量
typedef struct SeqList { ... } SL;- 结构体定义与别名: 提升书写效率
SLDataType* arr- 首元素地址
size- 有效数据个数 : 记录当前顺序表中实际已存入的元素数量
capacity- 物理容量上限: 指该顺序表最大能储存的元素个数
3.2 初始化顺序表
c
//初始化
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
让arr置为空,size,capacity置为0
3.3 检查空间容量是否足够
c
void Checkcapacity(SL* ps)
{
assert(ps);
if (ps->size == ps->capacity)
{
//检查空间容量是否为0
int newcapcity = (ps->capacity == 0) ? 4 : ps->capacity * 2;
//扩容
//realloc第二个参数,单位是字节
SLDataType* tmp = (SLDataType*)realloc(ps->arr, newcapcity * sizeof(SLDataType));
//扩容失败
if (tmp == NULL)
{
perror("realloc fail");
exit(1);
}
//扩容成功
ps->arr = tmp;
ps->capacity = newcapcity;
}
}
-
- 如果size == capacity,说明容量已经满了,需要扩容
-
- 检查空间容量是否为0,如果空间容量为0,则默认赋4个元素空间;如果空间容量不为0, 则按 2 倍增长
-
- 用realloc进行扩容,并用临时变量tmp接收
-
- 判断扩容是否成功,若扩容成功,则更新结构体元数据
3.4 打印顺序表
c
//打印
void SLPrint(SL* ps)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}
-
- 用for循环遍历所有元素并打印
-
- 换行
3.5 销毁顺序表
c
//销毁
void SLDestroy(SL* ps)
{
assert(ps);
ps->capacity = ps->size = 0;
free(ps->arr);
ps->arr = NULL;
}
-
- 令capacity和size都为0
-
- free掉之前通过realloc申请的内存
-
- 将arr指针置空,规避野指针
3.6 尾插
c
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);
Checkcapacity(ps);
ps->arr[ps->size++] = x;
}
-
- 先
checkcapacity检查一下空间容量是否足够
- 先
-
- 有效元素自增1,并且将数据写入
3.7 头插
c
//头插
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
Checkcapacity(ps);
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i-1];
}
ps->arr[0] = x;
//增加size数量
ps->size++;
}
-
- 先用
Checkcapcity检查空间容量是否足够
- 先用
-
- 利用for循环将每个元素向后移动1位
-
- 将首位元素写入并将有效元素个数加1
3.8 尾删
c
//尾删
void SLPopBack(SL* ps)
{
//检查ps和size都不为空
assert(ps && ps->size);
Checkcapacity(ps);
ps->size--;
}
-
- 确保
ps和size都不为空
- 确保
-
- 将有效元素个数减1
3.9 头删
c
//头删
void SLPopFront(SL* ps)
{
//检查
assert(ps && ps->size );
for (int i = 0; i < ps->size-1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
-
- 先用
Checkcapacity检查空间容量是否充足
- 先用
-
- 将每个元素复制到前一个元素位置
-
- 将有效元素个数减1
3.10 在指定位置之前插入数据
c
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps && pos >= 0 && pos <= ps->size);
//检查空间是否足够
Checkcapacity(ps);
for (int i = ps->size; i > pos; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos] = x;
ps->size++;
}
-
- 检查空间容量是否足够
-
- 将下标区间
[pos, size - 1]内的元素整体向后移动一位。
- 将下标区间
-
- 将目标值 x 写入
pos位置
- 将目标值 x 写入
-
- 有效元素个数加1
3.11 删除pos位置的数据
c
//删除pos位置的数据
void SLErase(SL* ps, int pos)
{
assert(ps && pos >= 0 && pos < ps->size);
for (int i = pos; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
-
- 将下标区间
[pos, size - 1]内的元素整体向前移动一位。
- 将下标区间
-
- 有效元素个数减1
3.12 查找
c
//查找
int SLFind(SL* ps, SLDataType x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
//找到了
if (ps->arr[i] == x)
return i;
}
//没找到
return -1;
}
-
- 将所有元素遍历一遍,寻找目标元素
-
- 找到目标元素,返回该元素下标
-
- 没找到该元素,返回-1
总结
以上就是本篇博客的核心内容。本文介绍了线性表的概念与分类,重点讲解了顺序表的定义及其两种实现方式(静态与动态顺序表),详细实现了动态顺序表的常用接口:初始化、扩容、头尾插入删除、指定位置插入删除、查找、打印与销毁,并分析了各接口的代码逻辑与注意事项。掌握顺序表,就为后续学习链表、栈、队列等数据结构打下了坚实基础。