教你学会数据机构中的顺序表和单链表

前言:

数据的存储结构称为数据结构,数据结构又分为线性表和非线性表。线性表包含顺序表和链表,下面分别介绍顺序表和链表。

1. 线性表

线性表是具有一类相同特性的数据结构的集合,包含顺序表和链表等。

线性表特点:

在逻辑结构上都是线性的,在物理结构上不一定是线性的。

2. 顺序表

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。

但是顺序表和数组是不一样的,顺序表是在数组的基础上进行各种封装,如增删改查等。

为了更好的理解,我们可以通过下面的例子来理解。

普通的菜品可以表示数组,但通过加工后便成为了米其林餐厅的菜,即顺序表。

2.1 顺序表的分类

顺序表按照内存的固定与否分为静态顺序表和动态顺序表。

静态顺序表在创建时便固定了最大的内存大小,不可更改。

动态顺序表在创建时没固定内存大小,可以修改内存大小。

静态顺序表的缺点:

内存给大了浪费,给小了不够用。

为了减少静态顺序表的缺点,有了动态顺序表。

3. 动态顺序表实现的基本需要

我们知道,顺序表的基础是数组,因此需要一个数组参数即*arr,数组中存储的数据多少即size,数组的最大容量即capacity。知道了这些参数,我们就可以来实现顺序表了

3.1 文件需要

我们需要Seqlist.h头文件来声明函数以及一个Seqlist.c文件来写函数以及一个test.c文件来测试每个函数。

注意:每次写完一个函数都需要测试,不然后面再测试报很多错就很头大。

3.2 结构体的定义

要存储上面三个参数,我们需要用到结构体。

因为顺序表需要增删改查,所以我们需要将int类型重新定义为SLdatatype以便后面统一更改。避免更改的时候需要更改很多,造成时间资源的浪费。

为了避免每次用结构的时候都需要使用struct SeqList ,很难打字,我们对这个结构体也进行重命名。

3.3 初始化结构体

传入结构体的地址进行初始化。将容量和有效数量变为0,数组为空数组。

3.4  销毁结构体

传入地址进行销毁。当数组不为空时,释放掉数组的空间,然后将其置为空。将size

和capacit置为0;

注意:要养成内存free后置为null的好习惯。

3.5 增容顺序表

当插入顺序表的时候,如果顺序表满了,我们就需要考虑到增容。因此需要将增容顺序表单独封装成一个函数,方便调用。

判断是否需要增容就用size和capacity进行比较,如果相等则进行增容。在增容时还需要思考是否为0,若为0,则设定一个基本的值。否则一般按照原容量的二倍来进行增容。用一个tmp表示新的地址,用realloc增容。申请内存过后,需要判断是否申请成功,不成功就退出程序,成功就将地址赋值给arr,并将capacity更新。

3.6 打印顺序表

为了检查各种函数,往往就需要将顺序表打印出来判断是否结果符合预期。

直接用for循环打印数组的所有值。

4. 动态顺序表实现增删改查

4.1 插入

插入分为头插和尾插,还有任意位置插入。

4.1.1 头插

头插需要数组不为空,我们通过assert判断指针是否为空。然后再检查是否容量充足。将第二个及后面的数据往后移一位。再将数组的第一个数据赋值给arr[0]。最后将size++。

对于这个for循环,我们需要将最后面的数据先移动,避免造成覆盖的现象。

4.1.2 尾插

尾插相对于头插要简单一些。只需要判断指针,检查容量。最后再将数组的最后一位变为x即可。

这里为了减少代码量,将最后的size++融合到了赋值式中。

4.1.3 任意位置插入

还是需要检查容量和进行判断指针。不同的是,将pos位置及后面的数据都向后移动,还是采用先移动最后面数据的方法。最后进行插入,插入后size++。

4.2 删除

删除分为头删,尾删和任意位置删除。

4.2.1 头删

删除就不需要再判断容量,但是需要检查指针。头删需要将下标为1及以后的数据向前移动一个位置。这里需要采用先移动前面的方法,不然会造成覆盖。移动完之后将size--。

4.2.2 尾删

尾删只需要判断指针然后再将size--。打印出来的时候就不会出现尾部的数据。

4.2.3 任意位置删除

指定位置删除需要用后面的值覆盖指定位置,然后将后面的值依次往前移一位,实现覆盖。

采用先移动前面的方法。

5.链表

顺序表虽然解决了数组的部分问题,但是由于其2倍的增容,还是可能会带来空间的问题,因此链表诞生了。

链表由一个一个结点组成,就跟小火车的每节车厢一样。火车的车厢可增可减,同一个车厢后面可以跟着不同的车厢。

一般的链表由两部分组成,分别是存储的值和指向下一个结点的指针。为了存储这两个数据,还是需要用到结构体。

因此,介绍一下实现单链表的基本需要

5.1 文件需要

还是跟顺序表一样需要三个文件。但是由于是单链表(single list),命名不一样,分别是SList.h,Slist.c,test.c。

5.2 结构体的定义

