Breadth-first search 算法(Swift版)

在講解Breadth-first search 算法以前,咱們先簡單介紹兩種數據類型GraphQueuenode

Graph

這就是一個圖,它由兩部分組成: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
}

Queue

隊列一樣是一種數據結構,它遵循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

相關文章
相關標籤/搜索