File Coverage

blib/lib/Btrees.pm
Criterion Covered Total %
statement 90 107 84.1
branch 40 46 86.9
condition 20 27 74.0
subroutine 11 13 84.6
pod 0 13 0.0
total 161 206 78.1


line stmt bran cond sub pod time code
1              
2             package Btrees;
3             $VERSION=1.00;
4              
5             require 5.000;
6             require Exporter;
7              
8             =head1 NAME
9              
10             Btrees - Binary trees using the AVL balancing method.
11              
12             =head1 SYNOPSIS
13              
14             # yes, do USE the package ...
15             use Btrees;
16              
17             # no constructors
18              
19             # traverse a tree and invoke a function
20             traverse( $tree, $func );
21              
22             # find a node in a balanced tree
23             $node = bal_tree_find( $tree, $val $cmp );
24              
25             # add a node in a balanced tree, rebalancing if required
26             ($tree, $node) = bal_tree_add( $tree, $val, $cmp )
27              
28             # delete a node in a balanced tree, rebalancing if required
29             ($tree, $node) = bal_tree_del( $tree, $val , $cmp )
30              
31             =head1 DESCRIPTION
32              
33             Btrees uses the AVL balancing method, by G. M. Adelson-Velskii
34             and E.M. Landis. Bit scavenging, as done in low level languages like
35             C, is not used for height balancing since this is too expensive for
36             an interpreter. Instead the actual height of each subtree is stored
37             at each node. A null pointer has a height of zero. A leaf a height of
38             1. A nonleaf a height of 1 greater than the height of its two children.
39              
40             =head1 AUTHOR
41              
42             Ron Squiers (ron@broadcom.com). Adapted from "Mastering Algorithms with
43             Perl" by Jon Orwant, Jarkko Hietaniemi & John Macdonald. Copyright
44             1999 O'Reilly and Associates, Inc. All right reserved. ISBN: 1-56592-398-7
45              
46             =cut
47              
48             @ISA = qw(Exporter);
49             @EXPORT = qw( traverse bal_tree_find bal_tree_add bal_tree_del list );
50              
51             #########################################
52             #
53             # Method: list
54             #
55             # List $tree in order in turn
56             #
57             # list( $tree );
58             #
59             sub list {
60 0 0   0 0 0 my $tree = shift or return undef;
61              
62 0         0 local $max = $tree->{height};
63             sub List {
64 0     0 0 0 my $tree = shift;
65              
66 0   0     0 my $height = $tree->{height} || $max;
67 0         0 while( $max - $height ) { print " "; $height++; }
  0         0  
  0         0  
68 0         0 printf("0x%x\n", $tree->{val});
69             }
70 0         0 my $func = \&List;
71 0         0 traverse( $tree, $func );
72             }
73              
74             #########################################
75             #
76             # Method: traverse
77             #
78             # Traverse $tree in order, calling $func() for each element.
79             # in turn
80             # traverse( $tree, $func );
81             #
82             sub traverse {
83 161 100   161 0 387 my $tree = shift or return; # skip undef pointers
84 77         69 my $func = shift;
85              
86 77         137 traverse( $tree->{left}, $func );
87 77         137 &$func( $tree );
88 77         435 traverse( $tree->{right}, $func );
89             }
90              
91             #########################################
92             #
93             # Method: bal_tree_find
94             #
95             # Traverse $tree in order, calling $func() for each element.
96             # in turn
97             # $node = bal_tree_find( $tree, $val[, $cmp ] );
98             #
99             sub bal_tree_find {
100 88     88 0 1374 my( $tree, $val, $cmp) = @_;
101 88         90 my $result;
102              
103 88         179 while ( $tree ) {
104 273 100       733 my $relation = defined $cmp
105             ? $cmp->( $val, $tree->{val} )
106             : $val <=> $tree->{val};
107              
108             ### Stop when the desired node if found.
109 273 100       2705 return $tree if $relation == 0;
110              
111             ### Go down the correct subtree.
112 233 100       693 $tree = $relation < 0 ? $tree->{left} : $tree->{right};
113             }
114              
115             ### The desired node doesn't exist.
116 48         95 return undef;
117             }
118              
119             #########################################
120             #
121             # Method: bal_tree_add
122             #
123             # Search $tree looking for a node that has the value $val,
124             # add it if it does not already exist.
125             # If provided, $cmp compares values instead of <=>.
126             #
127             # ($tree, $node) = bal_tree_add( $tree, $val, $cmp )
128             # the return values:
129             # $tree points to the (possible new or changed) subtree that
130             # has resulted from the add operation.
131             # $node points to the (possibly new) node that contains $val
132             #
133             sub bal_tree_add {
134 366     366 0 1339 my( $tree, $val, $cmp) = @_;
135 366         364 my $result;
136              
137 366 100       718 unless ( $tree ) {
138 96         361 $result = {
139             left => undef,
140             right => undef,
141             val => $val,
142             height => 1
143             };
144 96         295 return( $result, $result );
145             }
146              
147 270 100       859 my $relation = defined $cmp
148             ? $cmp->( $val, $tree->{val} )
149             : $val <=> $tree->{val};
150              
151             ### Stop when the desired node if found.
152 270 100       1994 return ( $tree, $tree ) if $relation == 0;
153              
154             ### Add to the correct subtree.
155 269 100       395 if( $relation < 0 ) {
156 85         287 ($tree->{left}, $result) =
157             bal_tree_add ( $tree->{left}, $val, $cmp );
158             } else {
159 184         361 ($tree->{right}, $result) =
160             bal_tree_add ( $tree->{right}, $val, $cmp );
161             }
162              
163             ### Make sure that this level is balanced, return the
164             ### (possibly changed) top and the (possibly new) selected node.
165 269         489 return ( balance_tree( $tree ), $result );
166             }
167              
168             #########################################
169             #
170             # Method: bal_tree_del
171             #
172             # Search $tree looking for a node that has the value $val,
173             # and delete it if it does not already exist.
174             # If provided, $cmp compares values instead of <=>.
175             #
176             # ($tree, $node) = bal_tree_del( $tree, $val , $cmp )
177             #
178             # the return values:
179             # $tree points to the (possible empty or changed) subtree that
180             # has resulted from the delete operation.
181             # if found, $node points to the node that contains $val
182             # if not found, $node is undef
183             #
184             sub bal_tree_del {
185             # An empty (sub)tree does not contain the target.
186 39 100   39 0 191 my $tree = shift or return (undef,undef);
187              
188 38         44 my ($val, $cmp) = @_;
189 38         38 my $node;
190              
191 38 50       73 my $relation = defined $cmp
192             ? $cmp->( $val, $tree->{val} )
193             : $val <=> $tree->{val};
194              
195 38 100       55 if( $relation != 0 ) {
196             ### Not this node, go down the tree.
197 21 100       32 if( $relation < 0 ) {
198 17         39 ($tree->{left}, $node) =
199             bal_tree_del ( $tree->{left}, $val, $cmp );
200             } else {
201 4         18 ($tree->{right}, $node) =
202             bal_tree_del ( $tree->{right}, $val, $cmp );
203             }
204              
205             ### No balancing required if it wasn't found.
206 21 100       54 return ( $tree, undef ) unless $node;
207             } else {
208             # Must delete this node. Remember it to return it,
209 17         18 $node = $tree;
210              
211             # but splice the rest of the tree back together first
212 17         35 $tree = bal_tree_join( $tree->{left}, $tree->{right} );
213              
214             # and make the deleted node forget its children (precaution
215             # in case the caller tries to use the node).
216 17         32 $node->{left} = $node->{right} = undef;
217             }
218              
219             ### Make sure that this level is balanced, return the
220             ### (possibly undef) selected node.
221 35         78 return ( balance_tree($tree), $node );
222             }
223              
224             #########################################
225             #
226             # Method: bal_tree_join
227             #
228             # Join two trees together into a single tree
229             #
230             # the return values:
231             # $tree points to the joined subtrees that has resulted from
232             # the join operation.
233             #
234             sub bal_tree_join {
235 17     17 0 19 my ($l, $r) = @_;
236              
237             ### Simple case - onr or both is null.
238 17 100       38 return $l unless defined $r;
239 7 50       19 return $r unless defined $l;
240              
241             ### Nope - we've got two real trees to merge here.
242 0         0 my $top;
243              
244 0 0       0 if ( $l->{height} > $r->{height} ) {
245 0         0 $top = $l;
246 0         0 $top->{right} = bal_tree_join( $top->{right}, $r );
247             } else {
248 0         0 $top = $r;
249 0         0 $top->{left} = bal_tree_join( $l, $top->{left} );
250             }
251 0         0 return balance_tree( $top );
252             }
253              
254             #########################################
255             #
256             # Method: balance_tree
257             #
258             # Balance a potentially out of balance tree
259             #
260             # the return values:
261             # $tree points to the balanced tree root
262             #
263             sub balance_tree {
264             ### An empty tree is balanced already.
265 304 100   304 0 993 my $tree = shift or return undef;
266              
267             ### An empty link is height 0.
268 294   66     1042 my $lh = defined $tree->{left} && $tree->{left}{height};
269 294   66     1003 my $rh = defined $tree->{right} && $tree->{right}{height};
270              
271             ### Rebalance if needed, return the (possibly changed) root.
272 294 100       653 if ( $lh > 1+$rh ) {
    100          
273 7         19 return swing_right( $tree );
274             } elsif ( $lh+1 < $rh ) {
275 40         73 return swing_left( $tree );
276             } else {
277             ### Tree is either perfectly balanced or off by one.
278             ### Just fix its height.
279 247         9396 set_height( $tree );
280 247         841 return $tree;
281             }
282             }
283              
284             #########################################
285             #
286             # Method: set_height
287             #
288             # Set height of a node
289             #
290             sub set_height {
291 357     357 0 443 my $tree = shift;
292              
293 357         328 my $p;
294             ### get heights, an undef node is height 0.
295 357   66     5064 my $lh = defined ( $p = $tree->{left} ) && $p->{height};
296 357   66     1143 my $rh = defined ( $p = $tree->{right} ) && $p->{height};
297 357 100       879 $tree->{height} = $lh < $rh ? $rh+1 : $lh+1;
298             }
299              
300             #########################################
301             #
302             # Method: $tree = swing_left( $tree )
303             #
304             # Change t to r or rl
305             # / \ / \ / \
306             # l r t rr t r
307             # / \ / \ / \ / \
308             # rl rr l rl l rll rlr rr
309             # / \ / \
310             # rll rlr rll rlr
311             #
312             # t and r must both exist.
313             # The second form is used if height of rl is greater than height of rr
314             # (since the form would then lead to the height of t at least 2 more
315             # than the height of rr).
316             #
317             # changing to the second form is done in two steps, with first a move_right(r)
318             # and then a move_left(t), so it goes:
319             #
320             # Change t to t and then to rl
321             # / \ / \ / \
322             # l r l rl t r
323             # / \ / \ / \ / \
324             # rl rr rll r l rll rlr rr
325             # / \ / \
326             # rll rlr rlr rr
327             #
328             sub swing_left {
329 40     40 0 47 my $tree = shift;
330              
331 40         61 my $r = $tree->{right}; # must exist
332 40         47 my $rl = $r->{left}; # might exist
333 40         45 my $rr = $r->{right}; # might exist
334 40         41 my $l = $tree->{left}; # might exist
335              
336             ### get heights, an undef node has height 0
337 40   100     164 my $lh = $l && $l->{height} || 0;
338 40   100     155 my $rlh = $rl && $rl->{height} || 0;
339 40   100     163 my $rrh = $rr && $rr->{height} || 0;
340              
341 40 100       81 if ( $rlh > $rrh ) {
342 4         9 $tree->{right} = move_right( $r );
343             }
344              
345 40         79 return move_left( $tree );
346             }
347              
348             # and the opposite swing
349              
350             sub swing_right {
351 7     7 0 8 my $tree = shift;
352              
353 7         19 my $l = $tree->{left}; # must exist
354 7         12 my $lr = $l->{right}; # might exist
355 7         9 my $ll = $l->{left}; # might exist
356 7         9 my $r = $tree->{right}; # might exist
357              
358             ### get heights, an undef node has height 0
359 7   100     34 my $rh = $r && $r->{height} || 0;
360 7   100     45 my $lrh = $lr && $lr->{height} || 0;
361 7   100     48 my $llh = $ll && $ll->{height} || 0;
362              
363 7 100       14 if ( $lrh > $llh ) {
364 4         12 $tree->{left} = move_left( $l );
365             }
366              
367 7         16 return move_right( $tree );
368             }
369              
370             #########################################
371             #
372             # Method: $tree = move_left( $tree )
373             #
374             # Change t to r
375             # / \ / \
376             # l r t rr
377             # / \ / \
378             # rl rr l rl
379             #
380             # caller has determined that t and r both exist
381             # (l can be undef, so can one of rl and rr)
382             #
383             sub move_left {
384 44     44 0 44 my $tree = shift;
385 44         75 my $r = $tree->{right};
386 44         49 my $rl = $r->{left};
387              
388 44         57 $tree->{right} = $rl;
389 44         50 $r->{left} = $tree;
390 44         73 set_height( $tree );
391 44         65 set_height( $r );
392 44         196 return $r;
393             }
394              
395             #########################################
396             #
397             # Method: $tree = move_right( $tree )
398             #
399             # Change t to l
400             # / \ / \
401             # l r ll t
402             # / \ / \
403             # ll lr lr r
404             #
405             # caller has determined that t and l both exist
406             # (r can be undef, so can one of ll and lr)
407             #
408             sub move_right {
409 11     11 0 13 my $tree = shift;
410 11         16 my $l = $tree->{left};
411 11         16 my $lr = $l->{right};
412              
413 11         14 $tree->{left} = $lr;
414 11         15 $l->{right} = $tree;
415 11         17 set_height( $tree );
416 11         20 set_height( $l );
417 11         34 return $l;
418             }
419              
420             #########################################
421             # That's all folks ...
422             #########################################
423             #
424             1; # so that use() returns true
425