一、前言
在前面两章我们讲解了动态数组、栈和队列的讲解,这些底层都是依托静态数组,靠resize解决固定容量问题的,之前虽然用户看到的是动态数组,但是依然使用的是静态数组,他是依靠resize这个方法解决 固定容量问题 ,但是我们今天要讲解的链表不一样,链表是我们数据结构学习的一个重点,也有可能是一个难点,为什么链表这么重要呢?因为他是最简单的也是真正的动态数据结构。
二、为什么链表很重要
链表是一个真正的动态数据结构
最简单的动态数据结构
更深入的理解引用(或者指针)
更深入的理解递归
辅助组成其他数据结构
更深入的理解引用(或者指针):和内存相关,虽然在 java 中大家不用手动的管理内存,但是对链表这种数据结构,更加深入的理解,可以帮助大家对引用、指针、甚至计算机系统中和内存管理相关的很多话题,有更加深入的认识。
更深入的理解递归:链表本来也是有他非常清晰的递归结构的,、由于链表这种数据结构是 数据结构,我们可以更加深入理解递归,对于递归这种深入理解是不可获取的。
链表本身也是具有功能性:辅助组成其他数据结构(hashMap 、栈和队列)
三、什么是链表
链表是一种数据结构,在内存中通过节点记录内存地址而相互链接形成一条链的储存方式。相比数组而言,链表在内存中不需要连续的区域,只需要每一个节点都能够 记录下一个节点 的 内存地址,通过引用进行查找,这样的特点也就造就了链表增删操作时间消耗很小,而查找遍历时间消耗很大的特点。
我们日常在Java中使用的LinkedList即为双向链表。而在链表是由其基本组成单元节点(Node)来实现的。我们在日常中见到的链表大部分都是单链表和双链表
单元节点 (Node):
e就是链表元素
next指的是当前节点的下一个节点
对于链表来说它就像我们的火车一样,每一个节点其实就是一节车厢,我们在车厢中存储真正的数据,而车厢和车厢之间还要进行连接,让我们数据是整合在一起的,用户可以方便的在所有的数据上进行查询或其他操作,那么数据和数据连接就是由这个next来完成的
当然链表不能无穷无尽,如果一个节点的next是Null了,就说明这个节点是最后一个节点了,这就是链表
如下图所示(单链表):链表的优点:真正的动态,不需要处理固定容量的问题链表的缺点:丧失了随机访问的能力
在数组中:每一个索引,直接从数组中拿出索引对应的元素,这是因为从底层机制上,数组所开辟的空间,在内存里是连续分布的,所以我们可以直接可以去找这个数组的偏移,直接计算出这个数据所存储的内存地址,可以直接使用。
链表:而链表是靠Next一层一层连接的,需要借助这个Next一点一点的去找我们需要取出来的元素。
四、创建我们自己的链表
4.1 链表基本结构
内部类Node:设计私有的内部类,对于用户来说不需要知道链表底层实现,不需要知道node这个节点,对用户屏蔽编码实现的底层实现e:元素next:指向Node的一个引用
4.2 添加元素
之前我们讲的是如何在数组中添加元素,我们在数组尾添加元素是非常方便的,因为对于数组来说是顺序排放的,有意思的是对于链表来说,恰恰相反,在链表头添加元素是非常方便的,其实这样非常好理解,对于数组来说我们有size这个变量,它直接指向了数组中最后一个元素下一个位置,也就是下一个待添加元素的位置,所以直接添加就非常容易,因为有size这个变量,在跟踪数组的尾巴,而对于链表来说我们设立了链表的一个头head,而没有变量来跟踪链表的尾巴,所以我们在链表头添加元素是非常方便的,最关键的就是node.next = head 和 head = node,如下图所示:
4.2.1 链表头添加元素
代码实现:
4.2.2 链表中间添加元素:
我们需要在索引为2的地方添加元素 ,我们只需要找到 要插入之前的节点(1),我们管它叫prev,然后把之前节点的(1) next指向 ,然后在将 的这个节点指向之前节点(1)的之后的节点(2),就完成了整个插入了,其中关键代码就是 ,其中关键:我们要找到添加节点的前一个节点。
代码实现:
4.2.3 添加操作时间复杂度4.3 为链表设计虚拟头结点
上面我们介绍了链表的添加操作,那么我们在添加的时候遇到了一个问题,就是在链表任意一个地方的时候,添加一个元素,在链表头添加一个元素,和在链表其他地方添加元素,逻辑上会有差别,为什么在链表头添加元素会比较特殊呢,因为我们在链表添加元素的过程,要找到待添加的之前的一个节点,但是由于对于链表头没有之前的一个节点,不过我们可以自己创建一个头结点,这个头节点就是虚拟头结点,这个节点对于用户来说是不存在, 用户也不会感知到这个节点的存在,我们是屏蔽了这个节点的存在,如下图所示:
代码实现:
4.4 链表元素 get、set、是否存在操作
4.5.1 修改和查找操作时间复杂度
4.5 删除链表元素
加入我们想要删除索引为(2)位置的元素,我们需要找到待删除节点之前的一个位置,也就是(1),我们用prev表示,找到这个节点之后,那么(2)就是我们需要删除的索引了 我们叫delNode,如下图所示:
代码实现:
4.5.1 删除操作时间复杂度4.6 完整代码
4.2.7 结果测试:
打印结果:
四、链表时间复杂度分析
对于增加和删除来说,如果是对链表头进行操作,那么就是O(1)级别的复杂度,对于查询来说,也是一样
五、链表应用
5.1 使用栈实现链表5.1.1 接口类:
5.1.2 实现类:
5.1.3 运行结果:
5.1.4 结果打印:
5.2 使用链表实现队列
5.2.1 接口类
5.2.2 实现类
5.2.2 测试类
打印结果:
六、更多链表结构
6.1 双链表
代码:
6.1 循环列表
代码:
在java中,LinkedList底层使用的就是循环链表,也就是循环双向链表,到这里我们链表就讲解完成了,在阅读中大家如果觉得有改进或者疑问的地方,欢迎大家在下面进行留言,博主看到了会第一时间回复大家。
我是牧小农,怕什么真理无穷,进一步有进一步的欢喜,大家加油!
-- End --
———————
1.原创不易,你的「在看」是我创作的动力。
2.欢迎关注公众号 牧小码农,「带你一起学Java」!
3.疫情期间,勤洗手,戴口罩,做好个人防护。
“在看转发”是最大的支持
领取专属 10元无门槛券
私享最新 技术干货