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

基于igraph边ID的带最少换乘的最短路径求解问题

解决igraph中结合路径长度与最少边ID变化的最短路径问题

这个需求非常贴近现实中的交通网络场景(比如铁路/地铁换乘):我们需要优先找到边数最少的路径(测地距离最短),在路径长度相同的情况下,再选择边ID变化次数最少(换乘次数最少)的那条。下面提供两种实用的解决方案,你可以根据图的规模来选择:

方法一:分层图转换(适合中等/大规模图)

核心思路是把每个节点拆分成多个“状态节点”,每个状态代表到达该节点时所使用的边ID(列车)。这样换乘操作就变成了状态节点之间的跳转,我们给换乘设置一个极小的额外权重,保证路径长度优先的前提下,换乘越少的路径总权重越低。

代码实现

library(igraph)
set.seed(45)
g <- erdos.renyi.game(25, 1/10, directed = TRUE)
E(g)$id <- sample(1:3, length(E(g)), replace = TRUE)

# 1. 构建分层图:每个原始节点对应3个状态节点(对应3种边ID),加上原始起点
layered_node_names <- c(as.character(V(g)), 
                        paste0(rep(V(g), each=3), "_", rep(1:3, length(V(g)))))
layered_g <- make_empty_graph(n = length(layered_node_names), directed = TRUE)
V(layered_g)$name <- layered_node_names

# 2. 添加从原始节点到对应状态节点的边(表示开始乘坐某ID的列车)
for (v in V(g)) {
  v_name <- as.character(v)
  for (k in 1:3) {
    layered_g <- add_edges(layered_g, 
                           c(v_name, paste0(v_name, "_", k)), 
                           attr = list(weight = 0))
  }
}

# 3. 添加同ID边对应的分层边(无换乘,权重为1,保持路径长度)
for (e in E(g)) {
  u_name <- as.character(head(e))
  v_name <- as.character(tail(e))
  edge_id <- E(g)$id[e]
  layered_g <- add_edges(layered_g, 
                         c(paste0(u_name, "_", edge_id), paste0(v_name, "_", edge_id)), 
                         attr = list(weight = 1))
}

# 4. 添加换乘边(不同ID状态之间跳转,权重极小,不影响路径长度优先级)
transfer_cost <- 0.001
for (v in V(g)) {
  v_name <- as.character(v)
  state_nodes <- paste0(v_name, "_", 1:3)
  # 所有不同状态之间的跳转边
  for (i in 1:3) {
    for (j in 1:3) {
      if (i != j) {
        layered_g <- add_edges(layered_g, 
                               c(state_nodes[i], state_nodes[j]), 
                               attr = list(weight = transfer_cost))
      }
    }
  }
}

# 5. 求解从原始节点1到所有状态节点的最短路径
sp_results <- shortest_paths(layered_g, 
                             from = "1", 
                             to = grep("_\\d+$", V(layered_g)$name, value = TRUE),
                             weights = E(layered_g)$weight)

# 6. 解析结果,转换回原始节点路径并计算换乘次数
final_result <- list()
for (v in V(g)) {
  v_name <- as.character(v)
  if (v == 1) {
    final_result[[v_name]] <- list(path = c(1), transfer_count = 0, path_length = 0)
    next
  }
  # 获取到当前节点所有状态的路径
  target_paths <- sp_results$vpath[grepl(paste0("^", v_name, "_"), names(sp_results$vpath))]
  if (length(target_paths) == 0) {
    final_result[[v_name]] <- list(path = NA, transfer_count = NA, path_length = NA)
    next
  }
  # 找到总权重最小的路径(即路径最短+换乘最少)
  path_weights <- sapply(target_paths, function(p) sum(E(layered_g, path = p)$weight))
  best_path_idx <- which.min(path_weights)
  best_layer_path <- target_paths[[best_path_idx]]
  
  # 转换为原始节点路径
  original_nodes <- gsub("_\\d+$", "", V(layered_g)$name[best_layer_path])
  original_nodes <- unique(as.integer(original_nodes))
  
  # 计算换乘次数
  edge_ids <- c()
  for (i in 1:(length(original_nodes)-1)) {
    u <- original_nodes[i]
    v_node <- original_nodes[i+1]
    # 匹配路径中对应的边ID
    state_id <- strsplit(V(layered_g)$name[best_layer_path[i+1]], "_")[[1]][2]
    e <- E(g)[from(u) & to(v_node) & id == as.integer(state_id)]
    edge_ids <- c(edge_ids, E(g)$id[e])
  }
  transfer_count <- sum(diff(edge_ids) != 0)
  
  final_result[[v_name]] <- list(
    path = original_nodes,
    transfer_count = transfer_count,
    path_length = length(original_nodes)-1
  )
}

# 示例:查看节点5的最优路径
print(final_result[["5"]])

方法二:枚举所有最短路径后筛选(适合小规模图)

如果你的图不大,可以先找出所有边数最短的路径,再从中筛选出换乘次数最少的那条。这种方法更直观,不需要修改图结构。

代码实现

library(igraph)
set.seed(45)
g <- erdos.renyi.game(25, 1/10, directed = TRUE)
E(g)$id <- sample(1:3, length(E(g)), replace = TRUE)

# 1. 获取从节点1到所有节点的所有最短路径
all_shortest <- all_shortest_paths(g, from = 1, to = V(g))

# 2. 筛选每个节点的最优路径
final_result2 <- list()
for (v in V(g)) {
  v_name <- as.character(v)
  if (v == 1) {
    final_result2[[v_name]] <- list(path = c(1), transfer_count = 0, path_length = 0)
    next
  }
  paths <- all_shortest$res[[v_name]]
  if (length(paths) == 0) {
    final_result2[[v_name]] <- list(path = NA, transfer_count = NA, path_length = NA)
    next
  }
  # 计算每条路径的换乘次数
  transfer_counts <- sapply(paths, function(p) {
    if (length(p) <= 1) return(0)
    edge_ids <- E(g, path = p)$id
    sum(diff(edge_ids) != 0)
  })
  # 选择换乘次数最少的路径(若有多个,任选其一)
  best_idx <- which.min(transfer_counts)
  best_path <- as.integer(paths[[best_idx]])
  
  final_result2[[v_name]] <- list(
    path = best_path,
    transfer_count = transfer_counts[best_idx],
    path_length = length(best_path)-1
  )
}

# 示例:查看节点5的最优路径
print(final_result2[["5"]])

方法对比

  • 分层图法:效率更高,适合大规模图,能处理有向图和复杂权重,但需要额外构建分层图,代码稍复杂。
  • 枚举筛选法:逻辑直观,代码简单,但如果图很大,all_shortest_paths会生成大量路径,导致内存和效率问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:21:03