数据结构教程网盘链接
by Kevin Turney
凯文·特尼(Kevin Turney)
Like stacks and queues, Linked Lists are a form of a sequential collection. It does not have to be in order. A Linked list is made up of independent nodes that may contain any type of data. Each node has a reference to the next node in the link.
像堆栈和队列一样 ,链接列表是顺序集合的一种形式。 它不一定是有序的。 链接列表由可能包含任何类型的数据的独立节点组成。 每个节点都有对链接中下一个节点的引用。
We can emulate stacks and queues with linked lists. We can as well use it as a base to create or augment other data structures. With linked lists, our primary concerns are with fast insertions and deletions, which are more performant over arrays.
我们可以使用链接列表来模拟堆栈和队列。 我们也可以将其用作创建或扩充其他数据结构的基础。 对于链表,我们的主要关注点是快速插入和删除,它们在数组上的性能更高。
The building block of this structure is a Node.
此结构的构建块是节点。
const Node = function(value) { this.value = value; this.next = null;};
Our Node is built with two properties, a value
to hold data, and next
, a reference initially set to null. The next
property is used to “point” to the next Node in the linking. One of the disadvantages of linked lists is that each reference requires a larger memory overhead than an array.
我们的Node具有两个属性,一个用于保存数据的value
, next
是最初设置为null的引用。 next
属性用于“指向”链接中的下一个节点。 链表的缺点之一是每个引用比数组需要更大的内存开销。
实作 (Implementation)
const LinkedList = function(headvalue) { // !! coerces a value to a Boolean if (!!headvalue) { return "Must provide an initial value for the first node" } else { this._head = new Node(headvalue); this._tail = this.head; }};
In our second constructor, we test for a value to provide for the first Node. If true, we proceed to create a new Node with the value passed and set the head to tail initially.
在第二个构造函数中,我们测试要提供给第一个Node的值。 如果为true,我们将继续使用传递的值创建一个新的Node,并将head最初设置为tail。
插入 (Insertion)
LinkedList.prototype.insertAfter = function(node, value) { let newNode = new Node(value); let oldNext = node.next; newNode.next = oldNext; node.next = newNode; if (this._tail === node) { this._tail = newNode; } return newNode;};
For this method, we create a new Node and adjust the references. The former next reference of the original node is now directed to newNode. The newNode’s next reference is “pointed” to what the previous node’s next was referring to. Finally, we check and reset the tail property.
对于此方法,我们创建一个新的Node并调整引用。 现在将原始节点的前一个下一个引用定向到newNode。 newNode的下一个引用“指向”上一个节点的下一个引用。 最后,我们检查并重置tail属性。
LinkedList.prototype.insertHead = function(value) { let newHead = new Node(value); let oldHead = this._head newHead.next = oldHead; this._head = newHead; return this._head;};
LinkedList.prototype.appendToTail = function(value) { let newTail = new Node(value); this._tail.next = newTail; this._tail = newTail; return this._tail;};
Insertion at the beginning or end of a linked list is fast, operating in constant time. For this, we create a new node with a value and rearrange our reference variables. We reset the node which is now the head with insertHead
or the tail with appendToTail
.
快速插入链表的开头或结尾,并且操作时间固定。 为此,我们创建一个具有值的新节点,并重新排列参考变量。 我们将节点重置为现在的头,其头为insertHead
或尾部为appendToTail
。
These operations represent fast insertions for collections, push for stacks, and enqueue for queues. It may come to mind that unshift for arrays is the same. No, because with unshift all members of the collection must be moved one index over. This makes it a linear time operation.
这些操作表示对集合的快速插入,对堆栈的推送以及对队列的排队。 可能会想到,数组的不变移位是相同的。 不可以,因为在取消移位时,必须将集合的所有成员移到一个索引上。 这使其成为线性时间操作。
删除中 (Deletion)
LinkedList.prototype.removeAfter = function(node) { let removedNode = node.next; if (!!removedNode) { return "Nothing to remove" } else { let newNext = removedNode.next node.next = newNext; removedNode.next = null; // dereference to null to free up memory if (this._tail === removedNode) { this._tail = node; } } return removedNode;};
Starting with a test for a node to remove, we proceed to adjust the references. Dereferencing the removedNode
and setting it to null is important. This frees up memory and avoids having multiple references to the same object.
从测试要删除的节点开始,我们继续调整引用。 removedNode
引用removedNode
并将其设置为null很重要。 这样可以释放内存,并避免对同一对象有多个引用。
LinkedList.prototype.removeHead = function() { let oldHead = this._head; let newHead = this._head.next; this._head = newHead; oldHead.next = null; return this._head;};
Deletion of a head and of a specified node in, removeAfter, are constant time removals. In addition, if the value of the tail is known, then tail removal can be done in O(1). Else we have to move linearly to the end to remove it, O(N);
删除头和指定节点中的removeAfter是恒定时间删除。 另外,如果知道尾巴的值,则可以在O(1)中进行尾巴去除。 否则,我们必须线性移动到最后才能将其删除,O(N);
循环播放 (Looping and forEach)
We use the following to iterate through a linked list or to operate on each node value.
我们使用以下内容迭代链接列表或对每个节点值进行操作。
LinkedList.prototype.findNode = function(value) { let node = this._head; while(node) { if (node.value === value) { return node; } node = node.next; } return `No node with ${value} found`;};
LinkedList.prototype.forEach = function(callback) { let node = this._head; while(node) { callback(node.value); node = node.next; }};
LinkedList.prototype.print = function() { let results = []; this.forEach(function(value) { result.push(value); }); return result.join(', ');};
The main advantage of Linked Lists is fast insertions and deletions without rearranging items or reallocation of space. When we use an array, the memory space is contiguous, meaning we keep it all together. With linked lists, we can have memory spaces all over the place, non-contiguous storage through the use of references. For arrays, that locality of references means that arrays have better caching of values for faster lookup. With linked lists, caching is not optimized and access time takes longer.
链接列表的主要优点是快速插入和删除,而无需重新排列项目或重新分配空间。 当我们使用数组时,内存空间是连续的,这意味着我们将它们保持在一起。 使用链表,我们可以在各处拥有存储空间,通过使用引用可以实现非连续存储。 对于数组,引用的局部性意味着数组可以更好地缓存值以加快查找速度。 使用链接列表时,无法优化缓存,并且访问时间会更长。
Another aspect of linked lists is different types of configuration. Two primary examples are circularly linked, where the tail has a reference to the head and the head to the tail. Doubly linked is when, in addition to the node having a reference to the next node, also has a reference looking back to the previous node.
链表的另一方面是不同类型的配置。 两个主要的示例是循环链接的,其中,尾部引用了头部,头部引用了尾部。 双链接是指除了该节点具有对下一个节点的引用之外,还具有回溯到前一个节点的引用。
时间复杂度 (Time Complexity)
Insertion
插入
- insertHead, appendToTail — O(1) insertHead,appendToTail — O(1)
- if a specific node is known, insertAfter — O(1) 如果已知特定节点,则insertAfter — O(1)
Deletion
删除中
- removeHead — O(1); removeHead — O(1);
- if a specific node is known, removeAfter — O(1) 如果已知特定节点,则removeAfter — O(1)
- if the node is not known — O(N) 如果节点未知— O(N)
Traversing
遍历
- findNode, forEach, print — O(N) findNode,forEach,打印— O(N)
翻译自: https://www.freecodecamp.org/news/data-structures-101-linked-lists-254c82cf5883/
数据结构教程网盘链接