2015-11-18 15:06:25 +00:00
|
|
|
/**
|
|
|
|
* @file heaps.yap
|
|
|
|
* @author R.A.O'Keefe, included as an YAP library by Vitor Santos Costa, 1999.
|
|
|
|
* @date 29 November 1983
|
|
|
|
*
|
|
|
|
* @brief Implement heaps in Prolog.
|
|
|
|
*
|
|
|
|
*
|
|
|
|
*/
|
2001-04-09 20:54:03 +01:00
|
|
|
|
2014-12-24 15:32:29 +00:00
|
|
|
:- module(heaps,[
|
|
|
|
add_to_heap/4, % Heap x Key x Datum -> Heap
|
|
|
|
get_from_heap/4, % Heap -> Key x Datum x Heap
|
|
|
|
empty_heap/1, % Heap
|
|
|
|
heap_size/2, % Heap -> Size
|
|
|
|
heap_to_list/2, % Heap -> List
|
|
|
|
list_to_heap/2, % List -> Heap
|
|
|
|
min_of_heap/3, % Heap -> Key x Datum
|
|
|
|
min_of_heap/5 % Heap -> (Key x Datum) x (Key x Datum)
|
|
|
|
]).
|
|
|
|
|
2014-09-11 20:06:57 +01:00
|
|
|
|
2015-11-18 15:06:25 +00:00
|
|
|
/** @defgroup heaps Heaps
|
2015-01-04 23:58:23 +00:00
|
|
|
@ingroup library
|
2014-09-11 20:06:57 +01:00
|
|
|
@{
|
|
|
|
|
|
|
|
A heap is a labelled binary tree where the key of each node is less than
|
|
|
|
or equal to the keys of its sons. The point of a heap is that we can
|
|
|
|
keep on adding new elements to the heap and we can keep on taking out
|
|
|
|
the minimum element. If there are N elements total, the total time is
|
|
|
|
O(NlgN). If you know all the elements in advance, you are better off
|
|
|
|
doing a merge-sort, but this file is for when you want to do say a
|
|
|
|
best-first search, and have no idea when you start how many elements
|
|
|
|
there will be, let alone what they are.
|
|
|
|
|
|
|
|
The following heap manipulation routines are available once included
|
|
|
|
with the `use_module(library(heaps))` command.
|
|
|
|
|
2015-11-18 15:06:25 +00:00
|
|
|
- add_to_heap/4
|
|
|
|
- empty_heap/1
|
|
|
|
- get_from_heap/4
|
|
|
|
- heap_size/2
|
|
|
|
- heap_to_list/2
|
|
|
|
- list_to_heap/2
|
|
|
|
- min_of_heap/3
|
|
|
|
- min_of_heap/5
|
|
|
|
|
|
|
|
|
|
|
|
A heap is a labelled binary tree where the key of each node is less
|
|
|
|
than or equal to the keys of its sons. The point of a heap is that
|
|
|
|
we can keep on adding new elements to the heap and we can keep on
|
|
|
|
taking out the minimum element. If there are N elements total, the
|
|
|
|
total time is O(NlgN). If you know all the elements in advance, you
|
|
|
|
are better off doing a merge-sort, but this file is for when you want
|
|
|
|
to do say a best-first search, and have no idea when you start how
|
|
|
|
many elements there will be, let alone what they are.
|
2001-04-09 20:54:03 +01:00
|
|
|
|
|
|
|
A heap is represented as a triple t(N, Free, Tree) where N is the
|
|
|
|
number of elements in the tree, Free is a list of integers which
|
|
|
|
specifies unused positions in the tree, and Tree is a tree made of
|
|
|
|
t terms for empty subtrees and
|
|
|
|
t(Key,Datum,Lson,Rson) terms for the rest
|
|
|
|
The nodes of the tree are notionally numbered like this:
|
|
|
|
1
|
|
|
|
2 3
|
|
|
|
4 6 5 7
|
|
|
|
8 12 10 14 9 13 11 15
|
|
|
|
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
|
|
|
|
The idea is that if the maximum number of elements that have been in
|
|
|
|
the heap so far is M, and the tree currently has K elements, the tree
|
|
|
|
is some subtreee of the tree of this form having exactly M elements,
|
|
|
|
and the Free list is a list of K-M integers saying which of the
|
|
|
|
positions in the M-element tree are currently unoccupied. This free
|
|
|
|
list is needed to ensure that the cost of passing N elements through
|
|
|
|
the heap is O(NlgM) instead of O(NlgN). For M say 100 and N say 10^4
|
|
|
|
this means a factor of two. The cost of the free list is slight.
|
|
|
|
The storage cost of a heap in a copying Prolog (which Dec-10 Prolog is
|
|
|
|
not) is 2K+3M words.
|
|
|
|
|
|
|
|
|
|
|
|
|
2014-09-11 20:06:57 +01:00
|
|
|
*/
|
2001-04-09 20:54:03 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
:- mode
|
|
|
|
add_to_heap(+, +, +, -),
|
|
|
|
add_to_heap(+, +, +, +, -),
|
|
|
|
add_to_heap(+, +, +, +, +, +, -, -),
|
|
|
|
sort2(+, +, +, +, -, -, -, -),
|
|
|
|
get_from_heap(+, ?, ?, -),
|
|
|
|
repair_heap(+, +, +, -),
|
|
|
|
heap_size(+, ?),
|
|
|
|
heap_to_list(+, -),
|
|
|
|
heap_tree_to_list(+, -),
|
|
|
|
heap_tree_to_list(+, +, -),
|
|
|
|
list_to_heap(+, -),
|
|
|
|
list_to_heap(+, +, +, -),
|
|
|
|
min_of_heap(+, ?, ?),
|
|
|
|
min_of_heap(+, ?, ?, ?, ?),
|
|
|
|
min_of_heap(+, +, ?, ?).
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2014-12-24 15:32:29 +00:00
|
|
|
%% @pred add_to_heap(OldHeap, Key, Datum, NewHeap)
|
|
|
|
%
|
2001-04-09 20:54:03 +01:00
|
|
|
% inserts the new Key-Datum pair into the heap. The insertion is
|
|
|
|
% not stable, that is, if you insert several pairs with the same
|
|
|
|
% Key it is not defined which of them will come out first, and it
|
|
|
|
% is possible for any of them to come out first depending on the
|
|
|
|
% history of the heap. If you need a stable heap, you could add
|
|
|
|
% a counter to the heap and include the counter at the time of
|
|
|
|
% insertion in the key. If the free list is empty, the tree will
|
|
|
|
% be grown, otherwise one of the empty slots will be re-used. (I
|
|
|
|
% use imperative programming language, but the heap code is as
|
|
|
|
% pure as the trees code, you can create any number of variants
|
|
|
|
% starting from the same heap, and they will share what common
|
|
|
|
% structure they can without interfering with each other.)
|
|
|
|
|
|
|
|
add_to_heap(t(M,[],OldTree), Key, Datum, t(N,[],NewTree)) :- !,
|
|
|
|
N is M+1,
|
|
|
|
add_to_heap(N, Key, Datum, OldTree, NewTree).
|
|
|
|
add_to_heap(t(M,[H|T],OldTree), Key, Datum, t(N,T,NewTree)) :-
|
|
|
|
N is M+1,
|
|
|
|
add_to_heap(H, Key, Datum, OldTree, NewTree).
|
|
|
|
|
|
|
|
|
|
|
|
add_to_heap(1, Key, Datum, _, t(Key,Datum,t,t)) :- !.
|
|
|
|
add_to_heap(N, Key, Datum, t(K1,D1,L1,R1), t(K2,D2,L2,R2)) :-
|
|
|
|
E is N mod 2,
|
2002-01-08 03:47:18 +00:00
|
|
|
M is N//2,
|
2001-04-09 20:54:03 +01:00
|
|
|
% M > 0, % only called from list_to_heap/4,add_to_heap/4
|
|
|
|
sort2(Key, Datum, K1, D1, K2, D2, K3, D3),
|
|
|
|
add_to_heap(E, M, K3, D3, L1, R1, L2, R2).
|
|
|
|
|
|
|
|
|
|
|
|
add_to_heap(0, N, Key, Datum, L1, R, L2, R) :- !,
|
|
|
|
add_to_heap(N, Key, Datum, L1, L2).
|
|
|
|
add_to_heap(1, N, Key, Datum, L, R1, L, R2) :- !,
|
|
|
|
add_to_heap(N, Key, Datum, R1, R2).
|
|
|
|
|
|
|
|
|
|
|
|
sort2(Key1, Datum1, Key2, Datum2, Key1, Datum1, Key2, Datum2) :-
|
|
|
|
Key1 @< Key2,
|
|
|
|
!.
|
|
|
|
sort2(Key1, Datum1, Key2, Datum2, Key2, Datum2, Key1, Datum1).
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-11-18 15:06:25 +00:00
|
|
|
%% @pred @pred get_from_heap(+ _Heap_,- _key_,- _Datum_,- _Heap_)
|
2014-12-24 15:32:29 +00:00
|
|
|
%
|
2001-04-09 20:54:03 +01:00
|
|
|
% returns the Key-Datum pair in OldHeap with the smallest Key, and
|
|
|
|
% also a New Heap which is the Old Heap with that pair deleted.
|
|
|
|
% The easy part is picking off the smallest element. The hard part
|
|
|
|
% is repairing the heap structure. repair_heap/4 takes a pair of
|
|
|
|
% heaps and returns a new heap built from their elements, and the
|
|
|
|
% position number of the gap in the new tree. Note that repair_heap
|
|
|
|
% is *not* tail-recursive.
|
|
|
|
|
|
|
|
get_from_heap(t(N,Free,t(Key,Datum,L,R)), Key, Datum, t(M,[Hole|Free],Tree)) :-
|
|
|
|
M is N-1,
|
|
|
|
repair_heap(L, R, Tree, Hole).
|
|
|
|
|
|
|
|
|
|
|
|
repair_heap(t(K1,D1,L1,R1), t(K2,D2,L2,R2), t(K2,D2,t(K1,D1,L1,R1),R3), N) :-
|
|
|
|
K2 @< K1,
|
|
|
|
!,
|
|
|
|
repair_heap(L2, R2, R3, M),
|
|
|
|
N is 2*M+1.
|
|
|
|
repair_heap(t(K1,D1,L1,R1), t(K2,D2,L2,R2), t(K1,D1,L3,t(K2,D2,L2,R2)), N) :- !,
|
|
|
|
repair_heap(L1, R1, L3, M),
|
|
|
|
N is 2*M.
|
|
|
|
repair_heap(t(K1,D1,L1,R1), t, t(K1,D1,L3,t), N) :- !,
|
|
|
|
repair_heap(L1, R1, L3, M),
|
|
|
|
N is 2*M.
|
|
|
|
repair_heap(t, t(K2,D2,L2,R2), t(K2,D2,t,R3), N) :- !,
|
|
|
|
repair_heap(L2, R2, R3, M),
|
|
|
|
N is 2*M+1.
|
|
|
|
repair_heap(t, t, t, 1) :- !.
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-11-18 15:06:25 +00:00
|
|
|
%% @pred heap_size(+ _Heap_, - _Size_)
|
2014-12-24 15:32:29 +00:00
|
|
|
%
|
2001-04-09 20:54:03 +01:00
|
|
|
% reports the number of elements currently in the heap.
|
|
|
|
|
|
|
|
heap_size(t(Size,_,_), Size).
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-11-18 15:06:25 +00:00
|
|
|
%% @pred heap_to_list(+ _Heap_, - _List_)
|
2014-12-24 15:32:29 +00:00
|
|
|
%
|
2001-04-09 20:54:03 +01:00
|
|
|
% returns the current set of Key-Datum pairs in the Heap as a
|
|
|
|
% List, sorted into ascending order of Keys. This is included
|
|
|
|
% simply because I think every data structure foo ought to have
|
|
|
|
% a foo_to_list and list_to_foo relation (where, of course, it
|
|
|
|
% makes sense!) so that conversion between arbitrary data
|
|
|
|
% structures is as easy as possible. This predicate is basically
|
|
|
|
% just a merge sort, where we can exploit the fact that the tops
|
|
|
|
% of the subtrees are smaller than their descendants.
|
|
|
|
|
|
|
|
heap_to_list(t(_,_,Tree), List) :-
|
|
|
|
heap_tree_to_list(Tree, List).
|
|
|
|
|
|
|
|
|
|
|
|
heap_tree_to_list(t, []) :- !.
|
|
|
|
heap_tree_to_list(t(Key,Datum,Lson,Rson), [Key-Datum|Merged]) :-
|
|
|
|
heap_tree_to_list(Lson, Llist),
|
|
|
|
heap_tree_to_list(Rson, Rlist),
|
|
|
|
heap_tree_to_list(Llist, Rlist, Merged).
|
|
|
|
|
|
|
|
|
|
|
|
heap_tree_to_list([H1|T1], [H2|T2], [H2|T3]) :-
|
|
|
|
H2 @< H1,
|
|
|
|
!,
|
|
|
|
heap_tree_to_list([H1|T1], T2, T3).
|
|
|
|
heap_tree_to_list([H1|T1], T2, [H1|T3]) :- !,
|
|
|
|
heap_tree_to_list(T1, T2, T3).
|
|
|
|
heap_tree_to_list([], T, T) :- !.
|
|
|
|
heap_tree_to_list(T, [], T).
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-11-18 15:06:25 +00:00
|
|
|
%% @pred list_to_heap(+ _List_, - _Heap_)
|
2014-12-24 15:32:29 +00:00
|
|
|
%
|
2001-04-09 20:54:03 +01:00
|
|
|
% takes a list of Key-Datum pairs (such as keysort could be used to
|
|
|
|
% sort) and forms them into a heap. We could do that a wee bit
|
|
|
|
% faster by keysorting the list and building the tree directly, but
|
|
|
|
% this algorithm makes it obvious that the result is a heap, and
|
|
|
|
% could be adapted for use when the ordering predicate is not @<
|
|
|
|
% and hence keysort is inapplicable.
|
|
|
|
|
|
|
|
list_to_heap(List, Heap) :-
|
|
|
|
list_to_heap(List, 0, t, Heap).
|
|
|
|
|
|
|
|
|
|
|
|
list_to_heap([], N, Tree, t(N,[],Tree)) :- !.
|
|
|
|
list_to_heap([Key-Datum|Rest], M, OldTree, Heap) :-
|
|
|
|
N is M+1,
|
|
|
|
add_to_heap(N, Key, Datum, OldTree, MidTree),
|
|
|
|
list_to_heap(Rest, N, MidTree, Heap).
|
|
|
|
|
|
|
|
|
|
|
|
|
2014-12-24 15:32:29 +00:00
|
|
|
%% @pred min_of_heap(Heap, Key, Datum)
|
|
|
|
%
|
2001-04-09 20:54:03 +01:00
|
|
|
% returns the Key-Datum pair at the top of the heap (which is of
|
|
|
|
% course the pair with the smallest Key), but does not remove it
|
|
|
|
% from the heap. It fails if the heap is empty.
|
|
|
|
|
|
|
|
|
2015-11-18 15:06:25 +00:00
|
|
|
/** @pred min_of_heap(+ _Heap_, - _Key_, - _Datum_)
|
|
|
|
|
|
|
|
|
|
|
|
Returns the Key-Datum pair at the top of the heap (which is of course
|
|
|
|
the pair with the smallest Key), but does not remove it from the heap.
|
|
|
|
*/
|
2001-04-09 20:54:03 +01:00
|
|
|
min_of_heap(t(_,_,t(Key,Datum,_,_)), Key, Datum).
|
|
|
|
|
|
|
|
|
2015-12-15 09:28:43 +00:00
|
|
|
%% @pred @pred min_of_heap(+ _Heap_, - _Key1_, - _Datum1_, -_Key2_, - _Datum2_)
|
2015-11-18 15:06:25 +00:00
|
|
|
%
|
|
|
|
% returns the smallest (Key1) and second smallest (Key2) pairs in
|
|
|
|
% the heap, without deleting them. It fails if the heap does not
|
|
|
|
% have at least two elements.
|
2001-04-09 20:54:03 +01:00
|
|
|
min_of_heap(t(_,_,t(Key1,Datum1,Lson,Rson)), Key1, Datum1, Key2, Datum2) :-
|
|
|
|
min_of_heap(Lson, Rson, Key2, Datum2).
|
|
|
|
|
|
|
|
|
2002-01-10 21:42:29 +00:00
|
|
|
min_of_heap(t(Ka,_Da,_,_), t(Kb,Db,_,_), Kb, Db) :-
|
2001-04-09 20:54:03 +01:00
|
|
|
Kb @< Ka, !.
|
|
|
|
min_of_heap(t(Ka,Da,_,_), _, Ka, Da).
|
|
|
|
min_of_heap(t, t(Kb,Db,_,_), Kb, Db).
|
|
|
|
|
2015-11-18 15:06:25 +00:00
|
|
|
/** @pred empty_heap(? _Heap_)
|
|
|
|
|
|
|
|
|
|
|
|
Succeeds if _Heap_ is an empty heap.
|
|
|
|
*/
|
2002-01-08 03:47:18 +00:00
|
|
|
empty_heap(t(0,[],t)).
|
|
|
|
|
|
|
|
|
2017-04-07 23:10:59 +01:00
|
|
|
/** @} */
|
|
|
|
|