📜  门|门CS 2012 |问题 3(1)

📅  最后修改于: 2023-12-03 15:28:49.105000             🧑  作者: Mango

门|门CS 2012 |问题 3

这是一个关于求解最小生成树 (Minimum Spanning Tree, MST) 的问题,给出一个带权无向图,要求从指定的起点出发,遍历图中所有节点的最短路径的权值和。

解法

最小生成树算法有 Prim 和 Kruskal 两种,其中 Prim 算法基于贪心思想,每次选择割边权值最小的边,直到生成树的边数达到总节点数减一为止。Kruskal 算法则是维护已经构建的生成树的所有节点,每次选择连通两个集合的割边权值最小的边加入生成树,直到生成树的边数达到总节点数减一为止。

由于本问题需要求遍历图中所有节点的最短路径的权值和,因此可以先用 Dijkstra 算法求出指定起点到所有节点的单源最短路径,然后将这些边建成一个带权有向图,使用 Prim 算法求解该有向图的最小生成树即可。

代码片段
import heapq

def dijkstra(edges, start, end):
    # 初始化距离和 visited
    dist = {node: float('inf') for node in edges}
    visited = {node: False for node in edges}
    dist[start] = 0

    # 创建堆并加入起点
    heap = []
    heapq.heappush(heap, (dist[start], start))

    while heap:
        # 取出堆中最小的节点
        curr_dist, curr_node = heapq.heappop(heap)

        # 如果该节点已被访问过,则继续下一轮循环
        if visited[curr_node]:
            continue

        # 将当前节点设为已访问
        visited[curr_node] = True

        # 更新相邻节点的最短路径
        for neighbor, weight in edges[curr_node]:
            if not visited[neighbor]:
                alt = curr_dist + weight
                if alt < dist[neighbor]:
                    dist[neighbor] = alt
                    heapq.heappush(heap, (alt, neighbor))

    # 返回起点到终点的最短路径
    return dist[end]

def prim(edges):
    # 选出任意一个节点作为起点
    start_node = list(edges.keys())[0]

    # 初始化 visited 和 min_heap
    visited = {node: False for node in edges}
    min_heap = []

    # 加入起点的所有出边
    for edge in edges[start_node]:
        heapq.heappush(min_heap, edge)

    # 创建生成树和距离和
    mst = []
    total_weight = 0

    while min_heap:
        # 选出堆中权值最小的边
        weight, (u, v) = heapq.heappop(min_heap)

        # 如果该边的终点已访问过,则继续下一轮循环
        if visited[v]:
            continue

        # 将终点设为已访问
        visited[v] = True

        # 将该边加入生成树
        mst.append((u, v, weight))

        # 更新距离和
        total_weight += weight

        # 加入终点的所有出边
        for edge in edges[v]:
            if not visited[edge[1]]:
                heapq.heappush(min_heap, edge)

    return total_weight

def main():
    # 节点数量
    n = int(input())

    # 起点
    start = input().strip()

    # 边权
    edges = {}
    for _ in range(n):
        u, v, w = input().strip().split()
        w = int(w)
        if u not in edges:
            edges[u] = []
        if v not in edges:
            edges[v] = []
        edges[u].append((w, (u, v)))
        edges[v].append((w, (v, u)))

    # 求出单源最短路径
    shortest_paths = {}
    for node in edges:
        shortest_paths[node] = dijkstra(edges, start, node)

    # 将所有单源最短路径建成一个有向图
    directed_edges = {}
    for node1 in edges:
        for weight, (node2, _) in edges[node1]:
            directed_weight = weight + shortest_paths[node1] - shortest_paths[node2]
            if node1 not in directed_edges:
                directed_edges[node1] = []
            directed_edges[node1].append((directed_weight, node2))

    # 求最小生成树的权值和
    minimum_weight = prim(directed_edges)

    print(minimum_weight)

if __name__ == '__main__':
    main()

以上代码片段为 Python 实现,依次读入节点数量、起点和所有的边权,然后先使用 dijkstra 算法求解指定起点到所有节点的单源最短路径,再将这些边建成一个有向图,最后使用 prim 算法求解该有向图的最小生成树,即为遍历图中所有节点的最短路径的权值和。