云计算百科
云计算领域专业知识百科平台

2026-01-18:边反转的最小路径总成本。用go语言,给定一个包含 n 个点(编号 0 到 n-1)的有向带权图。边集合 edges 中的每一项 edges[i] = [ui, vi, wi] 表

2026-01-18:边反转的最小路径总成本。用go语言,给定一个包含 n 个点(编号 0 到 n-1)的有向带权图。边集合 edges 中的每一项 edges[i] = [ui, vi, wi] 表示从 ui 指向 vi 的有向边,权重为 wi。 每个点都有一次特殊操作的机会:当你到达某个点 u 且还没使用过该点的这次机会时,你可以选择任意一条原本指向 u 的入边 v → u,把它临时改为 u → v 并立即沿着这条新方向移动一次。使用这次临时改向并穿越该边的代价为原边权的两倍(2*wi)。这种改向仅对这次移动生效,之后边的方向恢复且该点的机会就不能再用了。

求从起点 0 到终点 n-1 的最小可能总花费,若无法到达则返回 -1。

2 <= n <= 50000。

1 <= edges.length <= 100000。

edges[i] = [ui, vi, wi]。

0 <= ui, vi <= n – 1。

1 <= wi <= 1000。

输入: n = 4, edges = [[0,2,1],[2,1,1],[1,3,1],[2,3,3]]。

输出: 3。

解释:

不需要反转。走路径 0 → 2 (成本 1),然后 2 → 1 (成本 1),再然后 1 → 3 (成本 1)。

总成本为 1 + 1 + 1 = 3。

题目来自力扣3650。

算法过程详解

1. 图构建(邻接表)

算法首先将输入的有向图转换为一个无向图的邻接表,但其中蕴含了反转边的规则:

  • 对于原始有向图中的每条边 u -> v(权重为 w),在邻接表中创建两条边:
    • 正常边:从 u 到 v,权重为 w。这表示不进行反转,直接沿原方向移动。
    • 反转边:从 v 到 u,权重为 2 * w。这表示当位于节点 v 时,可以使用其特殊操作机会,将一条指向 v 的入边(即 u -> v)反转,并从 v 移动到 u,代价是原权重的两倍 。
  • 通过这种方式,节点 v 的特殊操作机会被隐式地建模为一条从 v 出发、指向其某个入边起点的、代价加倍的边 。

2. 初始化

  • 创建一个距离数组 dis,用于记录从起点 0 到每个节点的当前已知最短距离。初始时,起点 0 的距离设为 0,其他所有节点的距离设为极大值(math.MaxInt)。
  • 创建一个最小堆(优先队列) h,用于高效地获取当前未处理节点中距离起点最近的节点。堆中存储的是 (distance, node) 对。初始时,将起点 (0, 0) 加入堆中 。

3. Dijkstra算法主循环

