Skip to content

Commit

Permalink
sagemathgh-38198: Improve complexity comments for graphs
Browse files Browse the repository at this point in the history
    
<!-- ^ Please provide a concise and informative title. -->
<!-- ^ Don't put issue numbers in the title, do this in the PR
description below. -->
<!-- ^ For example, instead of "Fixes sagemath#12345" use "Introduce new method
to calculate 1 + 2". -->
<!-- v Describe your changes below in detail. -->
<!-- v Why is this change required? What problem does it solve? -->
<!-- v If this PR resolves an open issue, please link to it here. For
example, "Fixes sagemath#12345". -->

As noted in sagemath#37642, there is a difference in the complexity of
enumerating neighbors or edges between sparse graphs and dense graphs.
This PR aims at making the comments about the time complexity of
algorithms clearer in regard of this difference.

Most of the changes I made in this PR are due to the fact that the
function `init_short_digraph` have the following time complexity:
- `O(n+m)` when the input graph has a sparse representation and
sort_neighbors is False
- `O(n+m log(m))` when the input graph has a sparse representation and
sort_neighbors is True
- `O(n^2)` when the input graph has a dense representation and
sort_neighbors is False
- `O(n^2 log(m))` when the input graph has a dense representation and
sort_neighbors is True

with `m` the number of edges and `n` the number of vertices of the
graph.


