File Coverage

blib/lib/Graph/Undirected/Hamiltonicity.pm
Criterion Covered Total %
statement 112 112 100.0
branch 29 32 90.6
condition 21 21 100.0
subroutine 10 10 100.0
pod 1 4 25.0
total 173 179 96.6


line stmt bran cond sub pod time code
1              
2             =encoding utf-8
3              
4             =head1 NAME
5              
6             Graph::Undirected::Hamiltonicity - decide whether a given Graph::Undirected
7             contains a Hamiltonian Cycle.
8              
9             =head1 VERSION
10              
11             Version 0.16
12              
13             =head1 LICENSE
14              
15             Copyright (C) Ashwin Dixit.
16              
17             This library is free software; you can redistribute it and/or modify
18             it under the same terms as Perl itself.
19              
20             =head1 AUTHOR
21              
22             Ashwin Dixit, C<< >>
23              
24             =cut
25              
26              
27 4     4   108519 use Modern::Perl;
  4         29  
  4         25  
28 4     4   2116 use lib 'local/lib/perl5';
  4         2386  
  4         22  
29              
30             package Graph::Undirected::Hamiltonicity;
31              
32             # ABSTRACT: decide whether a given Graph::Undirected contains a Hamiltonian Cycle.
33              
34             # You can get documentation for this module with this command:
35             # perldoc Graph::Undirected::Hamiltonicity
36              
37 4     4   2141 use Graph::Undirected::Hamiltonicity::Output qw(&output);
  4         9  
  4         581  
38 4     4   1834 use Graph::Undirected::Hamiltonicity::Tests qw(:all);
  4         12  
  4         694  
39 4     4   32 use Graph::Undirected::Hamiltonicity::Transforms qw(:all);
  4         9  
  4         423  
40              
41 4     4   26 use Exporter qw(import);
  4         50  
  4         5396  
