话不多说,先上整体动态顺序表实现的代码给大家
SeqList.h
#include <stdio.h> //包含标准输入输出流的头文件
#include <assert.h> //包含断言的头文件
#include <stdlib.h> //包含增容等的头文件
typedef int SLDataType; //类型重命名
typedef struct SeqList
{
SLDataType* a; //定义一个指针变量为了接下来动态内存的开辟
int size; //记录顺序表存储元素的个数
int capacity; //记录顺序表当前的容量大小(最多能存储多少个数据)
}SL; //结构体重命名
void SeqListInit(SL* ps); //顺序表的初始化
void SeqListPrint(SL* ps); //顺序表的打印
void SeqListDestroy(SL* ps); //顺序表的销毁
void SeqListCheckCapacity(SL* ps); //顺序表检查是否增容
void SeqListBackPush(SL* ps, SLDataType x); //顺序表的尾插
void SeqListBackPop(SL* ps); //顺序表的尾删
void SeqListFrontPush(SL* ps, SLDataType x); //顺序表的头插
void SeqListFrontPop(SL* ps); //顺序表的头删
int SeqListFind(SL* ps, SLDataType x); //顺序表的查找
void SeqListInsert(SL* ps, int pos, SLDataType x); //顺序表的任意插入
void SeqListErase(SL* ps, int pos); //顺序表的任意位置删除
SeqList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"
void SeqListInit(SL* ps)
{
ps->a = NULL;
ps->size = ps->capacity = 0;
} //顺序表的初始化
void SeqListPrint(SL* ps)
{
int i = 0;
for (i = 0; i < ps->size; ++i)
{
printf("%d ", ps->a[i]);
}
printf("\n");
} //顺序表的打印
void SeqListDestroy(SL* ps)
{
free(ps->a);
ps->a = NULL;
ps->size = ps->capacity = 0;
} //顺序表的销毁
void SeqListCheckCapacity(SL* ps)
{
if (ps->size == ps->capacity)
{
int newcapacity = (ps->capacity == 0 ? ps->capacity = 4 : ps->capacity * 2);
SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));
if (tmp == NULL)
{
printf("realloc invalid!\n");
exit(-1); //退出程序
}
ps->a = tmp;
ps->capacity = newcapacity;
}
} //顺序表检查是否增容
void SeqListBackPush(SL* ps, SLDataType x)
{
//SeqListCheckCapacity(ps); //检查是否需要增容
//int end = ps->size;
//ps->a[end] = x;
//ps->size++;
SeqListInsert(ps, ps->size, x);
} //顺序表的尾插
void SeqListBackPop(SL* ps)
{
粗暴的方式
//assert(ps->size > 0);
温柔的方式
///*if (ps->size <= 0)
//{
// printf("SeqListBackPop invalid!\n");
// return;
//}*/
//ps->size--;
SeqListErase(ps, ps->size - 1);
}//顺序表的尾删
void SeqListFrontPush(SL* ps, SLDataType x)
{
//SeqListCheckCapacity(ps); //检查是否需要增容
//int end = ps->size;
//while (end >= 0)
//{
// ps->a[end] = ps->a[end - 1];
// end--;
//}
//ps->a[0] = x;
//ps->size++;
SeqListInsert(ps, 0, x);
} //顺序表的头插
void SeqListFrontPop(SL* ps)
{
粗暴的方式
//assert(ps->size > 0);
温柔的方式
///*if (ps->size <= 0)
//{
// printf("SeqListFrontPop invalid!\n");
// return;
//}*/
//int begin = 0;
//while (begin < ps->size)
//{
// ps->a[begin] = ps->a[begin+1];
// begin++;
//}
//ps->size--;
SeqListErase(ps, 0);
} //顺序表的头删
int SeqListFind(SL* ps, SLDataType x)
{
int i = 0;
for (i = 0; i < ps->size; ++i)
{
if (ps->a[i] == x)
{
return i;
}
}
return -1;
} //顺序表的查找
void SeqListInsert(SL* ps, int pos, SLDataType x)
{
SeqListCheckCapacity(ps);
//粗暴的方式
assert(pos >= 0 && pos <= ps->size);
//温柔的方式
/*if (pos<0 || pos>ps->size)
{
printf("SeqListInsert invalid!\n");
return;
}*/
int end = ps->size;
while (end >= pos)
{
ps->a[end] = ps->a[end - 1];
end--;
}
ps->a[pos] = x;
ps->size++;
} //顺序表的任意插入
void SeqListErase(SL* ps, int pos)
{
//粗暴的方式
assert(pos >= 0 && pos < ps->size);
//温柔的方式
/*if (pos < 0 || pos >= ps->size)
{
printf("SeqListErase invalid!\n");
return;
}*/
int begin = pos;
while (begin < ps->size)
{
ps->a[begin] = ps->a[begin + 1];
begin++;
}
ps->size--;
} //顺序表的任意位置删除
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"
void TestSeqList1()
{
SL s1;
SeqListInit(&s1);
SeqListBackPush(&s1, 0);
SeqListBackPush(&s1, 1);
SeqListBackPush(&s1, 2);
SeqListBackPush(&s1, 3);
SeqListBackPush(&s1, 4);
SeqListBackPop(&s1);
SeqListBackPop(&s1);
SeqListBackPop(&s1);
SeqListFrontPush(&s1, 2);
SeqListFrontPush(&s1, 3);
SeqListFrontPush(&s1, 4);
SeqListFrontPush(&s1, 5);
SeqListFrontPush(&s1, 6);
SeqListFrontPop(&s1);
SeqListFrontPop(&s1);
SeqListFrontPop(&s1);
SeqListFrontPop(&s1);
SeqListPrint(&s1);
}
void TestSeqList2()
{
SL s1;
SeqListInit(&s1);
SeqListInsert(&s1, 0, 0);
SeqListInsert(&s1, 1, 1);
SeqListInsert(&s1, 2, 2);
SeqListInsert(&s1, 3, 3);
SeqListInsert(&s1, 4, 4);
SeqListErase(&s1, 1);
SeqListErase(&s1, 2);
SeqListPrint(&s1);
SeqListDestroy(&s1);
}
int main()
{
TestSeqList1();
//TestSeqList2();
return 0;
}
顺序表
1.基本概念:顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构。(一般情况下采用数组存储,在数组上完成数据的增删查改)
2.分类:静态顺序表(使用定长数组存储元素)和动态顺序表(使用动态开辟的数组存储)
附:
1.楼主使用的是VS2013编译器。
2.在一个比较正式的项目工程里,通常是类似于分为功能定义的头文件、功能实现的源文件以及测试用的源文件。(类似于下图)
3.本篇文章中楼主以动态顺序表的实现为主,静态顺序表的实现大家可以参考动态顺序表的实现。(为了后续方便学习C++中的STL,楼主的变量、函数等的命名风格均是根据C++的命名风格来命名的!)
动态顺序表的实现
SeqList.h
首先包含必要的头文件(包括输入输出、断言、增容等的相关所需的头文件)
我们定义一个结构体类型出来先,成员变量包括一个指针变量、记录该顺序表存了多少个元素的变量以及这个顺序表总容量的变量。(定义指针变量是为了进行动态内存开辟,然后我们可以通过顺序表目前所存储的元素个数和顺序表目前的总容量进行对比判断是否需要增容)同时我们也可以将结构体类型重命名一下,使我们更加地方便进行写代码操作。
对于指针的类型,我们最好采用typedef将某个类型重命名,使用这个重命名来作为指针的类型。(这样的好处是我们根据储存类型的不同,方便修改指针的类型)
同时定义好相应的函数接口。(初始化、销毁以及增删查改等)
注意:表面上我们把size变量理解为记录顺序表存储的元素个数,深层次理解实际上size变量则是代表顺序表(数组)下标当前所在的位置
SeqList.c
初始化、打印、销毁等功能的实现
初始化我们就将该结构体类型变量中的指针变量置空,存储的元素个数和存储的总容量都置0。
打印顺序表的内容就是遍历一遍数组输出即可,简单粗暴!(注意数组的下标是从0开始的)
销毁顺序表是为了防止内存泄露等相关问题的出现,一般我们不使用动态内存开辟的这块空间以后,最好使用free函数将其销毁,并将其置空!
判断是否需要增容功能的实现
需要清楚的是,不管是接下来要实现的尾插、头插以及任意位置的插入操作功能的实现,我们都需要判断是否需要增容。所以,为了方便代码复用,我们将这部分代码封装成一个函数。
当我们的数组已存储的元素个数和我们数组的总容量相等时,这就意味着我们需要增容了。我们先定义一个新的总容量变量根据原先总容量来进行赋值(采用一个三目运算符来进行判断赋值,若原先总容量为0,我们就令它为4,不然就在原来的基础上扩2倍),这个增容的方式你可以根据你个人的爱好来增就行,我只是拿一个相对普遍的增容方式而已,就像一个人吃饭通常吃2碗就可以了,你吃5碗当然也是没有问题的!接着我们定义一个新的指针变量用来增容(我使用的是realloc函数来进行增容的),增容完成后我们需要判断一下返回给我们指针变量的值是否为空,如果为空代表增容失败,这是一个比较严重的错误,所以我们直接用exit函数退出程序;不为空的话就代表增容成功,那么我们就让我们原先的指针也指向这个新指针的位置,原先的总容量变为新的总容量。(很多xdm可能会很疑惑,为什么不在原有的指针上直接进行增容,而还要借助一个新的指针来间接进行增容,因为如果原指针动态内存开辟失败,那么原先的指针也将找不到了!)
尾插功能的实现
首先需要调用判断是否需要增容的函数,防止存储空间未被开辟或者存储空间不足。(后续其他方式插入功能的实现也一样的道理)
尾插比较简单,直接在顺序表末尾的位置进行插入就行。
找到顺序表末尾的位置也就是记录顺序表存储多少个元素的变量所对应的数组下标,完成以后我们的记录顺序表存储多少个元素的变量记得要+1!(记录顺序表存储多少个元素的变量跟我们数组的下标是同步的,都是从0开始,+1代表移动到下一个位置,但是还没有存储元素)
尾删功能的实现
首先需要判断一下我们的顺序表是否有存储元素,如果一个元素都没有存储,怎么进行删除操作呢?我们可以用粗暴的方式(直接断言报错)或者温柔的方式(不满足条件时我们什么都不做),就比如你的娃犯错了你是直接打,还是说两句就不管了一样。当我们是存储了元素以后我们才能进行接下来的删除元素操作。(后续其他方式删除功能的实现一样的道理。)
因为这是尾部删除,简单粗暴,直接让记录顺序表的存储元素个数的变量-1即可完成尾删!
头插功能的实现
第一步还是先要调用判断是否需要增容函数。
根据顺序表的概念我们能明确的知道顺序表是连续存储的,如果目前已经存储了数据,那么我们就要将原有的数据全部往后挪动一个位置,腾出第一个位置给我们进行头插操作!
我们先定义一个变量是目前顺序表存储多少个元素的尾部下标,有了尾部下标的位置,我们紧接着就可以通过一个while循环将我们的顺序表元素依次往后迭代,循环结束以后,我们就将要插入的数据直接插入头部即可,完成以后记录我们存储多少个元素的变量记得要+1!
头删功能的实现
跟尾删功能一样,我们还是需要先做一个简单判断是否能进行接下来的删除操作!
当条件满足时才开始我们接下来的头部删除操作,先定义一个变量在顺序表首元素的位置,即0。通过一个while循环将顺序表中的元素依次往前迭代覆盖,循环结束(即删除完成),我们就将记录顺序表存储元素的个数-1即可!
查找功能的实现
简单粗暴,遍历一遍顺序表,然后依次跟要查找的数据进行对比,找到就返回对应的下标,最后数组都遍历完成后还找不到,就返回-1(代表找不到)即可!
任意位置插入功能的实现
第一步还是需要先判断是否需要增容。
同时我们也需要判断一下任意位置的插入位置是否合理,如果插入到-1的位置或者跳跃间隔插入,这样都是不合理的方式插入,是无法进行我们的插入操作的。
任意位置的插入操作跟头插道理一样,由于顺序表是连续存储的,所以我们需要将部分数据一起往后挪动,为要插入的位置腾出空间来存储。
先定义一个变量是顺序表最后一个元素下标的位置,然后引用一个while循环,将我们目标位置开始的所有元素依次往后迭代挪动,直至达到我们目标插入位置时才停止(包括移动我们目标插入位置的数据),此时再将要插入的数据插入到目标位置即可,完成以后我们记录顺序表存储元素的个数记得还是要+1。
任意位置删除功能的实现
首先我们得需要判断一下我们的删除是否合理(跟上述操作同理,没有元素怎么删?删除的位置不合理怎么删?)
任意位置的删除跟头删一个道理。
定义一个变量是在目标删除位置,通过一个while循环将目标删除位置后面的数据依次往前迭代覆盖(包含目标删除位置),直到我们最后一个元素也完成向前迭代。完成以后记得将我们记录顺序表存储元素的个数-1!
完善升级顺序表
这就是我们最基本的动态顺序表,我们还能将其更加完善一下,比如根据我们任意位置的插入和删除函数,将我们的尾插、尾删、头插以及头删等函数优化下,使代码更好的完成复用!操作非常简单粗暴,只需要分别传对应的顺序表尾部和头部位置给任意位置的插入和删除函数就能实现!
test.c
这个文件主要是用来测试的,建议大家编写边测试,不要写完在测试,以免出现调试了半天都出不来结果,还不知道错哪里,如果是边写边测试我们就能及时知道错误的代码片段在哪个功能实现的位置,不要贪快,“心急吃不了热豆腐!”。
大家可以像我这样写测试用的专门函数,以此为例。
以上便是完整的动态顺序表的讲解!
静态顺序表的实现
跟动态顺序表一样的操作,只不过是不能动态内存开辟而已。(我只是写了简单的定义而已,增删查改的实现参考动态顺序表)
上述便是本篇博客的主要内容
小贴士:
在我们学习学习数据结构的过程中不仅仅是要懂得思想,而是还要懂得将思想表达出来(画图+写代码),这才是最重要的,不然最终只能是纸上谈兵!
下篇链接:数据结构初阶:单链表的实现
分分钟带你拿捏初阶数据结构的单链表
备注:
楼主不才,不喜勿喷,若有错误或需要改进的地方,非常感谢你的指出,我会积极学习采纳。谢谢家人们一直以来的支持和鼓励,我会继续努力再接再励创作出更多优质的文章来回报家人们的。编程爱好的xdm,若有编程学习方面的问题可以私信我一同探讨(我尽力帮),毕竟“众人拾柴火焰高”,大家一起交流学习,共同进步!
2022年2月18日