链表1-数据结构与算法之Swift实现1
概念
链表又分为单向链表、双向链表、循环链表,单链表是只有一个前进方向,只能从一个节点链接到下一个节点,而双向链表既可以链接到下一个节点,也可以链接到上一个节点。循环列表则是链表首尾也是链接的。这里只实现单向链表。
性能
链表的元素读取性能是O(n),在最坏的情况下,需要遍历所有的元素,才能访问到需要的元素。
插入和删除性能是O(1),每次只需要常数的时间就能插入和删除元素。
Swift实现
根据上面的定义,我们可以知道,链表里的元素除了自身的值外,还需要指向下一个元素,所以我们可以定义Node类如下:
public class Node{public var value: Valuepublic var next: Node?public init(value: Value, next: Node? = nil) {self.value = valueself.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 = 0public init(value: Value? = nil) {if let value = value {head = Node(value: value)tail = headcount = 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 = 0var currentNode = headwhile currentIndex < index, currentNode != nil {currentIndex += 1currentNode = 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 = newNodeif 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 = newNodetail = newNodecount += 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的情况:
@discardableResultpublic 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。
public func pop() -> Value? {guard !isEmpty else { return nil }let oldHead = headhead = oldHead?.nextcount -= 1if tail === oldHead {tail = nil}return oldHead?.value}
removeLast
删除最后一个节点,其实是要做两件事,一是把tail的前驱节点的next设为nil,再tail设置为前驱节点。因此这里就涉及到要找tail的前驱节点,这个查找操作就需要O(n)的时间。我们上面已经实现了一个查找方法,这里可以直接使用了。
public func removeLast() -> Value? {guard count > 1 else {return pop()}let removedNode = taillet pre = node(at: count - 2)pre?.next = niltail = precount -= 1return removedNode?.value}
remove(after:)
删除一个节点的下一个节点,同样需要考虑删除的是否是尾节点,如果是,需要更新tail:
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个位置时,需要特殊处理:
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}
