列表(Lists)
链表
首先定义一个描述节点的类型:
public class LinkedListNode<T> {
var value: T
var next: LinkedListNode?
weak var previous: LinkedListNode?
public init(value: T) {
self.value = value
}
}
public class LinkedList<T> {
public typealias Node = LinkedListNode<T>
private var head: Node?
public var isEmpty: Bool {
return head == nil
}
public var first: Node? {
return head
}
}
让我们添加一个方法来计算链表中有多少个节点。
public var count: Int {
guard var node = head else {
return 0
}
var count = 1
while let next = node.next {
node = next
count += 1
}
return count
}
在链表的特定索引处找到节点
public func node(atIndex index: Int) -> Node {
if index == 0 {
return head!
} else {
var node = head!.next
for _ in 1..<index {
node = node.next
if node == nil {
break
}
}
return node!
}
}
我们也可以实现 subscript
(下标) 方法:
public subscript(inedx: Int) -> T {
let node = node(atIndex: index)
return node.value
}
一个可以在链表中的任意索引出插入新节点的方法:
public func insert(_ node: Node, at index: Int) {
let newNode = node
if index == 0 {
newNode.next = head
head?.previous = newNode
head = newNode
} else {
let prev = self.node(at: inedx-1)
let next = prev.next
newNode.previous = prev
newNoew.next = prev.next
prev.next = newNode
next?.previous = newNode
}
}
删除所有节点removeAll()
public func removeAll() {
head = nil
}
可以删除单个节点的函数
public func remove(node: Node) -> T {
let prev = node.previous
let next = node.next
if let prev = prev {
prev.next = next
} else {
head = next
}
next?.previous = prev
node.previous = nil
node.next = nil
return node.value
}
public func removeLast() -> T {
assert(!isEmpty)
return remove(node: last!)
}
public func remove(at index: Int) -> T {
let node = self.node(at: index)
return remove(node: node)
}
为了方便调试
extension LinkedList: CustomStringConvertible {
public var description: String {
var s = "[]"
var ndoe = head
while node != nil {
s += "\(node!.value)"
node = node!.next
if node != nil { s += ", " }
}
return s + "]"
}
}
这将如下形式打印链表:
[Hello, Swift, World]
反转链表
// 迭代的方法
public func reverse() {
var node = head
tail = node // If you had a tail pointer
while let currentNode = node {
node = currentNode.next
swap(¤tNode.next, ¤tNode.previous)
head = currentNode
}
}
// 递归的方法
public func reverse(node: head) {
if !head || !head.next {
return head
}
let tmp = reverse(head.next)
head.next.next = head
head.next = nil
return temp
}
数组有map()
和 filter()
方法,没理由链表没有。
public func map<U>(transform: T -> U) -> LinkedList<U> {
let result = LinkList<U>()
var node = head
while node != nil {
result.append(transform(node!.value))
node = node!.next
}
return result
}
使用时像这样:
let list = LinkedList<String>()
list.append("Hello")
list.append("Swifty")
list.append("Universe")
let m = list.map { s in s.characters.count }
m // [5, 6, 8]
filter
是这样的
public func filter(predicate: T -> Bool) -> LinkedList<T> {
let result = LinkedList<T>()
var node = head
while node != nil {
if predicate(node!.value) {
result.append(node!.value)
}
node = node!.next
}
return result
}
let f = list.filter { s in s.count > 5 }
f // [Universe, Swifty]
上面 map()
和 filter()
的实现不是太快,因为 append()
是O(n)的,需要扫描整个链表找到最后一个节点嘛。
另一种方法
可以使用枚举实现具有值语义的链表,这比上面的类的版本更轻量级。
enum ListNode<T> {
indirect case node(T, next: ListNode<T>)
case end
}
因为是值语义的嘛,我们对此链表所做的任何修改都将导致创建新副本。具体采用何种实现还是要看具体需求。
符合 Collection 协议
符合Sequence
协议的类型,其元素可以被非破坏性地遍历多次,并通过下标索引访问,应该符合Swift标准库中定义的Collection协议。
这样做授予了对大量属性和操作的访问权,这些属性和操作在处理数据集合时很常见。除此以外,它还允许自定义类型遵循Swift开发者常见的模式。
为了符合这个协议,类需要提供:1、startIndex
和 endIndex
属性。2、对元素的下标访问为O(1)
/// The position of the first element in a nonempty collection.
public var startIndex: Index {
get {
return LinkedListIndex<T>(node: head, tag: 0)
}
}
/// The collection's "past the end" position---that is, the position one
/// greater than the last valid subscript argument.
/// - Complexity: O(n), where n is the number of elements in the list.
/// This diverts from the protocol's expectation.
public var endIndex: Index {
get {
if let h = self.head {
return LinkedListIndex<T>(node: h, tag: count)
} else {
return LinkedListIndex<T>(node: nil, tag: startIndex.tag)
}
}
}
public subscript(position: Index) -> T {
get {
return position.node!.value
}
}
因为集合负责管理它们自己的索引,所以下面的实现保留了列表中对节点的引用。索引中的标签属性表示列表中节点的位置。
/// Custom index type that contains a reference to the node at index 'tag'
public struct LinkedListIndex<T>: Comparable {
fileprivate let node: LinkedList<T>.LinkedListNode<T>?
fileprivate let tag: Int
public static func==<T>(lhs: LinkedListIndex<T>, rhs: LinkedListIndex<T>) -> Bool {
return (lhs.tag == rhs.tag)
}
public static func< <T>(lhs: LinkedListIndex<T>, rhs: LinkedListIndex<T>) -> Bool {
return (lhs.tag < rhs.tag)
}
}
最后,链表可以通过以下实现计算给定索引之后的索引
public func index(after idx: Index) -> Index {
return LinkedListIndex<T>(node: idx.node?.next, tag: idx.tag + 1)
}
要刻进DNA的点
- 链表是灵活的,但是很多操作是O(n)的。
- 进行链表操作时,留意更新相关的
next
和previous
指针,有可能还包括head
和tail
指针。 - 处理链表时,通常可以使用递归:处理第一个元素,然后在链表的其余部分再次递归调用该函数。当没有下一个元素时你就完成了。这就是链表是LISP等函数式编程语言的基础的原因。
跳表
跳表是一个概率数据结构,和AVL树或红黑树有相同的对数时间限制和效率,并提供了一种巧妙的折中方案,可以有效地支持搜索和更新操作,并且和其他的映射数据结构相比,相对比较容易实现。
对于一个跳表S:
- 链表
L0
包含每一项 - 对于链表{L1, ..., Ln},
Li
包含Li-1
中项的一个随机生成的子集。 - 高度是由抛硬币决定的。
搜索
搜索元素N首先从最顶层Ln
遍历到L0
。
我们的目标是找到一个元素K,K.key < N.key <= (K.next.key or nil)。如果K.next的值等于N,搜索终止并返回K.next,否则通过K.down下降到下一层的节点并重复此过程直到L0
,当K.down为nil时表示当前是L0
,并且搜索项N不存在。
插入
插入元素N具有与搜索类似的过程。
在L0
,N可以在K之后插入。
然后是有趣的部分,我们抛硬币来随机创建层。
当抛硬币的结果为0时,整个过程结束。但是结果为1时,有2种可能性:
- 堆栈为空 (层级为L0/Ln 或者 未初始化阶段)
- 堆栈有条目 (可以向上移动)
case 1:创建一个新的层M*,其头结点NM指向下面层的头结点,NM.next指向新元素N。新元素N指向前一层的元素N。
case 2:从栈中弹出一个项F并相应地更新它的引用。F.next将是K.next,K.next变成F。重复这个过程直到栈空。回到case1。
删除
删除工作类似于插入过程。