链表是按线性单向序列排列的值的集合。与SWIFT语言里Array等连续存储数据结构相比,链表在理论上具有两个优势:
- 在链表头插入和删除元素的时间恒定
- 可靠的性能特征
![c42ec5014a901e5e543a5506c7ee8b09.png](https://i-blog.csdnimg.cn/blog_migrate/8e45f61cd13f0d80d480329351d3aa9f.png)
如图所示,链表是一个节点链。节点有两个职责:
- 持有一个元素
- 持有下一个节点的引用。最后一个节点持有的引用为nil值
![892ddc844532dbed9073eb6f54384846.png](https://i-blog.csdnimg.cn/blog_migrate/5dd0736345ea988b9a8822e478b45285.png)
节点(Node)
我们可以边动手边看文章,新建一个SOURCES目录,并在其中创建一个新的SWIFT文件,并将其命名为Node.swift。并将下列代码添加到文件里:
public class Node<Value> {
public var value: Value
public var next: Node?
public init(value: Value, next: Node? = nil) {
self.value = value
self.next = next
}
}
extension Node: CustomStringConvertible{
public var description: String {
guard let next = next else {
return "(value)"
}
return "(value) -> " + String(describing: next) + " "
}
}
导航到Playground页面并添加以下内容:
example(of: "creating and linking nodes") {
let node1 = Node(value: 1)
let node2 = Node(value: 2)
let node3 = Node(value: 3)
node1.next = node2
node2.next = node3
print(node1)
}
上边的代码其实就是让你创建了三个节点并将它们连接起来:
![1a711ddcadb51e2412362bc18f458842.png](https://i-blog.csdnimg.cn/blog_migrate/b864d0bdddc4f9746cae20c45ebd0498.png)
在控制台中,您应该看到以下输出:
---Example of creating and linking nodes---
1 -> 2 -> 3
就实用性而言,当前构建列表的方法还有很多不尽如人意的地方。您可以很容易地看到,以这种方式构建长列表是不切实际的。解决此问题的一种常见方法是构建管理Node对象的LinkedList。让我们继续。
链表(LinkedList)
在Sources目录中,创建一个新文件并将其命名为LinkedList.swift。将以下内容添加到文件中:
public struct LinkedList<Value> {
public var head: Node<Value>?
public var tail: Node<Value>?
public init() {}
public var isEmpty: Bool {
return head == nil
}
}
extension LinkedList: CustomStringConvertible {
public var description: String {
guard let head = head else {
return "Empty list"
}
return String(describing: head)
}
}
链表具有头部和尾部的概念,分别指链表的第一个和最后一个节点:
![81ec0df1f0f3b3b312759c776246b747.png](https://i-blog.csdnimg.cn/blog_migrate/a8d9e4071062b67ac573376d7a23890e.png)
添加数据到列表中
如前所述,我们可以提供一个接口来管理Node对象。你首先需要关心的是怎么添加值。这里有三种向链接列表添加值的方法,每种方法都具有各自独特的性能特征: 1.push:在列表前面增加一个值。 2.append:在列表末尾添加值。 3.insert(after:):在列表的特定节点后添加值。 你将在下一节中实现其中的每一个,并分析它们的性能特征。
push
在链表前面添加值称为push操作。这也称为头部优先插入。它的代码非常简单。 将以下方法添加到LinkedList:
public mutating func push(_ value: Value) {
head = Node(value: value, next: head)
if tail == nil {
tail = head
}
}
在push空链表的情况下,新节点既是链表的头部,也是链表的尾部。 返回playground页面并添加以下内容:
example(of: "push") {
var list = LinkedList<Int>()
list.push(3)
list.push(2)
list.push(1)
print(list)
}
你的控制台输出应显示以下内容:
---Example of push---
1 -> 2 -> 3
append
你将看到的下一个操作是append。这意味着在列表的末尾添加值,称为尾端插入。 返回LinkedList.swift并在push方法下面添加以下代码:
public mutating func append(_ value: Value) {
guard !isEmpty else {
push(value)
return
}
tail!.next = Node(value: value)
tail = tail!.next
}
此代码相对简单: 1.和以前一样,如果链表为空,则需要将头部和尾部都更新为新节点。由于applend在空列表上增加一个元素与push功能相同,因此只需调用push即可为您完成这项工作。 2.在所有其他情况下,您只需在尾节点之后创建一个新节点。由于在isEmpty情况下使用上述GUARD语句声明,因此强制解包一定会成功。 3.由于这是尾端插入,因此你的新节点也是列表的尾部。
跳回Playground,在底部写下以下内容:
example(of: "append") {
var list = LinkedList<Int>()
list.append(1)
list.append(2)
list.append(3)
print(list)
}
你在控制台中看到以下输出:
---Example of append---
1 -> 2 -> 3
insert(after:)
添加值的第三个也是最后一个操作是insert(after:)。
此操作在列表中的特定位置插入值,需要两个步骤: 1.在列表中查找特定节点。 2.插入新节点。 首先,我们将实现代码来查找要插入值的节点。 回到LinkedList.swift,在append方法下面添加以下代码:
public func node(at index: Int) -> Node<Value>? {
// 1
var currentNode = head
var currentIndex = 0
// 2
while currentNode != nil && currentIndex < index {
currentNode = currentNode!.next
currentIndex += 1
}
return currentNode
}
node(at:)将尝试根据给定索引检索列表中的节点。由于只能从Head节点访问列表的节点,因此必须进行迭代遍历。以下是步骤: 1. 创建一个新的Head引用,并跟踪当前遍历的数量。 2. 使用while循环,你可以在列表中向下移动引用,直到达到所需的索引。空列表或超出界限的索引将导致nil返回值。
现在需要插入新节点。 在node(at:)正下方添加以下方法:
// 1
@discardableResult
public mutating func insert(_ value: Value, after node: Node<Value>) -> Node<Value> {
// 2
guard tail !== node else {
append(value)
return tail!
}
// 3
node.next = Node(value: value, next: node.next)
return node.next!
}
以下是你所做的事情:
- @discardableResult允许调用者忽略此方法的返回值,而编译器不会上下跳警告你。
- 在使用尾节点调用此方法的情况下,你将调用功能等效的append方法。这将处理更新尾部。
- 否则,只需将新节点与列表的其余部分链接起来,然后返回新节点。
跳回playground页面测试上述。将以下内容添加到playground底部:
example(of: "inserting at a particular index") {
var list = LinkedList<Int>()
list.push(3)
list.push(2)
list.push(1)
print("Before inserting: (list)")
var middleNode = list.node(at: 1)!
for _ in 1...4 {
middleNode = list.insert(-1, after: middleNode)
}
print("After inserting: (list)")
}
你在控制台中看到以下输出:
---Example of inserting at a particular index---
Before inserting: 1 -> 2 -> 3
After inserting: 1 -> 2 -> -1 -> -1 -> -1 -> -1 -> 3
性能分析
棒!到目前为止你已经取得了很好的进步。简单地说,你已经实现了向链接列表添加值的三个操作,以及在特定索引处查找节点的方法。
![29ecc10cd886d87b60ed37f9867454cd.png](https://i-blog.csdnimg.cn/blog_migrate/a16031a2389c96d82370d04bd2837ed6.jpeg)
下一章节,我们将关注相反的操作:删除操作。