Graph 有向无环图中从源到目标的所有路径*长度*

Graph 有向无环图中从源到目标的所有路径*长度*,graph,networkx,igraph,depth-first-search,breadth-first-search,Graph,Networkx,Igraph,Depth First Search,Breadth First Search,我有一个带有邻接矩阵形状的图(adj_mat.shape=(40004000))。我当前的问题是找到从源(row=0)到目标(col=trans_mat.shape[0]-1)的路径长度列表(节点的顺序并不重要) 我对寻找路径序列不感兴趣;我只对传播路径长度感兴趣。因此,这与查找所有简单路径不同,后者速度太慢(即查找从源到目标的所有路径;然后对每条路径打分)。有没有一种有效的方法可以快速做到这一点 建议使用DFS。我当前的实施(如下)根本不是最优的: #创建图形 G=nx.from_numpy

我有一个带有邻接矩阵形状的图(
adj_mat.shape=(40004000)
)。我当前的问题是找到从源(
row=0
)到目标(
col=trans_mat.shape[0]-1
)的路径长度列表(节点的顺序并不重要)

我对寻找路径序列不感兴趣;我只对传播路径长度感兴趣。因此,这与查找所有简单路径不同,后者速度太慢(即查找从源到目标的所有路径;然后对每条路径打分)。有没有一种有效的方法可以快速做到这一点


建议使用DFS。我当前的实施(如下)根本不是最优的:

#创建图形
G=nx.from_numpy_矩阵(adj_mat,使用=nx.DiGraph()创建_)
#初始化节点
对于G.nodes中的节点:
G.nodes[node]['cprob']=[]
#设置起始节点值
G.nodes[0]['cprob']=[0]
def prob(G,节点):
#查找节点的传入边
前置程序=列表(G.前置程序(节点))
curr_node_arr=[]
对于前置程序中的上一个节点:
#获取传入的边权重
边缘权重=G.获取边缘数据(上一个节点,节点)['weight']
#获取前置节点值
如果len(G.nodes[prev_node]['cprob'])==0:
G.nodes[prev_node]['cprob']=传播_prob(G,prev_node)
prev_node_arr=G.nodes[prev_node]['cprob']
#将传入边权重添加到上一个节点arr
curr\u node\u arr=np.连接([curr\u node\u arr,np.数组(边权重)+np.数组(上一个节点\u arr)])
#更新当前节点阵列
G.nodes[node]['cprob']=curr\u node\u arr
返回G.nodes[node]['cprob']
#计算从源到接收器的所有路径长度
part_func=prob(G,4000)

使用
igraph
我可以在~1秒内计算多达300个节点。我还发现访问邻接矩阵本身(而不是调用
igraph
的函数来检索边/顶点)也可以节省时间。两个关键瓶颈是:1)以高效的方式追加一个长列表(同时保留内存);2)找到并行化的方法。这一次在超过300个节点后呈指数增长,我想看看是否有人有更快的解决方案(同时也适合内存)

导入igraph
#从邻接矩阵创建图形
G=igraph.Graph.adjacence((trans\u mat\u pad>0.tolist())
#添加边权重
G.es['weight']=trans_mat_pad[trans_mat_pad.nonzero()]
#初始化节点
对于范围内的节点(横截面形状[0]):
G.vs[node]['cprob']=[]
#设置起始节点值
G.vs[0]['cprob']=[0]
def传播探头(G、节点、传输垫):
#查找节点的传入边
前置项=trans_mat_pad[:,node].nonzero()[0]\G.get_adjlist(mode='IN')[node]
curr_node_arr=[]
对于前置程序中的上一个节点:
#获取传入的边权重
边缘重量=传输材料垫[prev_node,node]#G.es[prev_node]['weight']
#获取前置节点值
如果len(G.vs[prev_node]['cprob'])==0:
curr\u node\u arr=np.连接([curr\u node\u arr,np.数组(边权重)+传播\u prob(G,上一个节点,trans\u mat\u pad)])
其他:
curr_node_arr=np.连接([curr_node_arr,np.数组(边权重)+np.数组(G.vs[prev_node]['cprob']))
##注意:如果内存受限,请取消下面的注释
#设置最大大小
#如果长度(当前节点长度)>100:
#curr_node_arr=np.sort(curr_node_arr)[:100]
#更新当前节点阵列
G.vs[node]['cprob']=curr\u node\u arr
返回G.vs[node]['cprob']
#计算路径长度
路径长度=传播概率(G,横向材料垫。形状[0]-1,横向材料垫)

我手头没有一个大型示例(例如>300个节点),但我找到了一个非递归解决方案:

import networkx as nx

g = nx.DiGraph()

nx.add_path(g, range(7))

g.add_edge(0, 3)
g.add_edge(0, 5)
g.add_edge(1, 4)
g.add_edge(3, 6)

# first step retrieve topological sorting
sorted_nodes = nx.algorithms.topological_sort(g)

start = 0
target = 6

path_lengths = {start: [0]}

for node in sorted_nodes:
    if node == target:
        print(path_lengths[node])
        break

    if node not in path_lengths or g.out_degree(node) == 0:
        continue
    new_path_length = path_lengths[node]
    new_path_length = [i + 1 for i in new_path_length]
    for successor in g.successors(node):
        if successor in path_lengths:
            path_lengths[successor].extend(new_path_length)
        else:
            path_lengths[successor] = new_path_length.copy()

    if node != target:
        del path_lengths[node]
输出:[2,4,2,4,4,6]

如果您只对不同长度的路径数感兴趣,例如上面的例子中的
{2:2,4:3,6:1}
,您甚至可以将列表减少为dicts

背景
我正在做的一些解释(我希望对更大的例子也有用)。第一步是检索拓扑排序。为什么?然后我知道边的流向,我可以简单地按顺序处理节点,而不会像递归变量那样“丢失任何边”或“回溯”。然后,我用一个包含当前路径长度的列表初始化开始节点(
[0]
)。在更新路径长度(所有元素+1)时,此列表将复制到所有后续元素。目标是在每次迭代中,计算从起始节点到所有处理节点的路径长度,并将其存储在dict
path\u length
中。循环在到达
目标
-节点后停止。

我认为如果不找到路径,就无法找到路径长度。可能我误解了。我可以想象在每个节点传播传入边权重的列表。连接边权重+所有前面的传入边权重描绘了所有可能的路径长度。但同样也构建了所有路径。我根据您提出的关于查找路径与查找路径长度的语义的问题编辑了我的问题。但是,在不记录路径序列的情况下查找路径长度真的找到了路径吗?我不太确定。例如,在HMMs中,前向算法不需要枚举发出感兴趣序列的所有可能路径;它只是传播从先前状态传播的概率。是的,拓扑顺序很重要-你完全正确。将其排序需要O(V+E)时间。我将在更大的图上给它一个镜头来测试性能。我给了它一个镜头,但它在200个节点后会标记。我怀疑这与每次networkx调用的开销有更大的关系。我希望你不介意,我对你的回答投了更高的票,但我将保留这个问题。你的意思是,它需要太多的内存或内存