diff --git a/IOS/Task02:算法实现/1.三种递归问题求解.md b/IOS/Task02:算法实现/1.三种递归问题求解.md new file mode 100644 index 0000000..f4b0424 --- /dev/null +++ b/IOS/Task02:算法实现/1.三种递归问题求解.md @@ -0,0 +1,172 @@ +# 三种递归问题求解 + +## 斐波那契数列 + +斐波那契数列是一系列数字,除了第一个和第二个数字外,任何数字都是前两个数字之和。 +$$ +0、1、2、3、5、8、13、21... +$$ +于是我们可以写出斐波那契数列的伪代码: + +```swift +fib(n) = fib(n - 1) + fib(n - 2) +``` + +这种方式十分适合递归求解,对于斐波那契数列而言,我们可以指定数列最前面两个元素为:0与1。于是我们可以将伪代码翻译成Swift中源码: + +```swift +func fib2(n: UInt) -> UInt { + if (n < 2) { + // base cases + return n + } + return fib2(n: n - 2) + fib2(n: n - 1) + // recursive cases +} +``` + +这种方式虽然可以运行,但是时间复杂度过大,随着调用数值的不断增加,这种算法的时间复杂度达到了指数级别!每次的计算都会存在大量的重复计算: + +- 当我们计算fib(4)时,算法会计算fib(3)与fib(2) +- 在计算fib(3)时,算法会计算fib(2)与fib(1),这样fib(2)被计算了两次,当计算fib(100)时,算法会存在大量计算重复的内容 + +如何修复这种问题呢?既然fib(2)被计算了两次,那么我们可以开启一份`备忘录`,每当算法计算到fib(2)时,如果这个值已经存在,那么直接从备忘录中调用即可! + +```swift +var fibMemo: [UInt: UInt] = [0: 0, 1: 1] // our old base cases +func fib3(n: UInt) -> UInt { + if let result = fibMemo[n] { // our new base case + return result + } else { + fibMemo[n] = fib3(n: n - 1) + fib3(n: n - 2) // memoization + } + return fibMemo[n]! +} +``` + +保持斐波那契数列还有一种性能更高的办法:迭代 + +```swift +func fib4(n: UInt) -> UInt { + if (n == 0) { // special case + return n + } + var last: UInt = 0, next: UInt = 1 // initially set to fib(0) & fib(1) + for _ in 1.. Double { + let numerator: Double = 4 + var denominator: Double = 1 + var operation: Double = -1 + var pi: Double = 0 + for _ in 0..: CustomStringConvertible { + private var container: [T] = [T]() + public func push(_ thing: T) { container.append(thing) } + public func pop() -> T { return container.removeLast() } + public var description: String { return container.description } +} +``` + +下面我们将塔定义为Stack,并初始化汉诺塔 + +```swift +var numDiscs = 3 +var towerA = Stack() +var towerB = Stack() +var towerC = Stack() +for i in 1...numDiscs { // initialize the first tower + towerA.push(i) +} +``` + +如何求解汉诺塔问题?我们只需要处理两种情况: + +- 移动一个盘子(base case) +- 移动多个盘子(递归处理) + +我们可以将汉诺塔问题的递归解决方案分为3个步骤: + +1. 以塔C为中介,蒋上面的n-1个盘子从塔A移动到塔B(临时塔) +2. 将底部的盘子从塔A移动到塔C +3. 将n-1个盘子从塔B移动到塔C + +这种算法不仅用于3个盘子的情况,甚至可以递归解决任意数量的盘子。我们只需要完成base case的搭建,剩下的任务交给递归处理即可: + +```swift +func hanoi(from: Stack, to: Stack, temp: Stack, n: Int) { + if n == 1 { // base case + to.push(from.pop()) // move 1 disk + } else { // recursive case + hanoi(from: from, to: temp, temp: to, n: n-1) + hanoi(from: from, to: to, temp: temp, n: 1) + hanoi(from: temp, to: to, temp: from, n: n-1) + } +} +``` + +我们不必理解将多个盘子从塔A移动到塔C所需的每一步。这就是采用递归方法的魅力所在——只需要考虑抽象的解决方案,而不是罗列出每一种单独的动作。不过值得注意的是,上述算法的时间复杂度处在指数级别。 + +## 练习作业 + +1. 自己设计一种斐波那契数列的计算算法,编写测试单元并对比上述算法的性能差异 +2. 寻找相应数学公式,计算无理数e +3. 编写汉诺塔问题的解决程序,以解决任意数量的汉诺塔问题 + + + +[1] 百度百科-汉诺塔 + +*参考代码开源版权说明: + +```swift +// Copyright 2017 David Kopec +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +``` + + + diff --git a/IOS/Task02:算法实现/2.搜索问题.md b/IOS/Task02:算法实现/2.搜索问题.md new file mode 100644 index 0000000..58d0c9e --- /dev/null +++ b/IOS/Task02:算法实现/2.搜索问题.md @@ -0,0 +1,214 @@ +# 搜索问题 + +## 二分搜索 + +搜索问题通常有较多的解决方法。一般而言,**线性搜索**按照原始的数据结构顺序,遍历搜索空间中的每一个元素。线性搜索是最简单、最自然、最直观的搜索方法。该算法的时间复杂度最坏情况下为 $O(n)$ ,其中 $n$ 为待搜索结构中的元素总个数。 + +线性搜索的代码也十分简单: + +```swift +func linearContains(_ array: Array, Target: ourTarget ) -> Bool { + for element in Array where Target == ourTarget { + return true + } + return false +} +``` + + + +然而,如果我们已经知道了数据结构的顺序,即每一种元素都是排列好的,并且可以通过索引立即访问数据结构内部的任何项,那么我们就可以执行**二分搜索**(Binary Search)。 + +假设存在一个按照字母序排列的数组Array,我们的目标是搜索f: + +```swift +Array = [a, b, c, d, e, f, g] +``` + +这7个字母的中间值为 $d$,我们可以确定要搜索的目标位于 $d$ 之后,因此我们再于 $e, f, g$ 中搜索中间元素 $f$,即可完成目标。 + +二分搜索与线性搜索不同,它不需要遍历结构中的每一个元素。二分搜索能不断将搜索空间减半,因此最坏情况下的时间复杂度为 $O(\text{lg } n)$ 。二分搜索的缺点也显而易见:我们需要对数组进行排序,排序的最佳时间复杂度也需要 $O(n \text{lg }n)$ ,实际上,如果只运行一次,且原始数据为未排序数组,线性搜索的效果要好于二分搜索。 + +以基因和密码子的二分搜索为例,其中Gene类型为Array,而Codon可以与其他Codon进行比较,代码如下 + +```swift +func binaryContains(_ array: Gene, item: Codon) -> Bool { + var low = 0 + var high = array.count - 1 + while low <= high { + let mid = (low + high) / 2 + if array[mid] < item { + low = mid + 1 + } else if array[mid] > item { + high = mid - 1 + } else { + return true + } + } + return false +} +``` + +## 深度优先搜索(DFS) + +**深度优先搜索**(Depth-First Search, DFS)为首先尽可能深入地搜索,如果到达终点,则回溯到最后一个决策点。此处我们以一款小游戏为例,假设右下角的绿色顶点为起始点,橙色方块为障碍物,最左上角的白色方块为终点,DFS的算法过程如下: + +1 + +深度优先搜索给出的答案: + +2 + + + + + +深度优搜索依赖于**栈**(Stack)这种数据结构,前文的递归中我们已经提到过:栈有两种最基本的操作 + +1. push() 压栈,将元素送入栈顶 +2. pop() 移除栈顶元素并返回该元素 + +在Swift中栈的实现也十分简单: + +```swift +public class Stack { + private var container: [T] = [T]() + public var isEmpty: Bool { return container.isEmpty } + public func push(_ thing: T) { container.append(thing) } + public func pop() -> T { return container.removeLast() } +} +``` + +DFS的精髓在于可以回溯,要实现这个功能,我们需要添加一个类Node,用于记录当前搜索的状态(或从一个状态到另一个状态的方式)。Node可以看成包围状态的包装器。我们将状态来源的Node称为parent。此外,我们还需定义Node的类拥有cost和heuristic属性,并且能够比较Comparable和散列化Hashable + +```swift +class Node: Comparable, Hashable { + let state: T + let parent: Node? + let cost: Float + let heuristic: Float + init(state: T, parent: Node?, cost: Float = 0.0, heuristic: Float = 0.0) { + self.state = state + self.parent = parent + self.cost = cost + self.heuristic = heuristic + } + + var hashValue: Int { return Int(cost + heuristic) } +} + +func < (lhs: Node, rhs: Node) -> Bool { + return (lhs.cost + lhs.heuristic) < (rhs.cost + rhs.heuristic) +} + +func == (lhs: Node, rhs: Node) -> Bool { + return lhs === rhs +} +``` + +深度优先搜索中需要记录两种结构:正准备搜索的状态和已经搜索的状态,分别用frontier和explored表示。只要frontier中还有更多的状态要访问,DFS就会继续检查他们是否为目标值,并且将这些状态的后者加入frontier中。DFS还会把搜索过的点标记为explored,从而避免DFS陷入死循环,到达那些作为后继者先前已经访问过的状态。如果frontier是空的,则意味着没有地方可以继续搜索。 + +```swift +func dfs(initialState: StateType, goalTestFn: (StateType) -> Bool, successorFn: (StateType) -> [StateType]) -> Node? { + // frontier is where we've yet to go + let frontier: Stack> = Stack>() + frontier.push(Node(state: initialState, parent: nil)) + // explored is where we've been + var explored: Set = Set() + explored.insert(initialState) + + // keep going while there is more to explore + while !frontier.isEmpty { + let currentNode = frontier.pop() + let currentState = currentNode.state + // if we found the goal, we're done + if goalTestFn(currentState) { return currentNode } + // check where we can go next and haven't explored + for child in successorFn(currentState) where !explored.contains(child) { + explored.insert(child) + frontier.push(Node(state: child, parent: currentNode)) + } + } + return nil // never found the goal +} +``` + +## 广度优先搜索(BFS) + +深度优先搜索尽管提出了一种较为可靠的搜索方式,但是时间复杂度较大。通过深度优先遍历的路径通常不是最短路径。与之对应的**广度优先算法**(Breadth-First Search,BFS)总是查找到最短的路径,因为广度优先搜索的策略为优先遍历距离当前节点较近的点,找到的路径一定是最短的。但是,广度优先搜索并不总是比深度优先搜索性能优异,有些情况下深度优先搜索可能在广度优先搜索之前找到解决办法。运用DFS还是BFS,取决于我们在快速找到解决方法的可能性与查找到目标的最短路径的确定性之间的权衡。 + +3 + +值得注意的是,BFS给出的路径是最短的,但并不一定唯一。 + +与深度优先搜索不同,广度优先搜索依赖于**队列**(queue)结构,栈与队列的区别在于: + +- 栈为后进先出(LIFO) +- 队列为先进先出(FIFO,First-In-First-Out) + +队列也对应着至少两种操作: + +1. push() 将元素添加到队列中 +2. pop() 将先添加的元素从队列中删除 + +实际上,在Swift中Array的队列实现与栈的实现几乎完全相同,区别就是从Array的左侧而不是右侧移除元素。Array最左侧元素是Array中存在最久的一个元素。队列的参考实现如下: + +```swift +public class Queue { + private var container: [T] = [T]() + public var isEmpty: Bool { return container.isEmpty } + public func push(_ thing: T) { container.append(thing) } + public func pop() -> T { return container.removeFirst() } +} +``` + +BFS的实现很大程度上与DFS类似甚至相同,只是frontier从栈变成了队列。将frontier从栈更改为队列就会更改状态被搜索的顺序,确保首先搜索最长接近初始状态的状态。 + +```swift +func bfs(initialState: StateType, goalTestFn: (StateType) -> Bool, successorFn: (StateType) -> [StateType]) -> Node? { + // frontier is where we've yet to go + let frontier: Queue> = Queue>() + frontier.push(Node(state: initialState, parent: nil)) + // explored is where we've been + var explored: Set = Set() + explored.insert(initialState) + // keep going while there is more to explore + while !frontier.isEmpty { + let currentNode = frontier.pop() + let currentState = currentNode.state + // if we found the goal, we're done + if goalTestFn(currentState) { return currentNode } + // check where we can go next and haven't explored + for child in successorFn(currentState) where !explored.contains(child) { + explored.insert(child) + frontier.push(Node(state: child, parent: currentNode)) + } + } + return nil // never found the goal +} +``` + +## 练习 + +1. 如果二分搜索过程中数组长度为偶数该怎么办?代码如何实现? +2. 为dfs()、bfs()函数各添加一个计数器,以查看每个函数在搜索相同迷宫时所遍历的状态数量。并在上图中画出dfs和bfs的实现过程。 +3. 运用dfs与bfs实现“八皇后”问题。 + +*参考代码开源版权说明: + +```swift +// Copyright 2017 David Kopec +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +``` + diff --git a/IOS/Task02:算法实现/3.图问题.md b/IOS/Task02:算法实现/3.图问题.md new file mode 100644 index 0000000..a973950 --- /dev/null +++ b/IOS/Task02:算法实现/3.图问题.md @@ -0,0 +1,372 @@ +# 图问题 + +## 构建图框架 + +Swift语言的定义为面向协议的编程范式(区别于传统的面相对象或函数范式)。尽管这种新范式的正统性仍然在充实阶段,但可以明确的是:这种语义将接口和复合放在继承之前。相对于类是面向对象语言的基本构建块,函数是函数式编程语言的基本构建块,所以在面向协议的编程语言中,协议是基本的构建块。下面,我们将以协议的方式构建块。 + +```swift +public protocol Edge: CustomStringConvertible { + var u: Int { get set } // index of the "from" vertex + var v: Int { get set } // index of the "to" vertex + var reversed: Edge { get } +} +``` + +我们对Edge定义为两个顶点之间的连接。用一个整数索引表示。按照习惯,我们通常用 $u$ 表示第一个顶点,用 $v$ 表示第二个顶点。本章中,我们只处理双向边,而在图论中,有向边也常常是讨论的对象。 + +我们使用Swfit语言关键字associatedtype来定义Graph采用者(adopters)可以配置的类型。 + +```swift +protocol Graph: class, CustomStringConvertible { + associatedtype VertexType: Equatable + associatedtype EdgeType: Edge + var vertices: [VertexType] { get set } + var edges: [[EdgeType]] { get set } +} +``` + +实现图数据结构的方法有很多种,最常用的两种就是顶点矩阵和邻接矩阵。在顶点矩阵方法中,每个矩阵单元表示两个个顶点的交接,下面是对Graph的一种完整协议拓展,为协议拓展添加了基本函数: + +```swift +extension Graph { + /// How many vertices are in the graph? + public var vertexCount: Int { return vertices.count } + + /// How many edges are in the graph? + public var edgeCount: Int { return edges.joined().count } + + /// Get a vertex by its index. + /// + /// - parameter index: The index of the vertex. + /// - returns: The vertex at i. + public func vertexAtIndex(_ index: Int) -> VertexType { + return vertices[index] + } + + /// Find the first occurence of a vertex if it exists. + /// + /// - parameter vertex: The vertex you are looking for. + /// - returns: The index of the vertex. Return nil if it can't find it. + public func indexOfVertex(_ vertex: VertexType) -> Int? { + if let i = vertices.index(of: vertex) { + return i + } + return nil + } + + /// Find all of the neighbors of a vertex at a given index. + /// + /// - parameter index: The index for the vertex to find the neighbors of. + /// - returns: An array of the neighbor vertices. + public func neighborsForIndex(_ index: Int) -> [VertexType] { + return edges[index].map({self.vertices[$0.v]}) + } + + /// Find all of the neighbors of a given Vertex. + /// + /// - parameter vertex: The vertex to find the neighbors of. + /// - returns: An optional array of the neighbor vertices. + public func neighborsForVertex(_ vertex: VertexType) -> [VertexType]? { + if let i = indexOfVertex(vertex) { + return neighborsForIndex(i) + } + return nil + } + + /// Find all of the edges of a vertex at a given index. + /// + /// - parameter index: The index for the vertex to find the children of. + public func edgesForIndex(_ index: Int) -> [EdgeType] { + return edges[index] + } + + /// Find all of the edges of a given vertex. + /// + /// - parameter vertex: The vertex to find the edges of. + public func edgesForVertex(_ vertex: VertexType) -> [EdgeType]? { + if let i = indexOfVertex(vertex) { + return edgesForIndex(i) + } + return nil + } + + /// Add a vertex to the graph. + /// + /// - parameter v: The vertex to be added. + /// - returns: The index where the vertex was added. + public func addVertex(_ v: VertexType) -> Int { + vertices.append(v) + edges.append([EdgeType]()) + return vertices.count - 1 + } + + /// Add an edge to the graph. + /// + /// - parameter e: The edge to add. + public func addEdge(_ e: EdgeType) { + edges[e.u].append(e) + edges[e.v].append(e.reversed as! EdgeType) + } +} +``` + +正如前面所述,本章我们只讨论双向边。除了分为双向边和无向边外,边还可以赋予权重。下面我们实现一种不带权重的边UnweightedEdge,当然也会实现Edge协议。Edge协议必须定义“from”顶点u “to” 顶点v,以及一种反转Edge的方式。Edge协议还要按照Edge的要求必须实现CustomStringConvertible,这意味着定义一个description属性。 + +```swift +open class UnweightedEdge: Edge { + public var u: Int // "from" vertex + public var v: Int // "to" vertex + public var reversed: Edge { + return UnweightedEdge(u: v, v: u) + } + + public init(u: Int, v: Int) { + self.u = u + self.v = v + } + + //MARK: CustomStringConvertable + public var description: String { + return "\(u) <-> \(v)" + } +} +``` + +Graph的具体实现十分简单,UnweightedGraph就是一个顶点可以是任意Equatable类型、边是UnweightedEdge类型的Graph。通过定义vertices和edges数组的类型,我们在Graph协议中隐式地填充关联类型VertexType和EdgeType + +```swift +open class UnweightedGraph: Graph { + var vertices: [V] = [V]() + var edges: [[UnweightedEdge]] = [[UnweightedEdge]]() //adjacency lists + + public init() { + } + + public init(vertices: [V]) { + for vertex in vertices { + _ = self.addVertex(vertex) + } + } + + /// This is a convenience method that adds an unweighted edge. + /// + /// - parameter from: The starting vertex's index. + /// - parameter to: The ending vertex's index. + public func addEdge(from: Int, to: Int) { + addEdge(UnweightedEdge(u: from, v: to)) + } + + /// This is a convenience method that adds an unweighted, undirected edge between the first occurence of two vertices. + /// + /// - parameter from: The starting vertex. + /// - parameter to: The ending vertex. + public func addEdge(from: V, to: V) { + if let u = indexOfVertex(from) { + if let v = indexOfVertex(to) { + addEdge(UnweightedEdge(u: u, v: v)) + } + } + } + + /// MARK: Implement CustomStringConvertible + public var description: String { + var d: String = "" + for i in 0.. \(vertexAtIndex(edge.v))") + } + } +} +``` + +根据上面的原理,我们可以定带权重的图结构: + +```swift +/// A subclass of Graph that has convenience methods for adding and removing WeightedEdges. All added Edges should have the same generic Comparable type W as the WeightedGraph itself. +open class WeightedGraph: Graph { + var vertices: [V] = [V]() + var edges: [[WeightedEdge]] = [[WeightedEdge]]() //adjacency lists + + public init() { + } + + public init(vertices: [V]) { + for vertex in vertices { + _ = self.addVertex(vertex) + } + } + + /// Find all of the neighbors of a vertex at a given index. + /// + /// - parameter index: The index for the vertex to find the neighbors of. + /// - returns: An array of tuples including the vertices as the first element and the weights as the second element. + public func neighborsForIndexWithWeights(_ index: Int) -> [(V, W)] { + var distanceTuples: [(V, W)] = [(V, W)]() + for edge in edges[index] { + distanceTuples += [(vertices[edge.v], edge.weight)] + } + return distanceTuples + } + + /// This is a convenience method that adds a weighted edge. + /// + /// - parameter from: The starting vertex's index. + /// - parameter to: The ending vertex's index. + /// - parameter weight: the Weight of the edge to add. + public func addEdge(from: Int, to: Int, weight:W) { + addEdge(WeightedEdge(u: from, v: to, weight: weight)) + } + + /// This is a convenience method that adds a weighted edge between the first occurence of two vertices. It takes O(n) time. + /// + /// - parameter from: The starting vertex. + /// - parameter to: The ending vertex. + /// - parameter weight: the Weight of the edge to add. + public func addEdge(from: V, to: V, weight: W) { + if let u = indexOfVertex(from) { + if let v = indexOfVertex(to) { + addEdge(WeightedEdge(u: u, v: v, weight:weight)) + } + } + } + + //Implement Printable protocol + public var description: String { + var d: String = "" + for i in 0.. Bool { + return lhs.distance < rhs.distance + } + + public static func == (lhs: DijkstraNode, rhs: DijkstraNode) -> Bool { + return lhs.distance == rhs.distance + } + } + + /// Finds the shortest paths from some route vertex to every other vertex in the graph. + /// + /// - parameter graph: The WeightedGraph to look within. + /// - parameter root: The index of the root node to build the shortest paths from. + /// - parameter startDistance: The distance to get to the root node (typically 0). + /// - returns: Returns a tuple of two things: the first, an array containing the distances, the second, a dictionary containing the edge to reach each vertex. Use the function pathDictToPath() to convert the dictionary into something useful for a specific point. + public func dijkstra(root: Int, startDistance: W) -> ([W?], [Int: WeightedEdge]) { + var distances: [W?] = [W?](repeating: nil, count: vertexCount) // how far each vertex is from start + distances[root] = startDistance // the start vertex is startDistance away + var pq: PriorityQueue = PriorityQueue(ascending: true) + var pathDict: [Int: WeightedEdge] = [Int: WeightedEdge]() // how we got to each vertex + pq.push(DijkstraNode(vertex: root, distance: startDistance)) + + while let u = pq.pop()?.vertex { // explore the next closest vertex + guard let distU = distances[u] else { continue } // should already have seen it + for we in edgesForIndex(u) { // look at every edge/vertex from the vertex in question + let distV = distances[we.v] // the old distance to this vertex + if distV == nil || distV! > we.weight + distU { // if we have no old distance or we found a shorter path + distances[we.v] = we.weight + distU // update the distance to this vertex + pathDict[we.v] = we // update the edge on the shortest path to this vertex + pq.push(DijkstraNode(vertex: we.v, distance: we.weight + distU)) // explore it soon + } + } + } + + return (distances, pathDict) + } + + + /// A convenience version of dijkstra() that allows the supply of the root + /// vertex instead of the index of the root vertex. + public func dijkstra(root: V, startDistance: W) -> ([W?], [Int: WeightedEdge]) { + if let u = indexOfVertex(root) { + return dijkstra(root: u, startDistance: startDistance) + } + return ([], [:]) + } + + /// Helper function to get easier access to Dijkstra results. + public func distanceArrayToVertexDict(distances: [W?]) -> [V : W?] { + var distanceDict: [V: W?] = [V: W?]() + for i in 0.. [Item] { + //build up dynamic programming table + var table: [[Float]] = [[Float]](repeating: [Float](repeating: 0.0, count: maxCapacity + 1), count: items.count + 1) //initialize table - overshooting in size + for (i, item) in items.enumerated() { + for capacity in 1...maxCapacity { + let previousItemsValue = table[i][capacity] + if capacity >= item.weight { // item fits in knapsack + let valueFreeingWeightForItem = table[i][capacity - item.weight] + table[i + 1][capacity] = max(valueFreeingWeightForItem + item.value, previousItemsValue) // only take if more valuable than previous combo + } else { // no room for this item + table[i + 1][capacity] = previousItemsValue //use prior combo + } + } + } + // figure out solution from table + var solution: [Item] = [Item]() + var capacity = maxCapacity + for i in stride(from: items.count, to: 0, by: -1) { // work backwards + if table[i - 1][capacity] != table[i][capacity] { // did we use this item? + solution.append(items[i - 1]) + capacity -= items[i - 1].weight // if we used an item, remove its weight + } + } + return solution +} +``` + +我们将各种物品填入表单中,进行最优求解: + +```swift +let items = [Item(name: "television", weight: 50, value: 500), + Item(name: "candlesticks", weight: 2, value: 300), + Item(name: "stereo", weight: 35, value: 400), + Item(name: "laptop", weight: 3, value: 1000), + Item(name: "food", weight: 15, value: 50), + Item(name: "clothing", weight: 20, value: 800), + Item(name: "jewelry", weight: 1, value: 4000), + Item(name: "books", weight: 100, value: 300), + Item(name: "printer", weight: 18, value: 30), + Item(name: "refrigerator", weight: 200, value: 700), + Item(name: "painting", weight: 10, value: 1000)] +knapsack(items: items, maxCapacity: 75) +``` + +即可解决背包问题。 + +## 旅行商问题 + +旅行商问题是最经典、最常被讨论的内容之一。一名推销员必须对地图上的所有城市访问一次,最终回到起点城市。每座城市之间都有一条路直连路径,推销员可以按任何顺序访问这些城市。那么对于推销员而言的最短路径是什么? + +这个问题看起来简单,但是还没有能够针对任意数量的城市快速解决该问题的算法。“快速”的含义在此处指的是:这个问题是一个NP难题。NP难题是不存在多项式时间算法的。随着推销员需要访问的城市数量的增加,解决这个问题的难度急剧提高。20个城市的旅行推销员问题相比10个城市的旅行推销员问题要难得多。当城市数量达到上百万个,在合理的时间内要完美解决这个问题是不可能的。 + +```swift +let vtCities = ["Rutland", "Burlington", "White River Junction", "Bennington", "Brattleboro"] + +let vtDistances = [ + "Rutland": + ["Burlington": 67, "White River Junction": 46, "Bennington": 55, "Brattleboro": 75], + "Burlington": + ["Rutland": 67, "White River Junction": 91, "Bennington": 122, "Brattleboro": 153], + "White River Junction": + ["Rutland": 46, "Burlington": 91, "Bennington": 98, "Brattleboro": 65], + "Bennington": + ["Rutland": 55, "Burlington": 122, "White River Junction": 98, "Brattleboro": 40], + "Brattleboro": + ["Rutland": 75, "Burlington": 153, "White River Junction": 65, "Bennington": 40] +] +``` + +上面的表格输入了旅行商问题需要到达的城市与对应距离,对应的解决办法完全代码如下: + +```swift +// backtracking permutations algorithm +func allPermutationsHelper(contents: [T], permutations: inout [[T]], n: Int) { + guard n > 0 else { permutations.append(contents); return } + var tempContents = contents + for i in 0..(_ original: [T]) -> [[T]] { + var permutations = [[T]]() + allPermutationsHelper(contents: original, permutations: &permutations, n: original.count) + return permutations +} + +// test allPermutations +let abc = ["a","b","c"] +let testPerms = allPermutations(abc) +print(testPerms) +print(testPerms.count) + +// make complete paths for tsp +func tspPaths(_ permutations: [[T]]) -> [[T]] { + return permutations.map { + if let first = $0.first { + return ($0 + [first]) // append first to end + } else { + return [] // empty is just itself + } + } +} + +print(tspPaths(testPerms)) + +func solveTSP(cities: [T], distances: [T: [T: Int]]) -> (solution: [T], distance: Int) { + let possiblePaths = tspPaths(allPermutations(cities)) // all potential paths + var bestPath: [T] = [] // shortest path by distance + var minDistance: Int = Int.max // distance of the shortest path + for path in possiblePaths { + if path.count < 2 { continue } // must be at least one city pair to calculate + var distance = 0 + var last = path.first! // we know there is one becuase of above line + for next in path[1..