mirror of
https://github.com/netwide-assembler/nasm.git
synced 2025-03-01 17:35:38 +08:00
715 lines
17 KiB
Perl
715 lines
17 KiB
Perl
package Graph::Traversal;
|
|
|
|
use strict;
|
|
|
|
# $SIG{__DIE__ } = sub { use Carp; confess };
|
|
# $SIG{__WARN__} = sub { use Carp; confess };
|
|
|
|
sub DEBUG () { 0 }
|
|
|
|
sub reset {
|
|
my $self = shift;
|
|
$self->{ unseen } = { map { $_ => $_ } $self->{ graph }->vertices };
|
|
$self->{ seen } = { };
|
|
$self->{ order } = [ ];
|
|
$self->{ preorder } = [ ];
|
|
$self->{ postorder } = [ ];
|
|
$self->{ roots } = [ ];
|
|
$self->{ tree } =
|
|
Graph->new( directed => $self->{ graph }->directed );
|
|
delete $self->{ terminate };
|
|
}
|
|
|
|
my $see = sub {
|
|
my $self = shift;
|
|
$self->see;
|
|
};
|
|
|
|
my $see_active = sub {
|
|
my $self = shift;
|
|
delete @{ $self->{ active } }{ $self->see };
|
|
};
|
|
|
|
sub has_a_cycle {
|
|
my ($u, $v, $t, $s) = @_;
|
|
$s->{ has_a_cycle } = 1;
|
|
$t->terminate;
|
|
}
|
|
|
|
sub find_a_cycle {
|
|
my ($u, $v, $t, $s) = @_;
|
|
my @cycle = ( $u );
|
|
push @cycle, $v unless $u eq $v;
|
|
my $path = $t->{ order };
|
|
if (@$path) {
|
|
my $i = $#$path;
|
|
while ($i >= 0 && $path->[ $i ] ne $v) { $i-- }
|
|
if ($i >= 0) {
|
|
unshift @cycle, @{ $path }[ $i+1 .. $#$path ];
|
|
}
|
|
}
|
|
$s->{ a_cycle } = \@cycle;
|
|
$t->terminate;
|
|
}
|
|
|
|
sub configure {
|
|
my ($self, %attr) = @_;
|
|
$self->{ pre } = $attr{ pre } if exists $attr{ pre };
|
|
$self->{ post } = $attr{ post } if exists $attr{ post };
|
|
$self->{ pre_vertex } = $attr{ pre_vertex } if exists $attr{ pre_vertex };
|
|
$self->{ post_vertex } = $attr{ post_vertex } if exists $attr{ post_vertex };
|
|
$self->{ pre_edge } = $attr{ pre_edge } if exists $attr{ pre_edge };
|
|
$self->{ post_edge } = $attr{ post_edge } if exists $attr{ post_edge };
|
|
if (exists $attr{ successor }) { # Graph 0.201 compatibility.
|
|
$self->{ tree_edge } = $self->{ non_tree_edge } = $attr{ successor };
|
|
}
|
|
if (exists $attr{ unseen_successor }) {
|
|
if (exists $self->{ tree_edge }) { # Graph 0.201 compatibility.
|
|
my $old_tree_edge = $self->{ tree_edge };
|
|
$self->{ tree_edge } = sub {
|
|
$old_tree_edge->( @_ );
|
|
$attr{ unseen_successor }->( @_ );
|
|
};
|
|
} else {
|
|
$self->{ tree_edge } = $attr{ unseen_successor };
|
|
}
|
|
}
|
|
if ($self->graph->multiedged || $self->graph->countedged) {
|
|
$self->{ seen_edge } = $attr{ seen_edge } if exists $attr{ seen_edge };
|
|
if (exists $attr{ seen_successor }) { # Graph 0.201 compatibility.
|
|
$self->{ seen_edge } = $attr{ seen_edge };
|
|
}
|
|
}
|
|
$self->{ non_tree_edge } = $attr{ non_tree_edge } if exists $attr{ non_tree_edge };
|
|
$self->{ pre_edge } = $attr{ tree_edge } if exists $attr{ tree_edge };
|
|
$self->{ back_edge } = $attr{ back_edge } if exists $attr{ back_edge };
|
|
$self->{ down_edge } = $attr{ down_edge } if exists $attr{ down_edge };
|
|
$self->{ cross_edge } = $attr{ cross_edge } if exists $attr{ cross_edge };
|
|
if (exists $attr{ start }) {
|
|
$attr{ first_root } = $attr{ start };
|
|
$attr{ next_root } = undef;
|
|
}
|
|
if (exists $attr{ get_next_root }) {
|
|
$attr{ next_root } = $attr{ get_next_root }; # Graph 0.201 compat.
|
|
}
|
|
$self->{ next_root } =
|
|
exists $attr{ next_root } ?
|
|
$attr{ next_root } :
|
|
$attr{ next_alphabetic } ?
|
|
\&Graph::_next_alphabetic :
|
|
$attr{ next_numeric } ?
|
|
\&Graph::_next_numeric :
|
|
\&Graph::_next_random;
|
|
$self->{ first_root } =
|
|
exists $attr{ first_root } ?
|
|
$attr{ first_root } :
|
|
exists $attr{ next_root } ?
|
|
$attr{ next_root } :
|
|
$attr{ next_alphabetic } ?
|
|
\&Graph::_next_alphabetic :
|
|
$attr{ next_numeric } ?
|
|
\&Graph::_next_numeric :
|
|
\&Graph::_next_random;
|
|
$self->{ next_successor } =
|
|
exists $attr{ next_successor } ?
|
|
$attr{ next_successor } :
|
|
$attr{ next_alphabetic } ?
|
|
\&Graph::_next_alphabetic :
|
|
$attr{ next_numeric } ?
|
|
\&Graph::_next_numeric :
|
|
\&Graph::_next_random;
|
|
if (exists $attr{ has_a_cycle }) {
|
|
my $has_a_cycle =
|
|
ref $attr{ has_a_cycle } eq 'CODE' ?
|
|
$attr{ has_a_cycle } : \&has_a_cycle;
|
|
$self->{ back_edge } = $has_a_cycle;
|
|
if ($self->{ graph }->is_undirected) {
|
|
$self->{ down_edge } = $has_a_cycle;
|
|
}
|
|
}
|
|
if (exists $attr{ find_a_cycle }) {
|
|
my $find_a_cycle =
|
|
ref $attr{ find_a_cycle } eq 'CODE' ?
|
|
$attr{ find_a_cycle } : \&find_a_cycle;
|
|
$self->{ back_edge } = $find_a_cycle;
|
|
if ($self->{ graph }->is_undirected) {
|
|
$self->{ down_edge } = $find_a_cycle;
|
|
}
|
|
}
|
|
$self->{ add } = \&add_order;
|
|
$self->{ see } = $see;
|
|
delete @attr{ qw(
|
|
pre post pre_edge post_edge
|
|
successor unseen_successor seen_successor
|
|
tree_edge non_tree_edge
|
|
back_edge down_edge cross_edge seen_edge
|
|
start get_next_root
|
|
next_root next_alphabetic next_numeric next_random next_successor
|
|
first_root
|
|
has_a_cycle find_a_cycle
|
|
) };
|
|
if (keys %attr) {
|
|
require Carp;
|
|
my @attr = sort keys %attr;
|
|
Carp::croak(sprintf "Graph::Traversal: unknown attribute%s @{[map { qq['$_'] } @attr]}\n", @attr == 1 ? '' : 's');
|
|
}
|
|
}
|
|
|
|
sub new {
|
|
my $class = shift;
|
|
my $g = shift;
|
|
unless (ref $g && $g->isa('Graph')) {
|
|
require Carp;
|
|
Carp::croak("Graph::Traversal: first argument is not a Graph");
|
|
}
|
|
my $self = { graph => $g, state => { } };
|
|
bless $self, $class;
|
|
$self->reset;
|
|
$self->configure( @_ );
|
|
return $self;
|
|
}
|
|
|
|
sub terminate {
|
|
my $self = shift;
|
|
$self->{ terminate } = 1;
|
|
}
|
|
|
|
sub add_order {
|
|
my ($self, @next) = @_;
|
|
push @{ $self->{ order } }, @next;
|
|
}
|
|
|
|
sub visit {
|
|
my ($self, @next) = @_;
|
|
delete @{ $self->{ unseen } }{ @next };
|
|
print "unseen = @{[sort keys %{$self->{unseen}}]}\n" if DEBUG;
|
|
@{ $self->{ seen } }{ @next } = @next;
|
|
print "seen = @{[sort keys %{$self->{seen}}]}\n" if DEBUG;
|
|
$self->{ add }->( $self, @next );
|
|
print "order = @{$self->{order}}\n" if DEBUG;
|
|
if (exists $self->{ pre }) {
|
|
my $p = $self->{ pre };
|
|
for my $v (@next) {
|
|
$p->( $v, $self );
|
|
}
|
|
}
|
|
}
|
|
|
|
sub visit_preorder {
|
|
my ($self, @next) = @_;
|
|
push @{ $self->{ preorder } }, @next;
|
|
for my $v (@next) {
|
|
$self->{ preordern }->{ $v } = $self->{ preorderi }++;
|
|
}
|
|
print "preorder = @{$self->{preorder}}\n" if DEBUG;
|
|
$self->visit( @next );
|
|
}
|
|
|
|
sub visit_postorder {
|
|
my ($self) = @_;
|
|
my @post = reverse $self->{ see }->( $self );
|
|
push @{ $self->{ postorder } }, @post;
|
|
for my $v (@post) {
|
|
$self->{ postordern }->{ $v } = $self->{ postorderi }++;
|
|
}
|
|
print "postorder = @{$self->{postorder}}\n" if DEBUG;
|
|
if (exists $self->{ post }) {
|
|
my $p = $self->{ post };
|
|
for my $v (@post) {
|
|
$p->( $v, $self ) ;
|
|
}
|
|
}
|
|
if (exists $self->{ post_edge }) {
|
|
my $p = $self->{ post_edge };
|
|
my $u = $self->current;
|
|
if (defined $u) {
|
|
for my $v (@post) {
|
|
$p->( $u, $v, $self, $self->{ state });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sub _callbacks {
|
|
my ($self, $current, @all) = @_;
|
|
return unless @all;
|
|
my $nontree = $self->{ non_tree_edge };
|
|
my $back = $self->{ back_edge };
|
|
my $down = $self->{ down_edge };
|
|
my $cross = $self->{ cross_edge };
|
|
my $seen = $self->{ seen_edge };
|
|
my $bdc = defined $back || defined $down || defined $cross;
|
|
if (defined $nontree || $bdc || defined $seen) {
|
|
my $u = $current;
|
|
my $preu = $self->{ preordern }->{ $u };
|
|
my $postu = $self->{ postordern }->{ $u };
|
|
for my $v ( @all ) {
|
|
my $e = $self->{ tree }->has_edge( $u, $v );
|
|
if ( !$e && (defined $nontree || $bdc) ) {
|
|
if ( exists $self->{ seen }->{ $v }) {
|
|
$nontree->( $u, $v, $self, $self->{ state })
|
|
if $nontree;
|
|
if ($bdc) {
|
|
my $postv = $self->{ postordern }->{ $v };
|
|
if ($back &&
|
|
(!defined $postv || $postv >= $postu)) {
|
|
$back ->( $u, $v, $self, $self->{ state });
|
|
} else {
|
|
my $prev = $self->{ preordern }->{ $v };
|
|
if ($down && $prev > $preu) {
|
|
$down ->( $u, $v, $self, $self->{ state });
|
|
} elsif ($cross && $prev < $preu) {
|
|
$cross->( $u, $v, $self, $self->{ state });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ($seen) {
|
|
my $c = $self->graph->get_edge_count($u, $v);
|
|
while ($c-- > 1) {
|
|
$seen->( $u, $v, $self, $self->{ state } );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sub next {
|
|
my $self = shift;
|
|
return undef if $self->{ terminate };
|
|
my @next;
|
|
while ($self->seeing) {
|
|
my $current = $self->current;
|
|
print "current = $current\n" if DEBUG;
|
|
@next = $self->{ graph }->successors( $current );
|
|
print "next.0 - @next\n" if DEBUG;
|
|
my %next; @next{ @next } = @next;
|
|
# delete $next{ $current };
|
|
print "next.1 - @next\n" if DEBUG;
|
|
@next = keys %next;
|
|
my @all = @next;
|
|
print "all = @all\n" if DEBUG;
|
|
delete @next{ $self->seen };
|
|
@next = keys %next;
|
|
print "next.2 - @next\n" if DEBUG;
|
|
if (@next) {
|
|
@next = $self->{ next_successor }->( $self, \%next );
|
|
print "next.3 - @next\n" if DEBUG;
|
|
for my $v (@next) {
|
|
$self->{ tree }->add_edge( $current, $v );
|
|
}
|
|
if (exists $self->{ pre_edge }) {
|
|
my $p = $self->{ pre_edge };
|
|
my $u = $self->current;
|
|
for my $v (@next) {
|
|
$p->( $u, $v, $self, $self->{ state });
|
|
}
|
|
}
|
|
last;
|
|
} else {
|
|
$self->visit_postorder;
|
|
}
|
|
return undef if $self->{ terminate };
|
|
$self->_callbacks($current, @all);
|
|
# delete $next{ $current };
|
|
}
|
|
print "next.4 - @next\n" if DEBUG;
|
|
unless (@next) {
|
|
unless ( @{ $self->{ roots } } ) {
|
|
my $first = $self->{ first_root };
|
|
if (defined $first) {
|
|
@next =
|
|
ref $first eq 'CODE' ?
|
|
$self->{ first_root }->( $self, $self->{ unseen } ) :
|
|
$first;
|
|
return unless @next;
|
|
}
|
|
}
|
|
unless (@next) {
|
|
return unless defined $self->{ next_root };
|
|
return unless @next =
|
|
$self->{ next_root }->( $self, $self->{ unseen } );
|
|
}
|
|
return if exists $self->{ seen }->{ $next[0] }; # Sanity check.
|
|
print "next.5 - @next\n" if DEBUG;
|
|
push @{ $self->{ roots } }, $next[0];
|
|
}
|
|
print "next.6 - @next\n" if DEBUG;
|
|
if (@next) {
|
|
$self->visit_preorder( @next );
|
|
}
|
|
return $next[0];
|
|
}
|
|
|
|
sub _order {
|
|
my ($self, $order) = @_;
|
|
1 while defined $self->next;
|
|
my $wantarray = wantarray;
|
|
if ($wantarray) {
|
|
@{ $self->{ $order } };
|
|
} elsif (defined $wantarray) {
|
|
shift @{ $self->{ $order } };
|
|
}
|
|
}
|
|
|
|
sub preorder {
|
|
my $self = shift;
|
|
$self->_order( 'preorder' );
|
|
}
|
|
|
|
sub postorder {
|
|
my $self = shift;
|
|
$self->_order( 'postorder' );
|
|
}
|
|
|
|
sub unseen {
|
|
my $self = shift;
|
|
values %{ $self->{ unseen } };
|
|
}
|
|
|
|
sub seen {
|
|
my $self = shift;
|
|
values %{ $self->{ seen } };
|
|
}
|
|
|
|
sub seeing {
|
|
my $self = shift;
|
|
@{ $self->{ order } };
|
|
}
|
|
|
|
sub roots {
|
|
my $self = shift;
|
|
@{ $self->{ roots } };
|
|
}
|
|
|
|
sub is_root {
|
|
my ($self, $v) = @_;
|
|
for my $u (@{ $self->{ roots } }) {
|
|
return 1 if $u eq $v;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
sub tree {
|
|
my $self = shift;
|
|
$self->{ tree };
|
|
}
|
|
|
|
sub graph {
|
|
my $self = shift;
|
|
$self->{ graph };
|
|
}
|
|
|
|
sub vertex_by_postorder {
|
|
my ($self, $i) = @_;
|
|
exists $self->{ postorder } && $self->{ postorder }->[ $i ];
|
|
}
|
|
|
|
sub postorder_by_vertex {
|
|
my ($self, $v) = @_;
|
|
exists $self->{ postordern } && $self->{ postordern }->{ $v };
|
|
}
|
|
|
|
sub postorder_vertices {
|
|
my ($self, $v) = @_;
|
|
exists $self->{ postordern } ? %{ $self->{ postordern } } : ();
|
|
}
|
|
|
|
sub vertex_by_preorder {
|
|
my ($self, $i) = @_;
|
|
exists $self->{ preorder } && $self->{ preorder }->[ $i ];
|
|
}
|
|
|
|
sub preorder_by_vertex {
|
|
my ($self, $v) = @_;
|
|
exists $self->{ preordern } && $self->{ preordern }->{ $v };
|
|
}
|
|
|
|
sub preorder_vertices {
|
|
my ($self, $v) = @_;
|
|
exists $self->{ preordern } ? %{ $self->{ preordern } } : ();
|
|
}
|
|
|
|
sub has_state {
|
|
my ($self, $var) = @_;
|
|
exists $self->{ state } && exists $self->{ state }->{ $var };
|
|
}
|
|
|
|
sub get_state {
|
|
my ($self, $var) = @_;
|
|
exists $self->{ state } ? $self->{ state }->{ $var } : undef;
|
|
}
|
|
|
|
sub set_state {
|
|
my ($self, $var, $val) = @_;
|
|
$self->{ state }->{ $var } = $val;
|
|
return 1;
|
|
}
|
|
|
|
sub delete_state {
|
|
my ($self, $var) = @_;
|
|
delete $self->{ state }->{ $var };
|
|
delete $self->{ state } unless keys %{ $self->{ state } };
|
|
return 1;
|
|
}
|
|
|
|
1;
|
|
__END__
|
|
=pod
|
|
|
|
=head1 NAME
|
|
|
|
Graph::Traversal - traverse graphs
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
Don't use Graph::Traversal directly, use Graph::Traversal::DFS
|
|
or Graph::Traversal::BFS instead.
|
|
|
|
use Graph;
|
|
my $g = Graph->new;
|
|
$g->add_edge(...);
|
|
use Graph::Traversal::...;
|
|
my $t = Graph::Traversal::...->new(%opt);
|
|
$t->...
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
You can control how the graph is traversed by the various callback
|
|
parameters in the C<%opt>. In the parameters descriptions below the
|
|
$u and $v are vertices, and the $self is the traversal object itself.
|
|
|
|
=head2 Callback parameters
|
|
|
|
The following callback parameters are available:
|
|
|
|
=over 4
|
|
|
|
=item tree_edge
|
|
|
|
Called when traversing an edge that belongs to the traversal tree.
|
|
Called with arguments ($u, $v, $self).
|
|
|
|
=item non_tree_edge
|
|
|
|
Called when an edge is met which either leads back to the traversal tree
|
|
(either a C<back_edge>, a C<down_edge>, or a C<cross_edge>).
|
|
Called with arguments ($u, $v, $self).
|
|
|
|
=item pre_edge
|
|
|
|
Called for edges in preorder.
|
|
Called with arguments ($u, $v, $self).
|
|
|
|
=item post_edge
|
|
|
|
Called for edges in postorder.
|
|
Called with arguments ($u, $v, $self).
|
|
|
|
=item back_edge
|
|
|
|
Called for back edges.
|
|
Called with arguments ($u, $v, $self).
|
|
|
|
=item down_edge
|
|
|
|
Called for down edges.
|
|
Called with arguments ($u, $v, $self).
|
|
|
|
=item cross_edge
|
|
|
|
Called for cross edges.
|
|
Called with arguments ($u, $v, $self).
|
|
|
|
=item pre
|
|
|
|
=item pre_vertex
|
|
|
|
Called for vertices in preorder.
|
|
Called with arguments ($v, $self).
|
|
|
|
=item post
|
|
|
|
=item post_vertex
|
|
|
|
Called for vertices in postorder.
|
|
Called with arguments ($v, $self).
|
|
|
|
=item first_root
|
|
|
|
Called when choosing the first root (start) vertex for traversal.
|
|
Called with arguments ($self, $unseen) where $unseen is a hash
|
|
reference with the unseen vertices as keys.
|
|
|
|
=item next_root
|
|
|
|
Called when choosing the next root (after the first one) vertex for
|
|
traversal (useful when the graph is not connected). Called with
|
|
arguments ($self, $unseen) where $unseen is a hash reference with
|
|
the unseen vertices as keys. If you want only the first reachable
|
|
subgraph to be processed, set the next_root to C<undef>.
|
|
|
|
=item start
|
|
|
|
Identical to defining C<first_root> and undefining C<next_root>.
|
|
|
|
=item next_alphabetic
|
|
|
|
Set this to true if you want the vertices to be processed in
|
|
alphabetic order (and leave first_root/next_root undefined).
|
|
|
|
=item next_numeric
|
|
|
|
Set this to true if you want the vertices to be processed in
|
|
numeric order (and leave first_root/next_root undefined).
|
|
|
|
=item next_successor
|
|
|
|
Called when choosing the next vertex to visit. Called with arguments
|
|
($self, $next) where $next is a hash reference with the possible
|
|
next vertices as keys. Use this to provide a custom ordering for
|
|
choosing vertices, as opposed to C<next_numeric> or C<next_alphabetic>.
|
|
|
|
=back
|
|
|
|
The parameters C<first_root> and C<next_successor> have a 'hierarchy'
|
|
of how they are determined: if they have been explicitly defined, use
|
|
that value. If not, use the value of C<next_alphabetic>, if that has
|
|
been defined. If not, use the value of C<next_numeric>, if that has
|
|
been defined. If not, the next vertex to be visited is chose randomly.
|
|
|
|
=head2 Methods
|
|
|
|
The following methods are available:
|
|
|
|
=over 4
|
|
|
|
=item unseen
|
|
|
|
Return the unseen vertices in random order.
|
|
|
|
=item seen
|
|
|
|
Return the seen vertices in random order.
|
|
|
|
=item seeing
|
|
|
|
Return the active fringe vertices in random order.
|
|
|
|
=item preorder
|
|
|
|
Return the vertices in preorder traversal order.
|
|
|
|
=item postorder
|
|
|
|
Return the vertices in postorder traversal order.
|
|
|
|
=item vertex_by_preorder
|
|
|
|
$v = $t->vertex_by_preorder($i)
|
|
|
|
Return the ith (0..$V-1) vertex by preorder.
|
|
|
|
=item preorder_by_vertex
|
|
|
|
$i = $t->preorder_by_vertex($v)
|
|
|
|
Return the preorder index (0..$V-1) by vertex.
|
|
|
|
=item vertex_by_postorder
|
|
|
|
$v = $t->vertex_by_postorder($i)
|
|
|
|
Return the ith (0..$V-1) vertex by postorder.
|
|
|
|
=item postorder_by_vertex
|
|
|
|
$i = $t->postorder_by_vertex($v)
|
|
|
|
Return the postorder index (0..$V-1) by vertex.
|
|
|
|
=item preorder_vertices
|
|
|
|
Return a hash with the vertices as the keys and their preorder indices
|
|
as the values.
|
|
|
|
=item postorder_vertices
|
|
|
|
Return a hash with the vertices as the keys and their postorder
|
|
indices as the values.
|
|
|
|
=item tree
|
|
|
|
Return the traversal tree as a graph.
|
|
|
|
=item has_state
|
|
|
|
$t->has_state('s')
|
|
|
|
Test whether the traversal has state 's' attached to it.
|
|
|
|
=item get_state
|
|
|
|
$t->get_state('s')
|
|
|
|
Get the state 's' attached to the traversal (C<undef> if none).
|
|
|
|
=item set_state
|
|
|
|
$t->set_state('s', $s)
|
|
|
|
Set the state 's' attached to the traversal.
|
|
|
|
=item delete_state
|
|
|
|
$t->delete_state('s')
|
|
|
|
Delete the state 's' from the traversal.
|
|
|
|
=back
|
|
|
|
=head2 Backward compatibility
|
|
|
|
The following parameters are for backward compatibility to Graph 0.2xx:
|
|
|
|
=over 4
|
|
|
|
=item get_next_root
|
|
|
|
Like C<next_root>.
|
|
|
|
=item successor
|
|
|
|
Identical to having C<tree_edge> both C<non_tree_edge> defined
|
|
to be the same.
|
|
|
|
=item unseen_successor
|
|
|
|
Like C<tree_edge>.
|
|
|
|
=item seen_successor
|
|
|
|
Like C<seed_edge>.
|
|
|
|
=back
|
|
|
|
=head2 Special callbacks
|
|
|
|
If in a callback you call the special C<terminate> method,
|
|
the traversal is terminated, no more vertices are traversed.
|
|
|
|
=head1 SEE ALSO
|
|
|
|
L<Graph::Traversal::DFS>, L<Graph::Traversal::BFS>
|
|
|
|
=head1 AUTHOR AND COPYRIGHT
|
|
|
|
Jarkko Hietaniemi F<jhi@iki.fi>
|
|
|
|
=head1 LICENSE
|
|
|
|
This module is licensed under the same terms as Perl itself.
|
|
|
|
=cut
|