42              
43             our $VERSION = '0.16';
44             our @EXPORT = qw(graph_is_hamiltonian); # exported by default
45             our @EXPORT_OK = qw(graph_is_hamiltonian);
46             our %EXPORT_TAGS = ( all => \@EXPORT_OK );
47              
48             our $calls = 0; ### Number of calls to is_hamiltonian()
49              
50             ##########################################################################
51              
52             # graph_is_hamiltonian()
53             #
54             # Takes a Graph::Undirected object.
55             #
56             # Returns
57             # 1 if the given graph contains a Hamiltonian Cycle.
58             # 0 otherwise.
59             #
60              
61             sub graph_is_hamiltonian {
62 40     40 1 56938 my ($g) = @_;
63              
64 40         88 $calls = 0;
65 40         90 my ( $is_hamiltonian, $reason );
66 40         96 my $time_begin = time;
67 40         159 my @once_only_tests = ( \&test_trivial, \&test_dirac );
68 40         112 foreach my $test_sub (@once_only_tests) {
69 76         291 ( $is_hamiltonian, $reason ) = &$test_sub($g);
70 76 100       274 last unless $is_hamiltonian == $DONT_KNOW;
71             }
72              
73 40         178 my $params = {
74             transformed => 0,
75             tentative => 0,
76             };
77              
78 40 100       126 if ( $is_hamiltonian == $DONT_KNOW ) {
79 35         148 ( $is_hamiltonian, $reason, $params ) = is_hamiltonian($g, $params);
80             } else {
81 5         18 my $spaced_string = $g->stringify();
82 5         9121 $spaced_string =~ s/\,/, /g;
83 5         17 output("
");
84 5         19 output("In graph_is_hamiltonian($spaced_string)");
85 5         11 output($g);
86             }
87 40         138 my $time_end = time;
88              
89 40         162 $params->{time_elapsed} = int($time_end - $time_begin);
90 40         120 $params->{calls} = $calls;
91              
92 40 100       133 my $final_bit = ( $is_hamiltonian == $GRAPH_IS_HAMILTONIAN ) ? 1 : 0;
93 40 50       236 return wantarray ? ( $final_bit, $reason, $params ) : $final_bit;
94             }
95              
96             ##########################################################################
97              
98             # is_hamiltonian()
99             #
100             # Takes a Graph::Undirected object.
101             #
102             # Returns a result ( $is_hamiltonian, $reason )
103             # indicating whether the given graph contains a Hamiltonian Cycle.
104             #
105             #
106              
107             sub is_hamiltonian {
108 215     215 0 686 my ($g, $params) = @_;
109 215         441 $calls++;
110              
111 215         878 my $spaced_string = $g->stringify();
112 215         180637 $spaced_string =~ s/\,/, /g;
113 215         990 output("
");
114 215         947 output("Calling is_hamiltonian($spaced_string)");
115 215         692 output($g);
116              
117 215         443 my ( $is_hamiltonian, $reason );
118 215         1026 my @tests_1 = (
119             \&test_ore,
120             \&test_min_degree,
121             \&test_articulation_vertex,
122             \&test_graph_bridge,
123             );
124              
125 215         584 foreach my $test_sub (@tests_1) {
126 844         3051 ( $is_hamiltonian, $reason ) = &$test_sub($g, $params);
127 844 100       336620 return ( $is_hamiltonian, $reason, $params )
128             unless $is_hamiltonian == $DONT_KNOW;
129             }
130              
131             ### Create a graph made of only required edges.
132 204         377 my $required_graph;
133 204         970 ( $required_graph, $g ) = get_required_graph($g);
134              
135 204 100       626 if ( $required_graph->edges() ) {
136 194         5924 my @tests_2 = (
137             \&test_required_max_degree,
138             \&test_required_connected,
139             \&test_required_cyclic );
140 194         484 foreach my $test_sub (@tests_2) {
141 488         1946 ( $is_hamiltonian, $reason, $params ) = &$test_sub($required_graph, $g, $params);
142 488 100       6758 return ( $is_hamiltonian, $reason, $params )
143             unless $is_hamiltonian == $DONT_KNOW;
144             }
145              
146             ### Delete edges that can be safely eliminated so far.
147 116         716 my ( $deleted_edges , $g1 ) = delete_cycle_closing_edges($g, $required_graph);
148 116         522 my ( $deleted_edges2, $g2 ) = delete_non_required_neighbors($g1, $required_graph);
149 116 100 100     735 if ($deleted_edges || $deleted_edges2) {
150 73         329 $params->{transformed} = 1;
151 73         1209 @_ = ($g2, $params);
152 73         5074 goto &is_hamiltonian;
153             }
154             }
155              
156             ### If there are undecided vrtices, choose between them recursively.
157 53         425 my @undecided_vertices = grep { $g->degree($_) > 2 } $g->vertices();
  689         167087  
158 53 50       13959 if (@undecided_vertices) {
159 53 50       227 unless ( $params->{tentative} ) {
160 53         268 output( "Now running an exhaustive, recursive,"
161             . " and conclusive search,"
162             . " only slightly better than brute force.
" );
163             }
164              
165 53         318 my $vertex =
166             get_chosen_vertex( $g, $required_graph, \@undecided_vertices );
167              
168 53         217 my $tentative_combinations =
169             get_tentative_combinations( $g, $required_graph, $vertex );
170              
171 53         170 foreach my $tentative_edge_pair (@$tentative_combinations) {
172 107         430 my $g1 = $g->deep_copy_graph();
173             output("For vertex: $vertex, protecting " .
174 107         134428 ( join ',', map {"$vertex=$_"} @$tentative_edge_pair ) .
  214         1067  
175             "
" );
176 107         490 foreach my $neighbor ( $g1->neighbors($vertex) ) {
177 483 100       32611 next if $neighbor == $tentative_edge_pair->[0];
178 376 100       870 next if $neighbor == $tentative_edge_pair->[1];
179 269         1093 output("Deleting edge: $vertex=$neighbor
");
180 269         744 $g1->delete_edge( $vertex, $neighbor );
181             }
182              
183 107         5318 output( "The Graph with $vertex=" . $tentative_edge_pair->[0]
184             . ", $vertex=" . $tentative_edge_pair->[1]
185             . " protected:
" );
186 107         317 output($g1);
187              
188 107         282 $params->{tentative} = 1;
189 107         465 ( $is_hamiltonian, $reason, $params ) = is_hamiltonian($g1, $params);
190 107 100       404 if ( $is_hamiltonian == $GRAPH_IS_HAMILTONIAN ) {
191 39         5303 return ( $is_hamiltonian, $reason, $params );
192             }
193 68         266 output("...backtracking.
");
194             }
195             }
196              
197 14         783 return ( $GRAPH_IS_NOT_HAMILTONIAN,
198             "The graph passed through an exhaustive search " .
199             "for Hamiltonian Cycles.", $params );
200              
201             }
202              
203             ##########################################################################
204              
205             sub get_tentative_combinations {
206              
207             # Generate all allowable combinations of 2 edges,
208             # incident on a given vertex.
209              
210 53     53 0 164 my ( $g, $required_graph, $vertex ) = @_;
211 53         102 my @tentative_combinations;
212 53         206 my @neighbors = sort { $a <=> $b } $g->neighbors($vertex);
  355         4669  
213 53 100       269 if ( $required_graph->degree($vertex) == 1 ) {
214 26         4747 my ($fixed_neighbor) = $required_graph->neighbors($vertex);
215 26         1687 foreach my $tentative_neighbor (@neighbors) {
216 102 100       228 next if $fixed_neighbor == $tentative_neighbor;
217 76         181 push @tentative_combinations,
218             [ $fixed_neighbor, $tentative_neighbor ];
219             }
220             } else {
221 27         3379 for ( my $i = 0; $i < scalar(@neighbors) - 1; $i++ ) {
222 115         263 for ( my $j = $i + 1; $j < scalar(@neighbors); $j++ ) {
223 334         943 push @tentative_combinations,
224             [ $neighbors[$i], $neighbors[$j] ];
225             }
226             }
227             }
228              
229 53         180 return \@tentative_combinations;
230             }
231              
232             ##########################################################################
233              
234             sub get_chosen_vertex {
235 53     53 0 178 my ( $g, $required_graph, $undecided_vertices ) = @_;
236              
237             # 1. Choose the vertex with the highest degree first.
238             #
239             # 2. If degrees are equal, prefer vertices which already have
240             # a required edge incident on them.
241             #
242             # 3. Break a tie from rules 1 & 2, by picking the lowest
243             # numbered vertex first.
244              
245 53         171 my $chosen_vertex;
246             my $chosen_vertex_degree;
247 53         0 my $chosen_vertex_required_degree;
248 53         154 foreach my $vertex (@$undecided_vertices) {
249 530         1158 my $degree = $g->degree($vertex);
250 530         146625 my $required_degree = $required_graph->degree($vertex);
251 530 100 100     75981 if ( ( !defined $chosen_vertex_degree )
      100        
      100        
      100        
      100        
      100        
252             or ( $degree > $chosen_vertex_degree )
253             or ( ( $degree == $chosen_vertex_degree )
254             and ( $required_degree > $chosen_vertex_required_degree ) )
255             or ( ( $degree == $chosen_vertex_degree )
256             and ( $required_degree == $chosen_vertex_required_degree )
257             and ( $vertex < $chosen_vertex ) )
258             )
259             {
260 135         289 $chosen_vertex = $vertex;
261 135         224 $chosen_vertex_degree = $degree;
262 135         254 $chosen_vertex_required_degree = $required_degree;
263             }
264             }
265              
266 53         195 return $chosen_vertex;
267             }
268              
269             ##########################################################################
270              
271             1; # End of Graph::Undirected::Hamiltonicity