数据结构之链表

本篇是数据结构与算法之美学习笔记

链表和数组一样,都是编程语言中经常用到的数据结构。

相比数组,链表是一种稍微复杂一点的数据结构,我们可以两者对比着来看

数组需要一块连续的内存空间来存储数据,对内存的要求比较高,如果我们申请一个100MB大小的数组,如果内存中没有一个连续的100MB的空间,即使内存中剩余的空间大小大于100MB,也会申请失败

链表就不一样了,它不需要一块连续的内存空间,它是通过‘指针’把一组零散的内存块串联起来所以如果剩余内存如果大于100MB,即使不是连续的也没问题。

链表有很多种,比较常见的是:单链表、双向链表、循环链表。

单链表:
在这里插入图片描述
链表是通过指针把一组零散的内存块串联起来,我们把内存块称为链表的‘结点’为了把所有的结点串起来,每个链表的结点除了存储数据之外,还需要记录链上的下一个结点的地址。我们把这个记录下一个结点的地址的指针叫做‘后继指针 next’

从上面的图中可以看到,有两个结点是特殊的,那就是第一个结点和最后一个结点,即头结点尾结点,头结点用来记录地阿里表的基地址,有了它我们就可以遍历得到整条链表,而为节点最后指向的不是下一个结点,而是一个空的地址null

和数组一样,链表也支持数据的查找、删除、插入操作。不过执行的效率他们之间是不同的,对于插入和删除操作,链表的效率要高,对于根据下标查询操作,数组的效率就高了。

从上一篇数组我们知道,数组在插入和删除数据的时候,为了保证内存数据的连续性,需要做大量的数据搬移,而在链表中插入或者删除一个数据,我们并不需要保证内存的连续性,也就不用进行大量的数据搬移,因为链表的存储空间本来就不是连续的。我们只需要考虑相邻的结点的指针改变就好了,所以删除和插入操作在链表中是非常快速的。

但是链表想要随机访问第k个元素,就没有数组那么高效了,因为链表中的数据并不是连续的,所以不能像数组那样,根据首地址和下标,通过寻址公式就能直接计算出对应的内存地址,而是需要根据指针一个结点一个结点的依次遍历,直到找到相应的结点。

我们可以把链表想象成一个队伍,每个人都只直到自己的后面是谁,所以,当我们想要直到排在第N个位置的人是谁的时候,只能从第一个开始一个一个往下数。

循环链表:
在这里插入图片描述
循环链表是一种特殊的单链表,实际上,循环链表也很简单,它跟单链表唯一的区别就是它的尾结点,单链表中是指向一个空地址,表示这是最后一个结点了,而循环链表的尾结点指向链表的头结点。

和单链表相比,它的优点就是从链尾到链头比较方便,当要处理的数据具有环形结构特点的时候,适合采用循环链表,比如约瑟夫问题

双向链表:
在这里插入图片描述
跟单链表只有一个方向不同,双向链表支持两个方向,每个节点上除了有一个‘后继指针next’指向后面的结点外,还有一个‘前驱指针prev’指向前面的结点。

双向链表需要额外的空间来来存储后继结点和前驱结点的地址。所以如果存储同样多的数据,双向链表要不单链表占用更多的内存空间。虽然两个指针比较浪费空间,但是可以支持双向遍历,这样也带来了操作上的灵活性。

从表结构上看,双向链表既知道前驱结点也知道后继结点,插入和删除结点等操作在某些情况下比单链表更加简单高效。

那吗些地方会更高效呢

比如删除操作,一般我们删除一个数据无外乎两种情况:
一个是删除节点中值等于某个给定值的结点,
一个是删除给定指针指向的结点

第一种情况,不管是单向还是双向链表,其实都是一样的,都得从头结点开始一个一个的遍历比对,直到找到相对应的值,然后杉树。

第二种情况就不一样了,我们已经找到了要删除的结点,但是删除某个结点,我们需要直到其前驱结点,让其前驱结点的next指向其后继结点的prve,但是单链表不能直接获取其前驱结点,为了找到其前驱结点,还得从头开始遍历链表。但是双向链表就不同了,双向链表中的结点已经保存了其前驱接单的指针,不需要再遍历查找了。所以这种情况下双向链表效率比单向的高。

同理当我们想要插入一个元素的时候,跟上面的删除操作一样,第二种情况下,双向链表更有优势。

对于一个有序的链表来说,双向链表的按值查询的效率也比单链表的要高,因为我们可以记录上次查找的位置p,下次查询的时候,根据查找的值于p的值比较来决定往前查找还是往后查找,而单链表就只能往后查找。

从上面的分析我们知道,双向链表在很多时候都比单链表效率高,这也是为什么实际的软件开发中,双向链表尽管比较费内存,但是比单链表应用更加广泛的原因。java中LinkedHashMap这个容器,其内部原理就是一个双向链表。

这里有一个用时间换空间的设计思想,当内存空间充足的时候,如果我们追求更快的代码执行速度,就可以选择空间复杂度交高时间复杂度较低的算法结构,反之,如果内存紧缺,就可以选择用时间换空间的设计思路了。

实际上缓存就是一个利用空间换时间的设计思想,如果我们把数据存储在硬盘上会比较节省内存,但是每次查找数据访问硬盘会比较慢,如果我们通过缓存技术,事先把数据加载到内存中,虽然会耗费一定的内存空间,但是每次查询数据的时间就快了。

双向循环链表:

双向链表结构如下图
在这里插入图片描述

使用链表实现一个LRU缓存淘汰算法:

维护一个有序的单链表,最早访问的放在尾部,当有一个新的数据被访问的时候,从链表的头部开始遍历链表:

(1)遍历此链表,如果此数据之前已经被缓存在链表中了,就删除它,然后把新的放在链表头部。
(2)遍历链表,如果数据没有在缓存链表中
<2.1>如果缓存未满,将此节点直接插入到链表的头部
<2.2>如果缓存满了,删除尾部结点,把新的数据插入到链表的头部。

OK这样就使用链表实现了一个LRU缓存。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值