SLList 已经高效的实现了链表,但此刻存在一个问题:我们对链表进行尾部插入的时候,效率极低,需要遍历整个链表。有没有能像头插法一样,时间复杂度为 O(1) 的操作呢?
在简短思考后似乎能得出方案 1:为 SLList 提供一个尾指针即可,此时插入操作则为 O(1)。
但存在问题:对链表的基本操作包含 add(), get(), size(), remove(),在 removeLast() 的时候,由于需要移除倒数第二个 node 上的 rest pointer,仍然需要做 O(n) 的遍历,仍需要进一步优化。
故而提出,可以为每一个 node 提供前向指针。node 由 previous pointer, value, next pointer 组成,此时即可 O(1) 完成所有基础操作。
但此操作有一个轻微的问题,和 SLList 中引入 sentinel node 的原因一致,如此丑陋的实现会带来一些 special cases。比如:
在空链表 SLList lst = new SLList()
时,sentinel node 指向 null,但 last 应当指向 null 表示没有 real node 存在还是指向 sentinel node?是否引入了大量特殊的 if?如此的实现不够简洁。
故而,考虑以下两种方案:
-
为 DLList 的封装再提供一个 last sentinel。当 DLList 为 empty 时,DLList 的 front sentinel pointer & back sentinel pointer 分别指向 前后两个 sentinel node,此时两个 sentinel node 的 previous pointer & next pointer 就可以互指了。如图所示:
两个 sentinel node 互相指向,核心目的是为了统一和简化所有插入和删除操作的逻辑,消除 special cases。
sentF
和sentB
这两个 sentinel node 创建了一个永不为空的框架,所有 real node 都被插入到这个框架中间,无论是对空还是对有值插入,都可以用 node.next & node.next.front 给新的插入 node 设置好 previous pointer & next pointer,这是简化的最大意义。 -
DLList 的 sentF 和 sentB 指向同一个 sentinel node 来完成循环,但我没看懂 🥺