package Graph::Base; use strict; local $^W = 1; use vars qw(@ISA); =head1 NAME Graph::Base - graph base class =head1 SYNOPSIS use Graph::Directed; use Graph::Undirected; $d1 = new Graph; $d2 = new Graph::Directed; $u = new Graph::Undirected; =head1 DESCRIPTION You create new graphs by calling the C constructors of classes C, C, and C. The classes C and C are identical. After creating the graph you can modify and explore the graph with following methods. =over 4 =cut require Exporter; @ISA = qw(Exporter); =pod =item new $G = Graph->new(@V) Returns a new graph $G with the optional vertices @V. =cut sub import { my ($class, %attr) = @_; if (%attr) { if ($attr{vertices_unsorted}) { delete $attr{vertices_unsorted}; local $^W = 0; *vertices = \&vertices_unsorted; } } *vertices = \&vertices_sorted unless defined &vertices; if (keys %attr) { require Carp; Carp::croak("Unknown attributes: @{[map { qq['$_'] } sort keys %attr]}"); } } sub new { my $class = shift; my $G = { }; bless $G, $class; $G->add_vertices(@_) if @_; return $G; } =pod =item add_vertices $G = $G->add_vertices(@v) Adds the vertices to the graph $G, returns the graph. =cut sub add_vertices { my ($G, @v) = @_; @{ $G->{ V } }{ @v } = @v; return $G; } =pod =item add_vertex $G = $G->add_vertex($v) Adds the vertex $v to the graph $G, returns the graph. =cut sub add_vertex { my ($G, $v) = @_; return $G->add_vertices($v); } =pod =item vertices @V = $G->vertices In list context returns the vertices @V of the graph $G. In scalar context returns the number of the vertices. By default this sorts the vertices. To avoid this extra overhead, use @V = $G->vertices_unsorted or do use Graph vertices_unsorted => 1; which will make vertices() to return unsorted results. =cut sub vertices_sorted { my $G = shift; my @V = exists $G->{ V } ? sort values %{ $G->{ V } } : (); return @V; } sub vertices_unsorted { my $G = shift; my @V = exists $G->{ V } ? values %{ $G->{ V } } : (); return @V; } =pod =item has_vertices $G->has_vertices(@v) In list context returns a list which contains the vertex of the vertices @v if the vertex exists in the graph $G and undef if it doesn't. In scalar context returns the number of the existing vertices. =cut sub has_vertices { my $G = shift; return wantarray ? map { exists $G->{ V }->{ $_ } ? $_ : undef } @_ : grep { exists $G->{ V }->{ $_ } } @_ ; } =pod =item has_vertex $b = $G->has_vertex($v) Returns true if the vertex $v exists in the graph $G and false if it doesn't. =cut sub has_vertex { my ($G, $v) = @_; return defined $v && exists $G->{ V } && exists $G->{ V }->{ $v }; } =pod =item vertex $v = $G->has_vertex($v) Returns the vertex $v if the vertex exists in the graph $G or undef if it doesn't. =cut sub vertex { my ($G, $v) = @_; return defined $v && $G->{ V }->{ $v }; } =pod =item directed $b = $G->directed($d) Set the directedness of the graph $G to $d or return the current directedness. Directedness defaults to true. =cut sub directed { my ($G, $d) = @_; if (defined $d) { if ($d) { my $o = $G->{ D }; # Old directedness. $G->{ D } = $d; if (defined $o and not $o) { my @E = $G->edges; while (my ($u, $v) = splice(@E, 0, 2)) { $G->add_edge($v, $u); } return bless $G, 'Graph::Directed'; # Re-bless. } return $G; # Don't re-bless unless needed. } else { return $G->undirected(not $d); } } return $G->{ D }; } =pod =item undirected $b = $G->undirected($d) Set the undirectedness of the graph $G to $u or return the current undirectedness. Undirectedness defaults to false. =cut sub undirected { my ($G, $u) = @_; $G->{ D } = 1 unless defined $G->{ D }; if (defined $u) { if ($u) { my $o = $G->{ D }; # Old directedness. $G->{ D } = not $u; if ($o) { my @E = $G->edges; my %E; while (my ($u, $v) = splice(@E, 0, 2)) { # Throw away duplicate edges. $G->delete_edge($u, $v) if exists $E{$v}->{$u}; $E{$u}->{$v}++; } } return bless $G, 'Graph::Undirected'; # Re-bless. } else { return $G->directed(not $u); } } return not $G->{ D }; } =pod =item has_edge $b = $G->has_edge($u, $v) Return true if the graph $G has the edge between the vertices $u, $v. =cut sub has_edge { my ($G, $u, $v) = @_; return exists $G->{ Succ }->{ $u }->{ $v } || ($G->undirected && exists $G->{ Succ }->{ $v }->{ $u }); } =pod =item has_edges $G->has_edges($u1, $v1, $u2, $v2, ...) In list context returns a list which contains true for each edge in the graph $G defined by the vertices $u1, $v1, ..., and false for each non-existing edge. In scalar context returns the number of the existing edges. =cut sub has_edges { my $G = shift; my @e; while (my ($u, $v) = splice(@_, 0, 2)) { push @e, $G->has_edge($u, $v); } return wantarray ? @e : grep { $_ } @e; } =pod =item has_path $G->has_path($u, $v, ...) Return true if the graph $G has the cycle defined by the vertices $u, $v, ..., false otherwise. =cut sub has_path { my $G = shift; my $u = shift; while (my $v = shift) { return 0 unless $G->has_edge($u, $v); $u = $v; } return 1; } =pod =item has_cycle $G->has_cycle($u, $v, ...) Return true if the graph $G has the cycle defined by the vertices $u, $v, ...,false otherwise. =cut sub has_cycle { my $G = shift; return $G->has_path(@_, $_[0]); # Just wrap around. } # _union_vertex_set # # $G->_union_vertex_set($u, $v) # # (INTERNAL USE ONLY) # Adds the vertices $u and $v in the graph $G to the same vertex set. # sub _union_vertex_set { my ($G, $u, $v) = @_; my $su = $G->vertex_set( $u ); my $sv = $G->vertex_set( $v ); return if $su eq $sv; my $ru = $G->{ VertexSetRank }->{ $su }; my $rv = $G->{ VertexSetRank }->{ $sv }; if ( $ru < $rv ) { # Union by rank (weight balancing). $G->{ VertexSetParent }->{ $su } = $sv; } else { $G->{ VertexSetParent }->{ $sv } = $su; $G->{ VertexSetRank }->{ $sv }++ if $ru == $rv; } } =pod =item vertex_set $s = $G->vertex_set($v) Returns the vertex set of the vertex $v in the graph $G. A "vertex set" is represented by its parent vertex. =cut sub vertex_set { my ($G, $v) = @_; if ( exists $G->{ VertexSetParent }->{ $v } ) { # Path compression. $G->{ VertexSetParent }->{ $v } = $G->vertex_set( $G->{ VertexSetParent }->{ $v } ) if $v ne $G->{ VertexSetParent }->{ $v }; } else { $G->{ VertexSetParent }->{ $v } = $v; $G->{ VertexSetRank }->{ $v } = 0; } return $G->{ VertexSetParent }->{ $v }; } =pod =item add_edge $G = $G->add_edge($u, $v) Adds the edge defined by the vertices $u, $v, to the graph $G. Also implicitly adds the vertices. Returns the graph. =cut sub add_edge { my ($G, $u, $v) = @_; $G->add_vertex($u); $G->add_vertex($v); $G->_union_vertex_set( $u, $v ); push @{ $G->{ Succ }->{ $u }->{ $v } }, $v; push @{ $G->{ Pred }->{ $v }->{ $u } }, $u; return $G; } =pod =item add_edges $G = $G->add_edges($u1, $v1, $u2, $v2, ...) Adds the edge defined by the vertices $u1, $v1, ..., to the graph $G. Also implicitly adds the vertices. Returns the graph. =cut sub add_edges { my $G = shift; while (my ($u, $v) = splice(@_, 0, 2)) { $G->add_edge($u, $v); } return $G; } =pod =item add_path $G->add_path($u, $v, ...) Adds the path defined by the vertices $u, $v, ..., to the graph $G. Also implicitly adds the vertices. Returns the graph. =cut sub add_path { my $G = shift; my $u = shift; while (my $v = shift) { $G->add_edge($u, $v); $u = $v; } return $G; } =pod =item add_cycle $G = $G->add_cycle($u, $v, ...) Adds the cycle defined by the vertices $u, $v, ..., to the graph $G. Also implicitly adds the vertices. Returns the graph. =cut sub add_cycle { my $G = shift; $G->add_path(@_, $_[0]); # Just wrap around. } # _successors # # @s = $G->_successors($v) # # (INTERNAL USE ONLY, use only on directed graphs) # Returns the successor vertices @s of the vertex # in the graph $G. # sub _successors { my ($G, $v) = @_; my @s = defined $G->{ Succ }->{ $v } ? map { @{ $G->{ Succ }->{ $v }->{ $_ } } } sort keys %{ $G->{ Succ }->{ $v } } : ( ); return @s; } # _predecessors # # @p = $G->_predecessors($v) # # (INTERNAL USE ONLY, use only on directed graphs) # Returns the predecessor vertices @p of the vertex $v # in the graph $G. # sub _predecessors { my ($G, $v) = @_; my @p = defined $G->{ Pred }->{ $v } ? map { @{ $G->{ Pred }->{ $v }->{ $_ } } } sort keys %{ $G->{ Pred }->{ $v } } : ( ); return @p; } =pod =item neighbors @n = $G->neighbors($v) Returns the neighbor vertices of the vertex in the graph. (Also 'neighbours' works.) =cut sub neighbors { my ($G, $v) = @_; my @n = ($G->_successors($v), $G->_predecessors($v)); return @n; } use vars '*neighbours'; *neighbours = \&neighbors; # Keep both sides of the Atlantic happy. =pod =item successors @s = $G->successors($v) Returns the successor vertices of the vertex in the graph. =cut sub successors { my ($G, $v) = @_; return $G->directed ? $G->_successors($v) : $G->neighbors($v); } =pod =item predecessors @p = $G->predecessors($v) Returns the predecessor vertices of the vertex in the graph. =cut sub predecessors { my ($G, $v) = @_; return $G->directed ? $G->_predecessors($v) : $G->neighbors($v); } =pod =item out_edges @e = $G->out_edges($v) Returns the edges leading out of the vertex $v in the graph $G. In list context returns the edges as ($start_vertex, $end_vertex) pairs. In scalar context returns the number of the edges. =cut sub out_edges { my ($G, $v) = @_; return () unless $G->has_vertex($v); my @e = $G->_edges($v, undef); return wantarray ? @e : @e / 2; } =pod =item in_edges @e = $G->in_edges($v) Returns the edges leading into the vertex $v in the graph $G. In list context returns the edges as ($start_vertex, $end_vertex) pairs; in scalar context returns the number of the edges. =cut sub in_edges { my ($G, $v) = @_; return () unless $G->has_vertex($v); my @e = $G->_edges(undef, $v); return wantarray ? @e : @e / 2; } =pod =item edges @e = $G->edges($u, $v) Returns the edges between the vertices $u and $v, or if $v is undefined, the edges leading into or out of the vertex $u, or if $u is undefined, returns all the edges, of the graph $G. In list context returns the edges as a list of $start_vertex, $end_vertex pairs; in scalar context returns the number of the edges. =cut sub edges { my ($G, $u, $v) = @_; return () if defined $v and not $G->has_vertex($v); my @e = defined $u ? ( defined $v ? $G->_edges($u, $v) : ($G->in_edges($u), $G->out_edges($u)) ) : $G->_edges; return wantarray ? @e : @e / 2; } =pod =item delete_edge $G = $G->delete_edge($u, $v) Deletes an edge defined by the vertices $u, $v from the graph $G. Note that the edge need not actually exist. Returns the graph. =cut sub delete_edge { my ($G, $u, $v) = @_; pop @{ $G->{ Succ }->{ $u }->{ $v } }; pop @{ $G->{ Pred }->{ $v }->{ $u } }; delete $G->{ Succ }->{ $u }->{ $v } unless @{ $G->{ Succ }->{ $u }->{ $v } }; delete $G->{ Pred }->{ $v }->{ $u } unless @{ $G->{ Pred }->{ $v }->{ $u } }; delete $G->{ Succ }->{ $u } unless keys %{ $G->{ Succ }->{ $u } }; delete $G->{ Pred }->{ $v } unless keys %{ $G->{ Pred }->{ $v } }; return $G; } =pod =item delete_edges $G = $G->delete_edges($u1, $v1, $u2, $v2, ..) Deletes edges defined by the vertices $u1, $v1, ..., from the graph $G. Note that the edges need not actually exist. Returns the graph. =cut sub delete_edges { my $G = shift; while (my ($u, $v) = splice(@_, 0, 2)) { if (defined $v) { $G->delete_edge($u, $v); } else { my @e = $G->edges($u); while (($u, $v) = splice(@e, 0, 2)) { $G->delete_edge($u, $v); } } } return $G; } =pod =item delete_path $G = $G->delete_path($u, $v, ...) Deletes a path defined by the vertices $u, $v, ..., from the graph $G. Note that the path need not actually exist. Returns the graph. =cut sub delete_path { my $G = shift; my $u = shift; while (my $v = shift) { $G->delete_edge($u, $v); $u = $v; } return $G; } =pod =item delete_cycle $G = $G->delete_cycle($u, $v, ...) Deletes a cycle defined by the vertices $u, $v, ..., from the graph $G. Note that the cycle need not actually exist. Returns the graph. =cut sub delete_cycle { my $G = shift; $G->delete_path(@_, $_[0]); # Just wrap around. } =pod =item delete_vertex $G = $G->delete_vertex($v) Deletes the vertex $v and all its edges from the graph $G. Note that the vertex need not actually exist. Returns the graph. =cut sub delete_vertex { my ($G, $v) = @_; $G->delete_edges($v); delete $G->{ V }->{ $v }; return $G; } =pod =item delete_vertices $G = $G->delete_vertices(@v) Deletes the vertices @v and all their edges from the graph $G. Note that the vertices need not actually exist. Returns the graph. =cut sub delete_vertices { my $G = shift; foreach my $v (@_) { $G->delete_vertex($v); } return $G; } =pod =item in_degree $d = $G->in_degree($v) Returns the in-degree of the vertex $v in the graph $G, or, if $v is undefined, the total in-degree of all the vertices of the graph, or undef if the vertex doesn't exist in the graph. =cut sub in_degree { my ($G, $v) = @_; return undef unless $G->has_vertex($v); if ($G->directed) { if (defined $v) { return scalar $G->in_edges($v); } else { my $in = 0; foreach my $v ($G->vertices) { $in += $G->in_degree($v); } return $in; } } else { return scalar $G->edges($v); } } =pod =item out_degree $d = $G->out_degree($v) Returns the out-degree of the vertex $v in the graph $G, or, if $v is undefined, the total out-degree of all the vertices of the graph, of undef if the vertex doesn't exist in the graph. =cut sub out_degree { my ($G, $v) = @_; return undef unless $G->has_vertex($v); if ($G->directed) { if (defined $v) { return scalar $G->out_edges($v); } else { my $out = 0; foreach my $v ($G->vertices) { $out += $G->out_degree($v); } return $out; } } else { return scalar $G->edges($v); } } =pod =item degree $d = $G->degree($v) Returns the degree of the vertex $v in the graph $G or, if $v is undefined, the total degree of all the vertices of the graph, or undef if the vertex $v doesn't exist in the graph. =cut sub degree { my ($G, $v) = @_; if (defined $v) { return undef unless $G->has_vertex($v); if ($G->directed) { return $G->in_degree($v) - $G->out_degree($v); } else { return $G->edges($v); } } else { if ($G->directed) { return 0; } else { my $deg = 0; foreach my $v ($G->vertices) { $deg += $G->degree($v); } return $deg; } } } =pod =item average_degree $d = $G->average_degree Returns the average degree of the vertices of the graph $G. =cut sub average_degree { my $G = shift; my $V = $G->vertices; return $V ? $G->degree / $V : 0; } =pod =item is_source_vertex $b = $G->is_source_vertex($v) Returns true if the vertex $v is a source vertex of the graph $G. A source vertex means that there are only outgoing egdes, and at least one of them (so that isolated vertices are not source vertices). If you want to test for no predecessors, use is_precessorless_vertex(). =cut sub is_source_vertex { my ($G, $v) = @_; $G->in_degree($v) == 0 && $G->out_degree($v) > 0; } =pod =item is_sink_vertex $b = $G->is_sink_vertex($v) Returns true if the vertex $v is a sink vertex of the graph $G. A sink vertex means that there are only incoming egdes, and at least one of them (so that isolated vertices are not sink vertices). If you want to test for no successors, use is_successorless_vertex(). =cut sub is_sink_vertex { my ($G, $v) = @_; $G->out_degree($v) == 0 && $G->in_degree($v) > 0; } =pod =item is_predecessorless_vertex $b = $G->is_predecessorless_vertex($v) Returns true if the vertex $v is a predecessorless vertex of the graph $G. If you want to test for source vertices, use is_source_vertex(). =cut sub is_predecessorless_vertex { my ($G, $v) = @_; $G->in_degree($v) == 0; } =pod =item is_successorless_vertex $b = $G->is_successorless_vertex($v) Returns true if the vertex $v is a successorless vertex of the graph $G. If you want to test for sink vertices, use is_sink_vertex(). =cut sub is_successorless_vertex { my ($G, $v) = @_; $G->out_degree($v) == 0; } =pod =item is_isolated_vertex $b = $G->is_isolated_vertex($v) Returns true if the vertex $v is a isolated vertex of the graph $G. =cut sub is_isolated_vertex { my ($G, $v) = @_; $G->in_degree($v) == 0 && $G->out_degree($v) == 0; } =pod =item is_exterior_vertex $b = $G->is_exterior_vertex($v) Returns true if the vertex $v is a exterior vertex of the graph $G. =cut sub is_exterior_vertex { my ($G, $v) = @_; $G->in_degree($v) == 0 xor $G->out_degree($v) == 0; } =pod =item is_interior_vertex $b = $G->is_interior_vertex($v) Returns true if the vertex $v is a interior vertex of the graph $G. =cut sub is_interior_vertex { my ($G, $v) = @_; $G->in_degree($v) && $G->out_degree($v); } =pod =item is_self_loop_vertex $b = $G->is_self_loop_vertex($v) Returns true if the vertex $v is a self-loop vertex of the graph $G. =cut sub is_self_loop_vertex { my ($G, $v) = @_; exists $G->{ Succ }->{ $v }->{ $v }; } =pod =item source_vertices @s = $G->source_vertices Returns the source vertices @s of the graph $G. =cut sub source_vertices { my $G = shift; return grep { $G->is_source_vertex($_) } $G->vertices; } =pod =item sink_vertices @s = $G->sink_vertices Returns the sink vertices @s of the graph $G. =cut sub sink_vertices { my $G = shift; return grep { $G->is_sink_vertex($_) } $G->vertices; } =pod =item successorless_vertices @s = $G->successorless_vertices Returns the successorless vertices @s of the graph $G. =cut sub successorless_vertices { my $G = shift; return grep { $G->is_successorless_vertex($_) } $G->vertices; } =item predecessorless_vertices @s = $G->predecessorless_vertices Returns the predecessorless vertices @s of the graph $G. =cut sub predecessorless_vertices { my $G = shift; return grep { $G->is_predecessorless_vertex($_) } $G->vertices; } =pod =item isolated_vertices @i = $G->isolated_vertices Returns the isolated vertices @i of the graph $G. =cut sub isolated_vertices { my $G = shift; return grep { $G->is_isolated_vertex($_) } $G->vertices; } =pod =item exterior_vertices @e = $G->exterior_vertices Returns the exterior vertices @e of the graph $G. =cut sub exterior_vertices { my $G = shift; return grep { $G->is_exterior_vertex($_) } $G->vertices; } =pod =item interior_vertices @i = $G->interior_vertices Returns the interior vertices @i of the graph $G. =cut sub interior_vertices { my $G = shift; return grep { $G->is_interior_vertex($_) } $G->vertices; } =pod =item self_loop_vertices @s = $G->self_loop_vertices Returns the self-loop vertices @s of the graph $G. =cut sub self_loop_vertices { my $G = shift; return grep { $G->is_self_loop_vertex($_) } $G->vertices; } =pod =item density_limits ($sparse, $dense, $complete) = $G->density_limits Returns the density limits for the number of edges in the graph $G. Note that reaching $complete edges does not really guarantee completeness because we can have multigraphs. The limit of sparse is less than 1/4 of the edges of the complete graph, the limit of dense is more than 3/4 of the edges of the complete graph. =cut sub density_limits { my $G = shift; my $V = $G->vertices; my $M = $V * ($V - 1); $M = $M / 2 if $G->undirected; return ($M/4, 3*$M/4, $M); } =pod =item density $d = $G->density Returns the density $d of the graph $G. =cut sub density { my $G = shift; my ($sparse, $dense, $complete) = $G->density_limits; return $complete ? $G->edges / $complete : 0; } =pod =item is_sparse $d = $G->is_sparse Returns true if the graph $G is sparse. =cut sub is_sparse { my $G = shift; my ($sparse, $dense, $complete) = $G->density_limits; return $complete ? $G->edges / $complete <= $dense : 1; } =pod =item is_dense $d = $G->is_dense Returns true if the graph $G is dense. =cut sub is_dense { my $G = shift; my ($sparse, $dense, $complete) = $G->density_limits; return $complete ? $G->edges / $complete >= $dense : 0; } =pod =item complete $C = $G->complete; Returns a new complete graph $C corresponding to the graph $G. =cut sub complete { my $G = shift; my $C = (ref $G)->new; my @V = $G->vertices; if ($G->directed) { foreach my $u (@V) { foreach my $v (@V) { $C->add_edge($u, $v) unless $u eq $v; } } } else { my %E; foreach my $u (@V) { foreach my $v (@V) { next if $u eq $v or $E{$u}->{$v} || $E{$v}->{$u}; $C->add_edge($u, $v); $E{$u}->{$v}++; $E{$v}->{$u}++; } } } $C->directed($G->directed); return $C; } =pod =item complement $C = $G->complement; Returns a new complement graph $C corresponding to the graph $G. =cut sub complement { my $G = shift; my $C = $G->complete; if (my @E = $G->edges) { while (my ($u, $v) = splice(@E, 0, 2)) { $C->delete_edge($u, $v); } } return $C; } =pod =item copy $C = $G->copy; Returns a new graph $C corresponding to the graph $G. =cut sub copy { my $G = shift; my $C = (ref $G)->new($G->vertices); if (my @E = $G->edges) { while (my ($u, $v) = splice(@E, 0, 2)) { $C->add_edge($u, $v); } } $C->directed($G->directed); return $C; } =pod =item transpose $T = $G->transpose; Returns a new transpose graph $T corresponding to the graph $G. =cut sub transpose { my $G = shift; return $G->copy if $G->undirected; my $T = (ref $G)->new($G->vertices); if (my @E = $G->edges) { while (my ($u, $v) = splice(@E, 0, 2)) { $T->add_edge($v, $u); } } return $T; } # _stringify # # $s = $G->_stringify($connector, $separator) # # (INTERNAL USE ONLY) # Returns a string representation of the graph $G. # The edges are represented by $connector and edges/isolated # vertices are represented by $separator. # sub _stringify { my ($G, $connector, $separator) = @_; my @E = $G->edges; my @e = map { [ $_ ] } $G->isolated_vertices; while (my ($u, $v) = splice(@E, 0, 2)) { push @e, [$u, $v]; } return join($separator, map { @$_ == 2 ? join($connector, $_->[0], $_->[1]) : $_->[0] } sort { $a->[0] cmp $b->[0] || @$a <=> @$b } @e); } =pod =item set_attribute $G->set_attribute($attribute, $value) $G->set_attribute($attribute, $v, $value) $G->set_attribute($attribute, $u, $v, $value) Sets the $attribute of graph/vertex/edge to $value but only if the vertex/edge already exists. Returns true if the attribute is set successfully, false if not. =cut sub set_attribute { my $G = shift; my $attribute = shift; my $value = pop; my ($u, $v) = @_; if (defined $u) { return 0 unless $G->has_vertex($u); if (defined $v) { return 0 unless $G->has_edge($u, $v); $G->{ Attr }->{ E }->{ $u }->{ $v }->{ $attribute } = $value; $G->{ Attr }->{ E }->{ $v }->{ $u }->{ $attribute } = $value if $G->undirected; } else { $G->{ Attr }->{ V }->{ $u }->{ $attribute } = $value; } } else { $G->{ Attr }->{ G }->{ $attribute } = $value; } return 1; } =pod =item get_attribute $value = $G->get_attribute($attribute) $value = $G->get_attribute($attribute, $v) $value = $G->get_attribute($attribute, $u, $v) Returns the $value of $attribute of graph/vertex/edge. =cut sub get_attribute { my $G = shift; my $attribute = shift; my ($u, $v) = @_; if (defined $u) { if (defined $v) { return undef unless exists $G->{ Attr }->{ E }; my $E = $G->{ Attr }->{ E }; if ( $G->directed ) { return $E->{ $u }->{ $v }->{ $attribute }; } else { return undef unless exists $G->{ Attr }->{ E }; return $E->{ $u }->{ $v }->{ $attribute } if exists $E->{ $u }->{ $v }->{ $attribute }; return $E->{ $v }->{ $u }->{ $attribute }; } } else { return $G->{ Attr }->{ V }->{ $u }->{ $attribute }; } } else { return $G->{ Attr }->{ G }->{ $attribute }; } } =pod =item has_attribute $value = $G->has_attribute($attribute) $value = $G->has_attribute($attribute, $v) $value = $G->has_attribute($attribute, $u, $v) Returns the $value of $attribute of graph/vertex/edge. =cut sub has_attribute { my $G = shift; my $attribute = shift; my ($u, $v) = @_; if (defined $u) { if (defined $v) { return undef unless exists $G->{ Attr }->{ E }; my $E = $G->{ Attr }->{ E }; if ( $G->directed ) { return exists $E->{ $u }->{ $v }->{ $attribute }; } else { return exists $E->{ $u }->{ $v }->{ $attribute } or exists $E->{ $v }->{ $u }->{ $attribute }; } } else { exists $G->{ Attr }->{ V }->{ $u }->{ $attribute }; } } else { exists $G->{ Attr } && exists $G->{ Attr }->{ G }->{ $attribute }; } } =pod =item get_attributes %attributes = $G->get_attributes() %attributes = $G->get_attributes($v) %attributes = $G->get_attributes($u, $v) Returns as a hash all the attribute names and values of graph/vertex/edge. =cut sub get_attributes { my $G = shift; my ($u, $v) = @_; return ( ) unless exists $G->{ Attr }; if (defined $u) { if (defined $v) { return exists $G->{ Attr }->{ E } && exists $G->{ Attr }->{ E }->{ $u } && exists $G->{ Attr }->{ E }->{ $u }->{ $v } ? %{ $G->{ Attr }->{ E }->{ $u }->{ $v } } : ( ); } else { return exists $G->{ Attr }->{ V } && exists $G->{ Attr }->{ V }->{ $u } ? %{ $G->{ Attr }->{ V }->{ $u } } : ( ); } } else { return exists $G->{ Attr }->{ G } ? %{ $G->{ Attr }->{ G } } : ( ); } } =pod =item delete_attribute $G->delete_attribute($attribute) $G->delete_attribute($attribute, $v) $G->delete_attribute($attribute, $u, $v) Deletes the $attribute of graph/vertex/edge. =cut sub delete_attribute { my $G = shift; my $attribute = shift; my ($u, $v) = @_; if (defined $u) { if (defined $v) { return undef unless exists $G->{ Attr }->{ E }; my $E = $G->{ Attr }->{ E }; if ( $G->directed ) { delete $E->{ $u }->{ $v }->{ $attribute }; } else { delete $E->{ $v }->{ $u }->{ $attribute }; delete $E->{ $v }->{ $u }->{ $attribute }; } } else { delete $G->{ Attr }->{ V }->{ $u }->{ $attribute }; } } else { delete $G->{ Attr }->{ G }->{ $attribute }; } } =pod =item delete_attributes $G->delete_attributes() $G->delete_attributes($v) $G->delete_attributes($u, $v) Deletes all the attributes of graph/vertex/edge. =cut sub delete_attributes { my $G = shift; my ($u, $v) = @_; if (defined $u) { if (defined $v) { delete $G->{ Attr }->{ E }->{ $u }->{ $v }; } else { delete $G->{ Attr }->{ V }->{ $u }; } } else { delete $G->{ Attr }->{ G }; } } =pod =item add_weighted_edge $G->add_weighted_edge($u, $w, $v, $a) Adds in the graph $G an edge from vertex $u to vertex $v and the edge attribute 'weight' set to $w. =cut sub add_weighted_edge { my ($G, $u, $w, $v, $a) = @_; $G->add_edge($u, $v); $G->set_attribute('weight', $u, $v, $w); } =pod =item add_weighted_edges $G->add_weighted_edges($u1, $w1, $v1, $u2, $w2, $v2, ...) Adds in the graph $G the weighted edges. =cut sub add_weighted_edges { my $G = shift; while (my ($u, $w, $v) = splice(@_, 0, 3)) { $G->add_weighted_edge($u, $w, $v); } } =pod =item add_weighted_path $G->add_weighted_path($v1, $w1, $v2, $w2, ..., $wnm1, $vn) Adds in the graph $G the n edges defined by the path $v1 ... $vn with the n-1 'weight' attributes $w1 ... $wnm1 =cut sub add_weighted_path { my $G = shift; my $u = shift; while (my ($w, $v) = splice(@_, 0, 2)) { $G->add_weighted_edge($u, $w, $v); $u = $v; } } =pod =item MST_Kruskal $MST = $G->MST_Kruskal; Returns Kruskal's Minimum Spanning Tree (as a graph) of the graph $G based on the 'weight' attributes of the edges. (Needs the ->vertex_set() method.) =cut sub MST_Kruskal { my $G = shift; my $MST = (ref $G)->new; my @E = $G->edges; my (@W, $u, $v, $w); while (($u, $v) = splice(@E, 0, 2)) { $w = $G->get_attribute('weight', $u, $v); next unless defined $w; # undef weight == infinitely heavy push @W, [ $u, $v, $w ]; } $MST->directed( $G->directed ); # Sort by weights. foreach my $e ( sort { $a->[ 2 ] <=> $b->[ 2 ] } @W ) { ($u, $v, $w) = @$e; $MST->add_weighted_edge( $u, $w, $v ) unless $MST->vertex_set( $u ) eq $MST->vertex_set( $v ); } return $MST; } =pod =item edge_classify @C = $G->edge_classify(%param) Returns the edge classification as a list where each element is a triplet [$u, $v, $class] the $u, $v being the vertices of an edge and $class being the class. The %param can be used to control the search. =cut sub edge_classify { my $G = shift; my $unseen_successor = sub { my ($u, $v, $T) = @_; # Freshly seen successors make for tree edges. push @{ $T->{ edge_class_list } }, [ $u, $v, 'tree' ]; }; my $seen_successor = sub { my ($u, $v, $T) = @_; my $class; if ( $T->{ G }->directed ) { $class = 'cross'; # Default for directed non-tree edges. unless ( exists $T->{ vertex_finished }->{ $v } ) { $class = 'back'; } elsif ( $T->{ vertex_found }->{ $u } < $T->{ vertex_found }->{ $v }) { $class = 'forward'; } } else { # No cross nor forward edges in # an undirected graph, by definition. $class = 'back'; } push @{ $T->{ edge_class_list } }, [ $u, $v, $class ]; }; use Graph::DFS; my $d = Graph::DFS-> new( $G, unseen_successor => $unseen_successor, seen_successor => $seen_successor, @_); $d->preorder; return @{ $d->{ edge_class_list } }; } =pod =item toposort @toposort = $G->toposort Returns the vertices of the graph $G sorted topologically. =cut sub toposort { my $G = shift; my $d = Graph::DFS->new($G); reverse $d->postorder; # That's it. } # _strongly_connected # # $s = $G->_strongly_connected # # (INTERNAL USE ONLY) # Returns a graph traversal object that can be used for # strong connection computations. # sub _strongly_connected { my $G = shift; my $T = $G->transpose; Graph::DFS-> new($T, # Pick the potential roots in their DFS postorder. strong_root_order => [ reverse Graph::DFS->new($G)->postorder ], get_next_root => sub { my ($T, %param) = @_; while (my $root = shift @{ $param{ strong_root_order } }) { return $root if exists $T->{ pool }->{ $root }; } } ); } =pod =item strongly_connected_components @S = $G->strongly_connected_components Returns the strongly connected components @S of the graph $G as a list of anonymous lists of vertices, each anonymous list containing the vertices belonging to one strongly connected component. =cut sub strongly_connected_components { my $G = shift; my $T = $G->_strongly_connected; my %R = $T->_vertex_roots; my @C; # Clump together vertices having identical root vertices. while (my ($v, $r) = each %R) { push @{ $C[ $r ] }, $v } return @C; } =pod =item strongly_connected_graph $T = $G->strongly_connected_graph Returns the strongly connected graph $T of the graph $G. The names of the strongly connected components are formed from their constituent vertices by concatenating their names by '+'-characters: "a" and "b" --> "a+b". =cut sub strongly_connected_graph { my $G = shift; my $C = (ref $G)->new; my $T = $G->_strongly_connected; my %R = $T->_vertex_roots; my @C; # We're not calling the strongly_connected_components() # method because we will need also the %R. # Create the strongly connected components. while (my ($v, $r) = each %R) { push @{ $C[$r] }, $v } foreach my $c (@C) { $c = join("+", sort @$c) } $C->directed( $G->directed ); foreach my $c ( @C ) { $C->add_vertex( $c ) } my @E = $G->edges; # Copy the edges between strongly connected components. my $edge_cnt = 0; while (my ($u, $v) = splice(@E, 0, 2)) { if ($R{ $u } != $R{ $v }) { ($u, $v) = ( $C[ $R{ $u } ], $C[ $R{ $v } ] ); $C->add_edge($u, $v) unless $C->has_edge($u, $v); $edge_cnt++; } } return $C; } =pod =item APSP_Floyd_Warshall $APSP = $G->APSP_Floyd_Warshall Returns the All-pairs Shortest Paths graph of the graph $G computed using the Floyd-Warshall algorithm and the attribute 'weight' on the edges. The returned graph has an edge for each shortest path. An edge has attributes "weight" and "path"; for the length of the shortest path and for the path (an anonymous list) itself. =cut sub APSP_Floyd_Warshall { my $G = shift; my @V = $G->vertices; my @E = $G->edges; my (%V2I, @I2V); my (@P, @W); # Compute the vertex <-> index mappings. @V2I{ @V } = 0..$#V; @I2V[ 0..$#V ] = @V; # Initialize the predecessor matrix @P and the weight matrix @W. # (The graph is converted into adjacency-matrix representation.) # (The matrix is a list of lists.) foreach my $i ( 0..$#V ) { $W[ $i ][ $i ] = 0 } while ( my ($u, $v) = splice(@E, 0, 2) ) { my ( $ui, $vi ) = ( $V2I{ $u }, $V2I{ $v } ); $P[ $ui ][ $vi ] = $ui unless $ui == $vi; $W[ $ui ][ $vi ] = $G->get_attribute( 'weight', $u, $v ); } # Do the O(N**3) loop. for ( my $k = 0; $k < @V; $k++ ) { my (@nP, @nW); # new @P, new @W for ( my $i = 0; $i < @V; $i++ ) { for ( my $j = 0; $j < @V; $j++ ) { my $w_ij = $W[ $i ][ $j ]; my $w_ik_kj = $W[ $i ][ $k ] + $W[ $k ][ $j ] if defined $W[ $i ][ $k ] and defined $W[ $k ][ $j ]; # Choose the minimum of w_ij and w_ik_kj. if ( defined $w_ij ) { if ( defined $w_ik_kj ) { if ( $w_ij <= $w_ik_kj ) { $nP[ $i ][ $j ] = $P[ $i ][ $j ]; $nW[ $i ][ $j ] = $w_ij; } else { $nP[ $i ][ $j ] = $P[ $k ][ $j ]; $nW[ $i ][ $j ] = $w_ik_kj; } } else { $nP[ $i ][ $j ] = $P[ $i ][ $j ]; $nW[ $i ][ $j ] = $w_ij; } } elsif ( defined $w_ik_kj ) { $nP[ $i ][ $j ] = $P[ $k ][ $j ]; $nW[ $i ][ $j ] = $w_ik_kj; } } } @P = @nP; @W = @nW; # Update the predecessors and weights. } # Now construct the APSP graph. my $APSP = (ref $G)->new; $APSP->directed( $G->directed ); # Copy the directedness. # Convert the adjacency-matrix representation # into a Graph (adjacency-list representation). for ( my $i = 0; $i < @V; $i++ ) { my $iv = $I2V[ $i ]; for ( my $j = 0; $j < @V; $j++ ) { if ( $i == $j ) { $APSP->add_weighted_edge( $iv, 0, $iv ); $APSP->set_attribute("path", $iv, $iv, [ $iv ]); next; } next unless defined $W[ $i ][ $j ]; my $jv = $I2V[ $j ]; $APSP->add_weighted_edge( $iv, $W[ $i ][ $j ], $jv ); my @path = ( $jv ); if ( $P[ $i ][ $j ] != $i ) { my $k = $P[ $i ][ $j ]; # Walk back the path. while ( $k != $i ) { push @path, $I2V[ $k ]; $k = $P[ $i ][ $k ]; # Keep walking. } } $APSP->set_attribute( "path", $iv, $jv, [ $iv, reverse @path ] ); } } return $APSP; } =pod =item TransitiveClosure_Floyd_Warshall $TransitiveClosure = $G->TransitiveClosure_Floyd_Warshall Returns the Transitive Closure graph of the graph $G computed using the Floyd-Warshall algorithm. The resulting graph has an edge between each *ordered* pair of vertices in which the second vertex is reachable from the first. =cut sub TransitiveClosure_Floyd_Warshall { my $G = shift; my @V = $G->vertices; my @E = $G->edges; my (%V2I, @I2V); my @C = ( '' ) x @V; # Compute the vertex <-> index mappings. @V2I{ @V } = 0..$#V; @I2V[ 0..$#V ] = @V; # Initialize the closure matrix @C. # (The graph is converted into adjacency-matrix representation.) # (The matrix is a bit matrix. Well, a list of bit vectors.) foreach my $i ( 0..$#V ) { vec( $C[ $i ], $i, 1 ) = 1 } while ( my ($u, $v) = splice(@E, 0, 2) ) { vec( $C[ $V2I{ $u } ], $V2I{ $v }, 1 ) = 1 } # Do the O(N**3) loop. for ( my $k = 0; $k < @V; $k++ ) { my @nC = ( '' ) x @V; # new @C for ( my $i = 0; $i < @V; $i++ ) { for ( my $j = 0; $j < @V; $j++ ) { vec( $nC[ $i ], $j, 1 ) = vec( $C[ $i ], $j, 1 ) | vec( $C[ $i ], $k, 1 ) & vec( $C[ $k ], $j, 1 ); } } @C = @nC; # Update the closure. } # Now construct the TransitiveClosure graph. my $TransitiveClosure = (ref $G)->new; $TransitiveClosure->directed( $G->directed ); # Convert the (closure-)adjacency-matrix representation # into a Graph (adjacency-list representation). for ( my $i = 0; $i < @V; $i++ ) { for ( my $j = 0; $j < @V; $j++ ) { $TransitiveClosure->add_edge( $I2V[ $i ], $I2V[ $j ] ) if vec( $C[ $i ], $j, 1 ); } } return $TransitiveClosure; } =pod =item articulation points @A = $G->articulation_points(%param) Returns the articulation points (vertices) @A of the graph $G. The %param can be used to control the search. =cut sub articulation_points { my $G = shift; my $articulate = sub { my ( $u, $T ) = @_; my $ap = $T->{ vertex_found }->{ $u }; my @S = @{ $T->{ active_list } }; # Current stack. $T->{ articulation_point }->{ $u } = $ap unless exists $T->{ articulation_point }->{ $u }; # Walk back the stack marking the active DFS branch # (below $u) as belonging to the articulation point $ap. for ( my $i = 1; $i < @S; $i++ ) { my $v = $S[ -$i ]; last if $v eq $u; $T->{ articulation_point }->{ $v } = $ap if not exists $T->{ articulation_point }->{ $v } or $ap < $T->{ articulation_point }->{ $v }; } }; my $unseen_successor = sub { my ($u, $v, $T) = @_; # We need to know the number of children for root vertices. $T->{ articulation_children }->{ $u }++; }; my $seen_successor = sub { my ($u, $v, $T) = @_; # If the $v is still active, articulate it. $articulate->( $v, $T ) if exists $T->{ active_pool }->{ $v }; }; my $d = Graph::DFS->new($G, articulate => $articulate, unseen_successor => $unseen_successor, seen_successor => $seen_successor, ); $d->preorder; # Now we need to find (the indices of) unique articulation points # and map them back to vertices. my (%ap, @vf); foreach my $v ( $G->vertices ) { $ap{ $d->{ articulation_point }->{ $v } } = $v; $vf[ $d->{ vertex_found }->{ $v } ] = $v; } %ap = map { ( $vf[ $_ ], $_ ) } keys %ap; # DFS tree roots are articulation points only # iff they have more than one children. foreach my $r ( $d->roots ) { delete $ap{ $r } if $d->{ articulation_children }->{ $r } < 2; } keys %ap; } =pod =item is_biconnected $b = $G->is_biconnected Returns true is the graph $G is biconnected (has no articulation points), false otherwise. =cut sub is_biconnected { my $G = shift; return $G->articulation_points == 0; } =pod =item largest_out_degree $v = $G->largest_out_degree( @V ) Selects the vertex $v from the vertices @V having the largest out degree in the graph $G. =cut sub largest_out_degree { my $G = shift; my $L = shift; my $O = $G->out_degree($L); for my $e (@_) { my $o = $G->out_degree($e); if ($o > $O) { $L = $e; $O = $o; } } return $L; } # _heap_init # # $G->_heap_init($heap, $u, \%in_heap, \%weight, \%parent) # # (INTERNAL USE ONLY) # Initializes the $heap with the vertex $u as the initial # vertex, its weight being zero, and marking all vertices # of the graph $G to be $in_heap, # sub _heap_init { my ($G, $heap, $u, $in_heap, $W, $P) = @_; use Graph::HeapElem; foreach my $v ( $G->vertices ) { my $e = Graph::HeapElem->new( $v, $W, $P ); $heap->add( $e ); $in_heap->{ $v } = $e; } $W->{ $u } = 0; } =pod =item MST_Prim $MST = $G->MST_Prim($u) Returns Prim's Minimum Spanning Tree (as a graph) of the graph $G based on the 'weight' attributes of the edges. The optional start vertex is $u, if none is given, a hopefully good one (a vertex with a large out degree) is chosen. =cut sub MST_Prim { my ( $G, $u ) = @_; my $MST = (ref $G)->new; $u = $G->largest_out_degree( $G->vertices ) unless defined $u; use Heap::Fibonacci; my $heap = Heap::Fibonacci->new; my ( %in_heap, %weight, %parent ); $G->_heap_init( $heap, $u, \%in_heap, \%weight, \%parent ); # Walk the edges at the current BFS front # in the order of their increasing weight. while ( defined $heap->minimum ) { $u = $heap->extract_minimum; delete $in_heap{ $u->vertex }; # Now extend the BFS front. foreach my $v ( $G->successors( $u->vertex ) ) { if ( defined( $v = $in_heap{ $v } ) ) { my $nw = $G->get_attribute( 'weight', $u->vertex, $v->vertex ); my $ow = $v->weight; if ( not defined $ow or $nw < $ow ) { $v->weight( $nw ); $v->parent( $u->vertex ); $heap->decrease_key( $v ); } } } } foreach my $v ( $G->vertices ) { $MST->add_weighted_edge( $v, $weight{ $v }, $parent{ $v } ) if defined $parent{ $v }; } return $MST; } # _SSSP_construct # # $SSSP = $G->_SSSP_construct( $s, $W, $P ); # # (INTERNAL USE ONLY) # Return the SSSP($s) graph of graph $G based on the computed # anonymous hashes for weights and parents: $W and $P. # The vertices of the graph will have two attributes: "weight", # which tells the length of the shortest single-source path, # and "path", which is an anymous list containing the path. # sub _SSSP_construct { my ($G, $s, $W, $P ) = @_; my $SSSP = (ref $G)->new; foreach my $u ( $G->vertices ) { $SSSP->add_vertex( $u ); $SSSP->set_attribute( "weight", $u, $W->{ $u } || 0 ); my @path = ( $u ); if ( defined $P->{ $u } ) { $SSSP->add_edge($P->{ $u }, $u ); $SSSP->set_attribute( "weight", $P->{ $u }, $u, $G->get_attribute("weight",$P->{ $u }, $u) || 0 ); push @path, $P->{ $u }; if ( $P->{ $u } ne $s ) { my $v = $P->{ $u }; while ( defined $v && exists $P->{ $v } && $v ne $s ) { push @path, $P->{ $v }; $v = $P->{ $v }; } } } $SSSP->set_attribute( "path", $u, [ reverse @path ] ); } return $SSSP; } =pod =item SSSP_Dijkstra $SSSP = $G->SSSP_Dijkstra($s) Returns the Single-source Shortest Paths (as a graph) of the graph $G starting from the vertex $s using Dijktra's SSSP algorithm. =cut sub SSSP_Dijkstra { my ( $G, $s ) = @_; use Heap::Fibonacci; my $heap = Heap::Fibonacci->new; my ( %in_heap, %weight, %parent ); # The other weights are by default undef (infinite). $weight{ $s } = 0; $G->_heap_init($heap, $s, \%in_heap, \%weight, \%parent ); # Walk the edges at the current BFS front # in the order of their increasing weight. while ( defined $heap->minimum ) { my $u = $heap->extract_minimum; delete $in_heap{ $u->vertex }; # Now extend the BFS front. my $uw = $u->weight; foreach my $v ( $G->successors( $u->vertex ) ) { if ( defined( $v = $in_heap{ $v } ) ) { my $ow = $v->weight; my $nw = $G->get_attribute( 'weight', $u->vertex, $v->vertex ) + ($uw || 0); # The || 0 helps for undefined $uw. # Relax the edge $u - $v. if ( not defined $ow or $ow > $nw ) { $v->weight( $nw ); $v->parent( $u->vertex ); $heap->decrease_key( $v ); } } } } return $G->_SSSP_construct( $s, \%weight, \%parent ); } =pod =item SSSP_Bellman_Ford $SSSP = $G->SSSP_Bellman_Ford($s) Returns the Single-source Shortest Paths (as a graph) of the graph $G starting from the vertex $s using Bellman-Ford SSSP algorithm. If there are one or more negatively weighted cycles, returns undef. =cut sub SSSP_Bellman_Ford { my ( $G, $s ) = @_; my ( %weight, %parent ); $weight{ $s } = 0; my $V = $G->vertices; my @E = $G->edges; foreach ( 1..$V ) { # |V|-1 times (*not* |V| times) my @C = @E; # Copy. while (my ($u, $v) = splice(@C, 0, 2)) { my $ow = $weight{ $v }; my $nw = $G->get_attribute( 'weight', $u, $v ); $nw += $weight{ $u } if defined $weight{ $u }; # Relax the edge $u - $w. if ( not defined $ow or $ow > $nw ) { $weight{ $v } = $nw; $parent{ $v } = $u; } } } my $negative; # Warn about detected negative cycles. while (my ($u, $v) = splice(@E, 0, 2)) { if ( $weight{ $v } > $weight{ $u } + $G->get_attribute( 'weight', $u, $v ) ) { warn "SSSP_Bellman_Ford: negative cycle $u $v\n"; $negative++; } } # Bail out if found negative cycles. return undef if $negative; # Otherwise return the SSSP graph. return $G->_SSSP_construct( $s, \%weight, \%parent ); } =pod =item SSSP_DAG $SSSP = $G->SSSP_DAG($s) Returns the Single-source Shortest Paths (as a graph) of the DAG $G starting from vertex $s. =cut sub SSSP_DAG { my ( $G, $s ) = @_; my $SSSP = (ref $G)->new; my ( %weight, %parent ); $weight{ $s } = 0; # Because by definition there can be no cycles # we can freely explore each successor of each vertex. foreach my $u ( $G->toposort ) { foreach my $v ( $G->successors( $u ) ) { my $ow = $weight{ $v }; my $nw = $G->get_attribute( 'weight', $u, $v ); $nw += $weight{ $u } if defined $weight{ $u }; # Relax the edge $u - $v. if ( not defined $ow or $ow > $nw ) { $weight{ $v } = $nw; $parent{ $v } = $u; } } } return $G->_SSSP_construct( $s, \%weight, \%parent ); } =pod =item add_capacity_edge $G->add_capacity_edge($u, $w, $v, $a) Adds in the graph $G an edge from vertex $u to vertex $v and the edge attribute 'capacity' set to $w. =cut sub add_capacity_edge { my ($G, $u, $w, $v, $a) = @_; $G->add_edge($u, $v); $G->set_attribute('capacity', $u, $v, $w); } =pod =item add_capacity_edges $G->add_capacity_edges($u1, $w1, $v1, $u2, $w2, $v2, ...) Adds in the graph $G the capacity edges. =cut sub add_capacity_edges { my $G = shift; while (my ($u, $w, $v) = splice(@_, 0, 3)) { $G->add_capacity_edge($u, $w, $v); } } =pod =item add_capacity_path $G->add_capacity_path($v1, $w1, $v2, $w2, ..., $wnm1, $vn) Adds in the graph $G the n edges defined by the path $v1 ... $vn with the n-1 'capacity' attributes $w1 ... $wnm1 =cut sub add_capacity_path { my $G = shift; my $u = shift; while (my ($w, $v) = splice(@_, 0, 2)) { $G->add_capacity_edge($u, $w, $v); $u = $v; } } =pod =item Flow_Ford_Fulkerson $F = $G->Flow_Ford_Fulkerson($S) Returns the (maximal) flow network of the flow network $G, parametrized by the state $S. The $G must have 'capacity' attributes on its edges. $S->{ source } must contain the source vertex and $S->{ sink } the sink vertex, and most importantly $S->{ next_augmenting_path } must contain an anonymous subroutine which takes $F and $S as arguments and returns the next potential augmenting path. Flow_Ford_Fulkerson will do the augmenting. The result graph $F will have 'flow' and (residual) 'capacity' attributes on its edges. =cut sub Flow_Ford_Fulkerson { my ( $G, $S ) = @_; my $F = (ref $G)->new; # The flow network. my @E = $G->edges; my ( $u, $v ); # Copy the edges and the capacities, zero the flows. while (($u, $v) = splice(@E, 0, 2)) { $F->add_edge( $u, $v ); $F->set_attribute( 'capacity', $u, $v, $G->get_attribute( 'capacity', $u, $v ) || 0 ); $F->set_attribute( 'flow', $u, $v, 0 ); } # Walk the augmenting paths. while ( my $ap = $S->{ next_augmenting_path }->( $F, $S ) ) { my @aps = @$ap; # augmenting path segments my $apr; # augmenting path residual capacity my $psr; # path segment residual capacity # Find the minimum capacity of the path. for ( $u = shift @aps; @aps; $u = $v ) { $v = shift @aps; $psr = $F->get_attribute( 'capacity', $u, $v ) - $F->get_attribute( 'flow', $u, $v ); $apr = $psr if $psr >= 0 and ( not defined $apr or $psr < $apr ); } if ( $apr > 0 ) { # Augment the path. for ( @aps = @$ap, $u = shift @aps; @aps; $u = $v ) { $v = shift @aps; $F->set_attribute( 'flow', $u, $v, $F->get_attribute( 'flow', $u, $v ) + $apr ); } } } return $F; } =pod =item Flow_Edmonds_Karp $F = $G->Flow_Edmonds_Karp($source, $sink) Return the maximal flow network of the graph $G built using the Edmonds-Karp version of Ford-Fulkerson. The input graph $G must have 'capacity' attributes on its edges; resulting flow graph will have 'capacity' and 'flow' attributes on its edges. =cut sub Flow_Edmonds_Karp { my ( $G, $source, $sink ) = @_; my $S; $S->{ source } = $source; $S->{ sink } = $sink; $S->{ next_augmenting_path } = sub { my ( $F, $S ) = @_; my $source = $S->{ source }; my $sink = $S->{ sink }; # Initialize our "todo" heap. unless ( exists $S->{ todo } ) { # The first element is a hash recording the vertices # seen so far, the rest are the path from the source. push @{ $S->{ todo } }, [ { $source => 1 }, $source ]; } while ( @{ $S->{ todo } } ) { # $ap: The next augmenting path. my $ap = shift @{ $S->{ todo } }; my $sv = shift @$ap; # The seen vertices. my $v = $ap->[ -1 ]; # The last vertex of path. if ( $v eq $sink ) { return $ap; } else { foreach my $s ( $G->successors( $v ) ) { unless ( exists $sv->{ $s } ) { push @{ $S->{ todo } }, [ { %$sv, $s => 1 }, @$ap, $s ]; } } } } }; return $G->Flow_Ford_Fulkerson( $S ); } use overload 'eq' => \&eq; =pod =item eq $G->eq($H) Return true if the graphs (actually, their string representations) are identical. This means really identical: they must have identical vertex names and identical edges between the vertices, and they must be similarly directed. (Just isomorphism isn't enough.) =cut sub eq { my ($G, $H) = @_; return ref $H ? $G->stringify eq $H->stringify : $G->stringify eq $H; } =pod =back =head1 COPYRIGHT Copyright 1999, O'Reilly & Associates. This code is distributed under the same copyright terms as Perl itself. =cut 1;