2

Using NetworkX

I want to get lowest common ancestor from node1 and node11 in DiGraph.

The following is the code.

import networkx as nx

G = nx.DiGraph() #Directed graph
G.add_nodes_from([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15])
G.add_edges_from([(2,1),(3,1),(4,1),(5,2),(6,2),(7,3),(8,3),(9,3),(10,4),(10,12),(14,9),(15,8),(12,11),(13,11),(14,12),(15,13)])

ancestors1 = nx.ancestors(G, 1)
ancestors2 = nx.ancestors(G, 11)

src_set = set(ancestors1)
tag_set = set(ancestors2)
matched_list = list(src_set & tag_set)

dic = {}

for elem in matched_list:
    print elem
    length1 = nx.dijkstra_path_length(G, elem, 1)
    path1 = nx.dijkstra_path(G, elem, 1)
    dist1 = len(path1)
    length2 = nx.dijkstra_path_length(G, elem, 11)
    path2 = nx.dijkstra_path(G, elem, 11)
    dist2 = len(path2)
    dist_sum = dist1 + dist2
    dic[elem] = dist_sum

min_num = min(dic.values()) 
for k, v in sorted(dic.items(), key=lambda x:x[1]):
    if v != min_num:
        break
    else:
        print k, v

I have a problem with a execution speed, so I want faster execution speed.

If you have any good idea or algorithm, please tell me the idea.

Sorry for the poor English.

kh_si0567
  • 51
  • 3

2 Answers2

5

Rerunning Dijkstra in a loop indeed seems like an overkill.

Say we build the digraph obtained by reversing the edges:

import networkx as nx

G = nx.DiGraph() #Directed graph
G.add_nodes_from([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15])
edges = [(2,1),(3,1),(4,1),(5,2),(6,2),(7,3),(8,3),(9,3),(10,4),(10,12),(14,9),(15,8),(12,11),(13,11),(14,12),(15,13)]
G.add_edges_from([(e[1], e[0]) for e in edges])

Now we run BFS from each of the two nodes:

preds_1 = nx.bfs_predecessors(G, 1)
preds_2 = nx.bfs_predecessors(G, 11)

Finding the common vertices reachable from both nodes in the reversed graph is easy:

common_preds = set([n for n in preds_1]).intersection(set([n for n in preds_2]))

Now you can query the above easily. For example, to find the common vertex reachable from both, closest to 1, is:

>>> min(common_preds, key=lambda n: preds_1[n])
10
Ami Tavory
  • 66,807
  • 9
  • 114
  • 153
1

You can use the functions defined in networkx.algorithms.lowest_common_ancestor to do this. It has a faster version that only works on trees, and a slower version that will work on any directed acyclic graph. Since your example is a DAG, I'll use the latter.

# Same as in your example
import networkx as nx
G = nx.DiGraph() #Directed graph
G.add_nodes_from([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15])
G.add_edges_from([(2,1),(3,1),(4,1),(5,2),(6,2),(7,3),(8,3),(9,3),(10,4),(10,12),(14,9),(15,8),(12,11),(13,11),(14,12),(15,13)])

# Compute the lowest common ancestor of 1 and 11. This will print 15.
print nx.algorithms.lowest_common_ancestor(G, 1, 11)

Note that this computes the lowest common ancestor as defined by maximal distance from sources of the graph; by this definition both 10 and 15 are lowest common ancestors for (1, 11), and this happens to give 15. Your original code (and the previous answer) additionally minimizes the path from 1 and 11 to the LCA, which gives 10 (sum distance of 6) instead of 15 (sum distance of 7). If you require that property as well, then this module will likely not be suitable.

Using this module, the runtime to find a LCA for a single pair of nodes is O(n log(n)) in the size of the graph. The runtime to find the LCA for all pairs of nodes is O(n^3).

Alex
  • 21
  • 4
  • I have clarified my answer to give more detail and walk through an example, but please do note that the original question specifically asked how to do it with the library I linked. – Alex Feb 06 '19 at 18:09