用 Swift 实现常用的数据结构 - 单链表

131 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

什么是单链表

单链表是一种常见的链式数据结构,它是由每个节点和指向节点的指针构成的。与同是链式结构的数组不同,它不需要操作系统分配整块的内存来存储元素。

在它进行添加元素的时候,操作系统只需要开辟存储单个元素的空间即可。

以下是实现单链表需要编写的函数:

func count() -> Int // 链表的长度
func isEmpty() -> Bool // 链表是否为空
func contains(ele: Element) -> Bool // 是否包含某个元素
func add(ele: Element) // 尾部添加元素
func insert(index: Int, ele: Element) // 在 index 插入元素
func getElement(index: Int) -> Element? // 获取 index 的元素
func setElement(index: Int, ele: Element) -> Element? //设置 index 的元素
func indexOf(ele: Element) -> Int? // 获取 ele 的当前索引
func remove(index: Int) -> Element? // 移除 index 的元素
func removeAll() // 移除所有元素

千里之行始于足下,下面让我们一个一个的实现。

代码实现

预备工作

在开始实现之前,我们需要做一些准备工作。

因为存放的元素需要放在节点中,所以首先我们需要创建一个节点类:

class Node<Element: Equatable> { 
    var element: Element
    var next: Node?
    init(ele: Element, next: Node?) {
        self.element = ele
        self.next = next
    }
}

上述代码的作用:

  • 因为节点需要存放任意类型的元素,所以我们将它声明为泛型。且后面需要进行元素判断,所以 Element 需要遵守 Equatable。
  • element 用来存储元素,next 用来指向下一个节点。

创建完节点类,我们还需要写一下 LinkedList 的基本内容:

class LinkedList<Element: Equatable> {
    var head: Node<Element>?
    var size = 0
    
    private func rangeCheck(index: Int) {
        if index < 0 || index >= size {
            fatalError("invalid array index")
        }
    }
    
    private func rangeCheckForInsert(index: Int) {
        if index < 0 || index > size {
            fatalError("invalid array index")
        }
    }
}

上述代码的作用:

  • 声明 head 指针,用来指向链表的头结点。
  • 声明 size 变量,用来存储链表的节点数。
  • rangeCheck:用来判断除插入操作的其他操作,索引是否合法;rangeCheckForInsert:用来判断插入操作的索引是否合法。

实现简单的函数

func count() -> Int {
    return size
}

func isEmpty() -> Bool {
    return size == 0
}

func contains(ele: Element) -> Bool {
    return indexOf(ele: ele) != nil
}

func add(ele: Element) {
   insert(index: size, ele: ele) // 在尾部添加元素
}

func removeAll() { // 清空链表直接将 size 设为 0,head 置为 nil,即可无需遍历去除所有的节点再置为 nil。
    size = 0
    head = nil
}

因为上述函数比较简单,这里就不做文字解释了。

insert

func insert(index: Int, ele: Element) {
    rangeCheckForInsert(index: index)
    if index == 0 {
        let newHead = Node(ele: ele, next: head)
        head = newHead
    } else {
        let pre = getNode(of: index - 1)
        let node = Node(ele: ele, next: pre?.next)
        pre?.next = node
    }
    size += 1
}

func getNode(of index: Int) -> Node<Element>? { // 遍历获取当前 index 的前一个节点
    rangeCheck(index: index)
    var cur = head
    for _ in 0..<index {
        cur = cur?.next
    }
    return cur
}

代码解释:

  • 首先需要检查索引是否合法:index >= 0 && index <= size。
  • 若 index 为 0,则代表在头部插入:
    • 创建新的节点 - newHead,将 newHead 的 next 指向 head。
    • 将 head 再指向 newHead 即可。
  • 在除头结点的其他位置插入:
    • 获取当前位置的前一个节点 - pre。
    • 创建新节点 - node,将 node 的 next 指向 pre 的 next。
    • 再讲 pre 的 next 指向 node 即可。
  • 最后,size + 1。

getElement

func getElement(index: Int) -> Element? {
    rangeCheck(index: index)
    if index == 0 {
        return head?.element
    } else {
        let node = getNode(of: index)
        return node?.element
    }
}

代码解释:

  • 首先检查索引是否合法:index >= 0 && index < size。
  • 若 index 为 0,则代表第一个节点,返回 head 的 element 即可。
  • 若 index 不为 0,则调用 getNode 获取当前节点 - node,返回 node 的 element 即可。

setElement

func setElement(index: Int, ele: Element) -> Element? {
    rangeCheck(index: index)
    var old = head?.element
    if index == 0 {
        head?.element = ele
    } else {
        let node = getNode(of: index)
        old = node?.element
        node?.element = ele
    }
    return old
}

代码解释:

  • 首先检查索引是否合法:index >= 0 && index < size。
  • 若 index 为 0,则代表第一个节点,设置 head 的 element。
  • 若 index 不为 0,则调用 getNode 获取当前节点 - node,保存 node 的 element,最后,设置 node 的 element 。
  • 返回设值之前的旧值 - old。

remove

func remove(index: Int) -> Element? {
    rangeCheck(index: index)
    var removed = head?.element
    if index == 0 {
        head = head?.next
    } else {
        let pre = getNode(of: index - 1)
        removed = pre?.next?.element
        pre?.next = pre?.next?.next
    }
    size -= 1
    return removed
}

代码解释:

  • 首先检查索引是否合法:index >= 0 && index < size。
  • 若 index 为 0,直接将 head 指向 head 的 next 即可删除头结点。
  • 若 index 不为 0:
    • 则调用 getNode 获取当前节点的前一个节点 - pre。
    • 保存 pre 的 next 的 element(即要删除的节点的 element)。
    • 将 pre 的 next 指向 pre 的 next 的 next。
  • size - 1。
  • 返回设值之前的旧值 - removed。

indexOf

func indexOf(ele: Element) -> Int? {
    var node = head
    
    for i in 0..<size {
        if ele == node?.element  {
            return i
        }
        node = node?.next
    }
    return nil
}

代码解释:

  • 声明 node,指向当前链表的头结点。
  • 遍历链表,若 ele 等于 node 的 element,即找到,直接返回 i。正是因为此处需要元素进行对比,所以 Element 需要遵守 Equatable
  • 若遍历完链表都未找到,则代表链表中没有 ele。

至此,整个单链表就已经实现完了。可以看到代码量还是比较小的。

对单链表感兴趣的同学可以自己来实现一遍,来体会一下。需要注意的就是对 next 指针的挪动,一定要搞对。