Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add method is_vertex_cut to (di)graphs #38418

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 144 additions & 61 deletions src/sage/graphs/connectivity.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Here is what the module can do:
:meth:`is_cut_edge` | Check whether the input edge is a cut-edge or a bridge.
:meth:`is_edge_cut` | Check whether the input edges form an edge cut.
:meth:`is_cut_vertex` | Check whether the input vertex is a cut-vertex.
:meth:`is_vertex_cut` | Check whether the input vertices form a vertex cut.
:meth:`edge_connectivity` | Return the edge connectivity of the graph.
:meth:`vertex_connectivity` | Return the vertex connectivity of the graph.

Expand Down Expand Up @@ -971,58 +972,64 @@ def is_cut_edge(G, u, v=None, label=None):
return sol


def is_cut_vertex(G, u, weak=False):
def is_vertex_cut(G, cut, weak=False):
r"""
Check whether the input vertex is a cut-vertex.
Check whether the input vertices form a vertex cut.

A vertex is a cut-vertex if its removal from the (di)graph increases the
number of (strongly) connected components. Isolated vertices or leafs are
not cut-vertices. This function works with simple graphs as well as graphs
with loops and multiple edges.
A set of vertices is a vertex cut if its removal from the (di)graph
increases the number of (strongly) connected components. This function works
with simple graphs as well as graphs with loops and multiple edges.

INPUT:

- ``G`` -- a Sage (Di)Graph

- ``u`` -- a vertex
- ``cut`` -- a set of vertices

- ``weak`` -- boolean (default: ``False``); whether the connectivity of
directed graphs is to be taken in the weak sense, that is ignoring edges
orientations

OUTPUT:

Return ``True`` if ``u`` is a cut-vertex, and ``False`` otherwise.

EXAMPLES:

Giving a LollipopGraph(4,2), that is a complete graph with 4 vertices with a
pending edge::
Giving a cycle graph of order 4::

sage: from sage.graphs.connectivity import is_cut_vertex
sage: G = graphs.LollipopGraph(4, 2)
sage: is_cut_vertex(G, 0)
sage: from sage.graphs.connectivity import is_vertex_cut
sage: G = graphs.CycleGraph(4)
sage: is_vertex_cut(G, [0, 1])
False
sage: is_cut_vertex(G, 3)
sage: is_vertex_cut(G, [0, 2])
True
sage: G.is_cut_vertex(3)

Giving a disconnected graph::

sage: from sage.graphs.connectivity import is_vertex_cut
sage: G = graphs.CycleGraph(4) * 2
sage: G.connected_components()
[[0, 1, 2, 3], [4, 5, 6, 7]]
sage: is_vertex_cut(G, [0, 2])
True
sage: is_vertex_cut(G, [4, 6])
True
sage: is_vertex_cut(G, [0, 6])
False
sage: is_vertex_cut(G, [0, 4, 6])
True

Comparing the weak and strong connectivity of a digraph::

sage: from sage.graphs.connectivity import is_strongly_connected
sage: D = digraphs.Circuit(6)
sage: is_strongly_connected(D)
sage: D.is_strongly_connected()
True
sage: is_cut_vertex(D, 2)
sage: is_vertex_cut(D, [2])
True
sage: is_cut_vertex(D, 2, weak=True)
sage: is_vertex_cut(D, [2], weak=True)
False

Giving a vertex that is not in the graph::

sage: G = graphs.CompleteGraph(4)
sage: is_cut_vertex(G, 7)
sage: is_vertex_cut(G, [7])
Traceback (most recent call last):
...
ValueError: vertex (7) is not a vertex of the graph
Expand All @@ -1031,7 +1038,7 @@ def is_cut_vertex(G, u, weak=False):

If ``G`` is not a Sage graph, an error is raised::

sage: is_cut_vertex('I am not a graph', 0)
sage: is_vertex_cut('I am not a graph', [0])
Traceback (most recent call last):
...
TypeError: the input must be a Sage graph
Expand All @@ -1040,68 +1047,144 @@ def is_cut_vertex(G, u, weak=False):
if not isinstance(G, GenericGraph):
raise TypeError("the input must be a Sage graph")

if u not in G:
raise ValueError("vertex ({0}) is not a vertex of the graph".format(repr(u)))

# Initialization
cdef set CC
cdef list neighbors_func
if not G.is_directed() or weak:
# Weak connectivity
cdef set cutset = set(cut)
for u in cutset:
if u not in G:
raise ValueError("vertex ({0}) is not a vertex of the graph".format(repr(u)))

if G.degree(u) < 2:
# An isolated or a leaf vertex is not a cut vertex
return False

neighbors_func = [G.neighbor_iterator]
start = next(G.neighbor_iterator(u))
CC = set(G)
if len(cutset) >= G.order() - 1:
# A vertex cut must be of size at most n - 2
return False

# We deal with graphs with multiple (strongly) connected components
cdef list CC
if G.is_directed() and not weak:
CC = G.strongly_connected_components()
else:
# Strong connectivity for digraphs

if not G.out_degree(u) or not G.in_degree(u):
# A vertex without in or out neighbors is not a cut vertex
return False
CC = G.connected_components(sort=False)
if len(CC) > 1:
for comp in CC:
subcut = cutset.intersection(comp)
if subcut and is_vertex_cut(G.subgraph(comp), subcut, weak=weak):
return True
return False

# We consider only the strongly connected component containing u
CC = set(strongly_connected_component_containing_vertex(G, u))
cdef list boundary = G.vertex_boundary(cutset)
if not boundary:
# We need at least 1 vertex in the boundary of the cut
return False

# We perform two DFS starting from an out neighbor of u and avoiding
# u. The first DFS follows the edges directions, and the second is
# in the reverse order. If both allow to reach all neighbors of u,
# then u is not a cut vertex
neighbors_func = [G.neighbor_out_iterator, G.neighbor_in_iterator]
start = next(G.neighbor_out_iterator(u))
cdef list cases = [(G.neighbor_iterator, boundary)]
if not weak and G.is_directed():
# Strong connectivity for digraphs.
# We perform two DFS starting from an out neighbor of cut and avoiding
# cut. The first DFS follows the edges directions, and the second is
# in the reverse order. If both allow to reach all neighbors of cut,
# then it is not a vertex cut.
# We set data for the reverse order
in_boundary = set()
for u in cutset:
in_boundary.update(G.neighbor_in_iterator(u))
in_boundary.difference_update(cutset)
if not in_boundary:
return False
cases.append((G.neighbor_in_iterator, list(in_boundary)))

CC.discard(u)
CC.discard(start)
cdef list queue
cdef set seen
cdef set targets
start = boundary[0]

for neighbors in neighbors_func:
for neighbors, this_boundary in cases:

# We perform a DFS starting from a neighbor of u and avoiding u
# We perform a DFS starting from start and avoiding cut
queue = [start]
seen = set(queue)
targets = CC.intersection(G.neighbor_iterator(u))
seen = set(cutset)
seen.add(start)
targets = set(this_boundary)
targets.discard(start)
while queue and targets:
while queue:
v = queue.pop()
for w in neighbors(v):
if w not in seen and w in CC:
if w not in seen:
seen.add(w)
queue.append(w)
targets.discard(w)

# If some neighbors cannot be reached, u is a cut vertex.
# If some neighbors cannot be reached, we have a vertex cut
if targets:
return True

return False


def is_cut_vertex(G, u, weak=False):
r"""
Check whether the input vertex is a cut-vertex.

