kotlin图算法

欢迎访问我的主页: 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()}")
}
相关推荐
☆cwlulu2 小时前
解码Android 系统蓝牙音频全流程
前端·人工智能·算法
2301_764441332 小时前
Python常见的排序算法及其特点和实现代码
python·算法·排序算法
自学小白菜2 小时前
常见算法实现系列01 - 排序算法
数据结构·算法·排序算法
ones~3 小时前
Python 简单算法题精选与题解汇总
数据结构·python·算法
Madison-No73 小时前
【C++】string类的常见接口的使用
开发语言·c++·算法
不枯石3 小时前
Matlab通过GUI实现点云的统计滤波(附最简版)
开发语言·图像处理·算法·计算机视觉·matlab
koddnty4 小时前
数据结构:字符串匹配 kmp算法
算法
-一杯为品-4 小时前
【足式机器人算法】#1 强化学习基础
算法·机器人
叫我詹躲躲5 小时前
🌟 回溯算法原来这么简单:10道经典题,一看就明白!
前端·算法·leetcode