【问题标题】:Python: how to compute the number of shortest paths passing through one node?Python:如何计算通过一个节点的最短路径数?
【发布时间】:2016-08-01 19:38:16
【问题描述】:

假设我有一个由NxN 节点组成的常规网络。两个节点之间的最短路径是从节点到达一个目标节点所需的最小跳数。现在,每条最短路径都会沿途经过许多节点。

我的目标:对于网络中的每个节点,我想计算通过特定节点的最短路径的数量,并将该数字保存在 dict 中。

在这个小例子中,节点 B 有 4 条最短路径通过它: A -> BA -> CC -> BC -> A我希望能够为通用图中的每个节点计算这个数字。

我知道我可以使用nx.betweenness_centrality(),但这会给我一个分子(即,对于每个节点,我想要的)除以一个分母,该分母存储两个节点之间所有可能的最短路径。我什至访问了源代码,但我无法弄清楚除法是在哪里进行的。

我知道这是一个冗长的问题,但我没有其他方法来解释我的问题。感谢任何愿意提供帮助的人。

编辑

这是nx.betweenness_centrality() 的源代码。我的图表是无向的。目前尚不清楚哪条线路承载我上面介绍的部门:

def betweenness_centrality(G, k=None, normalized=True, weight=None,
                           endpoints=False,
                           seed=None): #G is the graph

    betweenness = dict.fromkeys(G, 0.0)  
    if k is None:
        nodes = G
    else:
        random.seed(seed)
        nodes = random.sample(G.nodes(), k)
    for s in nodes:
        # single source shortest paths
        if weight is None:  # use BFS
            S, P, sigma = _single_source_shortest_path_basic(G, s)
        else:  # use Dijkstra's algorithm
            S, P, sigma = _single_source_dijkstra_path_basic(G, s, weight)
        # accumulation
        if endpoints:
            betweenness = _accumulate_endpoints(betweenness, S, P, sigma, s)
        else:
            betweenness = _accumulate_basic(betweenness, S, P, sigma, s)
    # rescaling
    betweenness = _rescale(betweenness, len(G),
                           normalized=normalized,
                           directed=G.is_directed(),
                           k=k)
    return betweenness #Returns a dict with the node ID as a key and the value

【问题讨论】:

  • 你能链接到源代码或指向一些模块/行号吗?
  • 为了更清楚,我将包含源代码。

标签: python networkx shortest-path


【解决方案1】:

你可以使用nx.all_pairs_shortest_path(G):

>>> G = nx.Graph()
>>> G.add_path([0,1,2])

>>> spaths = nx.all_pairs_shortest_path(G)
>>> spaths
{0: {0: [0], 1: [0, 1], 2: [0, 1, 2]},
 1: {0: [1, 0], 1: [1], 2: [1, 2]},
 2: {0: [2, 1, 0], 1: [2, 1], 2: [2]}}

它会为您找到图中所有节点对之间的所有最短路径(这对于大型图来说非常昂贵)。然后,下面的代码会给你想要的结果:

def num_spaths(G):
    n_spaths = dict.fromkeys(G, 0.0)
    spaths = nx.all_pairs_shortest_path(G)

    for source in G:
        for path in spaths[source].values():
            for node in path[1:]: # ignore firs element (source == node)
                n_spaths[node] += 1 # this path passes through `node`

    return n_spaths

在你的例子中:

>>> num_spaths(G)
{0: 2.0, 1: 4.0, 2: 2.0}

此外,如果您可以进入 all_pairs_shortest_path 代码并仔细编辑它以添加最短路径计数器和

for node in path[1:]:
    n_spaths[node] += 1

这样,您可以在找到路径的同时在线更新路径的数量,而不是在计算完所有路径之后(就像我的代码那样)遍历所有路径。

编辑:in networkx (github),第 251 行说:

paths[w]=paths[v]+[w]

您可以通过将n_spath 作为参数传递给修改后的函数并在内部更新它来修改该函数以在线更新最短路径的数量(同时找到它们)。然后调用该函数将是:

def num_spaths(G):
    n_spaths = dict.fromkeys(G, 0.0)
    for n in G:
        my_single_source_shortest_path(G, n, n_spaths)
    return n_spaths

n_spaths 将更新最短路径的数量。


