欢迎访问我的主页: https://heeheeaii.github.io/
kotlin
import java.util.*
import kotlin.math.*
// 边的数据类
data class Edge(val to: Int, val weight: Double = 1.0)
data class WeightedEdge(val from: Int, val to: Int, val weight: Double) : Comparable<WeightedEdge> {
override fun compareTo(other: WeightedEdge) = weight.compareTo(other.weight)
}
/**
* 集合: {0} {1} {2} {3} {4}
* parent: [0,1,2,3,4]
* rank: [0,0,0,0,0]
*/
// 并查集, 快速确定有限节点连通性
class UnionFind(n: Int) {
private val parent = IntArray(n) { it }
private val rank = IntArray(n)
fun find(x: Int): Int = if (parent[x] != x) find(parent[x]).also { parent[x] = it } else x
fun union(x: Int, y: Int): Boolean {
val px = find(x);
val py = find(y)
if (px == py) return false
if (rank[px] < rank[py]) parent[px] = py
else if (rank[px] > rank[py]) parent[py] = px
else {
parent[py] = px; rank[px]++
}
return true
}
}
class GraphAlgorithms {
// 1. 深度优先搜索 (DFS)
fun dfs(graph: Array<MutableList<Edge>>, start: Int, visited: BooleanArray = BooleanArray(graph.size)): List<Int> {
val result = mutableListOf<Int>()
fun dfsRec(v: Int) {
visited[v] = true
result.add(v)
graph[v].forEach { if (!visited[it.to]) dfsRec(it.to) }
}
dfsRec(start)
return result
}
// 2. 广度优先搜索 (BFS)
fun bfs(graph: Array<MutableList<Edge>>, start: Int): List<Int> {
val visited = BooleanArray(graph.size)
val queue = LinkedList<Int>()
val result = mutableListOf<Int>()
queue.offer(start)
visited[start] = true
while (queue.isNotEmpty()) {
val v = queue.poll()
result.add(v)
graph[v].forEach {
if (!visited[it.to]) {
visited[it.to] = true
queue.offer(it.to)
}
}
}
return result
}
// 3. Dijkstra算法
fun dijkstra(graph: Array<MutableList<Edge>>, startIdx: Int): DoubleArray {
val dist = DoubleArray(graph.size) { Double.POSITIVE_INFINITY }
val protq = PriorityQueue<Pair<Double, Int>>(compareBy { it.first })
dist[startIdx] = 0.0
protq.offer(0.0 to startIdx)
while (protq.isNotEmpty()) {
val (disVlu, idx) = protq.poll()
if (disVlu > dist[idx]) continue
graph[idx].forEach { edge ->
val newDist = dist[idx] + edge.weight
if (newDist < dist[edge.to]) {
dist[edge.to] = newDist
protq.offer(newDist to edge.to)
}
}
}
return dist
}
// 4. Floyd-Warshall算法
/**
* @param adj matrix
* @return min distance matrix
*/
fun floydWarshall(adj: Array<DoubleArray>): Array<DoubleArray> {
val inSize = adj.size
val dist = Array(inSize) { adj[it].clone() }
for (idx in 0 until inSize) // mid station
for (jdx in 0 until inSize) // from station
for (kdx in 0 until inSize) // to station
if (dist[jdx][idx] + dist[idx][kdx] < dist[jdx][kdx]) dist[jdx][kdx] =
dist[jdx][idx] + dist[idx][kdx]
return dist
}
fun primForest(graph: Array<List<Edge>>): List<List<WeightedEdge>> {
val inSize = graph.size
val visited = BooleanArray(inSize)
val forest = mutableListOf<List<WeightedEdge>>()
for (start in 0 until inSize) {
if (!visited[start]) {
// 对每个连通分量运行Prim
val tree = prim(graph, start, visited)
if (tree.isNotEmpty()) {
forest.add(tree)
}
}
}
return forest
}
// 5. 单Prim算法
/**
* 生成最小生成树 mst
* 包含所有顶点:生成树必须包含图中的每一个顶点。
* 无环:生成树是一棵树,因此它不能包含环。
* 连通:生成树必须是连通的,即从任意一个顶点都可以到达其他所有顶点
*/
private fun prim(
graph: Array<List<Edge>>,
start: Int,
globalVisited: BooleanArray,
): List<WeightedEdge> {
val inMST = BooleanArray(graph.size)
val protQ = PriorityQueue<WeightedEdge>() // weight queue
val mst = mutableListOf<WeightedEdge>()
// 从指定起点开始
inMST[start] = true
globalVisited[start] = true
graph[start].forEach { protQ.offer(WeightedEdge(start, it.to, it.weight)) }
while (protQ.isNotEmpty()) {
val edge = protQ.poll()
if (inMST[edge.to]) continue
inMST[edge.to] = true
globalVisited[edge.to] = true
mst.add(edge)
graph[edge.to].forEach {
if (!inMST[it.to]) protQ.offer(WeightedEdge(edge.to, it.to, it.weight))
}
}
return mst
}
/**
* 从边的角度出发,构建最小生成树森林
*/
fun kruskalForest(num: Int, edges: List<WeightedEdge>): List<WeightedEdge> {
if (num <= 0) return emptyList()
val uf = UnionFind(num)
val forest = mutableListOf<WeightedEdge>()
edges.sorted().forEach { edge ->
if (uf.union(edge.from, edge.to)) {
forest.add(edge)
// 最多有 num-1 条边(全连通的情况)
if (forest.size == num - 1) return forest
}
}
return forest
}
// 7. 拓扑排序
fun topologicalSortForest(graph: Array<List<Edge>>): List<List<Int>> {
val inSize = graph.size
val inDegree = IntArray(inSize)
val visited = BooleanArray(inSize)
val forest = mutableListOf<List<Int>>()
// 计算初始入度
graph.forEach { it.forEach { edge -> inDegree[edge.to]++ } }
while (visited.count { !it } > 0) { // 还有未访问的节点
val queue = LinkedList<Int>()
val curTree = mutableListOf<Int>()
// 找到当前未访问节点中入度为0的节点
for (idx in 0 until inSize) {
if (!visited[idx] && inDegree[idx] == 0) {
queue.offer(idx)
}
}
// 如果没有入度为0的节点,说明剩余节点形成环,跳过这些节点
if (queue.isEmpty()) {
// 标记所有剩余节点为已访问(因为它们形成环,无法拓扑排序)
for (i in 0 until inSize) {
if (!visited[i]) {
visited[i] = true
}
}
break
}
// 对当前连通分量进行拓扑排序
while (queue.isNotEmpty()) {
val current = queue.poll()
if (visited[current]) continue // 避免重复处理
visited[current] = true
curTree.add(current)
// 处理当前节点的邻接节点
graph[current].forEach { edge ->
if (!visited[edge.to]) {
inDegree[edge.to]--
if (inDegree[edge.to] == 0) {
queue.offer(edge.to)
}
}
}
}
// 将当前连通分量的结果添加到森林中
if (curTree.isNotEmpty()) {
forest.add(curTree)
}
}
return forest
}
/**
* 寻找全部强连通图
*
* 0 → 1
* 1 → 2
* 2 → 0 // 形成环 {0,1,2}
* 1 → 3 // 3是独立节点
*
* 1. 访问0: findTime[0]=0, minFindTime[0]=0, 栈=[0]
* 2. 访问1: findTime[1]=1, minFindTime[1]=1, 栈=[0,1]
* 3. 访问2: findTime[2]=2, minFindTime[2]=2, 栈=[0,1,2]
* 4. 2→0: 0在栈中(回边)
* minFindTime[2] = min(2, findTime[0]) = 0
* 5. 回溯到1:
* minFindTime[1] = min(1, minFindTime[2]) = min(1, 0) = 0
* 6. 访问3: findTime[3]=3, minFindTime[3]=3, 栈=[0,1,3]
* 7. 回溯到1:
* minFindTime[1] = min(0, minFindTime[3]) = min(0, 3) = 0
*/
// 8. Tarjan算法 (strongly connected components, 从一个点出发通过某个路径能达到任意一个点)
fun tarjanSCC(graph: Array<List<Edge>>): List<List<Int>> {
val inSize = graph.size
var time = 0
val firstFindTime = IntArray(inSize)
val perFindTime = IntArray(inSize)
val onStack = BooleanArray(inSize)
val stack = Stack<Int>()
val sccs = mutableListOf<List<Int>>()
fun findSccByFirstFindTimeDFS(fromIdx: Int) {
perFindTime[fromIdx] = time; firstFindTime[fromIdx] = time++
stack.push(fromIdx); onStack[fromIdx] = true
graph[fromIdx].forEach { edge ->
val toIdx = edge.to
if (perFindTime[toIdx] == -1) {
findSccByFirstFindTimeDFS(toIdx)
firstFindTime[fromIdx] = min(
firstFindTime[fromIdx], firstFindTime[toIdx]
) // firstFindTime may transport from other place
} else if (onStack[toIdx]) {
firstFindTime[fromIdx] = min(firstFindTime[fromIdx], perFindTime[toIdx]) // a explore stop place
}
}
if (firstFindTime[fromIdx] == perFindTime[fromIdx]) { // a scc input port
val scc = mutableListOf<Int>()
do {
val midIdx = stack.pop()
onStack[midIdx] = false
scc.add(midIdx)
} while (midIdx != fromIdx)
sccs.add(scc)
}
}
perFindTime.fill(-1)
for (idx in 0 until inSize) if (perFindTime[idx] == -1) findSccByFirstFindTimeDFS(idx)
return sccs
}
/**
* Kosaraju算法 - 寻找强连通分量
*
* 算法原理:
* 图: 0→1→2→0 (强连通分量{0,1,2}),3→4 (强连通分量{3},{4})
*
* 第一步:正向DFS,记录完成顺序
* 访问顺序可能是: 0→1→2,然后3→4
* 完成顺序(后序): [2,1,0,4,3]
*
* 第二步:构建转置图
* 原图: 0→1→2→0,3→4
* 转置: 0←1←2←0,3←4
*
* 第三步:按完成顺序逆序DFS转置图
* 从3开始:只能访问3 → SCC{3}
* 从4开始:只能访问4 → SCC{4}
* 从0开始:能访问0←1←2 → SCC{0,1,2}
*/
fun kosarajuStronglyConnectedComponents(graph: Array<List<Edge>>): List<List<Int>> {
val nodeCount = graph.size
val isNodeVisited = BooleanArray(nodeCount)
val findTime = mutableListOf<Int>() // 节点完成时间顺序
val reversedGraph = Array(nodeCount) { mutableListOf<Edge>() } // 转置图
// 第一步:正向DFS,记录节点完成顺序
fun recordFinishOrderByDFS(currentNode: Int) {
isNodeVisited[currentNode] = true
// 遍历当前节点的所有邻居
graph[currentNode].forEach { edge ->
if (!isNodeVisited[edge.to]) {
recordFinishOrderByDFS(edge.to)
}
}
// 当前节点处理完毕,加入完成顺序列表
findTime.add(currentNode)
}
// 第二步:在转置图中进行DFS,收集强连通分量
fun collectStronglyConnectedComponentByDFS(currentNode: Int, currentSCC: MutableList<Int>) {
isNodeVisited[currentNode] = true
currentSCC.add(currentNode)
// 在转置图中遍历邻居
reversedGraph[currentNode].forEach { edge ->
if (!isNodeVisited[edge.to]) {
collectStronglyConnectedComponentByDFS(edge.to, currentSCC)
}
}
}
// 执行第一步:记录完成顺序
for (nodeIndex in 0 until nodeCount) {
if (!isNodeVisited[nodeIndex]) {
recordFinishOrderByDFS(nodeIndex)
}
}
// 构建转置图:将所有边反向
graph.forEachIndexed { fromNode, edges ->
edges.forEach { edge ->
reversedGraph[edge.to].add(Edge(fromNode))
}
}
// 重置访问标记,准备第二步
isNodeVisited.fill(false)
val sccs = mutableListOf<List<Int>>()
// 执行第二步:按完成顺序逆序遍历转置图
for (nodeIndex in findTime.reversed()) {
if (!isNodeVisited[nodeIndex]) {
val currentSCC = mutableListOf<Int>()
collectStronglyConnectedComponentByDFS(nodeIndex, currentSCC)
sccs.add(currentSCC)
}
}
return sccs
}
/**
* Ford-Fulkerson最大流算法 (使用BFS寻找增广路径)
*
* 算法原理:
* 容量图: S→A(100), S→B(100), A→C(1), A→T(100), B→C(100), C→T(100)
*
* 第1次增广:S→A→C→T,流量=1,剩余容量:S→A(99), A→C(0), C→T(99), 反向边:A→S(1), C→A(1), T→C(1)
* 第2次增广:S→A→T,流量=99,剩余容量:S→A(0), A→T(1), 反向边:A→S(100), T→A(99)
* 第3次增广:S→B→C→T,流量=99,剩余容量:S→B(1), B→C(1), C→T(0), 反向边:B→S(99), C→B(99), T→C(100)
* 第4次增广:S→B→C→A→T,流量=1,使用反向边C→A,剩余容量:S→B(0), B→C(0), C→A(0), A→T(0)
* 最大流 = 1 + 99 + 99 + 1 = 200 (如果没有第4次反向边增广,最大流只能是199)
*
* Ford-Fulkerson算法数学原理详解
*
* 一、基础概念与定义
*
* 1.1 流网络基本结构
*
* 流网络定义:
*
* - 网络结构:有向图 G = (V, E),包含源点 s 和汇点 t
* - 边容量:c(u,v) ≥ 0,表示边 (u,v) 的最大流量容量
* - 流量函数
* :f(u,v) 表示边 (u,v) 上的实际流量,满足容量约束:
* 0 ≤ f(u,v) ≤ c(u,v)
*
* 1.2 流量守恒定律
*
* 中间节点的流量守恒 对于任意中间节点 u(u ≠ s 且 u ≠ t):
*
* ∑[v∈V] f(v,u) = ∑[v∈V] f(u,v)
*
* 含义:流入节点 u 的总流量 = 流出节点 u 的总流量
*
* 源点和汇点的流量性质
*
* - 源点 s:∑[v∈V] f(s,v) - ∑[v∈V] f(v,s) = |f| (净输出等于总流量)
* - 汇点 t:∑[v∈V] f(v,t) - ∑[v∈V] f(t,v) = |f| (净输入等于总流量)
*
* 1.3 残存网络
*
* 给定当前流 f,残存网络中的残存容量定义为:
*
* 残存容量(u,v) = {
* c(u,v) - f(u,v) 如果原图存在边 u→v(正向边剩余容量)
* f(v,u) 如果原图存在边 v→u(反向边可撤销容量)
* 0 其他情况
* }
*
* 二、核心理论基础
*
* 2.1 最大流-最小割定理
*
* 定理陈述:在任何流网络中,最大流的值等于最小割的容量。
*
* 割的定义: 割 (X,Y) 将节点集 V 分为两个不相交的子集,使得 s ∈ X,t ∈ Y。
*
* 割的容量 = ∑[u∈X,v∈Y] c(u,v)
*
* 证明过程
*
* 方向1:最大流 ≤ 最小割
*
* 对于任意流 f 和任意割 (X,Y):
*
* |f| = 从 X 到 Y 的净流量
* = ∑[u∈X,v∈Y] f(u,v) - ∑[v∈Y,u∈X] f(v,u)
* ≤ ∑[u∈X,v∈Y] c(u,v) - 0 (容量约束 + 流量非负)
* = 割(X,Y)的容量
*
* 方向2:最大流 ≥ 最小割
*
* 当 Ford-Fulkerson 算法终止时的流 f*:
*
* 1. 构造关键割:
* - X = {从 s 在残存网络中能到达的所有节点}
* - Y = V - X(注意:t ∈ Y,否则算法未终止)
* 2. 关键性质:
* - 若 u ∈ X, v ∈ Y 且原图有边 (u,v),则 f*(u,v) = c(u,v)(正向边饱和)
* - 若 u ∈ X, v ∈ Y 且原图有边 (v,u),则 f*(v,u) = 0(反向边无流)
* 3. 等式成立:
* |f*| = ∑[u∈X,v∈Y] f*(u,v) - ∑[v∈Y,u∈X] f*(v,u)
* = ∑[u∈X,v∈Y] c(u,v) - 0
* = 割(X,Y)的容量
*
* 结论:最大流 = 最小割
*
* 2.2 增广路径定理
*
* 定理:流 f 是最大流 ⟺ 残存网络中不存在从 s 到 t 的路径
*
* 证明要点:
*
* - 充分性:若残存网络中存在 s 到 t 的路径,则可继续增加流量,f 非最大
* - 必要性:若 f 是最大流,则残存网络中必无 s 到 t 的路径
*
* 三、算法运作机制
*
* 3.1 反向边的数学含义
*
* 作用机制:允许算法"撤销"先前的流量分配决策
*
* 数学表示:
*
* - 如果边 (u,v) 当前流量为 f(u,v)
* - 残存网络中反向边 (v,u) 的容量 = f(u,v)
* - 表示最多可撤销 f(u,v) 单位的流量重新分配
*
* 3.2 流量增广操作
*
* 增广步骤:
*
* 1. 寻找增广路径:在残存网络中找到 s 到 t 的路径 P
* 2. 计算增广量:δ = min{残存容量(u,v) | (u,v) ∈ P}
* 3. 更新流量
* :沿路径 P 的每条边 (u,v):
* - 若为正向边:f(u,v) ← f(u,v) + δ
* - 若为反向边:f(v,u) ← f(v,u) - δ
*
* 四、算法正确性分析
*
* 4.1 终止性保证
*
* 整数容量情况:
*
* - 每次增广至少增加 1 单位流量
* - 流量存在上界(等于最小割容量)
* - 因此算法必然在有限步内终止
*
* 4.2 最优性证明
*
* 算法终止时的流 f* 满足:
*
* 1. 终止条件:残存网络中 s 无法到达 t
* 2. 构造最小割
* :
* - X = {从 s 在残存网络中可达的节点}
* - Y = V - X
* 3. 流量等于割容量:|f*| = 割(X,Y)的容量
* 4. 最优性结论:根据最大流-最小割定理,f* 是最大流
*
* 五、反向边必要性分析
*
* 5.1 经典反例
*
* 网络结构:
*
* S → A (容量100) A → T (容量100)
* S → B (容量100) B → C (容量100)
* A → C (容量1) C → T (容量100)
*
* 5.2 贪心策略的失败
*
* 错误的增广序列:
*
* 第1次:S→A→C→T,流量 = 1
* 第2次:S→A→T, 流量 = 99
* 第3次:S→B→C→T,流量 = 99
* 总流量 = 199 (非最优)
*
* 5.3 反向边的拯救
*
* 修正增广:
*
* 第4次:S→B→C→A→T,流量 = 1
*
* - 利用反向边 C→A 撤销第1次的次优分配
* - 最终达到最优解:总流量 = 200
*
* @param capacityMatrix 如果两个节点之间有容量, (row, col) 表示 row 到 col的容量, 否则是0.
*/
fun fordFulkersonMaxFlow(capacityMatrix: Array<IntArray>, sourceNode: Int, sinkNode: Int): Int {
val nodeCount = capacityMatrix.size
val capacityMatrix = Array(nodeCount) { capacityMatrix[it].clone() } // 剩余容量图
var maxFlow = 0
// 使用BFS寻找从源点到汇点的增广路径
fun findAugmentingPathByBFS(): IntArray? {
val isNodeVisited = BooleanArray(nodeCount)
val parentNode = IntArray(nodeCount) { -1 } // 记录路径中每个节点的父节点
val nodeQueue = LinkedList<Int>()
nodeQueue.offer(sourceNode)
isNodeVisited[sourceNode] = true
while (nodeQueue.isNotEmpty()) {
val currentNode = nodeQueue.poll()
// 检查当前节点的所有邻居
for (neighborNode in 0 until nodeCount) {
// 如果邻居未访问且有剩余容量
if (!isNodeVisited[neighborNode] && capacityMatrix[currentNode][neighborNode] > 0) {
nodeQueue.offer(neighborNode)
isNodeVisited[neighborNode] = true
parentNode[neighborNode] = currentNode
// 如果到达汇点,返回路径
if (neighborNode == sinkNode) {
return parentNode
}
}
}
}
return null // 没有找到增广路径
}
// 主循环:不断寻找增广路径直到无法找到
while (true) {
val findPath = findAugmentingPathByBFS() ?: break
// 计算这条路径上的最小流
var minFlow = Int.MAX_VALUE
var currentNode = sinkNode
while (currentNode != sourceNode) {
val parentNode = findPath[currentNode]
minFlow = min(minFlow, capacityMatrix[parentNode][currentNode])
currentNode = parentNode
}
maxFlow += minFlow
var toNode = sinkNode
while (toNode != sourceNode) {
val fromNode = findPath[toNode]
capacityMatrix[fromNode][toNode] -= minFlow // 减少正向容量
capacityMatrix[toNode][fromNode] += minFlow // 增加反向容量
toNode = fromNode
}
}
return maxFlow
}
/**
* Bellman-Ford单源最短路径算法 (支持负权边)
*
* 算法原理:
* 图: A→B(1), B→C(-3), A→C(4), C→B(2)
*
* 初始化:dist[A]=0, dist[B]=∞, dist[C]=∞
* 第1轮松弛:
* A→B: dist[B] = min(∞, 0+1) = 1
* A→C: dist[C] = min(∞, 0+4) = 4
* B→C: dist[C] = min(4, 1+(-3)) = -2
* 第2轮松弛:
* C→B: dist[B] = min(1, -2+2) = 0
* 第3轮检测负环:无更新 → 无负环
*/
fun bellmanFordShortestPath(nodeCount: Int, edgeList: List<WeightedEdge>, startNode: Int): DoubleArray? {
val distanceFromStart = DoubleArray(nodeCount) { Double.POSITIVE_INFINITY }
distanceFromStart[startNode] = 0.0
// 进行(nodeCount-1)轮松弛操作
// 最短路径最多包含(nodeCount-1)条边
repeat(nodeCount - 1) { _ ->
var hasDistanceUpdate = false
edgeList.forEach { edge ->
val fromNode = edge.from
if (distanceFromStart[fromNode] == Double.POSITIVE_INFINITY) {
return@forEach
}
val toNode = edge.to
val edgeWeight = edge.weight
val newDistanceToTarget = distanceFromStart[fromNode] + edgeWeight
if (newDistanceToTarget < distanceFromStart[toNode]) {
distanceFromStart[toNode] = newDistanceToTarget
hasDistanceUpdate = true
}
}
// 如果本轮没有任何更新,可以提前结束
if (!hasDistanceUpdate) return@repeat
}
// 检测负环:再进行一轮松弛,如果还有更新说明存在负环
edgeList.forEach { edge ->
val fromNode = edge.from
val toNode = edge.to
val edgeWeight = edge.weight
if (distanceFromStart[fromNode] != Double.POSITIVE_INFINITY) {
val newDistanceToTarget = distanceFromStart[fromNode] + edgeWeight
if (newDistanceToTarget < distanceFromStart[toNode]) {
return null // 检测到负环
}
}
}
return distanceFromStart
}
/**
* Dijkstra最短路径算法 (用于Bellman-Ford和其他算法)
*/
fun dijkstraShortestPath(graph: Array<MutableList<Edge>>, startNode: Int): DoubleArray {
val nodeCount = graph.size
val distanceFromStart = DoubleArray(nodeCount) { Double.POSITIVE_INFINITY }
val isNodeProcessed = BooleanArray(nodeCount)
val priorityQueue = PriorityQueue<Pair<Double, Int>>(compareBy { it.first })
distanceFromStart[startNode] = 0.0
priorityQueue.offer(0.0 to startNode)
while (priorityQueue.isNotEmpty()) {
val (currentDistance, currentNode) = priorityQueue.poll()
if (isNodeProcessed[currentNode]) continue
isNodeProcessed[currentNode] = true
graph[currentNode].forEach { edge ->
val neighborNode = edge.to
val edgeWeight = edge.weight
val newDistance = currentDistance + edgeWeight
if (newDistance < distanceFromStart[neighborNode]) {
distanceFromStart[neighborNode] = newDistance
priorityQueue.offer(newDistance to neighborNode)
}
}
}
return distanceFromStart
}
/**
* A*启发式搜索算法
*
* 算法原理:
* f(n) = g(n) + h(n)
* g(n) = 从起点到当前节点的实际代价
* h(n) = 从当前节点到目标的启发式估计代价
*
* 示例:在网格中从(0,0)到(2,2)
* 启发式函数:曼哈顿距离 h(x,y) = |x-2| + |y-2|
*
* 过程:
* 1. 将起点(0,0)加入开放列表,g=0, h=4, f=4
* 2. 扩展(0,0)的邻居,选择f值最小的节点
* 3. 重复直到找到目标或开放列表为空
*/
fun aStarPathFinding(
graph: Array<List<Edge>>,
startNode: Int,
targetNode: Int,
heuristicFunction: (Int) -> Double,
): List<Int>? {
val nodeCount = graph.size
val prrtQ = PriorityQueue<Pair<Double, Int>>(compareBy { it.first })
val toFromPair = mutableMapOf<Int, Int>()
val knownCost = DoubleArray(nodeCount) { Double.POSITIVE_INFINITY }
val predictAllCost = DoubleArray(nodeCount) { Double.POSITIVE_INFINITY }
knownCost[startNode] = 0.0
predictAllCost[startNode] = heuristicFunction(startNode)
prrtQ.offer(predictAllCost[startNode] to startNode)
while (prrtQ.isNotEmpty()) {
val curNode = prrtQ.poll().second
if (curNode == targetNode) {
val findPath = mutableListOf<Int>()
var pathNode = curNode
while (pathNode in toFromPair) {
findPath.add(0, pathNode)
pathNode = toFromPair[pathNode]!!
}
findPath.add(0, startNode)
return findPath
}
// 扩展当前节点的所有邻居
graph[curNode].forEach { edge ->
val toNode = edge.to
val wight = edge.weight
// 计算通过当前节点到达邻居的代价
val newCost = knownCost[curNode] + wight
// 如果找到更好的路径
if (newCost < knownCost[toNode]) {
toFromPair[toNode] = curNode
knownCost[toNode] = newCost
predictAllCost[toNode] = knownCost[toNode] + heuristicFunction(toNode)
// 将邻居加入开放列表
prrtQ.offer(predictAllCost[toNode] to toNode)
}
}
}
return null // 无法找到路径
}
/**
* Johnson全对最短路径算法
*
* 定理1:对于任意从节点u到节点v的路径P,设P = u → w₁ → w₂ → ... → wₖ → v
* 原始路径权重:
* W_original(P) = weight(u,w₁) + weight(w₁,w₂) + ... + weight(wₖ,v)
* 重新赋权后的路径权重:
* W_new(P) = [weight(u,w₁) + h[u] - h[w₁]] +
* [weight(w₁,w₂) + h[w₁] - h[w₂]] +
* ... +
* [weight(wₖ,v) + h[wₖ] - h[v]]
* 展开并约简:
* W_new(P) = weight(u,w₁) + weight(w₁,w₂) + ... + weight(wₖ,v) +
* h[u] - h[w₁] + h[w₁] - h[w₂] + ... + h[wₖ] - h[v]
* = W_original(P) + h[u] - h[v]
* 关键结论:任何从u到v的路径,重新赋权后都增加了相同的常数 h[u] - h[v]
* 推论:最短路径保持不变
* 如果路径P₁是原图中u到v的最短路径,路径P₂是任意其他路径:
*
* 原图:W_original(P₁) ≤ W_original(P₂)
* 新图:W_original(P₁) + h[u] - h[v] ≤ W_original(P₂) + h[u] - h[v]
*
* 因此P₁在新图中仍然是最短路径!
* 性质2:负权边的消除
* 定理2:通过Bellman-Ford算法计算的h函数满足三角不等式,保证所有重新赋权后的边都是非负的。
* 三角不等式:对于任意边(i,j)
* h[j] ≤ h[i] + weight(i,j)
* 证明:
*
* h[j] = 从虚拟源点s到j的最短距离
* h[i] = 从虚拟源点s到i的最短距离
* 从s到j的路径可以是:s → i → j,距离为 h[i] + weight(i,j)
* 由于h[j]是最短距离,所以 h[j] ≤ h[i] + weight(i,j)
*
* 推导非负权重:
* 新权重 = weight(i,j) + h[i] - h[j] ≥ weight(i,j) + h[i] - (h[i] + weight(i,j)) = 0
* 性质3:距离恢复的正确性
* 定理3:最终的距离恢复公式能正确计算原图的最短距离。
* 设d_new[u][v]是重新赋权图中u到v的最短距离,那么原图中u到v的最短距离为:
* d_original[u][v] = d_new[u][v] - h[u] + h[v]
* 证明:
*
* 在重新赋权图中,u到v最短路径的权重 = 原图最短路径权重 + h[u] - h[v]
* 因此:原图最短路径权重 = 重新赋权图最短路径权重 - (h[u] - h[v])
* 即:d_original[u][v] = d_new[u][v] - h[u] + h[v]
*/
fun johnsonAllPairsShortestPath(inSize: Int, edgeList: List<WeightedEdge>): Array<DoubleArray>? {
val anyVlu = 0.0
val extendedEdgeList = edgeList + (0 until inSize).map {
WeightedEdge(inSize, it, anyVlu)
} // 选择几是无所谓的
val reFunc = bellmanFordShortestPath(inSize + 1, extendedEdgeList, inSize) // 起点到终点最小值
?: return null // 存在负环
val newWeights = edgeList.map { edge ->
WeightedEdge(edge.from, edge.to, edge.weight + reFunc[edge.from] - reFunc[edge.to])
}
val newGraph = Array(inSize) { mutableListOf<Edge>() }
newWeights.forEach { edge ->
newGraph[edge.from].add(Edge(edge.to, edge.weight))
}
val distanceMatrix = Array(inSize) { DoubleArray(inSize) }
for (idx in 0 until inSize) {
val minDistanceFromIdx = dijkstraShortestPath(newGraph, idx) // must find min distance from idx
for (target in 0 until inSize) {
distanceMatrix[idx][target] = when {
minDistanceFromIdx[target] == Double.POSITIVE_INFINITY -> Double.POSITIVE_INFINITY
else -> minDistanceFromIdx[target] - reFunc[idx] + reFunc[target]
}
}
}
return distanceMatrix
}
/**
* PageRank算法 - 网页排名算法
*
* 算法原理:
* PR(A) = (1-d)/N + d × Σ(PR(T)/C(T))
* 其中:d为阻尼系数(通常0.85), N为总页面数, T为链向A的页面, C(T)为T的出链数
*
* 示例:3个页面 A→B, B→C, C→A
* 初始:PR(A)=PR(B)=PR(C)=1/3
* 迭代:
* PR(A) = 0.15/3 + 0.85×PR(C)/1 = 0.05 + 0.85×(1/3) ≈ 0.33
* PR(B) = 0.05 + 0.85×PR(A)/1 ≈ 0.33
* PR(C) = 0.05 + 0.85×PR(B)/1 ≈ 0.33
*/
fun pageRankCalculation(
linkGraph: Array<MutableList<Edge>>,
iteratorTime: Int = 100,
dampingFactor: Double = 0.85,
): DoubleArray {
val inSize = linkGraph.size
val pageRanks = DoubleArray(inSize) { 1.0 / inSize } // 初始化为均匀分布
val addRanks = DoubleArray(inSize)
repeat(iteratorTime) { _ ->
addRanks.fill((1.0 - dampingFactor) / inSize)
for (jdx in 0 until inSize) {
val outSize = linkGraph[jdx].size
if (outSize > 0) {
val rankContribution = dampingFactor * pageRanks[jdx] / outSize
linkGraph[jdx].forEach { edge ->
addRanks[edge.to] += rankContribution
}
}
}
// 更新PageRank值
pageRanks.indices.forEach { idx ->
pageRanks[idx] = addRanks[idx]
}
}
return pageRanks
}
/**
* HITS算法 - 权威性和中心性计算
*
* 算法原理:
* Authority(p) = Σ Hub(q) (对所有指向p的页面q)
* Hub(p) = Σ Authority(q) (对所有p指向的页面q)
*
* 示例:A→B→C, A→C
* 初始:所有页面的Authority和Hub值都是1
*
* 第1次迭代:
* Auth(A)=0, Auth(B)=Hub(A)=1, Auth(C)=Hub(A)+Hub(B)=2
* Hub(A)=Auth(B)+Auth(C)=3, Hub(B)=Auth(C)=2, Hub(C)=0
*
* 然后归一化,继续迭代直到收敛
*/
fun hitsAuthorityHub(
links: Array<List<Edge>>,
maxIterations: Int = 100,
): Pair<DoubleArray, DoubleArray> {
val inSize = links.size
val authorityScore = DoubleArray(inSize) { 1.0 }
val navScore = DoubleArray(inSize) { 1.0 }
val tmpAtrtScore = DoubleArray(inSize)
val tmpNavScore = DoubleArray(inSize)
repeat(maxIterations) { _ ->
tmpAtrtScore.fill(0.0)
tmpNavScore.fill(0.0)
for (idx in 0 until inSize) {
links[idx].forEach { edge ->
tmpAtrtScore[edge.to] += navScore[idx]
}
}
for (idx in 0 until inSize) {
links[idx].forEach { edge ->
tmpNavScore[idx] += tmpAtrtScore[edge.to]
}
}
val atrtNormal = sqrt(tmpAtrtScore.sumOf { it * it })
if (atrtNormal > 0) {
for (idx in 0 until inSize) {
authorityScore[idx] = tmpAtrtScore[idx] / atrtNormal
}
}
val navNormal = sqrt(tmpNavScore.sumOf { it * it })
if (navNormal > 0) {
for (pageIndex in 0 until inSize) {
navScore[pageIndex] = tmpNavScore[pageIndex] / navNormal
}
}
}
return authorityScore to navScore
}
}
// 使用示例
fun main() {
val algorithms = GraphAlgorithms()
// 创建示例图
val graph = Array(5) { mutableListOf<Edge>() }
graph[0].add(Edge(1, 4.0))
graph[0].add(Edge(2, 2.0))
graph[1].add(Edge(3, 3.0))
graph[2].add(Edge(3, 1.0))
graph[3].add(Edge(4, 5.0))
// 测试算法
println("DFS from 0: ${algorithms.dfs(graph, 0)}")
println("BFS from 0: ${algorithms.bfs(graph, 0)}")
println("Dijkstra from 0: ${algorithms.dijkstra(graph, 0).contentToString()}")
val edges = listOf(
WeightedEdge(0, 1, 4.0),
WeightedEdge(0, 2, 2.0),
WeightedEdge(1, 3, 3.0),
WeightedEdge(2, 3, 1.0),
WeightedEdge(3, 4, 5.0)
)
println("MST (Kruskal): ${algorithms.kruskalForest(5, edges)}")
println("PageRank: ${algorithms.pageRankCalculation(graph).contentToString()}")
}