Skip to content

Commit 8c783a1

Browse files
reiniscirponswilfwilson
authored andcommitted
Reimplement DigraphVertexConnectivity
With max flow and single doubled digraph computation
1 parent 7b3e7b3 commit 8c783a1

File tree

7 files changed

+272
-165
lines changed

7 files changed

+272
-165
lines changed

doc/attr.xml

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2680,42 +2680,76 @@ gap> Length(M);
26802680
</ManSection>
26812681
<#/GAPDoc>
26822682

2683-
<#GAPDoc Label="VertexConnectivity">
2683+
<#GAPDoc Label="DigraphVertexConnectivity">
26842684
<ManSection>
2685-
<Attr Name="VertexConnectivity" Arg="digraph"/>
2685+
<Attr Name="DigraphVertexConnectivity" Arg="digraph"/>
26862686
<Returns>An non-negative integer.</Returns>
26872687
<Description>
2688-
For a digraph <A>digraph</A> with set of vertices <C>V</C>, the attribute
2689-
<C>VertexConnectivity(<A>digraph</A>)</C> returns the least cardinality
2690-
<C>|S|</C> of a subset <C>S</C> of <C>V</C> such that the induced subdigraph
2691-
of <A>digraph</A> on <C>V \ S</C> is disconnected, or has at most one
2692-
vertex. <P/>
2688+
This function returns the vertex connectivity of the digraph
2689+
<A>digraph</A>. This is also sometimes called the weak vertex connectivity.
2690+
<P/>
26932691

2694-
Note, in particular, that empty digraphs, and disconnected digraphs, have
2695-
vertex connectivity zero. <P/>
2692+
The vertex connectivity of a connected digraph (in the sense of
2693+
<Ref Prop="IsConnectedDigraph"/>) is the largest number <M>k</M> such that:
2694+
<List>
2695+
<Item> the digraph has at least <M>k + 1</M> vertices, and</Item>
2696+
<Item> the digraph remains connected after the removal of any set of at
2697+
most <M>k - 1</M> vertices.</Item>
2698+
</List>
2699+
If the digraph is not connected, then its vertex connectivity is 0.
2700+
For a non-empty digraph whose symmetric closure is not complete, the vertex
2701+
connectivity is equal to the size of the smallest set of vertices whose
2702+
removal would disconnect the digraph.
2703+
<P/>
26962704

2697-
The algorithm makes <C>n - d - 1 + d * (d - 1) / 2</C> calls to a max-flow
2698-
algorithm which itself has complexity <C>O((n ^ 2) * e)</C>, where <C>n</C>
2699-
is the number of vertices of <A>digraph</A>, and <C>e, d</C> are the number
2700-
of edges and the minimum degree (respectively) of the underlying undirected
2701-
graph of <A>digraph</A>.
2705+
Vertex connectivity of small digraphs may be counterintuitive
2706+
with respect to the notions of connectivity, as defined by
2707+
<Ref Prop="IsConnectedDigraph"/>, and biconnecivity, as defined by
2708+
<Ref Prop="IsBiconnectedDigraph"/>. Namely
2709+
<List>
2710+
<Item> the empty digraph is connected, but its vertex connectivity is 0;
2711+
</Item>
2712+
<Item> the singleton digraph is connected, but its vertex connectivity is
2713+
0;</Item>
2714+
<Item> the 2-cycle digraph is biconnected, but its vertex connectivity is
2715+
only 1.</Item>
2716+
</List>
2717+
However, for a digraph with at least 3 vertices, having vertex connectivity
2718+
greater than or equal to 1 is equivalent to being connected, and having
2719+
vertex connectivity greater than or equal to 2 is equivalent to being
2720+
biconnected.
2721+
<P/>
2722+
2723+
The algorithm makes <M>n - d - 1 + d \cdot (d - 1) / 2</M> calls to the
2724+
<Ref Attr="DigraphMaximumFlow"/> maximum flow algorithm, where <M>n</M>
2725+
is the number of vertices of <A>digraph</A>, and <M>d</M> is the
2726+
minimum degree of the symmetric closure of <A>digraph</A>,
2727+
see <Ref Oper="DigraphSymmetricClosure"/>.
27022728

