golang数据结构——链表

链表

1、链表的概念

  链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。

  链表是线性表的一种,所谓的线性表包含顺序线性表和链表,顺序线性表是用数组实现的。
  链表和顺序表的不同之处在于:顺序表不仅要求逻辑结构上也连续,还要求物理结构上连续;而链表只要求逻辑结构上连续,物理结构上可以不连续;
  所谓的逻辑结构指的是数据在逻辑上是如何存储的,这是由人们主观想象出来的;而物理结构则是数据在物理内存中实际存储的方式,不随人们的主观意志而改变。

  线性表链式存储结构的特点是用一组任意的存储单元来存储线性表中的数据元素,通过指针来访问它的后续元素。我们把存储数据元素信息的域成为数据域,把存储后继位置的域称为指针域,这两部分构成一个结点。n个结点链接成一个链表,即为线性表的链式存储结构。因为每个结点只有一个指针域,所以又将这样的链表称为单链表。

单链表的结构图示如下:在这里插入图片描述  从上面的图中我们也可以看出:链表在逻辑结构上连续指的是链表的每一个结点都记录着下一个结点的地址,我们可以根据此地址来找到链表的下一个结点,就好像它们被一根线连起来了一样。

  而实际上链表的每一个结点都是在堆区上随机申请的,前一个结点的地址可能比后一个结点大,也可能比后一个结点小,二者之前其实并没有物理结构上的关系。

2、链表和顺序表(数组)

区别

比较项数组链表
物理结构数组是顺序的存储结构,物理位置连续,所有元素按次序依次存储链表是链式的存储结构,物理位置不连续,链表的每一个结点都记录着下一个结点的地址
内存结构数组从栈上分配内存,静态分配内存(在定义时分配),在内存中是连续的链表从堆上分配内存,动态分配内存(在程序执行时动态向系统申请),在内存中是不连续的,自由度大,但是要注意内存泄漏
查找操作数组利用下标定位,查找的时间复杂度是O(1)链表通过遍历定位元素,查找的时间复杂度是O(N)
插入删除操作数组插入和删除需要移动其他元素,时间复杂度是O(N)链表的插入或删除不需要移动其他元素,时间复杂度是O(1)
越界问题数组大小固定,所以存在越界的风险只要可以申请到空间,链表就没有越界风险

各自优缺点

比较项数组链表
优点1、随机访问性比较强,可以通过下标进行快速定位。2、查找速度快。1、插入和删除的效率高,只需要改变指针的指向就可以进行插入和删除。2、内存利用率高,不会浪费内存,可以使用内存中细小的不连续的空间,只有在需要的时候才去创建空间。3、大小不固定,扩展很灵活。
缺点1、插入和删除的效率低,需要移动其他元素。2、会造成内存的浪费,因为内存是连续的,所以在申请数组的时候就必须规定内存的大小,如果不合适,就会造成内存的浪费。3、内存空间要求高,创建一个数组,必须要有足够的连续内存空间。4、数组的大小是固定的,在创建数组的时候就已经规定好,不能动态拓展。1、查找的效率低,不能随机查找,必须从第一个结点向后遍历查找。

使用场景

比较项数组使用场景链表使用场景
空间数组的存储空间是栈上分配的,存储密度大,当要求存储的大小变化不大时,且可以事先确定大小,采用数组存储数据链表的存储空间是堆上动态申请的,当要求存储的长度变化较大时,且事先无法估量数据规模,采用链表存储
时间数组访问效率高,当线性表主要是查找操作,很少插入和删除时,采用数组结构链表插入、删除效率高,当线性表要求频繁插入和删除时,采用链表结构

3、链表的分类

  在实际应用中,链表根据双向/单向、带头/不带头、循环/不循环一共可以组合出8种结构。

在这里插入图片描述

单向、双向

  • 单链表

  单链表中的指针域只能指向结点的下一个结点,不能进行回溯,适用于结点的增加和删除。链接的入口结点称为链表的头结点也就是 head。

在这里插入图片描述

  • 双链表

  双链表每一个结点有两个指针域,一个指向下一个结点,一个指向上一个结点,既可以向前查询也可以向后查询,可以快速的找到当前结点的前一个结点,适用于需要双向查找结点值的情况。

在这里插入图片描述

双链表相对于单链表的优点:

  • 删除指定结点,指针移动次数更少。

  删除单链表中的指定结点时,一定要得到指定结点的前驱结点,方法一般是在遍历链表的过程中一直保存当前结点的前驱结点,这样指针总的移动次数为2n次,如果使用双链表,就不需要去定位前驱结点,指针总的移动次数为n次。

  • 查找指定元素,效率更高

  查找时也是一样的,可以用二分法的思路,从头结点向后和尾结点向前同时进行,这样效率也可以提高一倍。

  但是为什么市场上对于单链表的使用要超过双链表呢?从存储结构来看,每一个双链表的结点都比单链表的结点多一个指针,如果长度是n,就需要n*lenght(32位是4字节,64位是8字节)的空间,这在一些追求时间效率不高的应用下就不适用了,因为他占的空间大于单链表的1/3,所以设计者就会以时间换空间。

带头、不带头

  • 头结点(哨兵结点)

  带头与不带头,头指的是头结点,头结点也被称为哨兵结点,可以用来简化边界条件,它是一个附加的链表结点,头结点作为第一个结点,它的数据域不存储任何东西,只是为了操作的方便而引入的。

  在很多时候,我们处理某个结点需要用到它的前驱结点,比如删除链表的某个结点,对于没有哨兵的单链表,当待删除的结点为链表的第一个结点,由于没有前驱结点,需要进行特殊处理,从而代码的复杂性增加。而如果有哨兵结点,对在第一个结点前插入结点和删除第一个结点,处理方式与其他结点相同,可以统一进行处理。

循环、非循环

  • 循环链表

  循环链表,顾名思义,就是链表首尾相连,循环链表可以用来解决 约瑟夫环问题。循环链表的最后一个结点的next指向链表的第一个结点,非循环链表的最后一个结点的next指向NULL。
在这里插入图片描述

常用链表

  虽然链表有这么多种结构,但是我们实际中最常用还是以下两种结构:无头单向非循环链表和双向带头循环链表。

  • 无头单向非循环链表

  无头单向非循环链表结构最简单,一般不会单独用来存数据,实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等;另外这种结构在笔试面试中出现很多;其实如果不做特殊声明,一般情况下无头单向非循环链表指的就是我们的单链表;

  • 双向带头循环链表

  双向带头循环链表的非空链表
在这里插入图片描述

  双向带头循环链表的空链表
在这里插入图片描述

  双向带头循环链表结构最复杂,一般用于单独存储数据;实际中我们使用的链表数据结构,都是带头双向循环链表;另外它虽然结构复杂,但是使用代码实现后会有很多优势,所以反而是链表中使用起来最简单的。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值