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.17
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   107789 use Modern::Perl;
  4         29  
  4         24  
28 4     4   2216 use lib 'local/lib/perl5';
  4         2519  
  4         21  
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   2158 use Graph::Undirected::Hamiltonicity::Output qw(&output);
  4         11  
  4         602  
38 4     4   1884 use Graph::Undirected::Hamiltonicity::Tests qw(:all);
  4         15  
  4         732  
39 4     4   31 use Graph::Undirected::Hamiltonicity::Transforms qw(:all);
  4         9  
  4         438  
40              
41 4     4   25 use Exporter qw(import);
  4         50  
  4         5344  
42              
43             our $VERSION = '0.17';
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 55760 my ($g) = @_;
63              
64 40         100 $calls = 0;
65 40         96 my ( $is_hamiltonian, $reason );
66 40         101 my $time_begin = time;
67 40         169 my @once_only_tests = ( \&test_trivial, \&test_dirac );
68 40         102 foreach my $test_sub (@once_only_tests) {
69 76         294 ( $is_hamiltonian, $reason ) = &$test_sub($g);
70 76 100       261 last unless $is_hamiltonian == $DONT_KNOW;
71             }
72              
73 40         188 my $params = {
74             transformed => 0,
75             tentative => 0,
76             };
77              
78 40 100       143 if ( $is_hamiltonian == $DONT_KNOW ) {
79 33         125 ( $is_hamiltonian, $reason, $params ) = is_hamiltonian($g, $params);
80             } else {
81 7         39 my $spaced_string = $g->stringify();
82 7         13022 $spaced_string =~ s/\,/, /g;
83 7         43 output("
");
84 7         34 output("In graph_is_hamiltonian($spaced_string)");
85 7         18 output($g);
86             }
87 40         135 my $time_end = time;
88              
89 40         152 $params->{time_elapsed} = int($time_end - $time_begin);
90 40         119 $params->{calls} = $calls;
91              
92 40 100       154 my $final_bit = ( $is_hamiltonian == $GRAPH_IS_HAMILTONIAN ) ? 1 : 0;
93 40 50       281 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 200     200 0 530 my ($g, $params) = @_;
109 200         458 $calls++;
110              
111 200         803 my $spaced_string = $g->stringify();
112 200         162518 $spaced_string =~ s/\,/, /g;
113 200         791 output("
");
114 200         881 output("Calling is_hamiltonian($spaced_string)");
115 200         625 output($g);
116              
117 200         440 my ( $is_hamiltonian, $reason );
118 200         878 my @tests_1 = (
119             \&test_ore,
120             \&test_min_degree,
121             \&test_articulation_vertex,
122             \&test_graph_bridge,
123             );
124              
125 200         526 foreach my $test_sub (@tests_1) {
126 795         2664 ( $is_hamiltonian, $reason ) = &$test_sub($g, $params);
127 795 100       312306 return ( $is_hamiltonian, $reason, $params )
128             unless $is_hamiltonian == $DONT_KNOW;
129             }
130              
131             ### Create a graph made of only required edges.
132 197         364 my $required_graph;
133 197         996 ( $required_graph, $g ) = get_required_graph($g);
134              
135 197 100       644 if ( $required_graph->edges() ) {
136 187         5754 my @tests_2 = (
137             \&test_required_max_degree,
138             \&test_required_connected,
139             \&test_required_cyclic );
140 187         436 foreach my $test_sub (@tests_2) {
141 481         1738 ( $is_hamiltonian, $reason, $params ) = &$test_sub($required_graph, $g, $params);
142 481 100       6259 return ( $is_hamiltonian, $reason, $params )
143             unless $is_hamiltonian == $DONT_KNOW;
144             }
145              
146             ### Delete edges that can be safely eliminated so far.
147 118         683 my ( $deleted_edges , $g1 ) = delete_cycle_closing_edges($g, $required_graph);
148 118         534 my ( $deleted_edges2, $g2 ) = delete_non_required_neighbors($g1, $required_graph);
149 118 100 100     676 if ($deleted_edges || $deleted_edges2) {
150 73         257 $params->{transformed} = 1;
151 73         942 @_ = ($g2, $params);
152 73         4973 goto &is_hamiltonian;
153             }
154             }
155              
156             ### If there are undecided vrtices, choose between them recursively.
157 55         404 my @undecided_vertices = grep { $g->degree($_) > 2 } $g->vertices();
  698         166580  
158 55 50       14644 if (@undecided_vertices) {
159 55 50       231 unless ( $params->{tentative} ) {
160 55         293 output( "Now running an exhaustive, recursive,"
161             . " and conclusive search,"
162             . " only slightly better than brute force.
" );
163             }
164              
165 55         263 my $vertex =
166             get_chosen_vertex( $g, $required_graph, \@undecided_vertices );
167              
168 55         240 my $tentative_combinations =
169             get_tentative_combinations( $g, $required_graph, $vertex );
170              
171 55         164 foreach my $tentative_edge_pair (@$tentative_combinations) {
172 94         405 my $g1 = $g->deep_copy_graph();
173             output("For vertex: $vertex, protecting " .
174 94         118659 ( join ',', map {"$vertex=$_"} @$tentative_edge_pair ) .
  188         898  
175             "
" );
176 94         403 foreach my $neighbor ( $g1->neighbors($vertex) ) {
177 393 100       25167 next if $neighbor == $tentative_edge_pair->[0];
178 299 100       694 next if $neighbor == $tentative_edge_pair->[1];
179 205         773 output("Deleting edge: $vertex=$neighbor
");
180 205         582 $g1->delete_edge( $vertex, $neighbor );
181             }
182              
183 94         4816 output( "The Graph with $vertex=" . $tentative_edge_pair->[0]
184             . ", $vertex=" . $tentative_edge_pair->[1]
185             . " protected:
" );
186 94         277 output($g1);
187              
188 94         253 $params->{tentative} = 1;
189 94         367 ( $is_hamiltonian, $reason, $params ) = is_hamiltonian($g1, $params);
190 94 100       333 if ( $is_hamiltonian == $GRAPH_IS_HAMILTONIAN ) {
191 41         4459 return ( $is_hamiltonian, $reason, $params );
192             }
193 53         176 output("...backtracking.
");
194             }
195             }
196              
197 14         616 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 55     55 0 197 my ( $g, $required_graph, $vertex ) = @_;
211 55         116 my @tentative_combinations;
212 55         233 my @neighbors = sort { $a <=> $b } $g->neighbors($vertex);
  318         5180  
213 55 100       214 if ( $required_graph->degree($vertex) == 1 ) {
214 32         5940 my ($fixed_neighbor) = $required_graph->neighbors($vertex);
215 32         2026 foreach my $tentative_neighbor (@neighbors) {
216 127 100       282 next if $fixed_neighbor == $tentative_neighbor;
217 95         238 push @tentative_combinations,
218             [ $fixed_neighbor, $tentative_neighbor ];
219             }
220             } else {
221 23         2859 for ( my $i = 0; $i < scalar(@neighbors) - 1; $i++ ) {
222 91         222 for ( my $j = $i + 1; $j < scalar(@neighbors); $j++ ) {
223 246         858 push @tentative_combinations,
224             [ $neighbors[$i], $neighbors[$j] ];
225             }
226             }
227             }
228              
229 55         195 return \@tentative_combinations;
230             }
231              
232             ##########################################################################
233              
234             sub get_chosen_vertex {
235 55     55 0 151 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 55         169 my $chosen_vertex;
246             my $chosen_vertex_degree;
247 55         0 my $chosen_vertex_required_degree;
248 55         154 foreach my $vertex (@$undecided_vertices) {
249 528         1218 my $degree = $g->degree($vertex);
250 528         142779 my $required_degree = $required_graph->degree($vertex);
251 528 100 100     75686 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 126         229 $chosen_vertex = $vertex;
261 126         198 $chosen_vertex_degree = $degree;
262 126         221 $chosen_vertex_required_degree = $required_degree;
263             }
264             }
265              
266 55         208 return $chosen_vertex;
267             }
268              
269             ##########################################################################
270              
271             1; # End of Graph::Undirected::Hamiltonicity