—package
Graph::UnionFind;
use
strict;
use
warnings;
sub
_PARENT () { 0 }
sub
_RANK () { 1 }
sub
new {
my
$class
=
shift
;
bless
{ },
$class
;
}
sub
add {
my
(
$self
,
@elems
) =
@_
;
@elems
=
grep
!
defined
$self
->{
$_
},
@elems
;
@$self
{
@elems
} =
map
[
$_
, 0 ],
@elems
;
}
sub
_parent {
return
undef
unless
defined
$_
[1];
Graph::__carp_confess(__PACKAGE__ .
"::_parent: bad arity"
)
if
@_
< 2 or
@_
> 3;
if
(
@_
== 2) {
exists
$_
[0]->{
$_
[ 1 ] } ?
$_
[0]->{
$_
[1] }->[ _PARENT ] :
undef
;
}
else
{
$_
[0]->{
$_
[1] }->[ _PARENT ] =
$_
[2];
}
}
sub
_rank {
return
unless
defined
$_
[1];
Graph::__carp_confess(__PACKAGE__ .
"::_rank: bad arity"
)
if
@_
< 2 or
@_
> 3;
if
(
@_
== 2) {
exists
$_
[0]->{
$_
[1] } ?
$_
[0]->{
$_
[1] }->[ _RANK ] :
undef
;
}
else
{
$_
[0]->{
$_
[1] }->[ _RANK ] =
$_
[2];
}
}
sub
find {
my
(
$self
,
@v
) =
@_
;
my
@ret
;
for
my
$x
(
@v
) {
push
(
@ret
,
undef
),
next
unless
defined
(
my
$px
=
$self
->_parent(
$x
));
$self
->_parent(
$x
,
$self
->find(
$px
) )
if
$px
ne
$x
;
push
@ret
,
$self
->_parent(
$x
);
}
@ret
;
}
sub
union {
my
(
$self
,
@edges
) =
@_
;
$self
->add(
map
@$_
,
@edges
);
for
my
$e
(
@edges
) {
my
(
$px
,
$py
) =
$self
->find(
@$e
);
next
if
$px
eq
$py
;
my
$rx
=
$self
->_rank(
$px
);
my
$ry
=
$self
->_rank(
$py
);
# print "union($x, $y): px = $px, py = $py, rx = $rx, ry = $ry\n";
if
(
$rx
>
$ry
) {
$self
->_parent(
$py
,
$px
);
}
else
{
$self
->_parent(
$px
,
$py
);
$self
->_rank(
$py
,
$ry
+ 1 )
if
$rx
==
$ry
;
}
}
}
sub
same {
my
(
$uf
,
$u
,
$v
) =
@_
;
my
(
$fu
,
$fv
) =
$uf
->find(
$u
,
$v
);
return
undef
if
grep
!
defined
,
$fu
,
$fv
;
$fu
eq
$fv
;
}
1;
__END__
=pod
=head1 NAME
Graph::UnionFind - union-find data structures
=head1 SYNOPSIS
use Graph::UnionFind;
my $uf = Graph::UnionFind->new;
# Add the vertices to the data structure.
$uf->add($u);
$uf->add($v);
# Join the partitions of the vertices.
$uf->union( $u, $v );
# Find the partitions the vertices belong to
# in the union-find data structure. If they
# are equal, they are in the same partition.
# If the vertex has not been seen,
# undef is returned.
my $pu = $uf->find( $u );
my $pv = $uf->find( $v );
$uf->same($u, $v) # Equal to $pu eq $pv.
# Has the union-find seen this vertex?
$uf->has( $v )
=head1 DESCRIPTION
I<Union-find> is a special data structure that can be used to track the
partitioning of a set into subsets (a problem also known as I<disjoint sets>).
C<Graph::UnionFind> is used for L<Graph/connected_components>,
L<Graph/connected_component>, and L<Graph/same_connected_components>
if you specify a true C<unionfind> parameter when you create an undirected
graph.
Union-find is one way: you cannot (easily) 'ununion' vertices once you
have 'unioned' them. This is why L<Graph> throws an exception if you
try to delete edges from a union-find graph.
=head2 API
=over 4
=item add
$uf->add(@v)
Add the vertices to the union-find.
=item union
$uf->union([$u, $v], [$w, $x], ...)
Add the edge u-v to the union-find. Also implicitly adds the vertices.
=item find
@partitions = $uf->find(@v)
For each given vertex, return the union-find partition it belongs to,
or C<undef> if it has not been added.
=item new
$uf = Graph::UnionFind->new()
The constructor.
=item same
$uf->same($u, $v)
Return true of the vertices belong to the same union-find partition
the vertex v belongs to, false otherwise.
=back
=head1 AUTHOR AND COPYRIGHT
Jarkko Hietaniemi F<jhi@iki.fi>
=head1 LICENSE
This module is licensed under the same terms as Perl itself.
=cut