0

Given an adjacency list Map<Vertex, Set<Vertex>> for a DAG, I want to calculate the reachability of every vertex (i.e. if there there is path from u to v).

static Map<Vertex, Set<Vertex>> reachability(Map<Vertex, Set<Vertex>> adjList) {}

I know it is possible using Floyd-Warshall in O(V^3)

// Convert adjList to adjacency matrix mat
void reachability(boolean mat[][]) {
  final int N = mat.length;
  for (int k = 0; k < N; k++)
    for (int i = 0; i < N; i++)
      for (int j = 0; j < N; j++)
        mat[i][j] |= mat[i][k] && mat[k][j];
}

But I have a sparse graph (and an adjacency list), so what is the fastest way to do this?

Guy Coder
  • 24,501
  • 8
  • 71
  • 136
pathikrit
  • 32,469
  • 37
  • 142
  • 221

2 Answers2

2

An O(V*(V+E)) solution could be to do a simple BFS from every node in the graph and calculate its reachability set. Assuming |E| << |V|^2 (you said the graph is sparsed), this is significantly faster (and simpler to code) than floyd-warshall.

However, that is still suboptimal, and can be improved:

Your graph is a DAG, so you can first do a topological sort in O(V+E), then, go from last to first:

connected(v) = union { connected(u) | for all edge (v,u) } U {v}

This can be calculated pretty efficiently, and giving you total answer in time complexity O(|V|+|E|+k) where |V| - number of vertices, |E| - number of edges, k - number of connected pairs (limited to O(|V|^2) in worst case).

This solution gives you O(|V|^2) worst case performance, even for none sparsed graphs.

Pseudo code:

V = [v0,v1,...,vn-1]
V = topological_sort(V) //O(V+E)
connect = new Map:V->2^V //Map<Vertex,Set<Vertex>> in java
i = n-1
while (i >= 0):
   let v be the vertex in V[i]
   connected[v].add(v)
   for each edge (v,u):
      //since the graph is DAG, and we process in reverse order
      //the value of connected[u] is already known, so we can use it.
      connected[v].addAll(connected[u])
amit
  • 175,853
  • 27
  • 231
  • 333
  • Can you explain this line `connected(v) = union { connected(u) | for all edge (v,u) } U {v}` a bit more? Thanks! – pathikrit Jul 15 '15 at 17:16
  • @wrick for each node `u`, you have a set of other nodes that are connected to it, and denoted by `connected(u)`. You take all outgoing edge leaving `v`, and union the nodes that are connected to them, and add `v` at the end. This gives you the set of connected nodes that are connected to `v`. – amit Jul 15 '15 at 17:25
  • But, don't I have to do that union recursively?? Can you update the answer with some pseudocode... – pathikrit Jul 15 '15 at 18:39
  • @wrick Are you familiar with Dynamic Programming? – amit Jul 15 '15 at 18:52
  • Yes, but here I don't understand - what is connected. Is it initially an empty map? – pathikrit Jul 15 '15 at 22:21
  • Are you sure your worst case analysis is correct? The boost graph library says worst case it is O(VE): http://www.boost.org/doc/libs/1_55_0/libs/graph/doc/transitive_closure.html – pathikrit Jul 15 '15 at 22:36
  • @wrick `connected` is a map that for each vertex, contains the set of vertices that are reachable from it. Look pseudo code, it might help you. Regarding time complexity - Not 100% sure, I need to analyze it better, the bottleneck might be unioning multiple sets, let me think about it a bit more – amit Jul 16 '15 at 07:12
0

Here is a simple O(|V||E|) solution in Scala (basically, for each vertex, do a DFS):

type AdjList[A] = Map[A, Set[A]]

def transitiveClosure[A](adjList: AdjList[A]): AdjList[A]  = {
  def dfs(seen: Set[A])(u: A): Set[A] = {
    require(!seen(u), s"Cycle detected with $u in it")
    (adjList(u) filterNot seen flatMap dfs(seen + u)) + u
  }
  adjList.keys map {u =>
    u -> dfs(Set.empty)(u)  
  } toMap
}

Executable version here: http://scalafiddle.net/console/4f1730a9b124eea813b992edebc06840

pathikrit
  • 32,469
  • 37
  • 142
  • 221