Python 有向图:连接所有路径的最近节点 一些背景

Python 有向图:连接所有路径的最近节点 一些背景,python,algorithm,graph,Python,Algorithm,Graph,我正在分析一个函数的控制流图,该函数基本上将传入数据映射到传出数据。大多数街区都是这样的: if(输入变量==特定常量){ 输出变量=真; } 否则{ 输出_变量=FALSE; } 此类代码的典型控制流图如下图所示 digraph G { 2 -> 3 -> 5; 2 -> 4 -> 5; } 其中3和4的执行受input_变量的值制约,但5是独立的 问题 给定一个有向图和一个起始节点,如何找到最近的节点,使起始节点的任何路径都通过该节点 示例:给定如何查找

我正在分析一个函数的控制流图,该函数基本上将传入数据映射到传出数据。大多数街区都是这样的:

if(输入变量==特定常量){
输出变量=真;
}
否则{
输出_变量=FALSE;
}
此类代码的典型控制流图如下图所示

digraph G {
  2 -> 3 -> 5;
  2 -> 4 -> 5;
}

其中
3
4
的执行受
input_变量的值制约,但
5
是独立的

问题 给定一个有向图和一个起始节点,如何找到最近的节点,使起始节点的任何路径都通过该节点

示例:给定如何查找从
2
开始的
6
或从
8
开始的
12

我可以反转一个最低的共同祖先算法吗?它会有效吗?像

for each node in graph:
    ancestors = node.get_all_ancestors()
    lca = find_lowest_common_ancestor(ancestors)
    junction_node[lca] = node

get_junction_point(node):
    return junction_node[node]
我的编程语言是Python,我刚刚发现了NetworkX,但任何算法都将受到欢迎。我不习惯图论,我想我错过了基本的词汇表来找到我要找的东西


谢谢你的帮助

您可以这样做:

对于每个节点,查找其所有祖先和后代的列表。如果大小(祖先)+大小(后代)+1等于网络大小,则它是候选。现在,找到这样一个节点,它至少有一个祖先,并且后代数量最多


祖先和后代的列表可以很容易地计算出来。如果您不确定,请告诉我,我将扩展我的答案。

不是最有效的解决方案,但以下是一些应该让您开始的内容:

执行DFS,然后计算所有路径(存在于每条路径中的节点)的交点。然后,在这些节点中,找到每个路径中最靠近起点的节点:

>>> paths
[]
>>> def dfs(G, s, path):
...     if s not in G or not G[s]:
...             paths.append(path)
...     else:
...             for t in G[s]:
...                     dfs({k:[_t for _t in v if _t!=t] for k,v in G.items()}, t, path+[t])
... 
>>> dfs(G, 2, [])
>>> paths
[[3, 4, 6, 7, 12], [3, 4, 6, 8, 9, 10, 12], [3, 4, 6, 8, 9, 12], [3, 4, 6, 8, 11, 12], [3, 5, 6, 7, 12], [3, 5, 6, 8, 9, 10, 12], [3, 5, 6, 8, 9, 12], [3, 5, 6, 8, 11, 12], [4, 6, 7, 12], [4, 6, 8, 9, 10, 12], [4, 6, 8, 9, 12], [4, 6, 8, 11, 12]]
>>> for path in paths: print(path)
... 
[3, 4, 6, 7, 12]
[3, 4, 6, 8, 9, 10, 12]
[3, 4, 6, 8, 9, 12]
[3, 4, 6, 8, 11, 12]
[3, 5, 6, 7, 12]
[3, 5, 6, 8, 9, 10, 12]
[3, 5, 6, 8, 9, 12]
[3, 5, 6, 8, 11, 12]
[4, 6, 7, 12]
[4, 6, 8, 9, 10, 12]
[4, 6, 8, 9, 12]
[4, 6, 8, 11, 12]
>>> nodes = [set(L) for L in paths]
>>> commons = functools.reduce(set.intersection, nodes)
>>> commons
{12, 6}
>>> min(commons, key=lambda v: max(L.index(v) for L in paths))
6

现在,请注意
6
如何在某些路径的索引2中显示,以及在某些其他路径的索引1中显示。如果有一个节点(比如x),它出现在路径的索引1处,其中6出现在索引2处,而在索引2处,6出现在索引1处,那么这将是一个平局,这个算法将任意打破这个平局。根据您的需要,您可能需要定义如何更好地处理此案例

阅读您提出的所有解决方案,我想到了一个想法。我给第一个节点的值为1。递归地,所有的孩子都会得到这个数量的相等部分。反过来,他们将这一数额向下分配。如果一个子系统收到的总金额为1(起始金额),则该子系统为“接合点”。这是我的实现(开放供评论!!)

我希望BFS构造可以限制访问的节点数量

类节点(对象):
"""
对象,该对象表示图形中的节点,我们在其中搜索
集中从开始节点开始的所有路径。
``reference`:附加包含关系的真实对象。
``初始_值``:节点的初始数量。通常为1作为开始
节点,其他节点为0。
``路径`:到达该节点的已遍历节点集。用于
修剪循环依赖项。
"""
def uu init uu(self,reference,initial_值,path=set()):
self.reference=reference
#有关说明,请参见dispatch()
self.value\u can\u dispatch=self.value\u has\u received=初始值
self.path=path
def接收(自身、值):
"""
为节点提供``value``。如果节点收到1个(或更多安全性)
总之,它将返回True,否则返回False。
"""
self.value\u已接收+=值
self.value\u can\u dispatch+=值
如果self.value\u接收到>=1:
返回真值
返回错误
def调度(自我、儿童):
"""
将收到的值分派给子对象。
返回已筛选的“children”列表,其中子项涉及到
循环依赖项被删除。
如果一个子系统发出信号,表示它总共收到了1,则该功能将
停下来,把这个孩子还给我。
"""
#筛选用于访问此节点的路径中的后续对象,以便剪切
#外循环
true_继任者=[如果子对象不在self.path中,则子对象在子对象中对应子对象]
#将收到的值切成等份
金额=自身价值\u可\u分派/len(真值\u后继值)
#我们只传输上次发送后收到的值
#因为路径可能在不同的迭代中指向同一节点(路径
#通过一个节点可能比通过另一个节点更长),因此相同
#可以要求节点多次分派给其子节点。
#已接收值的总额保留在值\u has\u received中,因为
#节点可以通过几个步骤接收全部金额。因此,部分
#它的值可能在我们注意到节点已接收之前已被调度
#总数为1。
self.value\u can\u dispatch=分数(0)
对于真正的子女:
#如果孩子发信号说他得到了1,那么就提高胜利者的分数
如果是子项,则接收(金额):
返回儿童
返回集(真值)
def触摸屏(自身、其他_节点):
"""
将一个节点与另一个节点“接触”,通知该节点是可访问的
通过已知路径以外的另一条路径。
它将新路径的元素添加为节点的祖先。
"""
self.path |=other_node.path{other_node}
def make_子项(自我、参考):
"""
创建节点的子节点,指向引用。子节点接收
它从当前节点的路径。
"""
#这就是我们的算法可能会发疯的原因。如果通过两个
#路径,该算法将只保护递归到第一个路径
#路径。如果后续路径递归到第二条路径,我们将不会检测到
#它。=>我们应该在第二条路径到达时更新子路径。
返回self.uu类_uuu(引用,分数(0),self.path |{self})
def___代表