1 |
+ |
package Graph::Base; |
2 |
+ |
use strict; |
3 |
+ |
local $^W = 1; |
4 |
+ |
use vars qw(@ISA); |
5 |
+ |
require Exporter; |
6 |
+ |
@ISA = qw(Exporter); |
7 |
+ |
|
8 |
+ |
sub new |
9 |
+ |
{ |
10 |
+ |
my $class = shift; |
11 |
+ |
my $G = { }; |
12 |
+ |
|
13 |
+ |
bless $G, $class; |
14 |
+ |
|
15 |
+ |
$G->add_vertices(@_) if @_; |
16 |
+ |
|
17 |
+ |
return $G; |
18 |
+ |
} |
19 |
+ |
|
20 |
+ |
sub add_vertices |
21 |
+ |
{ |
22 |
+ |
my ($G, @v) = @_; |
23 |
+ |
@{ $G->{ V } }{ @v } = @v; |
24 |
+ |
|
25 |
+ |
return $G; |
26 |
+ |
} |
27 |
+ |
|
28 |
+ |
sub add_vertex |
29 |
+ |
{ |
30 |
+ |
my ($G, $v) = @_; |
31 |
+ |
return $G->add_vertices($v); |
32 |
+ |
} |
33 |
+ |
|
34 |
+ |
sub vertices |
35 |
+ |
{ |
36 |
+ |
my $G = shift; |
37 |
+ |
my @V = exists $G->{ V } ? sort values %{ $G->{ V } } : (); |
38 |
+ |
|
39 |
+ |
return @V; |
40 |
+ |
} |
41 |
+ |
|
42 |
+ |
sub has_vertex |
43 |
+ |
{ |
44 |
+ |
my ($G, $v) = @_; |
45 |
+ |
return exists $G->{V}->{ $v }; |
46 |
+ |
} |
47 |
+ |
|
48 |
+ |
sub vertex |
49 |
+ |
{ |
50 |
+ |
my ($G, $v) = @_; |
51 |
+ |
|
52 |
+ |
return $G->{ V }->{ $v }; |
53 |
+ |
} |
54 |
+ |
|
55 |
+ |
sub directed |
56 |
+ |
{ |
57 |
+ |
my ($G, $d) = @_; |
58 |
+ |
|
59 |
+ |
if (defined $d) |
60 |
+ |
{ |
61 |
+ |
if ($d) |
62 |
+ |
{ |
63 |
+ |
my $o = $G->{ D }; # Old directedness. |
64 |
+ |
|
65 |
+ |
$G->{ D } = $d; |
66 |
+ |
if (not $o) |
67 |
+ |
{ |
68 |
+ |
my @E = $G->edges; |
69 |
+ |
|
70 |
+ |
while (my ($u, $v) = splice(@E, 0, 2)) |
71 |
+ |
{ |
72 |
+ |
$G->add_edge($v, $u); |
73 |
+ |
} |
74 |
+ |
} |
75 |
+ |
|
76 |
+ |
return bless $G, 'Graph::Directed'; # Re-bless. |
77 |
+ |
} |
78 |
+ |
else |
79 |
+ |
{ |
80 |
+ |
return $G->undirected(not $d); |
81 |
+ |
} |
82 |
+ |
} |
83 |
+ |
|
84 |
+ |
return $G->{ D }; |
85 |
+ |
} |
86 |
+ |
|
87 |
+ |
sub undirected |
88 |
+ |
{ |
89 |
+ |
my ($G, $u) = @_; |
90 |
+ |
$G->{ D } = 1 unless defined $G->{ D }; |
91 |
+ |
|
92 |
+ |
if (defined $u) |
93 |
+ |
{ |
94 |
+ |
if ($u) |
95 |
+ |
{ |
96 |
+ |
my $o = $G->{ D }; # Old directedness. |
97 |
+ |
|
98 |
+ |
$G->{ D } = not $u; |
99 |
+ |
if ($o) |
100 |
+ |
{ |
101 |
+ |
my @E = $G->edges; |
102 |
+ |
my %E; |
103 |
+ |
|
104 |
+ |
while (my ($u, $v) = splice(@E, 0, 2)) |
105 |
+ |
{ |
106 |
+ |
# Throw away duplicate edges. |
107 |
+ |
$G->delete_edge($u, $v) if exists $E{$v}->{$u}; |
108 |
+ |
$E{$u}->{$v}++; |
109 |
+ |
} |
110 |
+ |
} |
111 |
+ |
|
112 |
+ |
return bless $G, 'Graph::Undirected'; # Re-bless. |
113 |
+ |
} |
114 |
+ |
else |
115 |
+ |
{ |
116 |
+ |
return $G->directed(not $u); |
117 |
+ |
} |
118 |
+ |
} |
119 |
+ |
|
120 |
+ |
return not $G->{ D }; |
121 |
+ |
} |
122 |
+ |
|
123 |
+ |
sub _union_vertex_set |
124 |
+ |
{ |
125 |
+ |
my ($G, $u, $v) = @_; |
126 |
+ |
|
127 |
+ |
my $su = $G->vertex_set( $u ); |
128 |
+ |
my $sv = $G->vertex_set( $v ); |
129 |
+ |
my $ru = $G->{ VertexSetRank }->{ $su }; |
130 |
+ |
my $rv = $G->{ VertexSetRank }->{ $sv }; |
131 |
+ |
|
132 |
+ |
if ( $ru < $rv ) |
133 |
+ |
{ # Union by rank (weight balancing). |
134 |
+ |
$G->{ VertexSetParent }->{ $su } = $sv; |
135 |
+ |
} |
136 |
+ |
else |
137 |
+ |
{ |
138 |
+ |
$G->{ VertexSetParent }->{ $sv } = $su; |
139 |
+ |
$G->{ VertexSetRank }->{ $sv }++ if $ru == $rv; |
140 |
+ |
} |
141 |
+ |
} |
142 |
+ |
|
143 |
+ |
sub vertex_set |
144 |
+ |
{ |
145 |
+ |
my ($G, $v) = @_; |
146 |
+ |
|
147 |
+ |
if ( exists $G->{ VertexSetParent }->{ $v } ) |
148 |
+ |
{ |
149 |
+ |
# Path compression. |
150 |
+ |
$G->{ VertexSetParent }->{ $v } = |
151 |
+ |
$G->vertex_set( $G->{ VertexSetParent }->{ $v } ) |
152 |
+ |
if $v ne $G->{ VertexSetParent }->{ $v }; |
153 |
+ |
} |
154 |
+ |
else |
155 |
+ |
{ |
156 |
+ |
$G->{ VertexSetParent }->{ $v } = $v; |
157 |
+ |
$G->{ VertexSetRank }->{ $v } = 0; |
158 |
+ |
} |
159 |
+ |
return $G->{ VertexSetParent }->{ $v }; |
160 |
+ |
} |
161 |
+ |
|
162 |
+ |
sub add_edge |
163 |
+ |
{ |
164 |
+ |
my ($G, $u, $v) = @_; |
165 |
+ |
|
166 |
+ |
$G->add_vertex($u); |
167 |
+ |
$G->add_vertex($v); |
168 |
+ |
$G->_union_vertex_set( $u, $v ); |
169 |
+ |
push @{ $G->{ Succ }->{ $u }->{ $v } }, $v; |
170 |
+ |
push @{ $G->{ Pred }->{ $v }->{ $u } }, $u; |
171 |
+ |
return $G; |
172 |
+ |
} |
173 |
+ |
|
174 |
+ |
sub _successors |
175 |
+ |
{ |
176 |
+ |
my ($G, $v) = @_; |
177 |
+ |
my @s = |
178 |
+ |
defined $G->{ Succ }->{ $v } ? |
179 |
+ |
map { @{ $G->{ Succ }->{ $v }->{ $_ } } } |
180 |
+ |
sort keys %{ $G->{ Succ }->{ $v } } : |
181 |
+ |
( ); |
182 |
+ |
|
183 |
+ |
return @s; |
184 |
+ |
} |
185 |
+ |
|
186 |
+ |
sub _predecessors |
187 |
+ |
{ |
188 |
+ |
my ($G, $v) = @_; |
189 |
+ |
my @p = |
190 |
+ |
defined $G->{ Pred }->{ $v } ? |
191 |
+ |
map { @{ $G->{ Pred }->{ $v }->{ $_ } } } |
192 |
+ |
sort keys %{ $G->{ Pred }->{ $v } } : |
193 |
+ |
( ); |
194 |
+ |
|
195 |
+ |
return @p; |
196 |
+ |
} |
197 |
+ |
|
198 |
+ |
sub neighbors |
199 |
+ |
{ |
200 |
+ |
my ($G, $v) = @_; |
201 |
+ |
my @n = ($G->_successors($v), $G->_predecessors($v)); |
202 |
+ |
return @n; |
203 |
+ |
} |
204 |
+ |
|
205 |
+ |
use vars '*neighbours'; |
206 |
+ |
*neighbours = \&neighbors; # Keep both sides of the Atlantic happy. |
207 |
+ |
|
208 |
+ |
sub successors |
209 |
+ |
{ |
210 |
+ |
my ($G, $v) = @_; |
211 |
+ |
return $G->directed ? $G->_successors($v) : $G->neighbors($v); |
212 |
+ |
} |
213 |
+ |
|
214 |
+ |
sub out_edges |
215 |
+ |
{ |
216 |
+ |
my ($G, $v) = @_; |
217 |
+ |
return () unless $G->has_vertex($v); |
218 |
+ |
|
219 |
+ |
my @e = $G->_edges($v, undef); |
220 |
+ |
|
221 |
+ |
return wantarray ? @e : @e / 2; |
222 |
+ |
} |
223 |
+ |
|
224 |
+ |
sub edges |
225 |
+ |
{ |
226 |
+ |
my ($G, $u, $v) = @_; |
227 |
+ |
return () if defined $v and not $G->has_vertex($v); |
228 |
+ |
|
229 |
+ |
my @e = |
230 |
+ |
defined $u ? |
231 |
+ |
( defined $v ? |
232 |
+ |
$G->_edges($u, $v) : |
233 |
+ |
($G->in_edges($u), $G->out_edges($u)) ) : |
234 |
+ |
$G->_edges; |
235 |
+ |
return wantarray ? @e : @e / 2; |
236 |
+ |
} |
237 |
+ |
|
238 |
+ |
sub delete_edge |
239 |
+ |
{ |
240 |
+ |
my ($G, $u, $v) = @_; |
241 |
+ |
pop @{ $G->{ Succ }->{ $u }->{ $v } }; |
242 |
+ |
pop @{ $G->{ Pred }->{ $v }->{ $u } }; |
243 |
+ |
|
244 |
+ |
delete $G->{ Succ }->{ $u }->{ $v } |
245 |
+ |
unless @{ $G->{ Succ }->{ $u }->{ $v } }; |
246 |
+ |
delete $G->{ Pred }->{ $v }->{ $u } |
247 |
+ |
unless @{ $G->{ Pred }->{ $v }->{ $u } }; |
248 |
+ |
|
249 |
+ |
delete $G->{ Succ }->{ $u } |
250 |
+ |
unless keys %{ $G->{ Succ }->{ $u } }; |
251 |
+ |
delete $G->{ Pred }->{ $v } |
252 |
+ |
unless keys %{ $G->{ Pred }->{ $v } }; |
253 |
+ |
|
254 |
+ |
return $G; |
255 |
+ |
} |
256 |
+ |
|
257 |
+ |
sub out_degree |
258 |
+ |
{ |
259 |
+ |
my ($G, $v) = @_; |
260 |
+ |
return undef unless $G->has_vertex($v); |
261 |
+ |
|
262 |
+ |
if ($G->directed) |
263 |
+ |
{ |
264 |
+ |
if (defined $v) |
265 |
+ |
{ |
266 |
+ |
return scalar $G->out_edges($v); |
267 |
+ |
} |
268 |
+ |
else |
269 |
+ |
{ |
270 |
+ |
my $out = 0; |
271 |
+ |
|
272 |
+ |
foreach my $v ($G->vertices) |
273 |
+ |
{ |
274 |
+ |
$out += $G->out_degree($v); |
275 |
+ |
} |
276 |
+ |
return $out; |
277 |
+ |
} |
278 |
+ |
} |
279 |
+ |
else |
280 |
+ |
{ |
281 |
+ |
return scalar $G->edges($v); |
282 |
+ |
} |
283 |
+ |
} |
284 |
+ |
|
285 |
+ |
sub copy |
286 |
+ |
{ |
287 |
+ |
my $G = shift; |
288 |
+ |
my $C = (ref $G)->new($G->vertices); |
289 |
+ |
|
290 |
+ |
if (my @E = $G->edges) |
291 |
+ |
{ |
292 |
+ |
while (my ($u, $v) = splice(@E, 0, 2)) |
293 |
+ |
{ |
294 |
+ |
$C->add_edge($u, $v); |
295 |
+ |
} |
296 |
+ |
} |
297 |
+ |
|
298 |
+ |
$C->directed($G->directed); |
299 |
+ |
|
300 |
+ |
return $C; |
301 |
+ |
} |
302 |
+ |
|
303 |
+ |
sub edge_classify |
304 |
+ |
{ |
305 |
+ |
my $G = shift; |
306 |
+ |
my $unseen_successor = |
307 |
+ |
sub { |
308 |
+ |
my ($u, $v, $T) = @_; |
309 |
+ |
# Freshly seen successors make for tree edges. |
310 |
+ |
push @{ $T->{ edge_class_list } }, |
311 |
+ |
[ $u, $v, 'tree' ]; |
312 |
+ |
}; |
313 |
+ |
my $seen_successor = |
314 |
+ |
sub { |
315 |
+ |
my ($u, $v, $T) = @_; |
316 |
+ |
|
317 |
+ |
my $class; |
318 |
+ |
|
319 |
+ |
if ( $T->{ G }->directed ) |
320 |
+ |
{ |
321 |
+ |
$class = 'cross'; # Default for directed non-tree edges. |
322 |
+ |
|
323 |
+ |
unless ( exists $T->{ vertex_finished }->{ $v } ) |
324 |
+ |
{ |
325 |
+ |
$class = 'back'; |
326 |
+ |
} |
327 |
+ |
elsif ( $T->{ vertex_found }->{ $u } < |
328 |
+ |
$T->{ vertex_found }->{ $v }) |
329 |
+ |
{ |
330 |
+ |
$class = 'forward'; |
331 |
+ |
} |
332 |
+ |
} |
333 |
+ |
else |
334 |
+ |
{ |
335 |
+ |
# No cross nor forward edges in |
336 |
+ |
# an undirected graph, by definition. |
337 |
+ |
$class = 'back'; |
338 |
+ |
} |
339 |
+ |
|
340 |
+ |
push @{ $T->{ edge_class_list } }, [ $u, $v, $class ]; |
341 |
+ |
}; |
342 |
+ |
use Graph::DFS; |
343 |
+ |
my $d = |
344 |
+ |
Graph::DFS-> |
345 |
+ |
new( $G, |
346 |
+ |
unseen_successor => $unseen_successor, |
347 |
+ |
seen_successor => $seen_successor, |
348 |
+ |
@_); |
349 |
+ |
|
350 |
+ |
$d->preorder; |
351 |
+ |
|
352 |
+ |
return @{ $d->{ edge_class_list } }; |
353 |
+ |
} |
354 |
+ |
|
355 |
+ |
sub toposort |
356 |
+ |
{ |
357 |
+ |
my $G = shift; |
358 |
+ |
my $d = Graph::DFS->new($G); |
359 |
+ |
$d->postorder; # That's it. |
360 |
+ |
} |
361 |
+ |
|
362 |
+ |
sub largest_out_degree |
363 |
+ |
{ |
364 |
+ |
my $G = shift; |
365 |
+ |
my @R = map { $_->[ 0 ] } # A Schwartzian Transform. |
366 |
+ |
sort { $b->[ 1 ] <=> $a->[ 1 ] || $a cmp $b } |
367 |
+ |
map { [ $_, $G->out_degree($_) ] } |
368 |
+ |
@_; |
369 |
+ |
|
370 |
+ |
return $R[ 0 ]; |
371 |
+ |
} |
372 |
+ |
|
373 |
+ |
1; |