Like an array, a linked list is a collection of elements.
Classification of linked lists
Singly linked list
A node in a unidirectional list stores the address of the next node. The diagram below:
+--------+ +--------+ +--------+ +--------+ | | | | | | | | | node 0 |--->| node 1 |--->| node 2 |--->| node 3 | | | | | | | | | +--------+ +--------+ +--------+ +--------+Copy the code
Two-way linked list
Nodes in a bidirectional list store the addresses of the previous and the next nodes. The diagram below:
+--------+ +--------+ +--------+ +--------+
| |--->| |--->| |--->| |
| node 0 | | node 1 | | node 2 | | node 3 |
| |<---| |<---| |<---| |
+--------+ +--------+ +--------+ +--------+
Copy the code
Comparison of unidirectional and bidirectional linked lists
A unidirectional linked list uses less memory because it does not store the address of the previous node; But if we need to access the previous node, the one-way list is very cumbersome and needs to start from scratch, while the two-way list is very easy. In many cases, two-way lists are easier to use.
Linked list implementation
Here we take a unidirectional linked list as an example.
LinkedListNode
Since a linked list is made up of a series of nodes, we first define a node type.
final class LinkedListNode<T> {
var value: T
var next: LinkedListNode?
init(value: T.next: LinkedListNode? = nil) {
self.value = value
self.next = next
}
}
extension LinkedListNode: CustomStringConvertible {
var description: String {
guard let next = next else { return "\(value)" }
return "\(value) -> \(String(describing: next)) "}}Copy the code
Define a node as generic, so it can store any type of data; There are two properties, the value of the current node and the next node; Implemented CustomStringConvertible protocol, so it looks better when printing lists.
Let’s try it out:
let node1 = LinkedListNode(value: 1)
let node2 = LinkedListNode(value: 2)
let node3 = LinkedListNode(value: 3)
node1.next = node2
node2.next = node3
print(node1)
/ / the result
1 -> 2 -> 3
Copy the code
LinkedList
We define LinkedList as a Struct type, which is also generic.
struct LinkedList<T> {
var head: LinkedListNode<T>?
var tail: LinkedListNode<T>?
init(a){}}extension LinkedList: CustomStringConvertible {
var description: String {
guard let head = head else { return "Empty list" }
return String(describing: head)
}
}
extension LinkedList {
var isEmpty: Bool {
return head = = nil}}Copy the code
Head is the first element; Tail is the last element. The isEmpty property has also been added for later use.
Add elements
Here we define two ways to add elements: 1) append() : adds an element to the end of the list; 2) Insert (after:) : add elements after a node.
extension LinkedList {
mutating func append(_ value: T) {
guard !isEmpty else {
let node = LinkedListNode(value: value)
head = node
tail = node
return
}
let next = LinkedListNode(value: value)
tail?.next = next
tail = next
}
mutating func insert(_ value: T.after node: LinkedListNode<T>) {
guard tail ! = = node else { append(value); return }
node.next = LinkedListNode(value: value, next: node.next)
}
}
Copy the code
append()
: First determines whether it is null. If it is, thenhead
andtail
Are the currently added elements; If not, updatetail? .next
For the currently added element, finally in the puttail
Update to the currently added element.insert(after:)
: First judgenode
Is there already the last element, if so, call directlyappend()
Add; Otherwise, updatenode.next
.
Remove elements
1) removeLast() : remove the last element from the list; 2) remove(after:) : remove the elements after a node.
extension LinkedList {
mutating func removeLast(a) -> T? {
guard let head = head else { return nil }
guard head.next ! = nil else {
let headValue = head.value
self.head = nil
tail = nil
return headValue
}
var prev = head
var current = head
while let next = current.next {
prev = current
current = next
}
prev.next = nil
tail = prev
return current.value
}
mutating func remove(after node: LinkedListNode<T>) -> T? {
defer {
if node.next = = = tail {
tail = node
}
node.next = node.next?.next
}
return node.next?.value
}
}
Copy the code
removeLast()
: checks whether the element is empty. If it is empty, it returnsnil
;head.next ! = nil
Determine if there is only one element. If so, define a variable storehead.value
,head
andtail
All set tonil
And returnheadValue
; A linked list has multiple elements, usingwhile
Loop to find the last element, put the penultimate element ofnext
Set tonil
.tail
Set to the penultimate element and return the last elementvalue
.remove(after:)
:defer
The code inside will be inreturn
The statement is run after execution, so inreturn node.next? .value
After that, judgenode
Is the next element of omega the last element? If it is, the last element becomes omeganode
; Because we deleted itnode
The next element of theta, sonode.next
Turned out to benode.next? .next
.
Access to the elements
extension LinkedList {
func node(at index: Int) -> LinkedListNode<T>? {
var currentNode = head
var currentIndex = 0
while currentNode ! = nil && currentIndex < index {
currentNode = currentNode!.next
currentIndex + = 1
}
return currentNode
}
}
Copy the code
Here we mean implementing a method: find elements at an index. If currentIndex == index, then the node corresponding to index is currentNode.
Performance analysis
The method name | Methods an overview | Time complexity |
---|---|---|
append() |
Add the element at the end | O(1) |
insert(after:) |
Adds an element after a node | O(1) |
removeLast() |
Remove the last element | O(n), n is the number of elements, because you have to start at the beginning to find the last element, so it’s O(n). |
remove(after:) |
Removes the element after a node | O(1) |
node(at:) |
To find aindex Corresponding element |
O(I), I is index, because I have to start from scratch, so it’s O(I). |
Achieve SwiftCollection
agreement
Why implement the Collection protocol? First of all, a list can store a series of elements, so it is reasonable to implement the Sequence protocol. In addition, the elements of the linked list are limited, which is also in line with the requirements of the Collection protocol. With the Collection protocol implemented, we can use for-in loops and get a lot of handy methods. Since Collection inherits from Sequence, we can simply implement Collection. Sequence and Collection protocols, you can look at the official documents to understand.
As we can see from apple’s documentation, the implementation of the Collection protocol requires: 1) startIndex and endIndex properties; 2) the realization of subscript; 3) index(after:) implementation. It also requires O(1) time to access the startIndex and endIndex attributes and to access the element by subscript. However, in the linked list, the time complexity of accessing elements through index is O(I), which cannot meet the requirements of the Collection protocol. Therefore, we define a custom Index type to store the corresponding Node, which can meet the requirements of the Collection protocol.
extension LinkedList: Collection {
struct Index: Comparable {
var node: LinkedListNode<T>?
static func = =(lhs: Index.rhs: Index) -> Bool {
switch (lhs.node, rhs.node) {
case let (left?, right?) :return left.next = = = right.next
case (nil.nil) :return true
default:
return false}}static func <(lhs: Index.rhs: Index) -> Bool {
guard lhs ! = rhs else { return false }
let nodes = sequence(first: lhs.node) { $0?.next }
return nodes.contains { $0 = = = rhs.node }
}
}
var startIndex: Index {
return Index(node: head)
}
var endIndex: Index {
return Index(node: tail?.next)
}
func index(after i: Index) -> Index {
return Index(node: i.node?.next)
}
subscript(index: Index) -> T {
return index.node!.value
}
}
Copy the code
In the above code, we first define an Index type and then implement the properties and methods required by the Collection protocol.
theLinkedList
Change to value type
As anyone familiar with Swift knows, many types in the Swift standard library are value types. Let’s take an array as an example:
var array1 = [1.2.3.4.5]
let array2 = array1
print(array1)
print(array2)
print("======== dividing line ========")
array1.append(6)
print(array1)
print(array2)
/ / the result
array1: [1.2.3.4.5]
array2: [1.2.3.4.5]
= = = = = = = =The divider= = = = = = = =
array1: [1.2.3.4.5.6]
array2: [1.2.3.4.5]
Copy the code
Array1 is defined, array1 is assigned to Array2, and array1 and Array2 are identical. Then I added a 6 to array1, and when I print the result, ARRAY1 has an extra 6, but Array2 has not changed. This proves that the array is a value type.
So back to LinkedList, let’s define it as a Struct type. Under the impression that all Struct types are value types. Let’s imitate the array example and see what happens:
var list1 = LinkedList<Int>()
list1.append(1)
list1.append(2)
list1.append(3)
let list2 = list1
print("list1: \(list1)")
print("list2: \(list2)")
print("======== dividing line ========")
list1.append(4)
print("list1: \(list1)")
print("list2: \(list2)")
/ / the result
list1: 1 -> 2 -> 3
list2: 1 -> 2 -> 3
= = = = = = = =The divider= = = = = = = =
list1: 1 -> 2 -> 3 -> 4
list2: 1 -> 2 -> 3 -> 4
Copy the code
Result: Changing one list affects the other. Our LinkedList is currently a reference type. The reason for this is that the LinkedListNode of our underlying storage node is of type Class and is a reference type.
To fix this problem, we need to make a copy of LinkedListNode in all methods that change the LinkedListNode element before operating on the element, so that multiple LinkedListNode variables do not refer to the same instance. The implementation method is as follows:
mutating func copyNodes(a) {
guard !isKnownUniquelyReferenced(&head) else { return }
guard var oldNode = head else { return }
head = LinkedListNode(value: oldNode.value)
var newNode = head
while let nextOldNode = oldNode.next {
let nextNewNode = LinkedListNode(value: nextOldNode.value)
newNode?.next = nextNewNode
newNode = nextNewNode
oldNode = nextOldNode
}
tail = newNode
}
Copy the code
This method replicates all the nodes of the list. The guard! IsKnownUniquelyReferenced (& head) else {return} is used to determine whether the current list is only a variable reference, if it is, you do not need to copy; Otherwise, the replication will continue on all nodes.
Add the copyNodes() method to the top of all the methods that change linked list elements: append(), insert(after:), removeLast() and remove(after:). Such as:
mutating func append(_ value: T) {
copyNodes()
guard !isEmpty else {
let node = LinkedListNode(value: value)
head = node
tail = node
return
}
let next = LinkedListNode(value: value)
tail?.next = next
tail = next
}
Copy the code
This completes a simple linked list.
conclusion
This article mainly explains some features of the list, and a simple implementation of the list. Currently there are few methods to add and remove elements. If you are interested, you can add more methods, such as Append a list, Insert a list, and so on.
Welcome to join my Swift development group: 536353151.
Complete code >>
The resources
Data Structures and Algorithms in Swift — Raywenderlich.com. Click on the link to buy the original.