问题描述
简而言之,问题是,我想删除 DOUBLY LINKED LIST 的最后一个元素,我写了这个函数...
fun deleteLast(){
if(isEmpty()){println("list is empty")}
if (head!=null && head?.nextNode == null){
head = null
}else {
tail = tail?.prevIoUsNode
tail?.nextNode = null
}
}
我没有遍历它就走到了尾部,并将它的前一个作为新尾部和新尾部下一个(旧尾部)为空。我的结果是可取的。 写完这篇后,我 ping 谷歌检查这是否正确,我发现了这个......
void pop_back() {
if(this.head != null) {
//1. if head in not null and next of head
// is null,release the head
if(this.head.next == null) {
this.head = null;
} else {
//2. Else,traverse to the second last
// element of the list
Node temp = new Node();
temp = this.head;
while(temp.next.next != null)
temp = temp.next;
//3. Change the next of the second
// last node to null and delete the
// last node
Node lastNode = temp.next;
temp.next = null;
lastNode = null;
}
}
}
我想问一下我的代码是否正确,我只希望各位高手审核一下。提前致谢!
解决方法
问题只包含一个片段,而不是一个 minimal,reproducible example,所以为了论证(基于现有代码和评论),我将假设完整代码如下所示:>
class Node<T>(var previousNode: Node<T>?,var nextNode: Node<T>?,var value: T)
class DoublyLinkedList<T>(var head: Node<T>?,var tail: Node<T>?) {
fun isEmpty() = head == null
// …other methods…
fun deleteLast(){
if(isEmpty()){println("list is empty")}
if (head!=null && head?.nextNode == null){
head = null
}else {
tail = tail?.previousNode
tail?.nextNode = null
}
}
}
鉴于此,看起来您的代码是正确的。有几点可以解决*,例如:
- 在第二个
if
中,为了安全,我认为它也需要将tail
设置为 null。 - 在第二个和第三个分支中,您可能希望将要删除的节点中的
previousNode
和nextNode
引用设置为空,以方便任何其他仍然具有引用的代码到它。 (这也可能给垃圾收集器一个额外的提示,尽管这不是必需的。) - 与调用
isEmpty()
相比,检查head
是否为 null 会更简单、更一致。 - 我会在第二个
else
之前添加一个if
。实际上,该代码适用于空列表,但这可能只是运气;如果其他分支都不能在空列表上调用,它会更加健壮。 (在某些情况下,这可能让编译器将head
智能转换为不可为空;但这里不会发生这种情况,因为它是可变的。) - 在生产代码中,您不会像那样打印到标准输出。如果允许在空列表上调用
deleteLast()
,则无需打印任何内容;如果不是(这会更常见),你会抛出一个异常。 (并且该方法会有一个文档注释来解释这一点。) - 格式可以改进。
(*根据我的经验,任何代码都可以改进——即使是你以前处理过很多次的代码!)
但是,您不能直接将您的代码与来自 Google 的代码进行比较,因为它看起来像是用于单向链接的列表。
单向链表是一种简单得多的结构。它所拥有的只是对 head
节点的引用;具有对 next
节点的引用(并且大概是对存储在节点中的数据的引用);等等。因此,到达列表末尾的唯一方法是遍历所有节点,就像 Google 源代码所做的那样。 (这也意味着通常没有必要使用单独的类来表示整个列表;您只需要对第一个节点的引用即可。 许多操作可以用递归方法简洁地编写。 您也可以使其不可变,即有很多优点;这是某些语言中的主要数据结构。)
然而,您的列表直接引用了最后一个节点,这当然避免了遍历整个列表来查找它的需要。正如您的代码所示,这意味着某些操作在双链表中效率更高。但也有相应的缺点:每个节点占用更多内存;更改列表时有更多引用要更新;列表更容易进入不一致状态;它不太适合不可变列表。)