27032729
<Example><![CDATA[
2704-
gap> J := JohnsonDigraph(9, 2);
2705-
<immutable symmetric digraph with 36 vertices, 504 edges>
2706-
gap> VertexConnectivity(J);
2707-
14
27082730
gap> D := CompleteBipartiteDigraph(4, 5);
27092731
<immutable complete bipartite digraph with bicomponent sizes 4 and 5>
2710-
gap> VertexConnectivity(D);
2732+
gap> DigraphVertexConnectivity(D);
27112733
4
2712-
gap> VertexConnectivity(PancakeGraph(4));
2734+
gap> DigraphVertexConnectivity(PancakeGraph(4));
27132735
3
27142736
gap> D := Digraph([[2, 4, 5], [1, 4], [4, 7], [1, 2, 3, 5, 6, 7],
27152737
> [1, 4], [4, 7], [3, 4, 6]]);
27162738
<immutable digraph with 7 vertices, 20 edges>
2717-
gap> VertexConnectivity(D);
2739+
gap> DigraphVertexConnectivity(D);
27182740
1
2741+
gap> J := JohnsonDigraph(9, 2);
2742+
<immutable symmetric digraph with 36 vertices, 504 edges>
2743+
gap> DigraphVertexConnectivity(J);
2744+
14
2745+
gap> DigraphVertexConnectivity(Digraph([]));
2746+
0
2747+
gap> DigraphVertexConnectivity(Digraph([[1]]));
2748+
0
2749+
gap> DigraphVertexConnectivity(CycleDigraph(2));
2750+
1
2751+
gap> DigraphVertexConnectivity(CompleteDigraph(5));
2752+
4
27192753
]]></Example>
27202754
</Description>
27212755
</ManSection>

doc/z-chap4.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
<#Include Label="HamiltonianPath">
8989
<#Include Label="NrSpanningTrees">
9090
<#Include Label="DigraphDijkstra">
91-
<#Include Label="VertexConnectivity">
91+
<#Include Label="DigraphVertexConnectivity">
9292
<#Include Label="DigraphCycleBasis">
9393
<#Include Label="DigraphIsKing">
9494
<#Include Label="DigraphKings">

gap/attr.gd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ DeclareAttribute("DigraphCore", IsDigraph);
7777

7878
DeclareAttribute("CharacteristicPolynomial", IsDigraph);
7979
DeclareAttribute("NrSpanningTrees", IsDigraph);
80-
DeclareAttribute("VertexConnectivity", IsDigraph);
80+
DeclareAttribute("DigraphVertexConnectivity", IsDigraph);
8181

8282
# AsGraph must be mutable for grape to function properly
8383
DeclareAttribute("AsGraph", IsDigraph, "mutable");

gap/attr.gi

Lines changed: 129 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -3358,148 +3358,150 @@ function(D)
33583358
return Union(M, DIGRAPHS_MateToMatching(D, mateD));
33593359
end);
33603360

