From 72700e73b93ca798ad8c46e93c0feb60385b31dd Mon Sep 17 00:00:00 2001 From: Cyril Bouvier Date: Tue, 11 Jun 2024 13:32:18 +0200 Subject: [PATCH 1/3] graphs: improve (and fix) some comments about complexity --- src/sage/graphs/base/dense_graph.pyx | 10 +++++++++ src/sage/graphs/base/static_sparse_graph.pyx | 6 +++-- src/sage/graphs/convexity_properties.pyx | 15 ++++++++----- src/sage/graphs/distances_all_pairs.pyx | 13 +++++++++++ src/sage/graphs/generic_graph.py | 8 ++++--- .../clique_separators.pyx | 6 +++++ src/sage/graphs/traversals.pyx | 22 +++++++++++++------ 7 files changed, 62 insertions(+), 18 deletions(-) diff --git a/src/sage/graphs/base/dense_graph.pyx b/src/sage/graphs/base/dense_graph.pyx index 9aff72e066c..157ec89b961 100644 --- a/src/sage/graphs/base/dense_graph.pyx +++ b/src/sage/graphs/base/dense_graph.pyx @@ -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. + """ # **************************************************************************** diff --git a/src/sage/graphs/base/static_sparse_graph.pyx b/src/sage/graphs/base/static_sparse_graph.pyx index f74e1b28dec..898b69568d0 100644 --- a/src/sage/graphs/base/static_sparse_graph.pyx +++ b/src/sage/graphs/base/static_sparse_graph.pyx @@ -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 diff --git a/src/sage/graphs/convexity_properties.pyx b/src/sage/graphs/convexity_properties.pyx index 575c67b2386..c8a321ece81 100644 --- a/src/sage/graphs/convexity_properties.pyx +++ b/src/sage/graphs/convexity_properties.pyx @@ -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: @@ -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. diff --git a/src/sage/graphs/distances_all_pairs.pyx b/src/sage/graphs/distances_all_pairs.pyx index ea70f890005..5d793709dda 100644 --- a/src/sage/graphs/distances_all_pairs.pyx +++ b/src/sage/graphs/distances_all_pairs.pyx @@ -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 @@ -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]_:: diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index d3d6a4e8b0c..da5f404db89 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -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: @@ -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 diff --git a/src/sage/graphs/graph_decompositions/clique_separators.pyx b/src/sage/graphs/graph_decompositions/clique_separators.pyx index ed86654f595..0bd02e3b5db 100644 --- a/src/sage/graphs/graph_decompositions/clique_separators.pyx +++ b/src/sage/graphs/graph_decompositions/clique_separators.pyx @@ -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. diff --git a/src/sage/graphs/traversals.pyx b/src/sage/graphs/traversals.pyx index 13fc1c2775a..a8f2f2876ce 100644 --- a/src/sage/graphs/traversals.pyx +++ b/src/sage/graphs/traversals.pyx @@ -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: @@ -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. @@ -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. @@ -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. @@ -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 From b914497e69a9668ba9237db5b70cde73d166de7e Mon Sep 17 00:00:00 2001 From: Cyril Bouvier Date: Tue, 11 Jun 2024 15:24:06 +0200 Subject: [PATCH 2/3] graphs: more fixing comments about complexity --- src/sage/graphs/graph.py | 3 ++- src/sage/graphs/weakly_chordal.pyx | 14 +++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index 4069c6a079d..3eda130f4d8 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -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:: diff --git a/src/sage/graphs/weakly_chordal.pyx b/src/sage/graphs/weakly_chordal.pyx index 2b1172e0895..aa91cfa9cdc 100644 --- a/src/sage/graphs/weakly_chordal.pyx +++ b/src/sage/graphs/weakly_chordal.pyx @@ -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: @@ -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: @@ -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: From c6f9a50360ed636a62eb11397c2ac8f74a2647b4 Mon Sep 17 00:00:00 2001 From: Cyril Bouvier Date: Wed, 12 Jun 2024 17:46:57 +0200 Subject: [PATCH 3/3] graphs: use math mode in new comments --- src/sage/graphs/base/dense_graph.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/graphs/base/dense_graph.pyx b/src/sage/graphs/base/dense_graph.pyx index 157ec89b961..abcd0809af1 100644 --- a/src/sage/graphs/base/dense_graph.pyx +++ b/src/sage/graphs/base/dense_graph.pyx @@ -101,8 +101,8 @@ It also contains the following variables:: .. 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 + 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.