vlambda博客
学习文章列表

链表1-数据结构与算法之Swift实现1



概念


链表又分为单向链表、双向链表、循环链表,单链表是只有一个前进方向,只能从一个节点链接到下一个节点,而双向链表既可以链接到下一个节点,也可以链接到上一个节点。循环列表则是链表首尾也是链接的。这里只实现单向链表。


性能

链表的元素读取性能是O(n),在最坏的情况下,需要遍历所有的元素,才能访问到需要的元素。
插入和删除性能是O(1),每次只需要常数的时间就能插入和删除元素。

Swift实现

根据上面的定义,我们可以知道,链表里的元素除了自身的值外,还需要指向下一个元素,所以我们可以定义Node类如下:

public class Node{ public var value: Value public var next: Node? public init(value: Value, next: Node? = nil) { self.value = value self.next = next }}



同时为了方便打印数据,我们为Node实现CustomStringConvertible协议

extension Node: CustomStringConvertible { public var description: String { guard let next = next else { return "\(value)" } return "\(value) -> " + String(describing: next) + " " }}



接下来我们实现一个LinkedList类,这个类有head、trail、count三个属性,为了保护数据安全,这三个属性不允许外界直接设置,同样为其实现CustomStringConvertible协议:

public class LinkedList {
public private(set) var head: Node? public private(set) var tail: Node? public private(set) var count: Int = 0
public init(value: Value? = nil) { if let value = value { head = Node(value: value) tail = head count = 1 } }
public var isEmpty: Bool { return count == 0 }}
extension LinkedList: CustomStringConvertible { public var description: String { guard let head = head else { return "Empty list" } return String(describing: head) }}



如此,一个链表就定义好了,但现在只有初始化init方法,还没有查找、插入、删除等操作的方法,接下来我们来一一添加这些方法。

查找:

为了后续操作的方便,我们先添加一个查找方法,通过位置查找对应的节点。上面概念部分已经讲到,链表的存储位置和逻辑秩序是没有对应关系的,要查找某个位置的节点,只能通过前驱节点一步步找下一个节点。因此耗时是O(n),在最坏的情况下需要查找n次。具体实现如下:

 

public func node(at index: Int) -> Node? { guard index < count else { return 0 }

var currentIndex = 0 var currentNode = head while currentIndex < index, currentNode != nil { currentIndex += 1 currentNode = currentNode?.next } return currentNode }



添加操作:

1. push:添加一个元素到链表头部

2. append:添加一个元素到链表尾部

3. insert(after:):添加一个元素到特定节点后

做这三个操作的时候,需要注意要更新head、tail和count。
以上三个添加操作的性能都是O(1)。


push

添加元素到头部,有两种情况:
一是此时链表还没有头部,还是空链表,那么就需要同时设置head和tail;
二是链表已经有头部了,就需要先把原来的头部作为新节点的下一个节点,然后在把这个节点设为head。

最后count加1。

public func push(_ value: Value) { let newNode = Node(value: value, next: head) head = newNode if tail == nil { tail = head } count += 1}


append

添加元素到尾部,也是有两种情况:
一是此时链表为空,没有头部尾部,那么和push操作相同;
二是链表不为空,有尾部,那就需要把新节点设为旧尾部的下一个节点,并把新节点设为tail。
最后count加1.

public func append(_ value: Value) { if isEmpty { push(value) return    }
let newNode = Node(value: value) tail?.next = newNode tail = newNode count += 1}


 

insert(after:)

添加元素到某个节点后面,需要先把该节点的next设置为新节点的next,再把新节点设为该节点的next。此时链表不为空,head不需要更新,但是要考虑是否需要更新tail,如果是添加在tail后面,那就和append操作一样,可以直接调用append方法,具体实现如下:

public func insert(_ value: Value, after node: Node) -> Node{ if node === tail { append(value) return tail! }
node.next = Node(value: value, next: node.next) return node.next!}




insert(at:)

此外还可以添加节点到某个位置index,利用我们上面的查找方法,先找出index-1位置的节点,再调用上面的insert方法。此时就需要注意index == 0的情况:

@discardableResult public func insert(_ value: Value, at index: Int) -> Node? { guard count > 0 else { push(value) return head! }

guard index > 0 else { insert(value, after: head!) return head!.next! }

if let node = node(at: index - 1) { return insert(value, after: node) }

return nil }




删除操作:

删除操作也主要是有三个:
1. pop:删除头部节点,即head
2. removeLast:删除尾部节点:即tail
3. remove(after:):删除某个节点的下一个节点
4. remove(at:):删除某个位置的节点

删除操作也需要考虑是否需要更新head和tail,同时需要给count做减1操作

以上三个删除操作中1、3的性能是O(1),2的性能是O(n)。

pop

删除头部节点,删除后,需要把head.next设置为新head,实际只需要重新设置head即可。另外需要注意tail是否需要更新,当只有一个节点时,删除一个后,tail也需要设置为nil。

 

@discardableResult public func pop() -> Value? { guard !isEmpty else { return nil } let oldHead = head head = oldHead?.next count -= 1 if tail === oldHead { tail = nil } return oldHead?.value }



removeLast

删除最后一个节点,其实是要做两件事,一是把tail的前驱节点的next设为nil,再tail设置为前驱节点。因此这里就涉及到要找tail的前驱节点,这个查找操作就需要O(n)的时间。我们上面已经实现了一个查找方法,这里可以直接使用了。

@discardableResult public func removeLast() -> Value? { guard count > 1 else { return pop() }

let removedNode = tail let pre = node(at: count - 2) pre?.next = nil tail = pre count -= 1 return removedNode?.value }



remove(after:)

删除一个节点的下一个节点,同样需要考虑删除的是否是尾节点,如果是,需要更新tail:

@discardableResult public func remove(after node: Node) -> Value? { defer { if node.next === tail { tail = node } node.next = node.next?.next } if node.next != nil { count -= 1 } return node.next?.value }



remove(at:)

删除特定位置的节点,这个可以利用查找方法,找到前一个位置的node,再调用上面的remove(after:)方法即可,这里需要注意第0个位置时,需要特殊处理:

@discardableResult public func remove(at index: Int) -> Value? { guard index >= 0, index < count - 1 else { return nil } if index == 0 { return pop() } if let node = node(at: index - 1) { return remove(after: node) } return nil }