📜  权重大于等于1的边的乘积最小的路径(1)

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

求解权重大于等于1的边的乘积最小的路径

给定一个带权有向图,每一条边的权重大于等于1,需要求从起点开始到终点结束的一条路径,使得该路径所经过的边的权重乘积最小。如果存在多条满足条件的路径,输出其中任意一条。

解法

使用Dijkstra算法求解最短路径的思路有些相似,不过有以下两个要点需要注意:

  • 边的权重大于等于1,意味着不能使用标准版Dijkstra算法的贪心策略来寻找最优解,因为简单地取最小值不一定能够保证是最优解;
  • 边的乘积可能很大,容易导致出现溢出的问题,因此需要对中间结果进行取模运算;

因此,我们可以采用修改版的Dijkstra算法,根据当前点到起点的路径乘积来选出下一个顶点,同时使用堆来保存待选顶点集合,每一次取出来的顶点都是到起点的路径乘积最小的。而为了避免出现溢出的问题,我们每一步都对中间结果进行取模运算。

我们定义下列变量:

  • $n$:图中节点的总数;
  • $edges$:由任意两点之间边的权重所组成的邻接矩阵,其中Edge[i][j]代表从节点i到节点j的边的权重;
  • $source$:起点的编号;
  • $target$:终点的编号。
  1. 定义变量和数据结构

首先,我们需要定义一个待选顶点集合,用堆来实现。每次从堆中取出距离源点近、到起点的路径乘积小的顶点,并将其标记为已处理,同时将与其相邻的顶点(未被标记过)加入到待选顶点集合中。最终,堆中的元素都会被处理,此时到起点的路径乘积最小的那个顶点,就是从起点到终点的最短路径。

from queue import PriorityQueue
from typing import List

n = 0                      # 图中节点的总数
source = 0                 # 起点编号
target = 0                 # 终点编号
INF = float("inf")
base = 1000000007         # 取模的基数

# 图的邻接矩阵,Edge[i][j]表示i到j边的权重
edges = [[0] * (n+1) for _ in range(n+1)]

# 记录到每一个点的最小距离
dist = [INF] * (n+1)

# 用于记录路径的数组
path = [-1] * (n+1)

# 标记是否被处理过
seen = [False] * (n+1)

# 定义一个堆
heap = PriorityQueue()
  1. 初始化起点

从起点开始,路径长度为0,到起点的路径乘积为1。将其插入到堆中,并将其到起点的路径乘积设置为1。

dist[source] = 0
seen[source] = True
heap.put((-1, source, 1))
  1. 进行Dijkstra遍历

在接下来的每一步中,我们从堆中选择距离源点最近,到起点的路径乘积最小的顶点。对于每一条从当前顶点出发的边,我们计算到下一个顶点的路径乘积,并且将其插入到堆中,以便后续的处理。如果到达终点,遍历结束。

while not heap.empty():
    _,  node, prod = heap.get()  # 从堆中取出距离源点近、到起点的路径乘积小的顶点
    
    if node == target: # 到达终点
        break
    
    seen[node] = True
    
    # 遍历所有的邻居节点
    for nei in range(1, n+1):
        
        # 如果邻居节点已经处理过了,则跳过
        if seen[nei]:
            continue
        
        # 计算到邻居节点的路径乘积
        new_prod = prod * edges[node][nei] % base
        
        if new_prod < dist[nei]:
            dist[nei] = new_prod
            path[nei] = node
            heap.put((-dist[nei], nei, new_prod))
  1. 恢复路径

到达终点后,我们可以通过$path$数组从终点逆序找到路径的具体路线。我们定义一个列表$path$,从终点开始逆序遍历至起点,最终得到的就是从起点到终点的最短路径。

# 从终点开始逆序遍历,恢复具体路径
res = [target]
while path[target] != -1:
    target = path[target]
    res.append(target)
res.reverse()

# 返回最短路径
return res
完整代码
from queue import PriorityQueue
from typing import List

def min_product_path(n: int, edges: List[List[int]], source: int, target: int) -> List[int]:
    INF = float("inf")
    base = 1000000007
    dist = [INF] * (n+1)
    path = [-1] * (n+1)
    seen = [False] * (n+1)
    heap = PriorityQueue()
    edges_mtx = [[0] * (n+1) for _ in range(n+1)]
    for i, j, w in edges:
        edges_mtx[i][j] = w
    edges = edges_mtx
    
    # 初始化起点
    dist[source] = 0
    seen[source] = True
    heap.put((-1, source, 1))  # 用(-dist, node, prod)的元组来进行排序

    # 进行Dijkstra遍历
    while not heap.empty():
        _,  node, prod = heap.get()  # 从堆中取出距离源点近、到起点的路径乘积小的顶点

        if node == target: # 到达终点
            break

        seen[node] = True

        # 遍历所有的邻居节点
        for nei in range(1, n+1):
            # 如果邻居节点已经处理过了,则跳过
            if seen[nei]:
                continue

            # 计算到邻居节点的路径乘积
            new_prod = prod * edges[node][nei] % base

            if new_prod < dist[nei]:
                dist[nei] = new_prod
                path[nei] = node
                heap.put((-dist[nei], nei, new_prod))

    # 从终点开始逆序遍历,恢复具体路径
    res = [target]
    while path[target] != -1:
        target = path[target]
        res.append(target)
    res.reverse()

    # 返回最短路径
    return res
参考文献