Mind and Hand Help

Single-Source Shortest Path

Dijkstra's algorithm is a classic algorithm used to find the shortest path from a starting node to all other nodes in a weighted graph with non-negative edge weights. It is widely used in various applications, including network routing, geographic mapping, and game development.

具体实现

class Dijkstra(graph: Graph) : GraphCompute(graph) { init { checkEmpty() } fun compute(source: String): Result { return compute(source, null) } fun compute(source: String, brokenFilter: Set<String>?): Result { val sourceV = checkVertex(source, true) // 处理断点过滤器 val validBrokenFilter = brokenFilter?.let { filter -> filter.map { name -> checkVertex(name, false) } .map { it.name } .filter { it != sourceV.name } .toMutableSet() .takeIf { it.isNotEmpty() } } val record = Result(sourceV, graph) // 初始化最小堆:局部最优更新到全局最优 val minHeap = graph.adjacentEdges(sourceV.id).map { edge -> val weight = if (graph.isWeighted()) edge.weight else DEFAULT_UNWEIGHTED_VALUE TotalWeight(edge, weight).also { tw -> val otherVertexId = tw.edge.to.id record.dynamicDistanceToSource[otherVertexId] = tw.totalWeight record.dynamicPathFrom[otherVertexId] = tw.edge } }.let { list -> PriorityQueue<TotalWeight>(compareBy { it.totalWeight }).apply { addAll(list) } } // 设置源点 record.calculateCompleted[sourceV.id] = true record.dynamicDistanceToSource[sourceV.id] = 0.0 record.dynamicPathFrom[sourceV.id] = null // Dijkstra 主循环 while (minHeap.isNotEmpty()) { val min = minHeap.poll() if (!compute(min, minHeap, record, validBrokenFilter)) { break } } return record } /** * 单步计算逻辑 */ private fun compute( min: TotalWeight, minHeap: PriorityQueue<TotalWeight>, record: Result, brokenFilter: MutableSet<String>? ): Boolean { val completed = record.calculateCompleted val dts = record.dynamicDistanceToSource val pathFrom = record.dynamicPathFrom val pivotVertex = min.edge.to if (completed[pivotVertex.id]) { return true } val toW = dts[pivotVertex.id]!! val updatedEdges = graph.adjacentEdges(pivotVertex.id) updatedEdges.forEach { updatedEdge -> val toto = updatedEdge.to if (completed[toto.id]) { return@forEach } val updatedWeight = updatedEdge.weight + toW val totoDis = dts[toto.id] when { totoDis == null -> { // 没有到达过 dts[toto.id] = updatedWeight pathFrom[toto.id] = updatedEdge minHeap.add(TotalWeight(updatedEdge, updatedWeight)) } updatedWeight < totoDis -> { // 更新最短路径 dts[toto.id] = updatedWeight pathFrom[toto.id] = updatedEdge minHeap.add(TotalWeight(updatedEdge, updatedWeight)) } } } completed[pivotVertex.id] = true return !canBeBroken(brokenFilter, pivotVertex.name) } /** * 检查是否可以提前终止(所有断点都已处理) */ private fun canBeBroken(breakFilter: MutableSet<String>?, complete: String): Boolean { if (breakFilter == null) { return false } breakFilter.remove(complete) return breakFilter.isEmpty() } /** * 总权重数据类 */ private data class TotalWeight(val edge: Edge, val totalWeight: Double) /** * Dijkstra算法计算结果 */ class Result internal constructor( private val source: Vertex, private val graph: Graph ) { internal val calculateCompleted = BooleanArray(graph.getVertexesNum()) internal val dynamicDistanceToSource = arrayOfNulls<Double>(graph.getVertexesNum()) internal val dynamicPathFrom = arrayOfNulls<Edge>(graph.getVertexesNum()) /** * 获取到目标点的最短路径 */ fun getRoutes(destName: String): List<Edge> { var destV = graph.vertexIndex().getVertex(destName) ?: return emptyList() if (destV.id >= dynamicPathFrom.size) { return emptyList() } val reversedRoutes = mutableListOf<Edge>() while (source.name != destV.name) { val edge = dynamicPathFrom[destV.id] ?: break reversedRoutes.add(edge) destV = edge.from } return reversedRoutes.reversed() } /** * 获取到目标点的最短距离 */ fun getDistance(destName: String): Double? { val destV = graph.vertexIndex().getVertex(destName) ?: return null return dynamicDistanceToSource[destV.id] } /** * 检查是否有到目标点的路径 */ fun hasPath(destName: String): Boolean { return getDistance(destName) != null } /** * 获取所有可达点的最短距离 */ fun getAllDistances(): Map<String, Double> { return buildMap { graph.getVertexes().forEach { vertex -> dynamicDistanceToSource[vertex.id]?.let { distance -> put(vertex.name, distance) } } } } /** * 打印路径信息 */ fun printRoutes(edges: List<Edge>) { if (edges.isEmpty()) { println("No route found") return } val lastEdge = edges.last() val target = lastEdge.to.name val distance = dynamicDistanceToSource[lastEdge.to.id] println( """ Shortest Path: source: [${source.name}] target: [$target] """.trimIndent() ) println("Distance: $distance = ${edges.joinToString(" + ") { it.weight.toString() }}") print("Route: ") edges.forEachIndexed { index, edge -> val from = edge.from.name val weight = edge.weight when (index) { edges.size - 1 -> { val to = edge.to.name println("[$from] --$weight-> [$to]") } else -> print("[$from] --$weight-> ") } } println() } /** * 打印所有最短路径 */ fun printAllPaths() { println("=== Dijkstra Shortest Paths from [${source.name}] ===") graph.getVertexes() .filter { it.name != source.name } .forEach { vertex -> val routes = getRoutes(vertex.name) if (routes.isNotEmpty()) { printRoutes(routes) } else { println("No path to [${vertex.name}]") } } } } }
27 January 2026