算法核心是一个循环,直到堆为空为止:

  • 提取最小节点:从最小堆中弹出当前距离起点最近的节点 x 及其距离 disX 。
  • 验证最短路径:检查弹出的 disX 是否等于 dis[x] 数组中记录的值。如果 disX > dis[x],说明节点 x 已经被处理过(有更短的路径先到达了它),当前记录是过时的,直接跳过 。
  • 终止条件:如果当前弹出的节点 x 就是终点 n-1,则立即返回 dis[x] 作为答案,因为Dijkstra算法保证第一次处理终点时得到的就是最短路径 。
  • 松弛操作:遍历节点 x 的所有邻居(通过邻接表 g[x])。对于每条从 x 到 y、权重为 wt 的边:
    • 计算经由 x 到达 y 的新距离 newDisY = dis[x] + wt。
    • 如果 newDisY 小于 dis[y] 的当前值,则找到了一个更短的路径。此时更新 dis[y] = newDisY,并将 (newDisY, y) 这个新的距离估计值加入最小堆中 。
  • 4. 返回结果

    • 如果循环结束(堆为空)仍未到达终点 n-1,说明终点不可达,返回 -1 。

    算法逻辑与“反转”机会的体现

    该算法的巧妙之处在于,它通过图构建阶段的“加边”操作,将节点的“反转”机会直接融入了图的结构中。

    • 当算法在计算路径时,如果它选择了一条权重为 2*w 的“反转边”(从 v 到 u),这在逻辑上就等价于:在到达节点 v 后,使用了它的特殊操作,反转了边 u->v,并立即移动到了 u。
    • Dijkstra算法会自然地探索所有可能的路径(包含正常边和反转边),并最终找到综合成本最低的路径 。

    复杂度分析

    时间复杂度:O((V + E) log V)

    • V 是节点数 n,E 是原始有向图的边数。在算法构建的邻接表中,边的数量约为 2 * E。
    • 每个节点最多被从堆中弹出一次,每次弹出后需要遍历其所有出边进行松弛操作。每条边最多被处理一次。
    • 最小堆的插入(Push)和删除最小元(Pop)操作的时间复杂度均为 O(log V)。在最坏情况下,每条边都可能引发一次堆操作(插入新距离),因此总的时间复杂度为 O((V + E) log V) 。

    空间复杂度:O(V + E)

    • O(V):用于存储距离数组 dis。
    • O(E):用于存储邻接表 g。因为每条原始边被表示为两条边,所以邻接表的总空间是 O(E)。
    • O(V):最小堆在最坏情况下可能包含所有节点。
    • 因此,总的额外空间复杂度为 O(V + E) 。

    总结

    您提供的代码通过扩展图结构来建模节点特有的“边反转”操作,并成功运用了标准的Dijkstra算法和最小堆来高效求解单源最短路径问题。其时间复杂度和空间复杂度在图算法中属于高效范畴,能够处理题目中给出的数据规模(n <= 50000, edges.length <= 100000)。

    Go完整代码如下:

    package main

    import (
    "container/heap"
    "fmt"
    "math"
    )

    func minCost(n int, edges [][]int) int {
    type edge struct{ to, wt int }
    g := make([][]edge, n) // 邻接表
    for _, e := range edges {
    x, y, wt := e[0], e[1], e[2]
    g[x] = append(g[x], edge{y, wt})
    g[y] = append(g[y], edge{x, wt * 2}) // 反转边
    }

    dis := make([]int, n)
    for i := range dis {
    dis[i] = math.MaxInt
    }
    dis[0] = 0 // 起点到自己的距离是 0
    // 堆中保存 (起点到节点 x 的最短路长度,节点 x)
    h := &hp{{}}

    for h.Len() > 0 {
    p := heap.Pop(h).(pair)
    disX, x := p.dis, p.x
    if disX > dis[x] { // x 之前出堆过
    continue
    }
    if x == n1 { // 到达终点
    return disX
    }
    for _, e := range g[x] {
    y := e.to
    newDisY := disX + e.wt
    if newDisY < dis[y] {
    dis[y] = newDisY // 更新 x 的邻居的最短路
    // 懒更新堆:只插入数据,不更新堆中数据
    // 相同节点可能有多个不同的 newDisY,除了最小的 newDisY,其余值都会触发上面的 continue
    heap.Push(h, pair{newDisY, y})
    }
    }
    }

    return 1
    }

    type pair struct{ dis, x int }
    type hp []pair

    func (h hp) Len() int { return len(h) }
    func (h hp) Less(i, j int) bool { return h[i].dis < h[j].dis }
    func (h hp) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
    func (h *hp) Push(v any) { *h = append(*h, v.(pair)) }
    func (h *hp) Pop() (v any) { a := *h; *h, v = a[:len(a)1], a[len(a)1]; return }

    func main() {
    n := 4
    edges := [][]int{{0, 2, 1}, {2, 1, 1}, {1, 3, 1}, {2, 3, 3}}
    result := minCost(n, edges)
    fmt.Println(result)
    }

    在这里插入图片描述

    Python完整代码如下:

    # -*-coding:utf-8-*-

    import heapq
    from typing import List

    def minCost(n: int, edges: List[List[int]]) > int:
    # 邻接表
    g = [[] for _ in range(n)]
    for x, y, wt in edges:
    g[x].append((y, wt)) # 原边
    g[y].append((x, wt * 2)) # 反向边,权重翻倍

    # 初始化距离数组
    dist = [float('inf')] * n
    dist[0] = 0

    # 最小堆,元素为 (起点到节点x的最短距离, 节点x)
    heap = [(0, 0)] # (距离, 节点)

    while heap:
    dis_x, x = heapq.heappop(heap)

    # 如果当前距离大于已记录的最短距离,跳过
    if dis_x > dist[x]:
    continue

    # 到达终点
    if x == n 1:
    return dis_x

    # 遍历邻接节点
    for y, wt in g[x]:
    new_dis_y = dis_x + wt
    if new_dis_y < dist[y]:
    dist[y] = new_dis_y
    heapq.heappush(heap, (new_dis_y, y))

    return 1

    if __name__ == "__main__":
    n = 4
    edges = [[0, 2, 1], [2, 1, 1], [1, 3, 1], [2, 3, 3]]
    result = minCost(n, edges)
    print(result)

    在这里插入图片描述

    C++完整代码如下:

    #include <iostream>
    #include <vector>
    #include <queue>
    #include <climits>
    #include <functional>

    using namespace std;

    int minCost(int n, vector<vector<int>>& edges) {
    // 邻接表:存储 (邻居节点, 权重)
    vector<vector<pair<int, int>>> g(n);
    for (const auto& e : edges) {
    int x = e[0], y = e[1], wt = e[2];
    g[x].emplace_back(y, wt); // 原边
    g[y].emplace_back(x, wt * 2); // 反向边,权重翻倍
    }

    // 初始化距离数组
    vector<int> dist(n, INT_MAX);
    dist[0] = 0;

    // 最小堆:元素为 (起点到节点x的最短距离, 节点x)
    // 使用 greater 使 priority_queue 成为最小堆
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
    pq.emplace(0, 0); // (距离, 节点)

    while (!pq.empty()) {
    auto [dis_x, x] = pq.top();
    pq.pop();

    // 如果当前距离大于已记录的最短距离,跳过
    if (dis_x > dist[x]) {
    continue;
    }

    // 到达终点
    if (x == n 1) {
    return dis_x;
    }

    // 遍历邻接节点
    for (const auto& [y, wt] : g[x]) {
    int new_dis_y = dis_x + wt;
    if (new_dis_y < dist[y]) {
    dist[y] = new_dis_y;
    pq.emplace(new_dis_y, y);
    }
    }
    }

    return 1;
    }

    int main() {
    int n = 4;
    vector<vector<int>> edges = {{0, 2, 1}, {2, 1, 1}, {1, 3, 1}, {2, 3, 3}};
    int result = minCost(n, edges);
    cout << result << endl;
    return 0;
    }

    在这里插入图片描述

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 2026-01-18:边反转的最小路径总成本。用go语言,给定一个包含 n 个点(编号 0 到 n-1)的有向带权图。边集合 edges 中的每一项 edges[i] = [ui, vi, wi] 表
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!