Swift 中的链表

这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战


引言

数据结构是用于在计算机中组织和存储数据的容器,以便我们能够有效地执行操作。它们是编程最基本的组成部分。最知名和最常用的数据结构,除了 数组、 栈 和 队列等,还有一个非常有用的数据结构--链表。不过,Swift 没有提供内置的链表结构,所以,今天我们就来一步步的实现一个。


什么是单链表

image.png

链表是链接节点的列表。节点是一个单独的元素,它包含一个泛型值和一个指向下一个节点的指针。链表有以下几种类型:

  • 单链表 每个节点只有一个指向下一个节点的指针。操作只能向一个方向传递
  • 双向链表 每个节点有两个指针,一个指向下一个节点,另一个指向前一个节点。操作可以向前和向后两个方向进行
  • 循环链表 最后一个节点的下一个指针指向第一个节点,第一个节点的上一个指针指向最后一个节点

今天我们来实现 Swift 中的单链表。


Node

Node 必须定义为 Class。因为设计到引用,所以它需要是引用类型,Node 类有两个属性:

  • value 存储节点实际值的泛型数据类型
  • next 指向下一个节点的指针
class Node<T> {
    var value: T
    var next: Node<T>?

    init(value: T, next: Node<T>? = nil) {
        self.value = value
        self.next = next
    }
}
复制代码

链表

与 Node 不同,链表是一种值类型,定义为 Struct。默认情况下,链表有三个基本属性:

  • head链表的第一个节点
  • tail链表的最后一个节点
  • isEmpty链表是否为空

当然,你可以根据需要添加其他属性,例如:

  • count链表的节点个数
  • description链表的描述文本
struct LinkedList<T> {
  var head: Node<T>?
  var tail: Node<T>?

  var isEmpty: Bool { head == nil }

  init() {}
}
复制代码

与栈和队列不同,链表不包含可以直接调用和使用的数据集合。列表中的所有节点都必须链接到下一个可用的节点(如果有的话)。


Push

把数据添加到链表头部,这意味着当前的 head 将被替换为新节点,新节点将成为链表的 head。

struct LinkedList<T> {

    ...
    
    mutating func push(_ value: T) {
        head = Node(value: value, next: head)
        if tail == nil {
            tail = head

        }
    }
}
复制代码

通过调用 push(_ value: T) ,我们用该值创建一个新节点,并使新节点指向原来的 head。然后我们用新节点替换 head,这样原来的 head 就成为链表的第二个节点。

image.png


Append

与 push 类似,我们将数据添加到链表的末尾。这意味着当前的尾部将被替换为新节点,新节点将成为新的尾部。

struct LinkedList<T> {

    ...

    mutating func append(_ value: T) {
        let node = Node(value: value)
        tail?.next = node
        tail = node
    }
}
复制代码

通过调用 append(_ value: T) ,我们创建了一个新节点,并将原来的尾部指向新节点。最后,我们将原来的 tail 替换为新的节点,因此原来的 tail 成为列表的倒数第二个节点。新的节点将成为新的尾部

image.png


Node At

链表不能通过下标索引取值,因此我们不能像数组 array[0] 那样读取数据集合。

struct LinkedList<T> {

    ...

    func node(at index: Int) -> Node<T>? {
        var currentIndex = 0
        var currentNode = head
        while currentNode != nil && currentIndex < index {
            currentNode = currentNode?.next
            currentIndex += 1
        }
        return currentNode
    }
}
复制代码

为了获取某个索引对应的 Node,需要遍历。


Insert

正如我之前所说,链表不能通过索引下标取值。链表只知道哪个节点链接到哪个节点。为了告诉链表在特定位置插入一个节点,我们需要找到链接到该位置的节点。v需要用到上面的函数:node(at index: Int) -> Node<T>?

struct LinkedList<T> {

    ...

    func insert(_ value: T, after index: Int) {
        guard let node = node(at: index) else { return }
        node.next = Node(value: value, next: node.next)
    }
}
复制代码

首先,我们必须找到位于给定位置的节点。我们接下来让它指向新节点,新节点指向原来的下一个节点。

image.png


Pop

我们将 head 从列表中删除,这样第二个节点将成为 head:

struct LinkedList<T> {

    ...

    mutating func pop() -> T? {
        defer {
            head = head?.next
            if isEmpty {
                tail = nil
            }
        }
        return head?.value
    }
}
复制代码

返回原来的 head 的值,并用原来的下个节点替换原来的 head,这样原来的下个节点就成为了 head。

image.png


Remove Last

与 pop() 类似,这是删除链表的尾部,因此倒数第二个节点将成为尾部。

struct LinkedList<T> {

    ...

    mutating func removeLast() -> T? {
        guard let head = head else { return nil }
        guard let _ = head.next else { return pop() }

        var previousNode = head
        var currentNode = head

        while let next = currentNode.next {
            previousNode = currentNode
            currentNode = next
        }
        
        previousNode.next = nil
        tail = previousNode
        return currentNode.value
    }
}
复制代码

但与 pop() 不同的是,去掉尾部节点有点复杂,因为尾部不知道前一个节点是谁。我们需要遍历链表以找到尾部之前的节点,并将其设置为尾部。

  • 如果头部是nil,意味着该列表是空的,我们没有什么要删除,然后返回nil
  • 如果head.nextnil,这意味着只有一个节点在链表中,然后删除头
  • 循环遍历列表,找到尾部之前的节点,并将其设置为尾部

Remove After

insert(_ value: T, after index: Int) 类似,我们需要先找到待删除位置的节点。

struct LinkedList<T> {

    ...

    mutating func remove(after index: Int) -> T? {
        guard let node = node(at: index) else { return nil }
            defer {
                if node.next === tail {
                tail = node
            }
            node.next = node.next?.next
        }
        return node.next?.value
    }
}
复制代码

移除指定索引处的节点有点棘手,因此我们基本上只是跳过一个节点,然后指向那个节点之后的节点。

image.png

总结

这些是单链表通常具有的基本属性和函数。当然,你也可以在这些基础上添加其他属性和函数。

おすすめ

転載: juejin.im/post/7035278534037733407