目录
一.顺序表定义
1 、顺序表的概念及结构
1.1线性表
2、顺序表分类
2.1 静态顺序表
2.2 动态顺序表
二、动态顺序表的实现 ( 重要!)
1.准备工作及其注意事项
1.1 先创建三个文件
1.2 注意事项:帮助高效记忆和理解
2.顺序表的基本功能接口
2.0 创建一个 顺序表
2.1 顺序表的初始化
2.2 顺序表的销毁
2.3 顺序表的打印
3.顺序表的扩容检查接口
4.顺序表的增加功能接口
4.1 尾插接口
4.2 头插接口
4.3 指定位置插入接口
5.顺序表的删除功能接口
5.1 尾删接口
5.2头删接口
5.3指定位置删除接口
6.顺序表的查找实现接口
三.总代码
SeqList.h
SepList.c
test.c
线性表(linearlist)是n个具有相同特性的数据元素的有限序列。
线性表是一种在实际中广泛使 用的数据结构,常⻅的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的一条直线。
但是在物理结构上并不一定是连续的, 线性表在物理上存储时,通常以数组和链式结构的形式存储。 简单来说顺序表是线性表的一种,而且逻辑和物理都是线性结构
顺序表和数组的区别
静态顺序表(这里拿里面存储的整形举例)
typedef int SLDataType;
#define N 100
typedef struct SeqList
{
SLDatatype arr[N];
int size;//size是有效数据的个数
}SL;
解释一下为什么要把存储的类型变为SLDataType
这么做是为了方便以后修改 使用 不同 的类型
还有把数组存储的数据容量N 便于修改
动态顺序表的结构更加灵活, 可以对结构进行 增删查改,所以我们下面的接口都是通过动态顺序表实现的
解释这三个文件的作用 1、头文件SeqList.h 是来声明接口函数,定义顺序表,将几个公共用到的库函数集合起来 2、源文件SeqList.c 是用来具体实现接口 3、源文件test.c 用于接口的测试工作 ,即具体的使用场景
1.不管写任意一种接口函数,我们在处理之前都要进行断言,避免穿过来的是NULL指针 assert(ps->arr) ; 2.在进行扩容操作 的时候别忘记了 realloc要将新空间的地址和容量 更新给 自己的数组 和 容量变量(在下文) 3.进行插入操作之后 要把size++; 在进行删除操作的时候要进行size--;(很重要的一点) 4、插入操作 和 删除操作 用到循环 数据整体后移 或 前移 一定要注意 边界!!!
//动态顺序表
typedef int SLDataType;// 定义一个 类型名字,方便快速修改 类型
typedef struct SeqList
{
SLDataType* arr;
int capacity; // 记录动态顺序表 申请空间的大小
int size;// 记录 顺序表 当前有效的数据个数
}SL;
// 给 结构体命名,翻遍方便使用:直接写在下面也可以
// typedef struct SeqList SL;
void SLInit(SL* ps)
{
assert(ps);
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
void SLDestroy(SL* ps)
{
assert(ps);
free(ps->arr);
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
// 打印顺序表:这里可以 传值 也可 传址 ,
// 因为不需要对数据进行修改(可以传值),但是 为了保持接口一致性(其他接口都是传址),这里直接使用 传址
void SLPrint(SL* ps)
{
for (int i = 0; i < ps->size; ++i) printf("%d ", ps->arr[i]);
printf("\n");
}
// 公共的扩容判断
void SLCheckCapacity(SL* ps)
{
//当容量capacity 大小 == 标记点size 时,说明 已经满了:需要 扩容
if (ps->size == ps->capacity)
{
// 开辟空间时注意,当capacity初始化为 0 时,直接相乘的结果还是 0 !!
// 所以要先判断一下, 并且需要做 申请空间是否成功的判断,
// 还不能直接将申请的空间赋值给 目标数组,先给一个 临时变量
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
// 每次 两倍空间扩容(tip:这里推荐2倍的倍数扩容,可以自行查找为什么倍数扩容 较好!)
SLDataType* ptmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
if (ptmp == NULL)
{
perror("realloc fail!");
exit(1);
}
// 扩容成功
ps->arr = ptmp;
ps->capacity = newCapacity;
}
}
// (1)尾插法:有三种情况:1) 头部非空,尾部还有空间;2)全部为空;3)全部满了
void SLPushBack(SL* ps, SLDataType x)
{
// 先判断指针是否非空
assert(ps);
// 空间不够,扩容 : 3)
SLCheckCapacity(ps);
// 空间足够,直接插入:1 ) ; 2 )
// 在 size 这个位置插入数据: size 是标记第一个空位置
ps->arr[ps->size++] = x;
// ps->size++; // 标记点size 需要即使更新:size++ 也可以合并在上面
}
// 头插法
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
// 空间不够,扩容 : 3)
SLCheckCapacity(ps);
// 将数据向后挪动:注意边界问题!!!
for (int i = ps->size; i > 0; --i) ps->arr[i] = ps->arr[i - 1];
ps->arr[0] = x; // 将插入数据 放在头部
ps->size++; // 别忘 了 更新 size
}
// 指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
// 注意 : 可以指定位置插入 说明 指定的位置pos 可能不合理,可能越界, 必须要断言判断一下
assert(0 <= pos && pos < ps->size); // 注意 不能等于 size!!!!
// 判断是否有足够的空间
SLCheckCapacity(ps);
// 在 pos 位置中 插入 数据 x :就要将 pos 位置 起的 后面数据向后挪动
for (int i = ps->size; i >= pos + 1; --i) // 让 pos 这个位置 空出来
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos] = x;
ps->size++;
}
void SLPopBack(SL* ps)
{
// 删除前 检查:顺序表非空才能执行删除,同时 size 不能为 0
assert(ps);
assert(ps->size);// 顺序表不为空
ps->size--;
// 直接 size-- 将 size 位置的数值 丢出 我们的有效数据空间,那个位置不属于有效空间了,则类似达到删除的效果
}
void SLPopFront(SL* ps)
{
// 删除前 检查:顺序表非空才能执行删除,同时 size 不能为 0
assert(ps);
assert(ps->size);
// 将 后面的 数据 向前移动: 注意边界!!
for (int i = 1; i < ps->size; ++i) ps->arr[i - 1] = ps->arr[i];
ps->size--;
}
void SLErase(SL* ps, int pos)
{
assert(ps);
// 注意 : 可以指定位置插入 说明 指定的位置可能不合理,可能越界, 必须要断言判断一下
assert(0 <= pos && pos < ps->size); // 注意 不能等于 size
for (int i = pos + 1; i < ps->size; ++i)
{
ps->arr[i - 1] = ps->arr[i];
}
ps->size--;
}
int SLFind(SL* ps, int x)
{
for (int i = 0;i < ps->size;++i)
{
if (ps->arr[i] == x)
{
return i;
}
}
return -1;
}
#pragma once
#include
#include
#include
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* arr;
int capacity;
int size;
}SL;
// 一、初始化
void SLInit(SL* ps);
// 二、 尾插法
void SLPushBack(SL* ps, SLDataType x);
// 二、 头插法
void SLPushFront(SL* ps, SLDataType x);
// 三、尾删法
void SLPopBack(SL* ps);
// 四、头删法
void SLPopFront(SL* ps);
// 五、指定位置删除
void SLErase(SL* ps, int pos);
// 六、指定位置插入
void SLInsert(SL* ps, int pos, SLDataType x);
//顺序表的扩容检查(扩容的原则,以及扩容的方法)
void SLCheckCapacity(SL* ps);
// 打印函数
void SLPrint(SL* ps);
#include"SeqList.h"
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->Capacity = ps->size = 0;
}
// 开辟空间函数
void SLCheckCapacity(SL* ps)
{
if (ps->size == ps->Capacity)
{
int NewCapacity = ps->Capacity == 0 ? 4 : 2 * (ps->Capacity);
SLDataType* ptmp = realloc(ps->arr, NewCapacity * sizeof(SLDataType));
if (ptmp == NULL)
{
perror("realloc fail !");
exit(1);
}
ps->arr = ptmp;
ps->Capacity = NewCapacity;
}
}
// 二、 尾插法
void SLPushBack(SL* ps, SLDataType x)
{
// 先判断指针是否非空
assert(ps); // 别忘了
// 分两大类情况:空间足够 和 空间不够
// (1) 空间不够:空间扩展
SLCheckCapacity(ps);
// 开始尾插
ps->arr[ps->size++] = x;
}
// 二、 头插法
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++; // 别忘记了
}
// 三、尾删法
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->size);
ps->size--;
}
// 四、头删法
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size);
for (int i = 0; i < ps->size - 1; ++i)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
// 五、指定位置删除
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(0 <= pos && pos < ps->size);
for (int i = pos; i < ps->size - 1; ++i)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
// 六、指定位置插入
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps); // 别忘了
assert(0 <= pos && pos < ps->size);
SLCheckCapacity(ps);
for (int i = ps->size; i >= pos + 1; --i)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos] = x;
ps->size++; // 别忘记了
}
// 打印函数
void SLPrint(SL* ps)
{
for (int i = 0; i < ps->size; ++i)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}
#include"SeqList.h"
void slTest01()
{
SL sl; // 创建一个结构体变量 sl
SLInit(&sl); // 这里需要 传地址:才能真正被初始化
//测试尾插
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4); // ctrl + d == ctrl + c + v
printf("测试尾插: ");
SLPrint(&sl);
// 测试头插
SLPushFront(&sl, 10);
printf("测试头插: ");
SLPrint(&sl);
// 测试 尾删
SLPopBack(&sl);
printf("测试尾删: ");
SLPrint(&sl);
// 测试 头删
SLPopFront(&sl);
printf("测试头删: ");
SLPrint(&sl);
// 测试 指定位置插入
SLInsert(&sl, 1, 15);
printf("指定插入: ");
SLPrint(&sl);
SLErase(&sl, 1);
printf("指定删除: ");
SLPrint(&sl);
}
int main()
{
slTest01(); // 第一个测试函数
return 0;
}
1. 中间/头部的插⼊删除,时间复杂度为O(N)
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不⼩的消耗。
3. 增容⼀般是呈2倍的增⻓,势必会有⼀定的空间浪费。例如当前容量为100,满了以后增容到 200,我们再继续插⼊了5个数据,后⾯没有数据插⼊了,那么就浪费了95个数据空间。
有没有 这样一个 数据结构
1、中间 和 头尾的 数据插入 不用 挪动 其他数据,而是一步到位
2、不需要直接扩充连续的空间(用不完容易浪费,像上面第 3 点所说)
3、不会造成空间浪费
这样的强大的数据结构 即为 【 链表 】
可以 点击跳转 讲解 单链表 的实现文章 ---->:【数据结构】单链表的实现
完。
若上述文章有什么错误,欢迎各位大佬及时指出,我们共同进步!