网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
结构的实现代码为:
typedef int SLDataType;
typedef struct SeqList
{
SLDataType\* arr; // 指向动态开辟的数组
int Size; //数组内存放数据数量
int Capacity; //顺序表容量
}SeqList;
// 此结构的顺序表 创建后,使用前,需要初始化
顺序表接口实现🐬
静态顺序表 只适用于确定知道需要存多少数据的场景
所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小
以下也都是以动态顺序表为基础
一个动态顺序表一般拥有这些接口:
- 初始化
seqListInit
- 容量检查
checkCapacity
- 尾插
seqListPushBack
- 尾删
seqListPopBack
- 头插
seqListPushFront
- 头删
seqListPopFront
- 查找
seqListFind
- 指定位置插入
seqListInsert
- 指定位置删除
seqListErase
- 销毁顺序表
seqListDestory
- 打印顺序表
seqListPrint
下面就来一一实现这些接口:
🌈
顺序表动态存储结构🐟
typedef int SLDataType;
typedef struct SeqList
{
SLDataType\* arr; // 指向动态开辟的数组
int Size; //数组内存放数据数量
int Capacity; //顺序表容量
}SeqList;
// 此结构的顺序表 创建后,使用前,需要初始化
实现动态顺序表结构,需要在结构体内定义一个 某数据类型(需存放的数据类型)
的 数组。剩下的操作全部由接口函数实现。
🌈
顺序表初始化🐟
// 顺序表初始化
void seqListInit(SeqList* psl);
初始化函数非常的简单,只需要将创建好的顺序表结构体内:
arr
指向 NULL
Size
和 Capacity
赋予 0
就完成了一个顺序表的初始化
实现代码:
// 顺序表初始化
void seqListInit(SeqList\* psl)
{
assert(psl);
psl->arr = NULL;
psl->Size = psl->Capacity = 0;
}
创建一个顺序表验证一下:
创建出的顺序表,初始化成功。
🌈
顺序表尾插 及 容量检查🐟
// 顺序表容量检查
void checkCapacity(SeqList* psl);
// 顺序表尾插
void seqListPushBack(SeqList* psl, SLDataType x);
顺序表尾插,即 在顺序表最后一个数据之后存放数据
存放数据进入顺序表之前,要 先确保顺序表处于未满状态
的,不然数据无法存储。
所以在实现尾插之前,先 实现一个容量检查的函数
。
容量检查一般需要实现什么功能?
- 检查顺序表是否已满
- 如果顺序表已满,则扩容
具体实现如下:
void checkCapacity(SeqList\* psl)
{
assert(psl);
if(psl->Size == psl->Capacity)
{// 如果存入的数据数量 与 顺序表的容量相等,就代表顺序表已满
int newCapacity = psl->Capacity == 0 ? 2 : 2 \* psl->Capacity;
//如果当前容量等于0,代表新顺序表,新容量给 2
//如果当前容量不等于0,代表非新顺序表,新容量给原来容量的两倍
SLDataType\* tmp = (SLDataType\*)realloc(psl->arr, sizeof(SLDataType)\* newCapacity);
// 先用tmp 指向 realloc 扩容出新空间,以防止扩容失败导致元数据丢失
if(tmp == NULL)
{// tmp 为空 表示扩容失败,退出程序
printf("realloc fail!\n");
exit(-1);
}
// tmp 不为空,再将tmp 赋于 psl->arr;
psl->arr = tmp; //psl->arr 指向扩容后的地址
psl->Capacity = newCapacity; // 容量改为扩容后的容量
}
}
容量检查函数实现成功之后,就可以继续 实现尾插函数
:
先考虑一下,尾插都需要注意哪些事项
:
- 需要先用容量检查函数检查容量
- 需要在数组中
Size
的位置放入数据之后,Size
自增
(因为数组下标从 0 开始,所以 Size 位置即为最后一个数据之后)
具体实现如下:
// 尾插
void seqListPushBack(SeqList\* psl, SLDataType x)
{
assert(psl); //断言保证传入的结构体的地址不为空
checkCapacity(psl); //容量检查函数检查容量
psl->arr[psl->Size] = x; // 将 x 尾插
psl->Size++; // 存储数据量加1 , Size也要加1
}
每实现一个接口,最好都要验证一下
尾插三个整型数据,验证
尾插三次,扩容两次,尾插成功.
🌈
顺序表打印🐟
// 顺序表打印
void seqListPrint(SeqList* psl);
尾插存放数据是可以了,但是如何实现顺序表的打印呢?
顺序表打印
接口的实现也是非常的简单:
// 顺序表打印
void seqListPrint(SeqList\* psl)
{
assert(psl);
for (int i = 0; i < psl->Size; i++)
{
printf("%d ", psl->arr[i]);
}
printf("\n");
}
验证:
🌈
顺序表尾删🐟
// 顺序表尾删
void seqListPopBack(SeqList* psl);
实现了顺序表的尾插,如果需要删除数据,对应的还有顺序表的尾删。
尾删需要注意几个点:
- 尾删需不需要将
需要删除的数据
的空间释放掉? - 尾删需不需要将
需要删除的数据
赋值为0
? - 尾删需不需要控制一下顺序表中的
Size
? - 控制不控制
Size
的值,对顺序表来说有什么区别?
先来思考一下第一个问题,尾删需不需要将 需要删除的数据
的空间释放掉?
答案是,不需要,也无法释放。
因为 free
函数使用的条件是,malloc
或 realloc
函数开辟的整块空间
,且传入的必须是是这块空间的首地址
。所以无法释放其中的单独一块空间。
第二个问题,尾删需不需要将 需要删除的数据
赋值为 0
?
答案同样是,不需要。
因为尾删之后,下次使用这块空间一定是在插入数据的时候,到时候会有新的数据将其覆盖,所以不需要将其赋值为
0`
第三个问题和第四个问题,一起思考一下:
尾删需不需要控制一下顺序表中的 Size
?
控制不控制 Size
的值,对顺序表来说有什么区别?
答案是,需要控制 Size
的值,防止 过多次使用尾删(使用次数比存放数据数量多)
导致 Size
变成负数。
Size
变成负数,会发生什么情况?
使用下面段 尾删函数试验一下:
// 顺序表尾删
void seqListPopBack(SeqList\* psl)
{
assert(psl);
psl->Size--;
}
尾插 3 个数据,但是尾删了 5 次
可以看到,顺序表中的 Size
变成了 -3
变成负数之后,如果继续进行其他操作,一定会发生错误
比如:
即使,尾插了两次,顺序表中还是无法输出数据,也就是说,再次尾插的两个数据并没有存放至顺序表中。
因为 这两次尾插,是从 Size 为 -3
的地方执行的,并没有从 0
位置开始,所以无法存入,同时也发生了越界现象
。
这里
VS 编译器
不报错,是因为对于数组越界的情况, VS编译器 是抽查的
。也就是说,并不是所有的越界情况VS编译器
都能检查得到
所以尾删
,需要对顺序表中的 Size
进行一个简单的控制:
// 顺序表尾删
void seqListPopBack(SeqList\* psl)
{
assert(psl);
if (psl->Size > 0)
{// 当Size 大于零 再进行自减
psl->Size--;
}
}
这样就不会发生使 Size
减到负 的问题:
即使 尾删次数过多,Size
也不会变为负数,也就不会发生其他操作错误的情况。
🌈
顺序表头插🐟
// 顺序表头插
void seqListPushFront(SeqList* psl, SLDataType x);
顺序表的尾插比较简单,只需要在顺序表 Size
位置放入数据就可以了。
头插呢?头插需要注意到什么问题?
顺序表本质上是 数组实现的
,那么需要在数组的首位置插入数据就不仅仅是插入数据那么简单。需要先将数组中的数据向后移动一位,然后才能将新数据放入首位置
演示:
那么来实现头插一下:
// 头插
void seqListPushFront(SeqList\* psl, SLDataType x)
{
assert(psl); //断言保证传入的结构体的地址不为空
checkCapacity(psl); //插入前 首先检查容量
// 方法一:
int end = psl->Size;
while (end)
{
psl->arr[end] = psl->arr[end - 1];
end--;
}
// 方法二:
/\*int end = psl->Size - 1;
while (end >= 0)
{
psl->arr[end + 1] = psl->arr[end];
end--;
}\*/
// 这两个方法其实一样的,只是对 末尾元素的位置控制方式不同
psl->arr[0] = x; // 将 x 尾插
psl->Size++; // 存储数据量加1 , Size 加1
}
验证:
头插成功。
🌈
顺序表头删🐟
// 顺序表头删
void seqListPopFront(SeqList* psl);
实现了头插,接着来实现一下头删
头删和头插的思路相似,不过只是将从 1 ~ Size-1
位置的数据向前移动一位。
不过,头删同样需要保证 Size > 0
才能执行。
演示:
头删实现:
// 顺序表头删
void seqListPopFront(SeqList\* psl)
{
assert(psl);
if (psl->Size > 0)
{
int begin = 1;
while (begin < psl->Size)
{
psl->arr[begin] = psl->arr[begin - 1];
begin++;
}
psl->Size--;
}
}
验证:
即使删除过多,也不会出错。
🌈
顺序表查找🐟
int seqListFind(SeqList* psl, SLDataType x);
查找顺序表中某个数据所在的位置,可以用到顺序表查找的操作。
因为顺序表的本质是数组,所以 只需要将顺序表从头到尾遍历一遍
,找到值就返回 其所在的位置
,找不到就返回 -1
。
查找的实现:
// 顺序表查找
int seqListFind(SeqList\* psl, SLDataType x)
{
assert(psl);
for (int i = 0; i < psl->Size; i++)
{
if (x == psl->arr[i])
return i;
}
return -1;
}
验证:
验证了,一般情况、边界情况、未找到情况,均返回正确。
🌈
指定位置插入🐟
void seqListInsert(SeqList* psl, size_t pos, SLDataType x);
尾插、头插都实现了,接下来就实现一下指定 pos
位置插入数据。
在指定 pos
位置插入数据,和前插有一点相似,需要将数据向后移一位。
我们可以用相似的方法移动,不过是从 pos
位置以后的数据向后移动一位。
不过首先还是要检查容量:
// 指定位置插入
void seqListInsert(SeqList\* psl, size\_t pos, SLDataType x)
{
assert(psl);
SeqListCheckCapacity(psl); // 检查容量
size\_t end = psl->Size;
while(end > pos)
{
psl->arr[end] = psl->arr[end - 1];
end--;
}
psl->arr[pos] = x;
psl->Size++;
}
验证一下:
在插入之前可以使用 查找函数 获取一下
pos
位置
pos
位置插入数据 成功了。
但是真的成功了吗?
这个函数,现在还有没有什么问题?
比如,当我在 20
这个位置插入,会发生什么呢?
看一下:
在 20
位置插入数据没有报错,但是输出的时候输出的是随机值
而且,在实际的操作中,程序是延迟了一会才结束的
为什么?
因为,顺序表中存放的数据数量只有 9
个,占用的位置只有0 ~ 8
,如果在 20
位置插入数据,超出了顺序表应有的位置,这个时候 Size
照常自增
那么输出的时候就会输出 0 ~ 9
位置的数据,而 9
位置并没有赋予数据,所以输出的是随机值。
这就提出了一个问题:当传入的 pos
位置比实际应该插入的位置大,怎么解决?
其实也很简单,如果 pos
位置过大,直接结束 插入函数
就可以了。
那么改进后的代码就是:
// 指定位置插入
void seqListInsert(SeqList\* psl, size\_t pos, SLDataType x)
{
assert(psl);
checkCapacity(psl); // 检查容量
if(pos > psl->Size)
{// 尾插的位置是 Size,如果大于Size,就代表pos过大
printf("Insert fail. Pos > Size! Pos = %d \n", pos);
![img](https://img-blog.csdnimg.cn/img_convert/c165fd6fec7600ca7cace4c538d931e6.png)
![img](https://img-blog.csdnimg.cn/img_convert/74aa1a9a3038479af698193f93319e3e.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**
`pos` 位置过大,`直接结束 插入函数` 就可以了。
那么改进后的代码就是:
// 指定位置插入
void seqListInsert(SeqList* psl, size_t pos, SLDataType x)
{
assert(psl);
checkCapacity(psl); // 检查容量
if(pos > psl->Size)
{// 尾插的位置是 Size,如果大于Size,就代表pos过大
printf("Insert fail. Pos > Size! Pos = %d \n", pos);
[外链图片转存中…(img-izFLbgyF-1715758114370)]
[外链图片转存中…(img-0Nq9D8FZ-1715758114371)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新