1
-------------------------------------------------------------------------------
3
-- <STRONG>Copyright (c) 1999 - 2002 by Thomas Wolf.</STRONG>
5
-- AdaBrowse is free software; you can redistribute it and/or modify it
6
-- under the terms of the GNU General Public License as published by the
7
-- Free Software Foundation; either version 2, or (at your option) any
8
-- later version. AdaBrowse is distributed in the hope that it will be
9
-- useful, but <EM>without any warranty</EM>; without even the implied
10
-- warranty of <EM>merchantability or fitness for a particular purpose.</EM>
11
-- See the GNU General Public License for more details. You should have
12
-- received a copy of the GNU General Public License with this distribution,
13
-- see file "<A HREF="GPL.txt">GPL.txt</A>". If not, write to the Free
14
-- Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307,
18
-- As a special exception from the GPL, if other files instantiate generics
19
-- from this unit, or you link this unit with other files to produce an
20
-- executable, this unit does not by itself cause the resulting executable
21
-- to be covered by the GPL. This exception does not however invalidate any
22
-- other reasons why the executable file might be covered by the GPL.
26
-- Author:</STRONG><DD>
28
-- <ADDRESS><A HREF="mailto:twolf@acm.org">twolf@acm.org</A></ADDRESS></DL>
31
-- Purpose:</STRONG><DD>
32
-- Speed and space optimized quicksort. Actually, an <EM>introspective
33
-- quicksort</EM> with a <STRONG>worst-case</STRONG> runtime complexity of
34
-- <CODE>O (N * log2 (N))</CODE>.</DL>
37
-- Literature:</STRONG><DD>
38
-- Musser, D.R.: "Introspective Sorting and Selection Algorithms",
39
-- <EM>Software -- Practice & Experience (8):983-993</EM>; 1997.</DL>
42
-- Tasking semantics:</STRONG><DD>
43
-- N/A. Not abortion-safe.</DL>
46
-- Storage semantics:</STRONG><DD>
47
-- No dynamic storage allocation. Stack space used is
48
-- <CODE>O (log2 (N))</CODE>.</DL>
53
-- 21-JAN-1999 TW Initial version
54
-- 26-JAN-1999 TW Some minor fine-tuning.
56
-------------------------------------------------------------------------------
58
pragma License (Modified_GPL);
60
with Ada.Numerics.Elementary_Functions;
62
package body GAL.Sorting is
64
Cut_Off : constant := 10;
65
-- Empirically determined constant. For smaller arrays, it's actually
66
-- faster to use a simple insertion sort.
69
(Nof_Elements : in Natural)
72
-- Compute the recursion depth bound at which we'll switch to using
73
-- Heapsort instead of proceeding further down with Quicksort.
74
use Ada.Numerics.Elementary_Functions;
75
-- This seems to give us the fastest logarithm around...
77
return 2 * Natural (Log (Float (Nof_Elements), 2.0));
78
-- This is Musser's suggestion, which seems to work well in practice.
79
-- Note: it should not be too low, or we'll switch to using
80
-- Heapsort prematurely. Since Heapsort is slower than Quicksort
81
-- on the average (its constant factor in the O(N*log2(N)) is
82
-- larger), we might lose. If it's too high, we'll start using
83
-- Heapsort too late and hence we cannot ensure the O(N*log2(N))
87
----------------------------------------------------------------------------
88
-- A sort with a classic interface:
91
-- type Index_Type is (<>);
92
-- type Element_Type is private;
93
-- type Array_Type is array (Index_Type range <>) of Element_Type;
95
-- (Left, Right : in Element_Type) return Boolean is <>;
97
(To_Sort : in out Array_Type)
99
-- Recursion depth is bounded by log2 (To_Sort'Length).
100
-- Worst-case runtime complexity is O(N*log2(N)), not O(N**2)!
101
-- Note: for sensible performance measures, compile with a typical
102
-- optimization level and all checks off (e.g. gcc -O3 -gnatp). For
103
-- correctness testing, switch on checks!
105
pragma Suppress (Overflow_Check);
106
pragma Suppress (Index_Check);
107
pragma Suppress (Range_Check);
108
-- For correctness testing, you should also comment out these pragmas!
110
Pivot, Temp : Element_Type;
111
-- We need exactly two temporary locations, one for Quicksort's pivot
112
-- element, and a second one for swapping elements and as a general
113
-- temporary during Insertion_Sort or Heap_Sort.
115
procedure Swap (Left, Right : in Index_Type);
116
pragma Inline (Swap); -- inline this for increased performance.
118
procedure Swap (Left, Right : in Index_Type)
121
Temp := To_Sort (Left);
122
To_Sort (Left) := To_Sort (Right);
123
To_Sort (Right) := Temp;
126
-- For array slices with less than 'Cut_Off' elements, we use a simple
127
-- insertion sort: it's faster than quicksort.
129
procedure Insertion_Sort
130
(L, R : in Index_Type)
132
-- Precondition: L < R.
134
begin -- Insertion_Sort
135
for I in Index_Type range Index_Type'Succ (L) .. R loop
138
while J > L and then Temp < To_Sort (Index_Type'Pred (J)) loop
139
To_Sort (J) := To_Sort (Index_Type'Pred (J));
140
J := Index_Type'Pred (J);
146
-- If the (logical) recursion depth of quicksort gets too deep, we
147
-- assume that the input is one of the pathological cases causing
148
-- quadratic behavior of quicksort. At that moment, we switch to
149
-- heapsort to sort the sub-array. (This pays off because heap sort
150
-- has a worst-case runtime complexity of O(N*log2(N))).
153
(Left, Right : in Index_Type)
155
-- Precondition: Left < Right
158
-- Used for mapping the true index range to 1 .. N.
161
(L, R : in Index_Type)
163
-- Normal heapsort algorithms always sort an array indexed by a
164
-- range 1 .. N. We have to juggle a bit to map this back to a
165
-- range L .. R, and to avoid overflow if R happens to be
166
-- Index_Type'Base'Last.
171
while (Index_Type'Pos (C) - Offset + 1) <=
172
(Index_Type'Pos (R) - Offset + 1) / 2
175
((Index_Type'Pos (I) - Offset + 1) * 2 - 1 + Offset);
176
-- We must add 'Offset - 1'. Make sure to subtract one first,
177
-- otherwise we might get an overflow if I = 'Last and
180
C < R and then To_Sort (C) < To_Sort (Index_Type'Succ (C))
182
C := Index_Type'Succ (C);
184
exit when not (Temp < To_Sort (C));
185
To_Sort (I) := To_Sort (C);
194
-- Precondition: Left < Right
195
Offset := Index_Type'Pos (Left);
196
-- Set J to the middle:
198
((Index_Type'Pos (Right) - Offset + 1) / 2 + Offset - 1);
200
for I in reverse Index_Type range Left .. J loop
203
-- And now extract elements and re-build the heap.
206
-- Put the largest element (which is now in front) at the end and
207
-- replace it with the last element.
209
J := Index_Type'Pred (J);
211
-- Rebuild the remaining heap (one element less).
217
(L, R : in Index_Type;
220
-- This is a quicksort with median-of-three pivot selection and
221
-- stack depth optimization: the tail recusion is resolved by
222
-- always sorting the larger sub-array iteratively instead of
223
-- recursing. This limits the physical recursion depth to log2(N)
224
-- and thus avoids stack overflows even for pathological huge inputs.
225
-- Actually, this is an introspective sort (see Musser's paper).
226
-- It switches to heapsort if the logical recursion depth becomes
227
-- too deep and thus avoids quicksort's usual worst-case quadratic
230
Left : Index_Type := L;
231
Right : Index_Type := R;
232
-- L and R are in parameters, so copy.
233
Depth : Natural := D; -- Ditto for D.
234
I : Index_Type := Left;
235
J : Index_Type := Right;
239
while Index_Type'Pos (Right) - Index_Type'Pos (Left) > Cut_Off loop
240
-- Only proceed until we have a small unsorted array fragment
241
-- left. This fragment will then be sorted with Insertion_Sort,
242
-- which is faster than quicksort for small arrays.
243
if Depth = 0 then Heap_Sort (Left, Right); return; end if;
244
-- If the (logical) recursion depth gets too deep, we switch
245
-- to heapsort, which has a worst-case run-time complexity of
246
-- O(N*log(N)). This gives Intro_Sort an overall worst-case
247
-- bound of O(N*log(N)), compared with the O(N**2) bound of
248
-- plain quicksort (even with median-of-three).
250
-- Don't use (Left + Right) / 2; it might overflow. Instead use
251
-- Left + (Right - Left) / 2, which is equivalent.
254
(Index_Type'Pos (Left) +
255
(Index_Type'Pos (Right) - Index_Type'Pos (Left)) / 2);
257
if To_Sort (Middle) < To_Sort (Left) then
260
if To_Sort (Right) < To_Sort (Left) then
263
if To_Sort (Right) < To_Sort (Middle) then
264
Swap (Right, Middle);
266
Pivot := To_Sort (Middle);
267
-- Here, I = Left and J = Right
269
while To_Sort (I) < Pivot loop
270
I := Index_Type'Succ (I);
272
while Pivot < To_Sort (J) loop
273
J := Index_Type'Pred (J);
276
if I < J then Swap (I, J); end if;
277
-- Now increment I and decrement J..., but beware of
278
-- boundary conditions!
279
if I < Index_Type'Last then
280
I := Index_Type'Succ (I);
282
if J > Index_Type'First then
283
J := Index_Type'Pred (J);
285
-- I and J saturate at the bounds of Index_Type... this
286
-- doesn't hurt, we'll quit the loop in this case, and
287
-- continue only (both recusively and iteratively) if
288
-- this didn't happen.
291
-- Decrement the logical recursion depth. The next iteration will
292
-- hence be (correctly) seen like a true recursive call.
294
-- Now handle the shorter part recusively, and do the longer part
295
-- iteratively. This limits the (physical) recusion depth to
296
-- log2(N). Note: Musser advocates omitting this and just doing
297
-- one half recursively and the other one iteratively regardless
298
-- of their sizes, as the 'Depth' counter already puts an
299
-- O(log2(N)) bound on the maximum stack depth. The speed savings
300
-- are marginal, though, and I prefer a bound of log2(N) over one
302
if Index_Type'Pos (J) - Index_Type'Pos (Left) >
303
Index_Type'Pos (Right) - Index_Type'Pos (I)
305
-- Left part is longer. If I saturated, it is >= Right, and we
306
-- won't do the recursive call.
307
if I < Right then Intro_Sort (I, Right, Depth); end if;
308
-- Iteratively process To_Sort (Left .. J). Set up the indices:
309
I := Left; Right := J;
311
-- Right part is longer. If J saturated, it is <= Left, and we
312
-- won't do the recursive call.
313
if J > Left then Intro_Sort (Left, J, Depth); end if;
314
-- Iteratively process To_Sort (I .. Right). Set up the
316
J := Right; Left := I;
318
-- If either I or J saturated, Left >= Right now, and we'll
319
-- quit the outer loop.
321
if Left < Right then Insertion_Sort (Left, Right); end if;
322
-- Note: an alternative is to simply return and run a single
323
-- insertion sort over the whole input at the very end. However,
324
-- in my tests this made sorting slower. Nevertheless it should be
325
-- pointed out that some people advocate this single post-sorting
326
-- insertion sort, notably Musser in his paper.
330
if To_Sort'Last > To_Sort'First then
331
Intro_Sort (To_Sort'First, To_Sort'Last,
332
Max_Depth (To_Sort'Length));
336
----------------------------------------------------------------------------
337
-- The same with an access parameter and range bounds.
340
-- type Index_Type is (<>);
341
-- type Element_Type is private;
342
-- type Array_Type is array (Index_Type range <>) of Element_Type;
344
-- (Left, Right : in Element_Type) return Boolean is <>;
345
procedure Sort_Slice_G
346
(To_Sort : access Array_Type;
347
From, To : in Index_Type)
349
pragma Suppress (Overflow_Check);
350
pragma Suppress (Index_Check);
351
pragma Suppress (Range_Check);
353
Pivot, Temp : Element_Type;
356
(Table : access Array_Type;
357
Left, Right : in Index_Type);
358
pragma Inline (Swap); -- inline this for increased performance.
361
(Table : access Array_Type;
362
Left, Right : in Index_Type)
365
Temp := Table (Left);
366
Table (Left) := Table (Right);
367
Table (Right) := Temp;
370
procedure Insertion_Sort
371
(To_Sort : access Array_Type;
372
L, R : in Index_Type)
374
-- Precondition: L < R.
376
begin -- Insertion_Sort
377
for I in Index_Type range Index_Type'Succ (L) .. R loop
380
while J > L and then Temp < To_Sort (Index_Type'Pred (J)) loop
381
To_Sort (J) := To_Sort (Index_Type'Pred (J));
382
J := Index_Type'Pred (J);
389
(To_Sort : access Array_Type;
390
Left, Right : in Index_Type)
392
-- Precondition: Left < Right
397
(To_Sort : access Array_Type;
398
L, R : in Index_Type)
404
while (Index_Type'Pos (C) - Offset + 1) <=
405
(Index_Type'Pos (R) - Offset + 1) / 2
408
((Index_Type'Pos (I) - Offset + 1) * 2 - 1 + Offset);
410
C < R and then To_Sort (C) < To_Sort (Index_Type'Succ (C))
412
C := Index_Type'Succ (C);
414
exit when not (Temp < To_Sort (C));
415
To_Sort (I) := To_Sort (C);
424
-- Precondition: Left < Right
425
Offset := Index_Type'Pos (Left);
426
-- Set J to the middle:
428
((Index_Type'Pos (Right) - Offset + 1) / 2 + Offset - 1);
430
for I in reverse Index_Type range Left .. J loop
431
Sift (To_Sort, I, Right);
433
-- And now extract elements and re-build the heap.
436
-- Put the largest element (which is now in front) at the end and
437
-- replace it with the last element.
438
Swap (To_Sort, Left, J);
439
J := Index_Type'Pred (J);
441
-- Rebuild the remaining heap (one element less).
442
Sift (To_Sort, Left, J);
447
(To_Sort : access Array_Type;
448
L, R : in Index_Type;
452
Left : Index_Type := L;
453
Right : Index_Type := R;
454
-- L and R are in parameters, so copy.
455
Depth : Natural := D; -- Ditto for D.
456
I : Index_Type := Left;
457
J : Index_Type := Right;
461
while Index_Type'Pos (Right) - Index_Type'Pos (Left) > Cut_Off loop
462
if Depth = 0 then Heap_Sort (To_Sort, Left, Right); return; end if;
465
(Index_Type'Pos (Left) +
466
(Index_Type'Pos (Right) - Index_Type'Pos (Left)) / 2);
468
if To_Sort (Middle) < To_Sort (Left) then
469
Swap (To_Sort, Middle, Left);
471
if To_Sort (Right) < To_Sort (Left) then
472
Swap (To_Sort, Right, Left);
474
if To_Sort (Right) < To_Sort (Middle) then
475
Swap (To_Sort, Right, Middle);
477
Pivot := To_Sort (Middle);
478
-- Here, I = Left and J = Right
480
while To_Sort (I) < Pivot loop
481
I := Index_Type'Succ (I);
483
while Pivot < To_Sort (J) loop
484
J := Index_Type'Pred (J);
487
if I < J then Swap (To_Sort, I, J); end if;
488
-- Now increment I and decrement J..., but beware of
489
-- boundary conditions!
490
if I < Index_Type'Last then
491
I := Index_Type'Succ (I);
493
if J > Index_Type'First then
494
J := Index_Type'Pred (J);
499
if Index_Type'Pos (J) - Index_Type'Pos (Left) >
500
Index_Type'Pos (Right) - Index_Type'Pos (I)
502
if I < Right then Intro_Sort (To_Sort, I, Right, Depth); end if;
503
I := Left; Right := J;
505
if J > Left then Intro_Sort (To_Sort, Left, J, Depth); end if;
506
J := Right; Left := I;
508
-- If either I or J saturated, Left >= Right now, and we'll
509
-- quit the outer loop.
511
if Left < Right then Insertion_Sort (To_Sort, Left, Right); end if;
514
begin -- Sort_Slice_G
515
if To_Sort'Last > To_Sort'First and then From <= To then
516
if From < To_Sort'First or else To > To_Sort'Last then
517
-- If 'From' is > To_Sort'Last, then so is 'To'. And if 'To' is
518
-- < To_Sort'First, then so is 'From'. Hence the above condition
519
-- is sufficient to ensure that *both* indices are within range.
520
raise Constraint_Error;
524
Max_Depth (Index_Type'Pos (To) - Index_Type'Pos (From) + 1));
528
----------------------------------------------------------------------------
529
-- A very general sort that can be used to sort whatever you like. As
530
-- long as you can provide random access in constant time, this will
531
-- be a logarithmic sort. (It's an introspective quicksort, too.)
534
-- with function Is_Smaller (Left, Right : in Integer) return Boolean;
535
-- -- Shall return True if the element at index 'Left' is smaller than
536
-- -- the element at index 'Right' and Fasle otherwise.
537
-- with procedure Copy (To, From : in Integer);
538
-- -- Shall copy the element at index 'From' to position 'To'.
539
procedure Sort_Indexed_G
540
(Left, Right : in Natural)
542
pragma Suppress (Overflow_Check);
543
pragma Suppress (Index_Check);
544
pragma Suppress (Range_Check);
546
procedure Swap (L, R : in Natural);
547
pragma Inline (Swap); -- inline this for increased performance.
549
procedure Swap (L, R : in Natural)
552
Copy (-1, L); Copy (L, R); Copy (R, -1);
555
procedure Insertion_Sort
558
-- Precondition: L < R.
560
begin -- Insertion_Sort
561
for I in Natural range L + 1 .. R loop
564
while J > L and then Is_Smaller (-1, J - 1) loop
573
(Left, Right : in Positive)
575
-- Precondition: Left < Right
577
Offset : constant Integer := Left;
586
while (C - Offset + 1) <= (R - Offset + 1) / 2 loop
587
C := (I - Offset + 1) * 2 - 1 + Offset;
588
if C < R and then Is_Smaller (C, C + 1) then
591
exit when not Is_Smaller (-1, C);
601
-- Precondition: Left < Right
602
J := (Right - Offset + 1) / 2 + Offset - 1;
604
for I in reverse Positive range Left .. J loop
607
-- And now extract elements and re-build the heap.
610
-- Put the largest element (which is now in front) at the end and
611
-- replace it with the last element.
615
-- Rebuild the remaining heap (one element less).
624
Left : Natural := L; -- L and R are in parameters, so copy.
625
Right : Natural := R;
626
Depth : Natural := D; -- Ditto for D.
628
J : Natural := Right;
632
while Right - Left > Cut_Off loop
633
if Depth = 0 then Heap_Sort (Left, Right); return; end if;
634
Middle := Left + (Right - Left) / 2;
636
if Is_Smaller (Middle, Left) then Swap (Middle, Left); end if;
637
if Is_Smaller (Right, Left) then Swap (Right, Left); end if;
638
if Is_Smaller (Right, Middle) then Swap (Right, Middle); end if;
640
-- Here, I = Left and J = Right
642
while Is_Smaller (I, -2) loop I := I + 1; end loop;
643
while Is_Smaller (-2, J) loop J := J - 1; end loop;
645
if I < J then Swap (I, J); end if;
646
if I < Positive'Last then I := I + 1; end if;
647
if J > 1 then J := J - 1; end if;
652
Integer (J) - Integer (Left) > Integer (Right) - Integer (I)
654
-- Left part is longer.
655
if I < Right then Intro_Sort (I, Right, Depth); end if;
656
I := Left; Right := J;
658
-- Right part is longer.
659
if J > Left then Intro_Sort (Left, J, Depth); end if;
660
J := Right; Left := I;
663
if Left < Right then Insertion_Sort (Left, Right); end if;
666
begin -- Sort_Indexed_G
668
Intro_Sort (Left, Right, Max_Depth (Right - Left + 1));
672
----------------------------------------------------------------------------
673
-- Same as above, but using access-to-subroutines.
676
(Left, Right : in Natural;
677
Is_Smaller : in Comparator;
680
pragma Suppress (Overflow_Check);
681
pragma Suppress (Index_Check);
682
pragma Suppress (Range_Check);
683
pragma Suppress (Access_Check);
685
procedure Swap (L, R : in Natural);
686
pragma Inline (Swap); -- inline this for increased performance.
688
procedure Swap (L, R : in Natural)
691
Copy (-1, L); Copy (L, R); Copy (R, -1);
694
procedure Insertion_Sort
697
-- Precondition: L < R.
699
begin -- Insertion_Sort
700
for I in Natural range L + 1 .. R loop
703
while J > L and then Is_Smaller (-1, J - 1) loop
712
(Left, Right : in Natural)
714
-- Precondition: Left < Right
716
Offset : constant Integer := Left;
725
while (C - Offset + 1) <= (R - Offset + 1) / 2 loop
726
C := (I - Offset + 1) * 2 - 1 + Offset;
727
if C < R and then Is_Smaller (C, C + 1) then
730
exit when not Is_Smaller (-1, C);
740
-- Precondition: Left < Right
741
J := (Right - Offset + 1) / 2 + Offset - 1;
743
for I in reverse Positive range Left .. J loop
746
-- And now extract elements and re-build the heap.
749
-- Put the largest element (which is now in front) at the end and
750
-- replace it with the last element.
754
-- Rebuild the remaining heap (one element less).
763
Left : Natural := L; -- L and R are in parameters, so copy.
764
Right : Natural := R;
765
Depth : Natural := D; -- Ditto for D.
767
J : Natural := Right;
771
while Right - Left > Cut_Off loop
772
if Depth = 0 then Heap_Sort (Left, Right); return; end if;
773
Middle := Left + (Right - Left) / 2;
775
if Is_Smaller (Middle, Left) then Swap (Middle, Left); end if;
776
if Is_Smaller (Right, Left) then Swap (Right, Left); end if;
777
if Is_Smaller (Right, Middle) then Swap (Right, Middle); end if;
779
-- Here, I = Left and J = Right
781
while Is_Smaller (I, -2) loop I := I + 1; end loop;
782
while Is_Smaller (-2, J) loop J := J - 1; end loop;
784
if I < J then Swap (I, J); end if;
785
if I < Positive'Last then I := I + 1; end if;
786
if J > 1 then J := J - 1; end if;
791
Integer (J) - Integer (Left) > Integer (Right) - Integer (I)
793
-- Left part is longer.
794
if I < Right then Intro_Sort (I, Right, Depth); end if;
795
I := Left; Right := J;
797
-- Right part is longer.
798
if J > Left then Intro_Sort (Left, J, Depth); end if;
799
J := Right; Left := I;
802
if Left < Right then Insertion_Sort (Left, Right); end if;
807
-- We have suppressed access checks. Do it once and for all times
809
if Is_Smaller = null or else Copy = null then
810
raise Constraint_Error;
812
Intro_Sort (Left, Right, Max_Depth (Right - Left + 1));
816
----------------------------------------------------------------------------