顺序表的实现
目录
[1.3.3.静态顺序表 VS 动态顺序表](#1.3.3.静态顺序表 VS 动态顺序表)
一、顺序表的概念及结构
1.1.顺序表的概念
顺序表是线性表的一种,顺序表的底层结构为数组,
但实现了增删查改等功能,就变成了一个数据结构
1.2.顺序表的特性
**线性表:**相同数据类型的数据元素组成的有限数列
关键特性:
- 物理结构:不一定为线性的
- 逻辑结构:线性的
**常见的线性表:**顺序表、链表、栈、队列、字符串......
顺序表的特性:
- 物理结构:线性的
- 逻辑结构:线性的
**注:**物理结构指数据在内存中的实际存储方式,逻辑结构指数据元素之间的抽象关系
1.3.顺序表的分类
1.3.1.静态顺序表:
cpp
struct SeqList
{
int arr[100];//定长数组
int size;//有效数据个数
};
1.3.2.动态顺序表
cpp
struct SeqList
{
int* arr;//动态内存开辟
int size;//有效数据个数
int capacity;//空间大小
};
1.3.3.静态顺序表 VS 动态顺序表
静态顺序表:
- 空间容量固定
- 容量不够时,数据就会丢失
- 容量过大时,造成空间浪费
动态顺序表:
- 可以实现内存动态的增容
- 能够按需要调整存储空间
- 避免空间浪费与数据丢失
优先选择动态顺序表
二、顺序表的实现
2.1.顺序表文件结构
- 头文件(SeqList.h):顺序表的结构创建,顺序表的方法声明
- 源文件(SeqList.c):顺序表的方法实现
- 测试文件(test.c):测试数据结构的方法
**注:**SeqList是"sequence list"的缩写,表示顺序列表
2.2.头文件编写(SeqList.h)
2.2.1.头文件包含
cpp
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
2.2.2.数组元素类型重定义
cpp
typedef int SLDataType;
**注:**使用类型别名便于后续维护修改
2.2.3.顺序表结构定义
1.静态顺序表(不建议使用):
cpp
用宏定义常量,便于参数修改
#define N 100
struct SeqList
{
SLDataType arr[N];
int size;//有效数据个数
};
2.动态顺序表:
cpp
结构体重命名,便于后续使用
typedef struct SeqList
{
SLDataType* arr;
int size;//有效数据个数
int capacity;//空间大小
}SL;
2.2.4.顺序表的初始化
cpp
void SLInit(SL* ps);
2.2.5.顺序表的销毁
cpp
void SLDestroy(SL* ps);
2.2.6.顺序表的尾插
cpp
void SLPushBack(SL* ps, SLDataType x);
2.2.7.顺序表的头插
cpp
void SLPushFront(SL* ps, SLDataType x);
2.2.8.顺序表的尾删
cpp
void SLPopBack(SL* ps);
2.2.9.顺序表的头删
cpp
void SLPopFront(SL* ps);
2.2.10.顺序表的打印
cpp
void SLPrint(SL s);
2.2.11.指定位置前插入数据
cpp
void SLInsert(SL* ps, int pos, SLDataType x);
2.2.12.删除指定位置的数据
cpp
void SLErase(SL* ps, int pos);
2.2.13.顺序表的查找
cpp
int SLFind(SL* ps, SLDataType x);
2.3.源文件编写(SeqList.c)
2.3.1.头文件包含
cpp
#include "SeqList.h"
2.3.2.顺序表的初始化
cpp
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
解析:
- 将顺序表的指针变量设置为NULL
- 顺序表的有效数据个数和空间大小设置为0
2.3.3.顺序表的销毁
cpp
void SLDestroy(SL* ps)
{
if (ps->arr)
{
free(ps->arr);
}
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
解析:
- 判断动态内存分配的空间是否存在,如果存在则释放该内存空间
- 将顺序表的指针变量设置为NULL,顺序表的有效数据个数和空间大小设置为0
2.3.4.顺序表的尾插
cpp
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);
if (ps->capacity == ps->size)
{
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);//直接退出程序,不再继续执行
}
ps->arr = tmp;
ps->capacity = newCapacity;
}
ps->arr[ps->size++] = x;
}
解析:
- 将元素x存入有效数据的下一位(size)
- 有效数据个数加一
注:
- 增强代码的健壮性:为了避免函数传参时传NULL,导致程序报错,可以使用assert断言,并加上头文件<assert.h>
- 判断空间是否足够:在插入数据之前要先确保有足够的空间,通过当前顺序表的空间大小是否等于有效数据个数来判断,如果相等则说明空间不足
- 判断空间是否存在:顺序表的指针变量初始值为NULL,增容前要先用三目表达式来判断,如果没有空间就先申请4字节空间,如果有就将原空间加倍,并将增容后的空间大小赋给新的空间大小变量newCapacity
- 动态申请内存空间:使用realloc函数动态申请内存空间,并且创建一个临时变量tmp来接收该空间,因为如果空间申请失败,会导致原空间数据的丢失
- 判断空间申请成功:如果tmp为NULL,表示空间申请失败,程序直接退出,不再执行
- 保存已申请的空间:如果空间申请成功,将tmp赋给arr,newCapacity赋给capacity
2.3.5.顺序表的头插
cpp
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[0] = x;
ps->size++;
}
解析:
- 断言判断:判断函数参数是否为NULL
- 空间检测:对顺序表进行空间检测
- 位移元素:for循环将每一个元素往后移一位
- 初值设置:i初始值为size,表示有效数据的下一位
- 操作设置:将后一位【i】赋值为前一位【i - 1】的数据 → arr[size] = arr[size-1]
- 终止条件:最后一次循环中,后一位【1】赋值为前一位【0】的数据,此时i = 1,得出终止条件:i > 0
- 插入数据:将x存入首元素,有效数据个数加一