3361-
InstallMethod(VertexConnectivity, "for a digraph", [IsDigraph],
3362-
function(digraph)
3363-
local kappas, newnetw, edmondskarp, mat, degs, mindegv, mindeg, Nv, outn, k,
3364-
i, j, x, y;
3365-
3366-
if DigraphNrVertices(digraph) <= 1 or not IsConnectedDigraph(digraph) then
3361+
InstallMethod(DigraphVertexConnectivity, "for a digraph", [IsDigraph],
3362+
function(D)
3363+
local doubled_D_adj, doubled_D, max_flow, u, v, i, j,
3364+
neighbours_v, kappa, kappa_min, is_multi, has_loops, is_nonsymm;
3365+
3366+
# As per Wikipedia:
3367+
# "A graph is said to be k-vertex-connected if it contains at least k + 1
3368+
# vertices, but does not contain a set of k − 1 vertices whose removal
3369+
# disconnects the graph."
3370+
# https://en.wikipedia.org/wiki/Connectivity_(graph_theory)
3371+
# The knock-on effect is that the singleton graph has vertex connectivity 0.
3372+
# This is discussed in the documentation in more detail.
3373+
if DigraphNrVertices(D) <= 1 or not IsConnectedDigraph(D) then
33673374
return 0;
33683375
fi;
33693376

3370-
if IsMultiDigraph(digraph) then
3371-
digraph := DigraphRemoveAllMultipleEdges(digraph);
3377+
# Remove multiple edges, loops and symmetrize, if necessary
3378+
is_multi := IsMultiDigraph(D);
3379+
has_loops := DigraphHasLoops(D);
3380+
is_nonsymm := not IsSymmetricDigraph(D);
3381+
if is_multi or has_loops or is_nonsymm then
3382+
D := DigraphMutableCopy(D);
3383+
if is_multi then
3384+
DigraphRemoveAllMultipleEdges(D);
3385+
fi;
3386+
if has_loops then
3387+
DigraphRemoveLoops(D);
3388+
fi;
3389+
if is_nonsymm then
3390+
DigraphSymmetricClosure(D);
3391+
fi;
3392+
# NOTE: D is mutable following this operation. This should not cause issues
3393+
# or slow down the computations.
33723394
fi;
33733395

3374-
kappas := [DigraphNrVertices(digraph) - 1];
3375-
3376-
# The function newnetw is an implementation of Algorithm Nine from
3377-
# Abdol-Hossein Esfahanian's ``Connectivity Algorithms'' which can be found at
3378-
# https://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf
3379-
newnetw := function(digraph, source, sink)
3380-
local n, mat, outn, x, y;
3381-
n := DigraphNrVertices(digraph);
3382-
mat := List([1 .. 2 * n], x -> BlistList([1 .. 2 * n], []));
3383-
outn := OutNeighbours(digraph);
3384-
for x in [1 .. DigraphNrVertices(digraph)] do
3385-
if x <> source and x <> sink then
3386-
mat[x + n][x] := true;
3387-
fi;
3388-
for y in outn[x] do
3389-
if x = source or x = sink then
3390-
mat[x][y + n] := true;
3391-
mat[y][x] := true;
3392-
elif y = source or y = sink then
3393-
mat[y][x + n] := true;
3394-
mat[x][y] := true;
3395-
else
3396-
mat[y][x + n] := true;
3397-
mat[x][y + n] := true;
3398-
fi;
3399-
od;
3400-
od;
3401-
return List(mat, x -> ListBlist([1 .. 2 * n], x));
3402-
end;
3403-
3404-
# The following function is an implementation of the Edmonds-Karp algorithm
3405-
# with some minor adjustments that take into account the fact that the
3406-
# capacity of all edges is 1.
3407-
edmondskarp := function(netw, source, sink)
3408-
local flow, capacity, queue, m, predecessor, edgeindex, stop, current, n, v;
3409-
3410-
flow := 0;
3411-
capacity := List(netw, x -> BlistList(x, x));
3412-
# nredges := Sum(List(netw, Length));
3413-
3414-
while true do
3415-
queue := [source];
3416-
m := 1;
3417-
predecessor := List(netw, x -> 0);
3418-
edgeindex := List(netw, x -> 0);
3419-
stop := false;
3420-
while m <= Size(queue) and not stop do
3421-
current := queue[m];
3422-
n := 0;
3423-
for v in netw[current] do
3424-
n := n + 1;
3425-
if predecessor[v] = 0 and v <> source and capacity[current][n] then
3426-
predecessor[v] := current;
3427-
edgeindex[v] := n;
3428-
Add(queue, v);
3429-
fi;
3430-
if v = sink then
3431-
stop := true;
3432-
break;
3433-
fi;
3434-
od;
3435-
m := m + 1;
3436-
od;
3437-
3438-
if predecessor[sink] <> 0 then
3439-
v := predecessor[sink];
3440-
n := edgeindex[sink];
3441-
while v <> 0 do
3442-
capacity[v][n] := false;
3443-
n := edgeindex[v];
3444-
v := predecessor[v];
3445-
od;
3446-
flow := flow + 1;
3447-
else
3448-
return flow;
3449-
fi;
3450-
od;
3451-
end;
3452-
3453-
# Referring once again to Abdol-Hossein Esfahanian's paper
3454-
# (see newnetw, above).
3455-
# The following lines implement Algorithm Eleven of that paper.
3456-
mat := BooleanAdjacencyMatrix(digraph);
3457-
degs := ListWithIdenticalEntries(DigraphNrVertices(digraph), 0);
3458-
for i in DigraphVertices(digraph) do
3459-
for j in [i + 1 .. DigraphNrVertices(digraph)] do
3460-
if mat[i][j] or mat[j][i] then
3461-
degs[i] := degs[i] + 1;
3462-
degs[j] := degs[j] + 1;
3463-
fi;
3396+
# Special case complete digraph since no set of vertices disconnects it.
3397+
if IsCompleteDigraph(D) then
3398+
return DigraphNrVertices(D) - 1;
3399+
fi;
3400+
3401+
# Construct "doubled" digraph as per Theorem 6.4 of
3402+
# Even S. Applications of Network Flow Techniques.
3403+
# In: Even G, ed. Graph Algorithms.
3404+
# Cambridge University Press; 2011:117-145.
3405+
# https://doi.org/10.1017/CBO9781139015165
3406+
# Doubles the vertices of the digraph `D`. For every vertex v, 2*v-1 is
3407+
# called the "in"-vertex and 2*v is called the "out"-vertex. For every edge
3408+
# (v, u) in `D`, the doubled digraph contains an edge (2*v, 2*u-1) from
3409+
# the out-vertex of `v` to the in-vertex of `u`. Additionally, there is an
3410+
# edge (2*v-1, 2*v) for every vertex v in `D`.
3411+
doubled_D_adj := List([1 .. 2 * DigraphNrVertices(D)], x -> []);
3412+
for v in DigraphVertices(D) do
3413+
for u in OutNeighborsOfVertex(D, v) do
3414+
Add(doubled_D_adj[2 * v], 2 * u - 1);
34643415
od;
3416+
Add(doubled_D_adj[2 * v - 1], 2 * v);
34653417
od;
3466-
3467-
mindegv := 0;
3468-
mindeg := DigraphNrVertices(digraph) + 1;
3469-
for i in DigraphVertices(digraph) do
3470-
if degs[i] < mindeg then
3471-
mindeg := degs[i];
3472-
mindegv := i;
3473-
fi;
3474-
od;
3475-
3476-
Nv := OutNeighboursOfVertex(digraph, mindegv);
3477-
outn := OutNeighbours(digraph);
3478-
3479-
for x in DigraphVertices(digraph) do
3480-
if x <> mindegv and not mat[x][mindegv] and not mat[mindegv][x] then
3481-
k := edmondskarp(newnetw(digraph, mindegv, x), mindegv, x);
3482-
if k = 0 then
3483-
return 0;
3484-
else
3485-
AddSet(kappas, k);
3418+
doubled_D := EdgeWeightedDigraph(
3419+
doubled_D_adj,
3420+
List(doubled_D_adj, x -> ListWithIdenticalEntries(Length(x), 1)));
3421+
3422+
# The resulting graph, `doubled_D` is bipartite, and, additionally,
3423+
# there is a correspondence between paths in `D` and `doubled_D`
3424+
# given by mapping the path (v_1, v_2, ... v_n) in `D` to the path
3425+
# (2*v_1, 2*v_2 - 1, 2*v_2, ..., 2*v_{n-1} - 1, 2*v_{n-1}, 2*v_n - 1) in
3426+
# `doubled_D`. An conversely, any path starting with an even vertex and
3427+
# ending with an odd vertex in `doubled_D` is of the form
3428+
# (2*v_1, 2*v_2 - 1, 2*v_2, ..., 2*v_{n-1} - 1, 2*v_{n-1}, 2*v_n - 1) for
3429+
# some path (v_1, v_2, ... v_n) in `D`.
3430+
3431+
# The local vertex connectivity for a pair of vertices u, v is the
3432+
# size of the least set S that contain u and v
3433+
# such that any (u, v)-path (that is, a path with source u and target v)
3434+
# passes through S. If two vertices are adjacent, then the local connectivity
3435+
# is infinity by convention. The minimum cut with source u and target v is
3436+
# the least number of edges that need to be removed so that there is no
3437+
# longer a (u, v)-path.
3438+
3439+
# Let u and v be non-adjacent vertices in `D`. Because of the
3440+
# correspondence of paths in `D` and `doubled_D`, any such set S
3441+
# for `D` corresponds to a set of edges E_S (obtained by replacing the
3442+
# vertex w by the edge (2*w-1, 2*w)) in `doubled_D` whose removal
3443+
# disconnects 2*u from 2*v-1.
3444+
# Conversely, it can be shown that the smallest set of edges disconnecting
3445+
# 2*u from 2*v-1 has the same cardinality as E_S. This is because, whenever
3446+
# we remove any edge (2*s, 2*t-1) from `doubled_D` with the goal of
3447+
# disconnecting 2*u from 2*v-1, it is always just as
3448+
# efficient or more efficient to remove the edge (2*s-1, 2*s) instead, since
3449+
# any path from 2*u to 2*v-1 utilizing (2*s, 2*t-1) in `doubled_D` must
3450+
# pass through (2*s-1, 2*s) by construction. Note that u and v are
3451+
# non-adjacent by assumption, so it cannot be the case that
3452+
# (2*s, 2*t-1) = (2*u, 2*v-1).
3453+
# It follows that, for non-adjacent vertices, the local connectivity of u and
3454+
# v in `D` is equal to the minimum cut with source 2*u-1 and target 2*v
3455+
# in `doubled_D`.
3456+
3457+
# By the max-flow min-cut theorem, the size of the minimum cut with source u
3458+
# and target v equals the maximum flow between u and v see Wikipedia below:
3459+
# https://en.wikipedia.org/wiki/Max-flow_min-cut_theorem
3460+
# Hence we can compute local vertex connectivity by repeated calls to the
3461+
# max_flow function below:
3462+
max_flow := {digraph, source, target} ->
3463+
Sum(DigraphMaximumFlow(digraph, source, target)[source]);
3464+
3465+
# The vertex connectivity is the minimum of the local vertex connectivity
3466+
# over all pairs of vertices. However, it can be computed a bit more
3467+
# cleverly by utilizing some theory to reduce the number of pairs considered.
3468+
# In this function we implement Algorithm 11 from Abdol-Hossein
3469+
# Esfahanian's ``Connectivity Algorithms'' which can be found at
3470+
# https://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf
3471+
# In particular, we reduce the number of local vertex connectivity
3472+
# computations to n-d-1 + d*(d-1)/2 where n is the total number of vertices
3473+
# and d is the minimum degree of any vertex.
3474+
v := PositionMinimum(OutDegrees(D));
3475+
neighbours_v := OutNeighboursOfVertex(D, v);
3476+
kappa_min := fail;
3477+
for u in DigraphVertices(D) do
3478+
if u <> v and not IsDigraphEdge(D, v, u) then
3479+
kappa := max_flow(doubled_D, 2 * u, 2 * v - 1);
3480+
if kappa_min = fail or kappa < kappa_min then
3481+
kappa_min := kappa;
34863482
fi;
34873483
fi;
34883484
od;
34893485

3490-
for x in [1 .. Size(Nv) - 1] do
3491-
for y in [x + 1 .. Size(Nv)] do
3492-
if not mat[Nv[x]][Nv[y]] and not mat[Nv[y]][Nv[x]] then
3493-
k := edmondskarp(newnetw(digraph, Nv[x], Nv[y]), Nv[x], Nv[y]);
3494-
if k = 0 then
3495-
return 0;
3496-
else
3497-
AddSet(kappas, k);
3486+
for i in [1 .. Length(neighbours_v)] do
3487+
for j in [i + 1 .. Length(neighbours_v)] do
3488+
u := neighbours_v[i];
3489+
v := neighbours_v[j];
3490+
if not IsDigraphEdge(D, v, u) then
3491+
kappa := max_flow(doubled_D, 2 * u, 2 * v - 1);
3492+
if kappa_min = fail or kappa < kappa_min then
3493+
kappa_min := kappa;
34983494
fi;
34993495
fi;
35003496
od;
35013497
od;
3502-
return kappas[1];
3498+
3499+
# We can only be here if every vertex is adjacent to a vertex of minimum
3500+
# degree u and every pair of vertices v, w adjacent to u are also adjacent
3501+
# to each other. In other words, `D` is a complete graph, but we
3502+
# deal with these at the start. So this assert should pass.
3503+
Assert(1, kappa_min <> fail);
3504+
return kappa_min;
35033505
end);
35043506

35053507
# The following function is a transliteration from python to GAP of

0 commit comments

Comments
 (0)