mirror of
https://github.com/netwide-assembler/nasm.git
synced 2025-09-22 10:43:39 -04:00
483 lines
9.9 KiB
Perl
483 lines
9.9 KiB
Perl
package Heap071::Fibonacci;
|
|
|
|
use strict;
|
|
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
|
|
|
|
require Exporter;
|
|
require AutoLoader;
|
|
|
|
@ISA = qw(Exporter AutoLoader);
|
|
|
|
# No names exported.
|
|
# No names available for export.
|
|
@EXPORT = ( );
|
|
|
|
$VERSION = '0.71';
|
|
|
|
|
|
# Preloaded methods go here.
|
|
|
|
# common names
|
|
# h - heap head
|
|
# el - linkable element, contains user-provided value
|
|
# v - user-provided value
|
|
|
|
################################################# debugging control
|
|
|
|
my $debug = 0;
|
|
my $validate = 0;
|
|
|
|
# enable/disable debugging output
|
|
sub debug {
|
|
@_ ? ($debug = shift) : $debug;
|
|
}
|
|
|
|
# enable/disable validation checks on values
|
|
sub validate {
|
|
@_ ? ($validate = shift) : $validate;
|
|
}
|
|
|
|
my $width = 3;
|
|
my $bar = ' | ';
|
|
my $corner = ' +-';
|
|
my $vfmt = "%3d";
|
|
|
|
sub set_width {
|
|
$width = shift;
|
|
$width = 2 if $width < 2;
|
|
|
|
$vfmt = "%${width}d";
|
|
$bar = $corner = ' ' x $width;
|
|
substr($bar,-2,1) = '|';
|
|
substr($corner,-2,2) = '+-';
|
|
}
|
|
|
|
sub hdump;
|
|
|
|
sub hdump {
|
|
my $el = shift;
|
|
my $l1 = shift;
|
|
my $b = shift;
|
|
|
|
my $ch;
|
|
my $ch1;
|
|
|
|
unless( $el ) {
|
|
print $l1, "\n";
|
|
return;
|
|
}
|
|
|
|
hdump $ch1 = $el->{child},
|
|
$l1 . sprintf( $vfmt, $el->{val}->val),
|
|
$b . $bar;
|
|
|
|
if( $ch1 ) {
|
|
for( $ch = $ch1->{right}; $ch != $ch1; $ch = $ch->{right} ) {
|
|
hdump $ch, $b . $corner, $b . $bar;
|
|
}
|
|
}
|
|
}
|
|
|
|
sub heapdump {
|
|
my $h;
|
|
|
|
while( $h = shift ) {
|
|
my $top = $$h or last;
|
|
my $el = $top;
|
|
|
|
do {
|
|
hdump $el, sprintf( "%02d: ", $el->{degree}), ' ';
|
|
$el = $el->{right};
|
|
} until $el == $top;
|
|
print "\n";
|
|
}
|
|
}
|
|
|
|
sub bhcheck;
|
|
|
|
sub bhcheck {
|
|
my $el = shift;
|
|
my $p = shift;
|
|
|
|
my $cur = $el;
|
|
my $prev;
|
|
my $ch;
|
|
do {
|
|
$prev = $cur;
|
|
$cur = $cur->{right};
|
|
die "bad back link" unless $cur->{left} == $prev;
|
|
die "bad parent link"
|
|
unless (defined $p && defined $cur->{p} && $cur->{p} == $p)
|
|
|| (!defined $p && !defined $cur->{p});
|
|
die "bad degree( $cur->{degree} > $p->{degree} )"
|
|
if $p && $p->{degree} <= $cur->{degree};
|
|
die "not heap ordered"
|
|
if $p && $p->{val}->cmp($cur->{val}) > 0;
|
|
$ch = $cur->{child} and bhcheck $ch, $cur;
|
|
} until $cur == $el;
|
|
}
|
|
|
|
|
|
sub heapcheck {
|
|
my $h;
|
|
my $el;
|
|
while( $h = shift ) {
|
|
heapdump $h if $validate >= 2;
|
|
$el = $$h and bhcheck $el, undef;
|
|
}
|
|
}
|
|
|
|
|
|
################################################# forward declarations
|
|
|
|
sub ascending_cut;
|
|
sub elem;
|
|
sub elem_DESTROY;
|
|
sub link_to_left_of;
|
|
|
|
################################################# heap methods
|
|
|
|
# Cormen et al. use two values for the heap, a pointer to an element in the
|
|
# list at the top, and a count of the number of elements. The count is only
|
|
# used to determine the size of array required to hold log(count) pointers,
|
|
# but perl can set array sizes as needed and doesn't need to know their size
|
|
# when they are created, so we're not maintaining that field.
|
|
sub new {
|
|
my $self = shift;
|
|
my $class = ref($self) || $self;
|
|
my $h = undef;
|
|
bless \$h, $class;
|
|
}
|
|
|
|
sub DESTROY {
|
|
my $h = shift;
|
|
|
|
elem_DESTROY $$h;
|
|
}
|
|
|
|
sub add {
|
|
my $h = shift;
|
|
my $v = shift;
|
|
$validate && do {
|
|
die "Method 'heap' required for element on heap"
|
|
unless $v->can('heap');
|
|
die "Method 'cmp' required for element on heap"
|
|
unless $v->can('cmp');
|
|
};
|
|
my $el = elem $v;
|
|
my $top;
|
|
if( !($top = $$h) ) {
|
|
$$h = $el;
|
|
} else {
|
|
link_to_left_of $top->{left}, $el ;
|
|
link_to_left_of $el,$top;
|
|
$$h = $el if $v->cmp($top->{val}) < 0;
|
|
}
|
|
}
|
|
|
|
sub top {
|
|
my $h = shift;
|
|
$$h && $$h->{val};
|
|
}
|
|
|
|
*minimum = \⊤
|
|
|
|
sub extract_top {
|
|
my $h = shift;
|
|
my $el = $$h or return undef;
|
|
my $ltop = $el->{left};
|
|
my $cur;
|
|
my $next;
|
|
|
|
# $el is the heap with the lowest value on it
|
|
# move all of $el's children (if any) to the top list (between
|
|
# $ltop and $el)
|
|
if( $cur = $el->{child} ) {
|
|
# remember the beginning of the list of children
|
|
my $first = $cur;
|
|
do {
|
|
# the children are moving to the top, clear the p
|
|
# pointer for all of them
|
|
$cur->{p} = undef;
|
|
} until ($cur = $cur->{right}) == $first;
|
|
|
|
# remember the end of the list
|
|
$cur = $cur->{left};
|
|
link_to_left_of $ltop, $first;
|
|
link_to_left_of $cur, $el;
|
|
}
|
|
|
|
if( $el->{right} == $el ) {
|
|
# $el had no siblings or children, the top only contains $el
|
|
# and $el is being removed
|
|
$$h = undef;
|
|
} else {
|
|
link_to_left_of $el->{left}, $$h = $el->{right};
|
|
# now all those loose ends have to be merged together as we
|
|
# search for the
|
|
# new smallest element
|
|
$h->consolidate;
|
|
}
|
|
|
|
# extract the actual value and return that, $el is no longer used
|
|
# but break all of its links so that it won't be pointed to...
|
|
my $top = $el->{val};
|
|
$top->heap(undef);
|
|
$el->{left} = $el->{right} = $el->{p} = $el->{child} = $el->{val} =
|
|
undef;
|
|
$top;
|
|
}
|
|
|
|
*extract_minimum = \&extract_top;
|
|
|
|
sub absorb {
|
|
my $h = shift;
|
|
my $h2 = shift;
|
|
|
|
my $el = $$h;
|
|
unless( $el ) {
|
|
$$h = $$h2;
|
|
$$h2 = undef;
|
|
return $h;
|
|
}
|
|
|
|
my $el2 = $$h2 or return $h;
|
|
|
|
# add $el2 and its siblings to the head list for $h
|
|
# at start, $ell -> $el -> ... -> $ell is on $h (where $ell is
|
|
# $el->{left})
|
|
# $el2l -> $el2 -> ... -> $el2l are on $h2
|
|
# at end, $ell -> $el2l -> ... -> $el2 -> $el -> ... -> $ell are
|
|
# all on $h
|
|
my $el2l = $el2->{left};
|
|
link_to_left_of $el->{left}, $el2;
|
|
link_to_left_of $el2l, $el;
|
|
|
|
# change the top link if needed
|
|
$$h = $el2 if $el->{val}->cmp( $el2->{val} ) > 0;
|
|
|
|
# clean out $h2
|
|
$$h2 = undef;
|
|
|
|
# return the heap
|
|
$h;
|
|
}
|
|
|
|
# a key has been decreased, it may have to percolate up in its heap
|
|
sub decrease_key {
|
|
my $h = shift;
|
|
my $top = $$h;
|
|
my $v = shift;
|
|
my $el = $v->heap or return undef;
|
|
my $p;
|
|
|
|
# first, link $h to $el if it is now the smallest (we will
|
|
# soon link $el to $top to properly put it up to the top list,
|
|
# if it isn't already there)
|
|
$$h = $el if $top->{val}->cmp( $v ) > 0;
|
|
|
|
if( $p = $el->{p} and $v->cmp($p->{val}) < 0 ) {
|
|
# remove $el from its parent's list - it is now smaller
|
|
|
|
ascending_cut $top, $p, $el;
|
|
}
|
|
|
|
$v;
|
|
}
|
|
|
|
|
|
# to delete an item, we bubble it to the top of its heap (as if its key
|
|
# had been decreased to -infinity), and then remove it (as in extract_top)
|
|
sub delete {
|
|
my $h = shift;
|
|
my $v = shift;
|
|
my $el = $v->heap or return undef;
|
|
|
|
# if there is a parent, cut $el to the top (as if it had just had its
|
|
# key decreased to a smaller value than $p's value
|
|
my $p;
|
|
$p = $el->{p} and ascending_cut $$h, $p, $el;
|
|
|
|
# $el is in the top list now, make it look like the smallest and
|
|
# remove it
|
|
$$h = $el;
|
|
$h->extract_top;
|
|
}
|
|
|
|
|
|
################################################# internal utility functions
|
|
|
|
sub elem {
|
|
my $v = shift;
|
|
my $el = undef;
|
|
$el = {
|
|
p => undef,
|
|
degree => 0,
|
|
mark => 0,
|
|
child => undef,
|
|
val => $v,
|
|
left => undef,
|
|
right => undef,
|
|
};
|
|
$el->{left} = $el->{right} = $el;
|
|
$v->heap($el);
|
|
$el;
|
|
}
|
|
|
|
sub elem_DESTROY {
|
|
my $el = shift;
|
|
my $ch;
|
|
my $next;
|
|
$el->{left}->{right} = undef;
|
|
|
|
while( $el ) {
|
|
$ch = $el->{child} and elem_DESTROY $ch;
|
|
$next = $el->{right};
|
|
|
|
defined $el->{val} and $el->{val}->heap(undef);
|
|
$el->{child} = $el->{right} = $el->{left} = $el->{p} = $el->{val}
|
|
= undef;
|
|
$el = $next;
|
|
}
|
|
}
|
|
|
|
sub link_to_left_of {
|
|
my $l = shift;
|
|
my $r = shift;
|
|
|
|
$l->{right} = $r;
|
|
$r->{left} = $l;
|
|
}
|
|
|
|
sub link_as_parent_of {
|
|
my $p = shift;
|
|
my $c = shift;
|
|
|
|
my $pc;
|
|
|
|
if( $pc = $p->{child} ) {
|
|
link_to_left_of $pc->{left}, $c;
|
|
link_to_left_of $c, $pc;
|
|
} else {
|
|
link_to_left_of $c, $c;
|
|
}
|
|
$p->{child} = $c;
|
|
$c->{p} = $p;
|
|
$p->{degree}++;
|
|
$c->{mark} = 0;
|
|
$p;
|
|
}
|
|
|
|
sub consolidate {
|
|
my $h = shift;
|
|
|
|
my $cur;
|
|
my $this;
|
|
my $next = $$h;
|
|
my $last = $next->{left};
|
|
my @a;
|
|
do {
|
|
# examine next item on top list
|
|
$this = $cur = $next;
|
|
$next = $cur->{right};
|
|
my $d = $cur->{degree};
|
|
my $alt;
|
|
while( $alt = $a[$d] ) {
|
|
# we already saw another item of the same degree,
|
|
# put the larger valued one under the smaller valued
|
|
# one - switch $cur and $alt if necessary so that $cur
|
|
# is the smaller
|
|
($cur,$alt) = ($alt,$cur)
|
|
if $cur->{val}->cmp( $alt->{val} ) > 0;
|
|
# remove $alt from the top list
|
|
link_to_left_of $alt->{left}, $alt->{right};
|
|
# and put it under $cur
|
|
link_as_parent_of $cur, $alt;
|
|
# make sure that $h still points to a node at the top
|
|
$$h = $cur;
|
|
# we've removed the old $d degree entry
|
|
$a[$d] = undef;
|
|
# and we now have a $d+1 degree entry to try to insert
|
|
# into @a
|
|
++$d;
|
|
}
|
|
# found a previously unused degree
|
|
$a[$d] = $cur;
|
|
} until $this == $last;
|
|
$cur = $$h;
|
|
for $cur (grep defined, @a) {
|
|
$$h = $cur if $$h->{val}->cmp( $cur->{val} ) > 0;
|
|
}
|
|
}
|
|
|
|
sub ascending_cut {
|
|
my $top = shift;
|
|
my $p = shift;
|
|
my $el = shift;
|
|
|
|
while( 1 ) {
|
|
if( --$p->{degree} ) {
|
|
# there are still other children below $p
|
|
my $l = $el->{left};
|
|
$p->{child} = $l;
|
|
link_to_left_of $l, $el->{right};
|
|
} else {
|
|
# $el was the only child of $p
|
|
$p->{child} = undef;
|
|
}
|
|
link_to_left_of $top->{left}, $el;
|
|
link_to_left_of $el, $top;
|
|
$el->{p} = undef;
|
|
$el->{mark} = 0;
|
|
|
|
# propagate up the list
|
|
$el = $p;
|
|
|
|
# quit at the top
|
|
last unless $p = $el->{p};
|
|
|
|
# quit if we can mark $el
|
|
$el->{mark} = 1, last unless $el->{mark};
|
|
}
|
|
}
|
|
|
|
|
|
1;
|
|
|
|
__END__
|
|
|
|
=head1 NAME
|
|
|
|
Heap::Fibonacci - a Perl extension for keeping data partially sorted
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
use Heap::Fibonacci;
|
|
|
|
$heap = Heap::Fibonacci->new;
|
|
# see Heap(3) for usage
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
Keeps elements in heap order using a linked list of Fibonacci trees.
|
|
The I<heap> method of an element is used to store a reference to
|
|
the node in the list that refers to the element.
|
|
|
|
See L<Heap> for details on using this module.
|
|
|
|
=head1 AUTHOR
|
|
|
|
John Macdonald, jmm@perlwolf.com
|
|
|
|
=head1 COPYRIGHT
|
|
|
|
Copyright 1998-2003, O'Reilly & Associates.
|
|
|
|
This code is distributed under the same copyright terms as perl itself.
|
|
|
|
=head1 SEE ALSO
|
|
|
|
Heap(3), Heap::Elem(3).
|
|
|
|
=cut
|