注:
由于头插和尾插都需要判断空间是否足够,可以将这段代码封装成一个函数,专门解决这个问题
代码如下:
2.3.5.1内存检测函数
cpp
void SLCheckCapacity(SL * ps)
{
if (ps->capacity == ps->size)
{
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);
}
ps->arr = tmp;
ps->capacity = newCapacity;
}
}
2.3.6.顺序表的尾删
cpp
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->arr);
--ps->size;
}
解析:
- 断言判断:判断函数参数是否为NULL,顺序表是否为NULL
- 删除数据:有效数据个数减一,既尾删数据,又不影响后续的增删查改
2.3.7.顺序表的头删
cpp
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->arr);
for (int i = 0; i < size - 1; i++)
{
arr[i] = arr[i + 1];
}
ps->size--;
}
解析:
- 断言判断:判断函数参数是否为NULL,顺序表是否为NULL
- 位移元素:for循环将每一个元素往前移一位
- 初值设置:i初始值为0,表示首元素
- 赋值设置:将前一位【i】赋值为后一位【i + 1】的数据 → arr[0] = arr[1]
- 终止设置:最后一次循环中,前一位【size - 2】赋值为后一位【size - 1】的数据,此时i = size-2,得出终止条件:i < size - 1
- 删除数据:有效数据个数减一

2.3.8.顺序表的打印
cpp
void SLPrint(SL s)
{
for (int i = 0; i < s.size; i++)
{
printf("%d ", s.arr[i]);
}
printf("\n");
}
2.3.9.指定位置前插入数据
cpp
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
for (int i = ps->size; i > pos; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos] = x;
ps->size++;
}
解析:
- 断言判断:判断函数参数是否为NULL,pos值是否为有效值
- 空间检测:对顺序表进行空间检测
- 位移元素:for循环将pos位后的元素(包括pos)往后移一位
- 初值设置:i初始值为size,表示有效数据的下一位
- 赋值设置:将后一位【i】赋值为前一位【i - 1】的数据 → arr[size] = arr[size-1]
- 终止设置:最后一次循环中,后一位【pos + 1】赋值为前一位【pos】的数据,此时i = pos+1,得出终止条件:i > pos
- 插入数据:将x存入pos位,有效数据个数加一

2.3.10.删除指定位置的数据
cpp
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
for (int i = pos; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
解析:
- 断言判断:判断函数参数是否为NULL,pos值是否为有效值
- 位移元素:for循环将pos位后的元素(不包括pos)往前移一位
- 初值设置:i初始值为pos,表示pos位的数据
- 赋值设置:将前一位【i】赋值为后一位【i + 1】的数据 → arr[pos] = arr[pos+1]
- 终止设置:最后一次循环中,前一位【size - 2】赋值为后一位【size - 1】的数据,此时i = size-2,得出终止条件:i < size-1
- 删除数据:有效数据个数减一

2.3.11.顺序表的查找
cpp
int SLFind(SL* ps, SLDataType x)
{
assert(ps);
for (i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
return i;
}
}
return -1;
}
2.4.测试文件编写(test.c)
2.4.1.头文件包含
cpp
#include "SeqList.h"
2.4.2.测试方法01
cpp
void SLTest01()
{
顺序表结构声明
SL sl;
顺序表的初始化
SLInit(&sl);//传送的是sl的地址而非值
测试尾插
SLPushBack(&sl,1);
SLPushBack(&sl,2);
SLPushBack(&sl,3);
SLPushBack(&sl,4);
测试头插
SLPushFront(&sl,5);
SLPushFront(&sl,6);
SLPushFront(&sl,7);
SLPushFront(&sl,8);
测试尾删
SLPopBack(&sl);
测试头删
SLPopFront(&sl);
顺序表的打印
SLPrint(sl);
顺序表的销毁
SLDestroy(&sl);
}
int main()
{
SLTest01();
return 0;
}
**注:**由于sl变量还没有初始化,所以SLInit函数需要接收的是sl的地址而非值
2.4.3.测试方法02
cpp
void SLTest02()
{
顺序表结构声明
SL sl;
顺序表的初始化
SLInit(&sl);
顺序表的尾插
SLPushBack(&sl,1);
SLPushBack(&sl,2);
SLPushBack(&sl,3);
SLPushBack(&sl,4);
测试在指定位置之前插入数据
SLInsert(&sl,0,99);
SLInsert(&sl,sl.size,99);
测试删除指定位置的数据
SLErase(&sl,1);
测试顺序表的查找
int find = SLFind(&sl,4);
if(find < 0)
{
printf("没有找到!\n");
}
else
{
printf("找到了,下标为%d\n",find);
}
顺序表的打印
SLPrint(sl);
顺序表的销毁
SLDestroy(&sl);
}
int main()
{
SLTest02()
return 0;
}
三、总结
- 想要改变地址对应的数据就要传送地址,传送地址后先要判断是否为NULL
- 插入数据前要进行空间检测
- 不要忘记判断pos值是否有效
- 要掌握每个功能循环的起始、循环、终止条件
- 不要忘记对有效数据个数进行修改
本篇博客是数据结构中顺序表知识点的整理归纳,后续还会更新顺序表的应用等内容,如果对你有帮助,欢迎点赞+收藏+关注,让我们一起共同进步🌟~
