1. Overview
1.概述
Data structures are important parts of any programming language. Java provides most of them under the Collection<T> interface. Maps are also considered part of Java collections, but they don’t implement this interface.
数据结构是任何编程语言的重要组成部分。Java 在 Collection<T> 接口下提供了大部分数据结构。地图也被视为 Java 集合的一部分,但它们没有实现该接口。
In this tutorial, we’ll concentrate on a linked list data structure. In particular, we’ll discuss removing the last element in a singly-linked list.
在本教程中,我们将重点讨论链表数据结构。特别是,我们将讨论删除单链表中的最后一个元素。
2. Singly-Linked vs Doubly-Linked Lists
2.单链列表与双链列表
First, let’s define the differences between singly-linked and doubly-linked lists. Luckily, their names are quite descriptive. Each node in a doubly-linked list has a reference to the next and the previous one, except, obviously, for the head and tail:
首先,让我们定义单链表和双链表之间的 差异。幸运的是,它们的名称很好描述。双链表中的每个节点都有对下一个节点和上一个节点的引用,当然,头节点和尾节点除外:
A singly-linked list has a simpler structure and contains only the information about the next node:
单链表的结构更简单,只包含下一个节点的信息:
Based on the differences, we have a trade-off between these data structures. Singly-linked lists consume less space, as each node contains only one additional reference. At the same time, doubly-linked lists are more convenient for traversing nodes in reverse order. This might create problems not only when we iterate through the list but also for search, insert, and removal operations.
基于这些差异,我们需要在这些数据结构之间进行权衡。单链表占用的空间更少,因为每个节点只包含一个额外的引用。同时,双链路列表更便于以相反的顺序遍历节点。这不仅会在我们遍历列表时产生问题,也会在搜索、插入和删除操作时产生问题。
3. Removing the Last Element From Doubly-Linked Lists
3.从双链接列表中删除最后一个元素
Because a doubly-linked list contains information about its previous neighbor, the operation itself is trivial. We’ll take an example from Java standard LinkedList<T>. Let’s check the LinkedList.Node<E> first:
由于双链路列表包含其前一个相邻列表的信息,因此操作本身并不复杂。我们将以 Java 标准 LinkedList<T> 为例进行说明。让我们先检查 LinkedList.Node<E> :
class Node<E> {
E item;
LinkedList.Node<E> next;
LinkedList.Node<E> prev;
Node(LinkedList.Node<E> prev, E element, LinkedList.Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
It’s quite simple, but as we can see, there are two references: next and prev. They simplify our work significantly:
The entire process takes only several lines of code and is done in constant time:
这很简单,但正如我们所看到的,有两个引用:下一页和上一页它们大大简化了我们的工作:
整个过程只需几行代码,并在恒定时间内完成:
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
E element = l.item;
Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null) {
first = null;
} else {
prev.next = null;
}
size--;
modCount++;
return element;
}
4. Removing the Last Element From Singly-Linked Lists
4.从单链列表中删除最后一个元素
The main challenge for removing the last element from a singly linked list is that we have to update the node that’s second to last. However, our nodes don’t have the references that go back:
从单链表中删除最后一个元素的主要挑战在于,我们必须更新倒数第二个节点。然而,我们的节点没有回溯的引用:
public static class Node<T> {
private T element;
private Node<T> next;
public Node(T element) {
this.element = element;
}
}
Thus, we have to iterate all the way from the beginning just to identify the second to last node:
因此,我们必须从头开始遍历,才能确定倒数第二个节点:
。
The code also would be a little bit more complex than for a doubly-linked list:
代码也会比双链路列表复杂一些:
public void removeLast() {
if (isEmpty()) {
return;
} else if (size() == 1) {
tail = null;
head = null;
} else {
Node<S> secondToLast = null;
Node<S> last = head;
while (last.next != null) {
secondToLast = last;
last = last.next;
}
secondToLast.next = null;
}
--size;
}
As we have to iterate over the entire list, the operation takes linear time, which isn’t good if we plan to use our list as a queue. One of the optimization strategies is to store the secondToLast node alongside the head and tail:
由于我们必须遍历整个列表,因此该操作需要线性时间,如果我们计划将列表用作队列,那么这并不是一件好事。优化策略之一是将secondToLast节点与head和tail:节点一起存储。
public class SinglyLinkedList<S> {
private int size;
private Node<S> head = null;
private Node<S> tail = null;
// other methods
}
This won’t provide us with easy iteration, but it at least improves the removeLast() method, making it similar to the one we’ve seen for a doubly-linked list.
这不会为我们提供简单的迭代,但至少改进了 removeLast() 方法,使其类似于我们在双链表中看到的方法。
5. Conclusion
5.结论
It’s not possible to divide data structures into good and bad. They’re just tools. Thus, each task requires a specific data structure to accomplish its goals.
数据结构不可能有好坏之分。它们只是工具而已。因此,每项任务都需要特定的数据结构来实现其目标。
Singly-linked lists have some performance issues with removing the last element and aren’t flexible on other operations, but at the same time, they consume less memory. Doubly-linked lists have no constraints, but we’re paying for this with more memory.
单链表在移除最后一个元素时存在一些性能问题,而且在其他操作上也不灵活,但同时,它们消耗的内存较少。双链表没有任何限制,但我们要为此付出更多的内存。
Understanding the underlying implementation of data structures is crucial and allows us to pick the best tool for our needs. As usual, all the code from this tutorial is available over on GitHub.
了解数据结构的底层实现至关重要,它能让我们选择最适合自己需要的工具。与往常一样,本教程中的所有代码均可在 GitHub 上获取。