A vertex is a cut-vertex if its removal from the (di)graph increases the
number of (strongly) connected components. Isolated vertices or leaves are
not cut-vertices. This function works with simple graphs as well as graphs
with loops and multiple edges.

INPUT:

- ``G`` -- a Sage (Di)Graph

- ``u`` -- a vertex

- ``weak`` -- boolean (default: ``False``); whether the connectivity of
directed graphs is to be taken in the weak sense, that is ignoring edges
orientations

OUTPUT:

Return ``True`` if ``u`` is a cut-vertex, and ``False`` otherwise.

EXAMPLES:

Giving a LollipopGraph(4,2), that is a complete graph with 4 vertices with a
pending edge::

sage: from sage.graphs.connectivity import is_cut_vertex
sage: G = graphs.LollipopGraph(4, 2)
sage: is_cut_vertex(G, 0)
False
sage: is_cut_vertex(G, 3)
True
sage: G.is_cut_vertex(3)
True

Comparing the weak and strong connectivity of a digraph::

sage: D = digraphs.Circuit(6)
sage: D.is_strongly_connected()
True
sage: is_cut_vertex(D, 2)
True
sage: is_cut_vertex(D, 2, weak=True)
False

Giving a vertex that is not in the graph::

sage: G = graphs.CompleteGraph(4)
sage: is_cut_vertex(G, 7)
Traceback (most recent call last):
...
ValueError: vertex (7) is not a vertex of the graph

TESTS:

If ``G`` is not a Sage graph, an error is raised::

sage: is_cut_vertex('I am not a graph', 0)
Traceback (most recent call last):
...
TypeError: the input must be a Sage graph
"""
return is_vertex_cut(G, [u], weak=weak)


def edge_connectivity(G,
value_only=True,
implementation=None,
Expand Down
4 changes: 3 additions & 1 deletion src/sage/graphs/generic_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,8 @@
:meth:`~GenericGraph.blocks_and_cuts_tree` | Compute the blocks-and-cuts tree of the graph.
:meth:`~GenericGraph.is_cut_edge` | Check whether the input edge is a cut-edge or a bridge.
:meth:`~GenericGraph.`is_edge_cut` | Check whether the input edges form an edge cut.
:meth:`~GenericGraph.is_cut_vertex` | Return ``True`` if the input vertex is a cut-vertex.
:meth:`~GenericGraph.is_cut_vertex` | Check whether the input vertex is a cut-vertex.
:meth:`~GenericGraph.is_vertex_cut` | Check whether the input vertices form a vertex cut.
:meth:`~GenericGraph.edge_cut` | Return a minimum edge cut between vertices `s` and `t`
:meth:`~GenericGraph.vertex_cut` | Return a minimum vertex cut between non-adjacent vertices `s` and `t`
:meth:`~GenericGraph.flow` | Return a maximum flow in the graph from ``x`` to ``y``
Expand Down Expand Up @@ -25003,6 +25004,7 @@ def is_self_complementary(self):
from sage.graphs.connectivity import is_cut_edge
from sage.graphs.connectivity import is_edge_cut
from sage.graphs.connectivity import is_cut_vertex
from sage.graphs.connectivity import is_vertex_cut
from sage.graphs.connectivity import edge_connectivity
from sage.graphs.connectivity import vertex_connectivity
from sage.graphs.distances_all_pairs import szeged_index
Expand Down
Loading