编辑:上述方法仅适用于 A 和 B 之间只有 1 条最短路径的情况,因为 nx.all_pairs_shortest_path 每个节点对仅返回一条最短路径。在蛮力搜索下面找到所有可能的最短路径。我不建议在大图中运行它:

def bf_num_spaths(G):
    n_spaths = dict.fromkeys(G, 0.0)

    for source in G:
        for target in G:
            if source == target:
                continue
            for path in nx.all_shortest_paths(G, source, target):
                for node in path[1:]: # ignore firs element (source == node)
                    n_spaths[node] += 1 # this path passes through `node`

    return n_spaths

【讨论】:

  • 您能否准确告诉我,您的解决方案中的哪一行避免计算路径两次,因为该图是无向的?如果它不这样做,我可以将每个 dict 值除以 2。
  • @FC84 该代码现在适用于无向图,我只是在更新它。查看编辑,将single_source_shortest_pathnetworkx修改为可以在线计算最短路径数。
  • 所以根据您最后的添加,我需要做的就是修改all_pairs_shortest_path,在我的脚本中,在创建num_spaths 之后,只需将其分配给修改后的函数?也就是说,在这种情况下,num_spaths 现在将是一个 dict 而不是一个函数,对吧?
  • @FC84 是的,抱歉,更新了最后一个示例以避免名称冲突。您需要修改的是single_source_shortest_pathall_pairs_shortest_path 调用的函数),以便添加几行并接受n_spaths 作为输入字典以就地修改它。
  • @FC84 不同之处在于您不需要遍历所有最短路径。所以是和不是。一旦计算出最短路径,它将比循环遍历最短路径更便宜,但是,与计算所有最短路径所需的时间相比,这是无关的。所以,简而言之,你可以避免这种情况。
【解决方案2】:

我相信您可以修改Floyd-Warshall Algorithm 以将路径的长度保存在另一个矩阵中。伪代码(基于维基百科):

let dist and num be two |V| × |V| arrays of minimum
distances and lengths initialized to ∞ (infinity)

for each vertex v
   dist[v][v] ← 0
   num[v][v] ← 0

for each edge (u,v)
   dist[u][v] ← w(u,v)  // the weight of the edge (u,v)
   num[u][v] ← 1

for k from 1 to |V|
    for i from 1 to |V|
        for j from 1 to |V|
            if dist[i][j] > dist[i][k] + dist[k][j] 
                dist[i][j] ← dist[i][k] + dist[k][j]
                num[i][j] ← num[i][k] + num[k][j]
            end if

【讨论】:

    【解决方案3】:

    我会尽量给出纯粹的算法答案。

    如果从v_1v_2 有几条最短路径,其中一些包含v_mid,该怎么办?如果这种情况对于每个(v_1, v_2) 对都计为1,则可以使用Floyd-Warshall 算法。运行后,您将拥有最短路径长度的矩阵。对于每个u,遍历所有(v_1, v_2) 对,如果D[v_1, u] + D[u, v_2] == D[v_1, v_2],则计算+1。这是 O(n^3) 并且适用于密集图。

    我不确定,但我认为可以调整它以计算最短路径的数量。

    【讨论】:

      【解决方案4】:

      如果无法进一步访问源代码,就无法肯定地说,但我的赌注是:

      S, P, sigma = _single_source_shortest_path_basic(G, s)
      

      或者

      betweenness = _rescale(betweenness, len(G),...
      

      重新调整只是听起来就像它会包含一个部门。

      你试过用normalized=False调用这个函数吗?

      【讨论】:

      • 当您要求进一步访问源代码时,您到底想要什么?
      • 模块本身的源代码是可用的(虽然有点难以导航)。我真正的意思是有关您的用例的更多信息。
      • 好吧,我还没有建立一个。我明白你的意思,但我什么也写不出来。如果在源代码中,我会发现我提到的部门。然而,Normalize=False 似乎不是解决方案。
      • _rescale 有一个 1/2 的因子作为良好的衡量标准... def _rescale(betweenness, n, normalized,directed=False, k=None): ... 如果没有定向:scale = 1.0 / 2.0 else: scale = None 如果 scale 不是 None: 如果 k 不是 None: scale = scale * n / k for v in betweenness: betweenness[v] *= scale
      猜你喜欢
      • 1970-01-01
      • 2018-04-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-23
      相关资源
      最近更新 更多