I spotted 5 additional complexity claims in the comments that I was not
able to verify:
1. for strong_orientations_iterator in orientations.py
2. for yen_k_shortest_simple_paths in path_enumeration.pyx
3. for feng_k_shortest_simple_paths in path_enumeration.pyx
4. for edge_disjoint_spanning_trees in spanning_tree.pyx
    (there is loop over the sorted edges of the graphs, which should
imply a
    complexity of at least `n+m log(m)` for sparse graphs and `O(n^2
log(m))`  for dense graphs
5. for is_partial_cube in partial_cube.py
    (there is at least one enumeration of neighbors of a vertex (via
calling `contracted[root]`,
    line 318) so `m` should appear in the complexity)



### 📝 Checklist

<!-- Put an `x` in all the boxes that apply. -->

- [x] The title is concise and informative.
- [x] The description explains in detail what this PR is about.
- [x] I have linked a relevant issue or discussion.
- [ ] I have created tests covering the changes.
- [ ] I have updated the documentation and checked the documentation
preview.

### ⌛ Dependencies

<!-- List all open PRs that this PR logically depends on. For example,
-->
<!-- - sagemath#12345: short description why this is a dependency -->
<!-- - sagemath#34567: ... -->
    
URL: sagemath#38198
Reported by: cyrilbouvier
Reviewer(s): cyrilbouvier, David Coudert
  • Loading branch information
Release Manager committed Jun 16, 2024
2 parents 7da2ae3 + c6f9a50 commit fd428c7
Show file tree
Hide file tree
Showing 9 changed files with 73 additions and 24 deletions.
10 changes: 10 additions & 0 deletions src/sage/graphs/base/dense_graph.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@ from ``CGraph`` (for explanation, refer to the documentation there)::
It also contains the following variables::
cdef binary_matrix_t edges
.. NOTE::
As the edges are stored as the adjacency matrix of the graph, enumerating
the edges of the graph has complexity `O(n^2)` and enumerating the neighbors
of a vertex has complexity `O(n)` (where `n` in the size of the bitset
active_vertices).
So, the class ``DenseGraph`` should be used for graphs such that the number
of edges is close to the square of the number of vertices.
"""

# ****************************************************************************
Expand Down
6 changes: 4 additions & 2 deletions src/sage/graphs/base/static_sparse_graph.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -231,11 +231,13 @@ cdef int init_short_digraph(short_digraph g, G, edge_labelled=False,
complexity of some methods. More precisely:
- When set to ``True``, the time complexity for initializing ``g`` is in
`O(m + m\log{m})` and deciding if ``g`` has edge `(u, v)` can be done in
`O(n + m\log{m})` for ``SparseGraph`` and `O(n^2\log{m})` for
``DenseGraph``, and deciding if ``g`` has edge `(u, v)` can be done in
time `O(\log{m})` using binary search.
- When set to ``False``, the time complexity for initializing ``g`` is
reduced to `O(n + m)` but the time complexity for deciding if ``g`` has
reduced to `O(n + m)` for ``SparseGraph`` and `O(n^2)` for
``DenseGraph``, but the time complexity for deciding if ``g`` has
edge `(u, v)` increases to `O(m)`.
"""
g.edge_labels = NULL
Expand Down
15 changes: 9 additions & 6 deletions src/sage/graphs/convexity_properties.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -507,8 +507,11 @@ def geodetic_closure(G, S):
each vertex `u \in S`, the algorithm first performs a breadth first search
from `u` to get distances, and then identifies the vertices of `G` lying on
a shortest path from `u` to any `v\in S` using a reversal traversal from
vertices in `S`. This algorithm has time complexity in `O(|S|(n + m))` and
space complexity in `O(n + m)`.
vertices in `S`. This algorithm has time complexity in
`O(|S|(n + m) + (n + m\log{m}))` for ``SparseGraph``,
`O(|S|(n + m) + n^2\log{m})` for ``DenseGraph`` and space complexity in
`O(n + m)` (the extra `\log` factor is due to ``init_short_digraph`` being
called with ``sort_neighbors=True``).
INPUT:
Expand Down Expand Up @@ -678,10 +681,10 @@ def is_geodetic(G):
Check whether the input (di)graph is geodetic.
A graph `G` is *geodetic* if there exists only one shortest path between
every pair of its vertices. This can be checked in time `O(nm)` in
unweighted (di)graphs with `n` nodes and `m` edges. Examples of geodetic
graphs are trees, cliques and odd cycles. See the
:wikipedia:`Geodetic_graph` for more details.
every pair of its vertices. This can be checked in time `O(nm)` for
``SparseGraph`` and `O(nm+n^2)` for ``DenseGraph`` in unweighted (di)graphs
with `n` nodes and `m` edges. Examples of geodetic graphs are trees, cliques
and odd cycles. See the :wikipedia:`Geodetic_graph` for more details.
(Di)graphs with multiple edges are not considered geodetic.
Expand Down
13 changes: 13 additions & 0 deletions src/sage/graphs/distances_all_pairs.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -1750,6 +1750,11 @@ def diameter(G, algorithm=None, source=None):
error if the initial vertex is not in `G`. This parameter is not used
when ``algorithm=='standard'``.
.. NOTE::
As the graph is first converted to a short_digraph, all complexity
have an extra `O(m+n)` for ``SparseGraph`` and `O(n^2)` for
``DenseGraph``.
EXAMPLES::
sage: from sage.graphs.distances_all_pairs import diameter
Expand Down Expand Up @@ -2278,6 +2283,14 @@ def szeged_index(G, algorithm=None):
By default (``None``), the ``"low"`` algorithm is used for graphs and the
``"high"`` algorithm for digraphs.
.. NOTE::
As the graph is converted to a short_digraph, the complexity for the
case ``algorithm == "high"`` has an extra `O(m+n)` for ``SparseGraph``
and `O(n^2)` for ``DenseGraph``. If ``algorithm == "low"``, the extra
complexity is `O(n + m\log{m})` for ``SparseGraph`` and `O(n^2\log{m})`
for ``DenseGraph`` (because ``init_short_digraph`` is called with
``sort_neighbors=True``).
EXAMPLES:
True for any connected graph [KRG1996]_::
Expand Down
8 changes: 5 additions & 3 deletions src/sage/graphs/generic_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -4572,8 +4572,9 @@ def eulerian_orientation(self):
has no non-oriented edge (this vertex must have odd degree), the walk
resumes at another vertex of odd degree, if any.

This algorithm has complexity `O(m)`, where `m` is the number of edges
in the graph.
This algorithm has complexity `O(n+m)` for ``SparseGraph`` and `O(n^2)`
for ``DenseGraph``, where `m` is the number of edges in the graph and
`n` is the number of vertices in the graph.

EXAMPLES:

Expand Down Expand Up @@ -14905,7 +14906,8 @@ def is_chordal(self, certificate=False, algorithm="B"):
ALGORITHM:

This method implements the algorithm proposed in [RT1975]_ for the
recognition of chordal graphs with time complexity in `O(m)`. The
recognition of chordal graphs. The time complexity of this algorithm is
`O(n+m)` for ``SparseGraph`` and `O(n^2)` for ``DenseGraph``. The
algorithm works through computing a Lex BFS on the graph, then checking
whether the order is a Perfect Elimination Order by computing for each
vertex `v` the subgraph induced by its non-deleted neighbors, then
Expand Down
3 changes: 2 additions & 1 deletion src/sage/graphs/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -3047,7 +3047,8 @@ def strong_orientation(self):
.. NOTE::
- This method assumes the graph is connected.
- This algorithm works in O(m).
- This time complexity is `O(n+m)` for ``SparseGraph`` and `O(n^2)`
for ``DenseGraph`` .
.. SEEALSO::
Expand Down
6 changes: 6 additions & 0 deletions src/sage/graphs/graph_decompositions/clique_separators.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,12 @@ def atoms_and_clique_separators(G, tree=False, rooted_tree=False, separators=Fal
:meth:`~sage.graphs.traversals.maximum_cardinality_search_M` graph traversal
and has time complexity in `O(|V|\cdot|E|)`.
.. NOTE::
As the graph is converted to a short_digraph (with
``sort_neighbors=True``), the complexity has an extra
`O(|V|+|E|\log{|E|})` for ``SparseGraph`` and `O(|V|^2\log{|E|})` for
``DenseGraph``.
If the graph is not connected, we insert empty separators between the lists
of separators of each connected components. See the examples below for more
details.
Expand Down
22 changes: 15 additions & 7 deletions src/sage/graphs/traversals.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,8 @@ def lex_BFS(G, reverse=False, tree=False, initial_vertex=None, algorithm="fast")
- ``"fast"`` -- This algorithm uses the notion of *slices* to refine the
position of the vertices in the ordering. The time complexity of this
algorithm is in `O(n + m)`, and our implementation follows that
complexity. See [HMPV2000]_ and next section for more details.
complexity for ``SparseGraph``. For ``DenseGraph``, the complexity is
`O(n^2)`. See [HMPV2000]_ and next section for more details.
ALGORITHM:
Expand Down Expand Up @@ -505,8 +506,9 @@ def lex_UP(G, reverse=False, tree=False, initial_vertex=None):
appended to the codes of all neighbors of the selected vertex that are left
in the graph.
Time complexity is `O(n+m)` where `n` is the number of vertices and `m` is
the number of edges.
Time complexity is `O(n+m)` for ``SparseGraph`` and `O(n^2)` for
``DenseGraph`` where `n` is the number of vertices and `m` is the number of
edges.
See [Mil2017]_ for more details on the algorithm.
Expand Down Expand Up @@ -677,8 +679,9 @@ def lex_DFS(G, reverse=False, tree=False, initial_vertex=None):
codes are updated. Lex DFS differs from Lex BFS only in the way codes are
updated after each iteration.
Time complexity is `O(n+m)` where `n` is the number of vertices and `m` is
the number of edges.
Time complexity is `O(n+m)` for ``SparseGraph`` and `O(n^2)` for
``DenseGraph`` where `n` is the number of vertices and `m` is the number of
edges.
See [CK2008]_ for more details on the algorithm.
Expand Down Expand Up @@ -851,8 +854,9 @@ def lex_DOWN(G, reverse=False, tree=False, initial_vertex=None):
prepended to the codes of all neighbors of the selected vertex that are left
in the graph.
Time complexity is `O(n+m)` where `n` is the number of vertices and `m` is
the number of edges.
Time complexity is `O(n+m)` for ``SparseGraph`` and `O(n^2)` for
``DenseGraph`` where `n` is the number of vertices and `m` is the number of
edges.
See [Mil2017]_ for more details on the algorithm.
Expand Down Expand Up @@ -1582,6 +1586,10 @@ def maximum_cardinality_search(G, reverse=False, tree=False, initial_vertex=None
chosen at each step `i` to be placed in position `n - i` in `\alpha`. This
ordering can be computed in time `O(n + m)`.
Time complexity is `O(n+m)` for ``SparseGraph`` and `O(n^2)` for
``DenseGraph`` where `n` is the number of vertices and `m` is the number of
edges.
When the graph is chordal, the ordering returned by MCS is a *perfect
elimination ordering*, like :meth:`~sage.graphs.traversals.lex_BFS`. So
this ordering can be used to recognize chordal graphs. See [He2006]_ for
Expand Down
14 changes: 9 additions & 5 deletions src/sage/graphs/weakly_chordal.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,9 @@ def is_long_hole_free(g, certificate=False):
This is done through a depth-first-search. For efficiency, the auxiliary
graph is constructed on-the-fly and never stored in memory.
The run time of this algorithm is `O(m^2)` [NP2007]_ ( where
`m` is the number of edges of the graph ) .
The run time of this algorithm is `O(n+m^2)` for ``SparseGraph`` and
`O(n^2 + m^2)` for ``DenseGraph`` [NP2007]_ (where `n` is the number of
vertices and `m` is the number of edges of the graph).
EXAMPLES:
Expand Down Expand Up @@ -393,8 +394,9 @@ def is_long_antihole_free(g, certificate=False):
This is done through a depth-first-search. For efficiency, the auxiliary
graph is constructed on-the-fly and never stored in memory.
The run time of this algorithm is `O(m^2)` [NP2007]_ (where
`m` is the number of edges of the graph).
The run time of this algorithm is `O(n+m^2)` for ``SparseGraph`` and
`O(n^2\log{m} + m^2)` for ``DenseGraph`` [NP2007]_ (where `n` is the number
of vertices and `m` is the number of edges of the graph).
EXAMPLES:
Expand Down Expand Up @@ -526,7 +528,9 @@ def is_weakly_chordal(g, certificate=False):
contain an induced cycle of length at least 5.
Using is_long_hole_free() and is_long_antihole_free() yields a run time
of `O(m^2)` (where `m` is the number of edges of the graph).
of `O(n+m^2)` for ``SparseGraph`` and `O(n^2\log{m} + m^2)` for
``DenseGraph`` (where `n` is the number of vertices and `m` is the number of
edges of the graph).
EXAMPLES:
Expand Down

0 comments on commit fd428c7

Please sign in to comment.