You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

含环图中S到E且必经A/B/C各一次的k最短路径求解问询

解决带约束的k条最短路径问题:恰好访问A、B、C各一次

嘿,这个问题挺有挑战性的——既要在带环的图里找k条最短路径,还得严格保证A、B、C三个节点恰好被访问一次。下面我会详细讲怎么用Dijkstra算法的变形来解决,也会聊聊Viterbi算法的适配思路。

核心思路:用状态跟踪关键点访问情况

因为有“恰好访问A、B、C各一次”的硬约束,我们不能直接跑普通的k最短路径算法,必须把已访问的关键点集合作为状态的一部分,这样才能确保不会重复访问或者漏访问目标节点。

状态定义

我们把每个搜索状态定义为 (当前节点, 已访问关键点掩码):

  • 掩码用3位二进制数表示,比如0b000表示还没访问任何目标节点,0b001表示访问过A,0b011表示访问过A和B,最终目标状态是(E, 0b111)(到达终点且三个节点都恰好访问过一次)。
  • 每个状态我们需要记录前k条最短路径的长度和具体路径,这样后续组合的时候能覆盖所有可能的候选。

基于Dijkstra的实现步骤

我们可以用带状态的优先队列Dijkstra来处理,本质是把每个(节点, 掩码)当成一个独立的“虚拟节点”,然后在这个扩展的状态图上找k条最短路径:

  1. 初始化

    • 准备一个优先队列(最小堆),每个元素是(总路径长度, 当前节点, 掩码, 路径列表),初始时把起点状态(0, S, 0b000, [S])加入队列。
    • 准备一个字典state_paths,键是(节点, 掩码),值是一个列表,用来存储到达该状态的前k条最短路径(避免重复记录过长的路径)。
  2. 迭代搜索
    每次从堆中弹出当前最短的路径:

    • 如果当前(节点, 掩码)对应的列表已经有k条路径了,直接跳过(我们只需要前k短的)。
    • 否则,把这条路径加入state_paths[(节点, 掩码)]。
    • 遍历当前节点的所有邻接节点v:
      • 计算新掩码:
        • 如果v是A/B/C中的一个,且掩码中对应位为0(还没访问过),则新掩码 = 原掩码 | (1 << 对应索引)(比如A对应索引0,就是| 0b001)。
        • 如果v是A/B/C但已经被访问过(掩码对应位为1),直接跳过这条边(违反“恰好访问一次”的约束)。
        • 如果v是其他节点,新掩码和原掩码一致。
      • 计算新路径长度:原长度 + 边当前节点->v的权重。
      • 把新状态(新长度, v, 新掩码, 原路径 + [v])加入优先队列。
  3. 提取结果
    当队列处理完成后,从state_paths[(E, 0b111)]中取出所有路径,按长度排序,取前k条就是满足要求的答案。

处理环的情况

因为我们的状态只限制A/B/C的访问次数,图中的环(由非目标节点组成)会被自然处理:如果绕环的路径总长度比其他候选路径短,它会被优先加入队列;如果绕环后长度太长,会被我们的state_paths过滤掉(因为每个状态只保留前k条最短路径)。

Viterbi算法的适配思路

Viterbi本来是用来在隐马尔可夫模型中找最可能的状态序列的,不过我们可以把路径长度转化为“负对数概率”(路径越短,对应的概率越高),然后用Viterbi的变形来求前k个最可能的序列:

  • 每个状态还是(节点, 掩码),转移概率设为exp(-边权重)(权重越小,概率越高)。
  • 维护每个状态的前k个最大概率序列,最终取(E, 0b111)对应的前k个序列,再转化回路径长度排序即可。
    不过这种方法本质和带状态的Dijkstra思路一致,只是把“最短长度”换成了“最大概率”,实际场景中Dijkstra更直接适配最短路径问题。

伪代码示例

import heapq

def k_shortest_paths_with_constraint(graph, S, E, A, B, C, k):
    # 给A/B/C分配索引,方便掩码计算
    key_nodes = {A:0, B:1, C:2}
    state_paths = {}  # 键是(u, mask),值是[(length, path), ...]
    
    heap = []
    heapq.heappush(heap, (0, S, 0b000, [S]))
    
    while heap:
        length, u, mask, path = heapq.heappop(heap)
        state_key = (u, mask)
        
        # 如果该状态已经有k条路径了,跳过
        if state_key in state_paths and len(state_paths[state_key]) >= k:
            continue
        
        # 记录这条路径
        if state_key not in state_paths:
            state_paths[state_key] = []
        state_paths[state_key].append((length, path))
        
        # 遍历邻接边
        for v, weight in graph[u].items():
            new_mask = mask
            # 处理目标节点的访问约束
            if v in key_nodes:
                idx = key_nodes[v]
                if (mask >> idx) & 1:
                    # 已经访问过该节点,跳过
                    continue
                new_mask = mask | (1 << idx)
            
            new_length = length + weight
            new_path = path.copy()
            new_path.append(v)
            
            heapq.heappush(heap, (new_length, v, new_mask, new_path))
    
    # 提取终点且掩码为0b111的路径
    target_state = (E, 0b111)
    if target_state not in state_paths:
        return []  # 没有满足条件的路径
    
    # 排序后取前k条
    state_paths[target_state].sort()
    return [path for length, path in state_paths[target_state][:k]]

内容的提问来源于stack exchange,提问作者Gossip Girl

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:15:44