public enum Visit<T: Hashable> {
case start
// The vertex has an associated edge that leads to a path back to the starting vertex.
case edge(Edge<T>)
}
public class Dijkstra<T: Hashable> {
public typealias Graph = AdjacencyList<T>
let graph: Graph
public init(graph: Graph) {
self.graph = graph
}
}
// Tracing back to the start
// 從目前所在的點回到起始點的路徑
// paths: 記錄在每個點時拜訪其他點的紀錄
private func route(to destination: Vertex<T>, with paths: [Vertex<T> : Visit<T>]) -> [Edge<T>] {
var vertex = destination
var path: [Edge<T>] = []
// As long as you have not reached the start case, continue to extract the next edge
while let visit = paths[vertex], case .edge(let edge) = visit {
path = [edge] + path
// Set the current vertex to the edge’s source vertex. This moves you closer to the start vertex.
vertex = edge.source
}
return path
}
// Calculating total distance
private func distance(to destination: Vertex<T>, with paths: [Vertex<T> : Visit<T>]) -> Double {
let path = route(to: destination, with: paths)
let distance = path.compactMap { $0.weight }
return distance.reduce(0.0, +)
}
public func shortestPath(from start: Vertex<T>) -> [Vertex<T> : Visit<T>] {
var paths: [Vertex<T> : Visit<T>] = [start : .start]
// Create a min-priority queue to store the vertices that must be visited.
var priorityQueue = PriorityQueue<Vertex<T>> (sort:{
// distance越小越優先
self.distance(to: $0, with: paths) < self.distance(to: $1, with: paths)
})
// Enqueue the start vertex as the first vertex to visit
priorityQueue.enqueue(start)
while let vertex = priorityQueue.dequeue() {
// 找到與此點相連所有邊
for edge in graph.edges(from: vertex) {
guard let weight = edge.weight else { continue }
// 此邊的另一端點尚未拜訪過
// 或找到一條更cheaper path
if paths[edge.destination] == nil || distance(to: vertex, with: paths) + weight < distance(to: edge.destination, with: paths) {
paths[edge.destination] = .edge(edge)
priorityQueue.enqueue(edge.destination)
}
}
}
return paths
}