【问题标题】:Reduce the complexity of the algorithm to construct a directed graph (DAG) from an undirected graph that satisfies the given constraints降低算法的复杂度,从满足给定约束的无向图构造有向图 (DAG)
【发布时间】:2021-09-18 01:39:44
【问题描述】:

我有一个包含 4,000 多个节点的网络,并且我有一个边列表(节点对之间的连接)。所有节点都应该收敛到一个中心点,但我无法对节点进行排序,因为它们没有以重新排序可行的方式编号或标记。

我需要什么?:根据附上的小例子,我需要所有的节点都指向节点F(所有节点都可以到达F),这样无向图就变成了有向图( DAG) 并且作为 限制,每个节点对之间只有一条边。当且仅当要删除循环(例如 A -> B,B

我拥有的是这样的:

 library(igraph)
 library(tidygraph)
 library(ggraph)
 library(tidyverse)

 # edge list
 edgelist <- tribble(
  ~from, ~to,
  "A", "B",
  "A", "C",
  "B", "D",
  "C", "D",
  "C", "E",
  "D", "E",
  "D", "F")
 
 # create the graph
 g <- as_tbl_graph(edgelist)
 
 # undirected graph 
 g %>% 
  ggraph(layout = "graphopt") +
  geom_edge_link() +
  geom_node_point(shape = 21, size = 18, fill = 'white') +
  geom_node_text(aes(label = name), size = 3) +
  theme_graph() 

这是我提出的排序过程,以便边列表成为 DAG:

 s <- names(V(g))
 
 # define node objective
 target <- "F"
 
 # exclude target from vertex list
 vertex_list <- s[s != target]
 
 # calculate the simple path of each node to the destination node (target)
 route_list <- map(vertex_list, ~ all_simple_paths(graph = g, 
                                                   from = .x,
                                                   to = target)) %>% 
  set_names(vertex_list) %>% 
  map(~ map(., ~ names(.x))) %>%
  flatten() %>% 
  map(~ str_c(.x, collapse = ","))
 
 
 # generate the list of ordered edges
 ordered_edges <- do.call(rbind, route_list) %>% 
  as.data.frame(row.names = F) %>%  
  set_names("chain") %>% 
  group_by(chain) %>% 
  summarise(destination = str_split(chain, ","), .groups = "drop") %>% 
  mutate(
   
   from = map(destination, ~ lag(.x)) %>% 
    map(~ .x[!is.na(.x)]),
   
   to = map(destination, ~ lead(.x)) %>% 
    map(~ .x[!is.na(.x)]),
  ) %>% 
  
  select(from, to) %>% 
  unnest(cols = everything()) %>% 
  group_by(across(everything())) %>% 
  summarise(enlaces = n(), .groups = "drop") %>% 
  select(-enlaces)

警告:当节点数达到一定大小(比如 90 个)时,该算法会生成使图非循环的循环,因此我要做的一个附加过程是应用一个函数在 Python 中调用 feedback_arc_set 来删除将使图成为 DAG 的边。

为简单起见,我没有包含删除这些循环所需的代码,因为在这个特定示例中没有生成循环。

 # draw the graph again
 as_tbl_graph(ordered_edges) %>% 
  ggraph(layout = "graphopt") +
  geom_edge_link(arrow = arrow(length = unit(3, 'mm'),
                               type = "closed", 
                               angle = 30),
                 end_cap = circle(7, 'mm')) +
  geom_node_point(shape = 21, size = 18, fill = 'white') +
  geom_node_text(aes(label = name), size = 3) +
  theme_graph() 

reprex package (v2.0.0) 于 2021-07-07 创建

那么有什么问题?:节点数大于2000时算法的复杂度

如果我尝试使用 2000 个节点来执行此操作,则算法永远不会结束。我让它运行了 24 小时,但没有完成。事实上,我没有找到一种方法来知道它是否有效。在这个place我发现{igraph}all_simple_paths的函数在后台使用了DFS,但是复杂度是O (|V|!) where |V|是顶点数,|V|!是顶点数的阶乘。

有没有办法降低复杂度?

【问题讨论】:

  • “我需要所有节点都指向节点 F”猜你的意思是“F 可以从所有节点到达”?
  • @ravenspoint。我编辑了我的问题以澄清你提到的内容。答案是是的你指出的一切。

标签: r time-complexity igraph graph-theory tidygraph


【解决方案1】:

快速解答

其实你可以按照distances"F"的方式将顶点分组,然后检查两个相邻组的节点之间的邻域来添加边。


背后的想法

关于到"F" 的距离,这个想法来自以下事实:

  1. 如果节点的距离为d,则其父节点的距离必须为d+1
  2. 如果X的距离为d+1,那么距离为d的节点必须是X的子节点当且仅当它们是X的邻居。

我的尝试

D <- distances(g)
d <- distances(g, "F")
lst <- split(colnames(d), d)
lst <- lst[order(as.integer(names(lst)))]
res <- c()
for (k in head(seq_along(lst), -1)) {
    pre <- lst[[k]]
    nxt <- lst[[k + 1]]
    for (p in pre) {
        dp <- D[p, nxt, drop = FALSE]
        if (any(dp == 1)) {
            res[[length(res) + 1]] <- data.frame(
                from = colnames(dp)[dp == 1],
                to = p
            )
        }
    }
}
dag <- graph_from_data_frame(do.call(rbind, res))

然后

plot(dag)

给予

【讨论】:

  • 嗨@ThomasIsCoding。这在某些特定场景下对 2000 个节点的初始测试非常有效。我将尝试针对大量节点对其进行验证,并包括可能出现的情况,但它看起来很有希望。谢谢!!
  • @William 欢迎您。如果您有任何问题,请给我留言。
【解决方案2】:

没有办法避免进行 DFS。然而,问题不是由于 DFS 算法的复杂性。我可以在不到一秒的时间内对包含 403,394 个节点和 3,387,388 个链接的图进行 DFS https://github.com/JamesBremner/PathFinder2/wiki/Performance

可能的问题是您的算法需要执行大量的 DFS。

我建议使用以下算法,该算法应该在大约一秒钟内运行,并带有一个中等大小的图形,例如 4,000 个节点。

您需要做的第一件事是检查是否可以从 F 访问每个节点。从 F 开始的单个 DFS 会告诉您这一点。如果每个节点都不可达,那么不添加边就无法解决问题。

现在,遍历路径以确定每个链接的方向。请注意,任何未遍历的边都是不必要的,可以删除 - 从而防止“意外”引入循环

请注意,如果您有一个不错的 DFS 实现,允许您指定访问者,您可以一步完成,在 DFS 进行时标记边的方向。剩下的就是删除没有被访问过的不必要的边。整个过程将在 4,000 个节点的图上快速运行。

===

对快速解决此问题的应用程序感兴趣吗?在 MSWindows 机器上运行,用 C++17 编写,基于 PathFinder 类,保证性能 > 1,000 个节点/秒?

【讨论】:

  • 我正在审查您的应用程序,但很抱歉我的无知,我找不到如何安装应用程序。我知道它在带有 GUI 的 Windows 上运行
  • 该应用程序是几个不同问题的演示,可以使用 PathFinder 图形类来解决。您必须下载并构建源代码。我想知道您是否有兴趣我开发一个类似的应用程序,但专门用于解决您的特定问题。
  • 您的意思是在商业意义上为软件或服务付费?
  • 是的。你有兴趣吗?
  • 好吧,我的公司雇佣了一个像我这样没有经验的程序员来避免为像你这样的专家的服务付费,所以即使我愿意,我也有预算限制,这就是我在 stackoverflow 上的原因(哈哈)。感谢您的帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-28
  • 1970-01-01
  • 2014-04-21
相关资源
最近更新 更多