
6.单向链表实现的正确方式
文中涉及的代码可访问 GitHub:https://github.com/UniqueDong/algorithms.git
上一篇《链表导论心法》讲解了链表的理论知识以及链表操作的实现原理。「talk is cheap, show me the code !」 今天让一起把代码撸一遍,在写代码之前一定要根据上一篇的原理多画图才能写得好代码。举例画图,辅助思考。
比如插入节点,在已知节点 b 的前面插入 x
废话少说,撸起袖子干。
接口定义
首先我们定义链表的基本接口,为了显示出 B 格,我们模仿我们 Java 中的 List 接口定义。
抽象链表模板
看起来是不是很像骚包,接着我们再抽象一个抽象类,后续我们还会继续写双向链表,循环链表。双向循环链表。把他们的共性放在抽象类中,将不同点延迟到子类实现。
Node 节点
单向链表的每个节点有两个字段,一个用于保存当前节点的数据 item,另外一个则是 指向下一个节点的 next 指针。所以我们在单向链表定义一个静态内部类表示 Node 节点。
构造方法分别将数据构建成 Node 节点,默认 next 指针是 null。
单项链表,size 属性保存当前节点数量,Node<E> head指向 第一个节点的指针,并且存在
是真事件。以及 last指针指向最后的节点。最后我们还要重写 toString 方法,便于测试。
代码如下所示:
添加元素
添加元素可能存在三种情况:
1.头结点添加。
2.中间任意节点添加。
3.尾节点添加。
在尾节点添加
将数据构造成 newNode 节点,将原先的 last 节点 next 指向 newNode 节点,如果 last 节点是 null 则将 head 指向 newNode ,同时 size + 1。
在头结点添加
将数据构造成 newNode 节点,同时 newNode.next 指向原先 head节点,并将 head 指向 newNode 节点,如果 head == null 标识当前链表还没有数据,将 last 指针指向 newNOde 节点。
在指定位置添加
分两种情况,当 index = size 的时候意味着在最后节点添加,否则需要找到当前链表指定位置的节点元素,并在该元素前面插入新的节点数据,重新组织两者节点的 next指针。
linkLast 请看前面,这里主要说明 linkBefore。
首先我们先查询出 index 位置的 Node 节点,并将 newNode.next 指向 该节点。同时还需要找到index 位置的上一个节点,将 pred.next = newNode。这样就完成了节点的插入,我们只要画图辅助思考就很好理解了。
判断是否存在
indexOf(Object o) 用于查找元素所在位置,若不存在则返回 -1。
查找方法
根据 index 查找该位置的节点数据,其实就是遍历该链表。所以这里的时间复杂度是 O(n)。
删除节点
删除有两种情况,分别是删除指定位置的节点和根据数据找到对应的节点删除。
先来看第一种根据 index 删除节点:
先检验 index 是否合法,然后根据 index 找到待删除 node。这里的删除比较复杂,老铁们记得多画图来辅助理解,防止出现指针指向错误造成意想不到的结果。
最难理解的就是后面这段代码了,但是只要多画图,多思考就好多了。
1.x 节点就是当前要删除的节点数据,我们先把该节点保存的 item 以及 next 指针保存到临时变量。第 10 ~13 这块 while 循环主要是用于找出被删除节点的 引用 cur 以及上一个节点的引用 prev。
2.在找到被删除的 cur 指针以及 cur 上一个节点指针 prev 后我们做删除操作,这里有三种情况:当链表只有一个节点的时候,当一个以上节点情况下分为删除头结点、尾节点、其他节点。
单元测试
我们使用 junit做单元测试,引入maven 依赖。
创建测试用例
到这里单向链表的代码就写完了,我们一定要多写才能掌握指针打断的正确操作,尤其是在删除操作最复杂。
课后思考
Java 中的 LinkedList 是什么链表结构呢?
如何使用 java 中的LinkedList 实现一个 LRU 缓存淘汰算法呢?
文章转载自公众号:码哥字节