结构体由值和结构体指针构成。还是将int 重命名为SLDatatype以便后期更改.

5.3 结构体创建

我们手动创建多个结点,然后将其赋值,然后将各个结点的next指针指向下一个结点,最后一个结点指向NULL。返回node1指针作为头指针。

5.4 打印结构体

将头指针传过来之后,为了打印结构体,我们必须要遍历结构体。但是为了能找到原始的头指针,我们定义一个新的结构体指针pcur用来代替phead遍历,当pcur指针不为空时,都将他的值打印出来,当其为空时再打印一个NULL,表示最后指向空指针。

这便是一般链表的形式。

5.5 销毁链表

链表的销毁和顺序表的销毁不一样,需要将每一个结点都释放掉。设置指针pcur遍历链表,将其逐个释放。最后将头指针设置为NULL。

(这里为什么要用二级指针后面会讲解。)

5.6 结点申请

不同于顺序表,链表实现增加功能需要申请新的结点。

申请结点的参数只需要一个x。运用malloc申请内存,用(Node*)强制转换后赋值给new,再判断结点申请是否成功,不成功则退出。成功则对结点进行初始化。最后返回结点。

6. 链表的增删改查

6.1 链表的插入

链表的插入分为链表的头插,尾插,指定位置前面插入,指定位置后面插入。

6.1.1  头插

注意:当我们可能需要改变头指针的时候,我们就需要将参数设置为二级指针。因为一级指针的调用只是对于链表的传值调用,指向指针的指针才是传址调用,才能改变头指针。这里需要改变头结点,所以使用二级指针。

判断pplist是否为空,申请新节点。将新结点指向之前的头结点,然后将新结点作为新的头结点。

这是实参和形参的对比图。

6.1.2 尾插

这里传的是一级指针,因为不可能改变头结点。申请一个新结点。然后需要先判断头指针是否是空指针,如果是空指针,则直接把新结点当作头结点。如果不是,则遍历链表,找到尾结点,将尾结点指向新结点,再将新结点作为尾结点。

6.1.3 在指定位置前面插入

先判断pos和pplist是否为空。如果prev 是pos的话,则相当于头插。如果prev不是pos,则用prev遍历,直到prev的next指向pos。先将新结点的next指针指向pos,然后将prev的nex指针指向新结点。(不能先将prev的nex指针指向新结点,再将新结点的next指针指向pos,这是无效的。)

这里传入二级指针,因为可能导致头结点的改变。

6.1.4 在指定位置后插入

还是先检查pos是否为空。位置后插入只需要将申请的新节点的next指针指向pos的next结点,再将pos结点的next指针指向新结点。

这里引用一级指针,因为指定位置后插入不可能改变头结点。

6.2 链表的删除

链表的删除分为头删和尾删,以及删除指定位置前和删除指定位置后,删除指定位置。

6.2.1 尾删

删除需要判断链表是否为空,因为空链表不能删除。尾删需要一个pcur遍历链表找到尾结点以及一个prev指向尾结点的前一个结点。需要先判断头结点是不是尾结点,如果是,则直接将头结点变为空,如果不是则寻找尾结点。找到之后将prev的next指针指向空,然后free之前的尾指针,再将其置为空。

这里用二级指针,因为可能改变头结点。

6.2.2 头删

先判断链表是否为空。再将pcur指向第二个结点,再将pcur作为新的头结点。

这里用二级指针,因为会改变头结点。

6.2.3 删除指定位置

删除指定位置需要知道pos前面的和后面的结点。

先判断pcur是否为pos位置,如果是则直接将第二个结点作为头结点。否则先遍历找到pos结点,然后将pcur的next指向pos的next指针。最后free掉pos并将其置为NULL。

这里也用二级指针,可能改变头结点。

6.2.4 删除指定位置前面的结点

需要判断位置和链表是不是为空。再判断pos位置和pcur结点的关系,如果pcur == pos结点,则前面没有位置,不能删除。如果pos == pcur->next则是头删。如果都不是,则就是正常的删除,找到pos和指向pos的结点pcur,将prev的next指向pos,然后free掉pcur并将其置为空。

这里传二级指针,因为可能改变头结点。

6.2.5 删除指定位置后的结点

判断pos和pos的下一个结点是否为空。直接将pos的next指向pcur的next指针,即指向pos往后的第二个结点。然后free掉pcur并将其置为空。

6.3 链表的查找

先判断链表是否为空。再用pcur从头遍历,找到值为x的结点或找到尾结点。如果找到尾结点则返回NULL,表示没找到。如果找到值为x的结点,则返回pcur。

7. 源码

作者对顺序表和链表的实现及理解都在这啦,希望对大家有帮助。

下面是源码链接:work链表以及实现链表的更新 · 2746782 · 重邮阿江/c_study_experience - Gitee.com

链表的实现以及算法题 · c66b22f · 重邮阿江/c_study_experience - Gitee.com

数据结构顺序表的实现 · a033c49 · 重邮阿江/c_study_experience - Gitee.com

  • 15
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值