Memory Efficient Algorithm for Finding Cycles in Graphs
1 Cycle Detection in Graphs
Somewhere in 2015, I encountered a nice algorithmic question during an interview for an algotreading company. The question was how one can decide if there is a cycle in an undirected connected graph? There are two immediate answers here, the first is running Breadth First Search (BFS) on arbitrary node and memorizing the nodes that were already visited. The search ends either when the algorithm complete scanning the whole graph or when it reaches some node twice, outputting “No” or “Yes” respectively. Another possible solution is by running Depth First Search (DFS) and keeping a variable to count the length of the longest path explored. The algorithm declare a cycle if at any point in time the length of the path reaches the number of vertices in the graph. For the rest of this post, denote that number by
.
Those two solutions are simple, and more important for this discussion, have the same space complexity. That is, both consume around the same amount of working memory (in bits) ignoring the memory needed for the input and output of the algorithm. Even though DFS have implementation that requires less memory than BFS, ultimately both requires
bits. One can (informally) justify the aforementioned space complexity as follow: both algorithms have to keep track of their search (a queue in BFS and a stack is DFS) which potentially can be as large as the entire graph thus
, and each node need
bits for encoding so the total space complexity is
bits.
During the rest of this post we will focus on space complexity rather than time complexity. We will present an algorithm that can determine if there is a cycle in
bits [base on this paper] and then discuss the importance of related problem: the s-t connectivity.
2 The s-t Connectivity Problem
2.1 Solving s-t connectivity as a Sub-step
We will start from solving a slightly different problem then uses its solution as a procedure in our cycle-detection algorithm.
Introducing the undirected s-t connectivity problem: The input is an undirected graph
of size
and two vertices
. The output should be
if there is path in
that connects
and
, and
otherwise. The directed s-t connectivity is about the same but the input graph is directed (i.e its edges has directions) and the output should be
whether there is a directed path from
to
.
Before we present the algorithm, we first define the following predicate: for any
and
define
if exists a path from
to
of at most
steps (i.e. edges), otherwise
.
The next observations are key points needed to solve the s-t connectivity efficiently (both for directed and undirected case).
- For any and any , we have that if and only if such that and
- Let , they are connected if and only if
From here the algorithm is straightforward: denote the algorithm by
that upon receiving
from the graph
and
:
- If , it outputs , and if it outputs
- For each : if and then output
- Output
The solution for s-t connectivity can be achieved by outputting “Yes” if and only if
.
Obviously, the depth of the recursion is
and in each level of the recursion the algorithm only needs to keep track of
and
, which is
bits, therefore the total memory complexity of the algorithm is
bits.
2.2 Extending the algorithm to detect cycles
How solving the s-t connectivity problem helps us with cycles detection? The next observation answer that: let
be the graph in question and
two endpoints of some edge, they must stay connected after removing that edge from
if and only if both participating in a cycle contained in
.
For the directed case the algorithm is as follow: iterate over any edge
, delete it temporarily from
and check if
on the resulting graph, that is, if
is still connected to
even after you delete the edge from
to
. If
then declare a cycle, else, restore back the edge and continue to the next one. Note that this solution also works for undirected graphs. The memory complexity is
and we done.
2.3 Is it the best we can do?
Actually, for undirected graphs there is an algorithm with
bits, improving the square on the log in the construction from previous section. The curious reader can read more about it here. For the directed case it is an open question whether we can improve the
or not.
2.4 Beyond Cycles in Graphs
The directed s-t connectivity problem is an important theoretical problem because it captures the “hardness” of any problem that requires logarithmic amount of memory space. The reduction to show that is actually very easy, suppose we have an algorithm that solves some problem in
space where
is logarithmic in
(and
is the size of the input), generate the following directed graph: create a vertex
for any state (memory configuration) of the algorithm. Since its memory is
, there are at most
states (thus vertices). Create an edge from
to
iff
is a valid possible successor state of
. Add a vertex
and connects to it all the states that leads to outputting “Yes”. Denote by
the starting-state of the algorithm and now solving the original problem is equivalent to finding a directed path from
to
in the states-graph.
This reduction was presented by Walter Savitch in 1970, and it also works for Nondeterministic algorithms. The conclusion is that any nondeterministic problem that can be solved in
space (where
is logarithmic in the size of the input) can be solved deterministically in
. Combining that conclusion with the algorithm for directed s-t connectivity yields that any problem with nondeterministic
space complexity can be solved deterministically (via reduction to directed s-t connectivity) with
space complexity.