在講解Breadth-first search 算法以前,咱們先簡單介紹兩種數據類型Graph
和Queue
。node
這就是一個圖,它由兩部分組成:git
這種數據結構能夠形象的表示一個網絡
,而在實際解決問題的時候,咱們除了找到相似網絡
的模擬外,還須要考慮下邊兩點:github
而如何實現這個查找的過程就用到了算法。算法
在項目管理專業的工程方法中,存在一個有向鏈接圖方法,根據這個圖咱們就能夠劃出鄰接矩陣,而後再求出可達矩陣,縮減矩陣等等,說這些內容,是想表達在用代碼模擬圖的時候,可使用矩陣的方式來描述,但本篇中採用的是另外一種方式,咱們使用數組保存某個節點的neighbor節點。swift
上邊一段話會在下邊的代碼中進行展現:數組
Graph.swift // MARK: - Edge public class Edge: Equatable { public var neighbor: Node public init(neighbor: Node) { self.neighbor = neighbor } } public func == (lhs: Edge, rhs: Edge) -> Bool { return lhs.neighbor == rhs.neighbor } // MARK: - Node public class Node: CustomStringConvertible, Equatable { public var neighbors: [Edge] public private(set) var label: String public var distance: Int? public var visited: Bool public init(label: String) { self.label = label neighbors = [] visited = false } public var description: String { if let distance = distance { return "Node(label: \(label), distance: \(distance))" } return "Node(label: \(label), distance: infinity)" } public var hasDistance: Bool { return distance != nil } public func remove(edge: Edge) { neighbors.remove(at: neighbors.index { $0 === edge }!) } } public func == (lhs: Node, rhs: Node) -> Bool { return lhs.label == rhs.label && lhs.neighbors == rhs.neighbors } // MARK: - Graph public class Graph: CustomStringConvertible, Equatable { public private(set) var nodes: [Node] public init() { self.nodes = [] } public func addNode(_ label: String) -> Node { let node = Node(label: label) nodes.append(node) return node } public func addEdge(_ source: Node, neighbor: Node) { let edge = Edge(neighbor: neighbor) source.neighbors.append(edge) } public var description: String { var description = "" for node in nodes { if !node.neighbors.isEmpty { description += "[node: \(node.label) edges: \(node.neighbors.map { $0.neighbor.label})]" } } return description } public func findNodeWithLabel(_ label: String) -> Node { return nodes.filter { $0.label == label }.first! } public func duplicate() -> Graph { let duplicated = Graph() for node in nodes { _ = duplicated.addNode(node.label) } for node in nodes { for edge in node.neighbors { let source = duplicated.findNodeWithLabel(node.label) let neighbour = duplicated.findNodeWithLabel(edge.neighbor.label) duplicated.addEdge(source, neighbor: neighbour) } } return duplicated } } public func == (lhs: Graph, rhs: Graph) -> Bool { return lhs.nodes == rhs.nodes }
隊列一樣是一種數據結構,它遵循FIFO的原則,由於Swift沒有現成的這個數據結構,所以咱們手動實現一個。網絡
值得指出的是,爲了提升性能,咱們針對在數組中讀取數據作了優化。好比,當在數組中取出第一個值時,若是不作優化,那麼這一步的消耗爲O(n),咱們採起的解決方法就是把該位置先置爲nil,而後設置一個閾值,當達到閾值時,在對數組作進不去的處理。數據結構
這一部分的代碼至關簡單app
Queue.swift public struct Queue<T> { fileprivate var array = [T?]() fileprivate var head = 0 public init() { } public var isEmpty: Bool { return count == 0 } public var count: Int { return array.count - head } public mutating func enqueue(_ element: T) { array.append(element) } public mutating func dequeue() -> T? { guard head < array.count, let element = array[head] else { return nil } array[head] = nil head += 1 let percentage = Double(head) / Double(array.count) if array.count > 50 && percentage > 0.25 { array.removeFirst(head) head = 0 } return element } public var front: T? { if isEmpty { return nil } else { return array[head] } } }
其實這個算法的思想也很簡單,咱們已源點爲中心,一層一層的往外查找,在遍歷到某一層的某個節點時,若是該節點是咱們要找的數據,那麼就退出循環,若是沒找到,那麼就把該節點的neighbor節點加入到隊列中,這就是該算法的核心原理。性能
打破循環的條件須要根據實際狀況來設定。
//: Playground - noun: a place where people can play import UIKit import Foundation var str = "Hello, playground" func breadthFirstSearch(_ graph: Graph, source: Node) -> [String] { /// 建立一個隊列並把源Node放入這個隊列中 var queue = Queue<Node>() queue.enqueue(source) /// 建立一個數組用於存放結果 var nodesResult = [source.label] /// 設置Node的visited爲true,由於咱們會把這個當作一個開關 source.visited = true /// 開始遍歷 while let node = queue.dequeue() { for edge in node.neighbors { let neighborNode = edge.neighbor if !neighborNode.visited { queue.enqueue(neighborNode) neighborNode.visited = true nodesResult.append(neighborNode.label) } } } return nodesResult } let graph = Graph() let nodeA = graph.addNode("a") let nodeB = graph.addNode("b") let nodeC = graph.addNode("c") let nodeD = graph.addNode("d") let nodeE = graph.addNode("e") let nodeF = graph.addNode("f") let nodeG = graph.addNode("g") let nodeH = graph.addNode("h") graph.addEdge(nodeA, neighbor: nodeB) graph.addEdge(nodeA, neighbor: nodeC) graph.addEdge(nodeB, neighbor: nodeD) graph.addEdge(nodeB, neighbor: nodeE) graph.addEdge(nodeC, neighbor: nodeF) graph.addEdge(nodeC, neighbor: nodeG) graph.addEdge(nodeE, neighbor: nodeH) graph.addEdge(nodeE, neighbor: nodeF) graph.addEdge(nodeF, neighbor: nodeG) let nodesExplored = breadthFirstSearch(graph, source: nodeA) print(nodesExplored)
實現的代碼不是重點,重要的是理解這些思想,在實際狀況中可以得出解決的方法。固然跟實現的語言也沒有關係。
使用playground時,command + 1
能夠看到Source文件夾,把單獨的類放進去就能夠加載進來了。上邊的內容來自這個網站swift-algorithm-club