1. 程式人生 > >Dijkstra算法(Swift版)

Dijkstra算法(Swift版)

map 數組 double sta weight lte turn man 最短

原理

我們知道,使用Breadth-first search算法能夠找到到達某個目標的最短路徑,但這個算法沒考慮weight,因此我們再為每個edge添加了權重後,我們就需要使用Dijkstra算法來尋找權重和最小的路徑。

其實原理很簡單,我們最終的目的是計算出每一個節點到起點的權重之和,同時獲取得到這個權重和的路徑數組。
那麽權重和最小的那個自然就是我們要的結果。

在該算法中有一下幾個核心的思想:

  • 當我們遍歷到某個節點時,計算出該節點到起點的權重和之後=,該節點就不在使用了,或刪除或者標記為已檢閱
  • 當該節點的某個neighbor節點加上權重的值小於該neighbor節點時,跟新該neighbor節點的數據

實現這個算法的方式有多種,在該文章中,我們把某些數據直接封裝到了節點中。

Vertex

Vertex.swift

import Foundation

open class Vertex {

    open var identifier: String
    open var neighbors: [(Vertex, Double)] = []
    open var pathLengthFromStart = Double.infinity
    open var pathVerticesFromStart: [Vertex] = []

    public init(identifier: String) {
        self.identifier = identifier
    }

    open func clearCache() {
        pathLengthFromStart = Double.infinity
        pathVerticesFromStart = []
    }
}

extension Vertex: Hashable {
    open var hashValue: Int {
        return identifier.hashValue
    }
}

extension Vertex: Equatable {
    public static func ==(lhs: Vertex, rhs: Vertex) -> Bool {
        return lhs.hashValue == rhs.hashValue
    }
}

Dijkstra

Dijkstra.swift

import Foundation

public class Dijkstra {
    private var totalVertices: Set<Vertex>

    public init(vertices: Set<Vertex>) {
        totalVertices = vertices
    }

    private func clearCache() {
        totalVertices.forEach { $0.clearCache() }
    }

    public func findShortestPaths(from startVertex: Vertex) {
        clearCache()
        var currentVertices = self.totalVertices
        startVertex.pathLengthFromStart = 0
        startVertex.pathVerticesFromStart.append(startVertex)
        var currentVertex: Vertex? = startVertex
        while let vertex = currentVertex {
            currentVertices.remove(vertex)
            let filteredNeighbors = vertex.neighbors.filter { currentVertices.contains($0.0) }
            for neighbor in filteredNeighbors {
                let neighborVertex = neighbor.0
                let weight = neighbor.1

                let theoreticNewWeight = vertex.pathLengthFromStart + weight
                if theoreticNewWeight < neighborVertex.pathLengthFromStart {
                    neighborVertex.pathLengthFromStart = theoreticNewWeight
                    neighborVertex.pathVerticesFromStart = vertex.pathVerticesFromStart
                    neighborVertex.pathVerticesFromStart.append(neighborVertex)
                }
            }
            if currentVertices.isEmpty {
                currentVertex = nil
                break
            }
            currentVertex = currentVertices.min { $0.pathLengthFromStart < $1.pathLengthFromStart }
        }
    }
}

演示

技術分享

我們就演示這個例子

//: Playground - noun: a place where people can play
import Foundation

// last checked with Xcode 9.0b4
#if swift(>=4.0)
print("Hello, Swift 4!")
#endif

var vertices: Set<Vertex> = Set()

/// Create vertexs
var vertexA = Vertex(identifier: "A")
var vertexB = Vertex(identifier: "B")
var vertexC = Vertex(identifier: "C")
var vertexD = Vertex(identifier: "D")
var vertexE = Vertex(identifier: "E")
var vertexF = Vertex(identifier: "F")

/// Setting neighbors
vertexA.neighbors.append(contentsOf: [(vertexB, 5), (vertexD, 2)])
vertexB.neighbors.append(contentsOf: [(vertexC, 4), (vertexE, 2)])
vertexC.neighbors.append(contentsOf: [(vertexE, 6), (vertexF, 3)])
vertexD.neighbors.append(contentsOf: [(vertexB, 8), (vertexE, 7)])
vertexE.neighbors.append(contentsOf: [(vertexF, 1)])

vertices.insert(vertexA)
vertices.insert(vertexB)
vertices.insert(vertexC)
vertices.insert(vertexD)
vertices.insert(vertexE)
vertices.insert(vertexF)


let dijkstra = Dijkstra(vertices: vertices)
dijkstra.findShortestPaths(from: vertexA)

for vertex in vertices {
    let paths = vertex.pathVerticesFromStart.map({ $0.identifier })
    print("(A=>" + vertex.identifier + "): " + paths.joined(separator: " -> "))
}

打印結果:

(A=>B): A -> B
(A=>A): A
(A=>F): A -> B -> E -> F
(A=>C): A -> B -> C
(A=>D): A -> D
(A=>E): A -> B -> E

主要代碼來自於Dijkstra

Dijkstra算法(Swift版)