4 ms·
I assumed that we were talking about in place sorting. "I feel your pain." As I tried to explain, the first time I saw big-O notation taken seriously as the
by graycat 11y ago
I assumed that we were talking about
in place sorting.
"I feel your pain." As I tried
to explain, the first time I saw
big-O notation taken seriously as
the main metric for evaluating the
actual running time of
actual code on actual
data on an actual computer,
I had your reaction, that
the constants are important
and the big-O criterion, not good.
There's another point: The
silent assumption of the big-O criterion
is that we are interested in (A) something
fundamental about algorithms, just the
algorithms, and not (B) actual running time
of actual code on actual data on actual
computers.
And big progress in algorithms is also
important and, as we know, in some
cases can give much better performance
gains in real computing than anything
a programmer can do without the big progress.
And we have a great example: There
were days before heap sort, shell sort,
quicksort when the in place sorting
algorithm people coded were bubble sort
or something closely related and also O(n^2).
Then in practice, the difference was just
huge.
Early in my career, I ran into that at
Georgetown University: A prof had coded
up some software for teaching statistics
and had used the IBM Scientific Subroutine
Package (SSP) for a most of the actual
calculations. One of the SSP routines
was to find ranks, and it had two
loops, each essentially bubble sort.
In testing, the thing ran all lunch time.
Well, the data to be ranked was
32 bit integers, and an array index
was a 16 bit integer. So, I was
able to do a tricky overlay of
two 16 bit integers on the 32 bit
data, use heap sort, and get O( n ln(n) ).
In actually running time on nearly
any possible real data, my code
as so much faster than IBM's
SSP that I blew the doors off the
SSP.
Lesson 1: O( n ln(n) ) instead of O( n^2 )
can be much better in practice.
Lesson 2: Progress in big-O in algorithms
can be terrific stuff.
Back to practice, early in writing the
software for my startup, I tried to
write a polymorphic version of heap
sort. I wrote in Microsoft's Visual
Basic .NET. For the data type to
be sorted, I passed just the type
object. The software ran correctly
right away. Then I did some careful
timings -- the software commonly ran
ballpark 10 times longer than I
would have guessed. What the heck?
Well, the run time was smart enough
to figure out the actual data type
and do the comparison, but the overhead
here was enormous. Then I learned
that for polymorphic code I was
supposed to write some interfaces,
one for each data type to be sorted,
and for sorting a particular data
type pass the corresponding interface.
Okay. My confusion was that that use
of an interface didn't sound
polymorphic to me and sounded
no different than what I'd long
done passing entry variables
in PL/I and Fortran. Okay, for
polymorphism, use interfaces.
And in the code of the interface,
have to work essentially without
strong typing, essentially with
untyped pointers. Okay -- been there,
done that, in both PL/I and Fortran;
I was surprised that a serious
object oriented language with
polymorphism would have essentially
just syntactic sugar over what I'd
been doing in PL/I and Fortran. Okay.
Lesson learned: There are cases of
hype in parts of programming languages.
Heck, in PL/I my approach to sort an
array of PL/I structures would be
to pass a PL/I structure that
had an element variable that was
an entry variable to a procedure
that would do the comparisons.
That is, could have some conventions
that would let PL/I structures
be self-describing. Then
could write code that was more
general. Maybe under
the covers, that's just what's
going in the Microsoft common language
run time (CLR) that Visual Basic .NET
used for me without letting me know
-- more syntactic sugar?
So, when I rewrote my polymorphic
heap sort to do the comparisons
with a passed interface, the performance
got okay. Still, I have
non-polymorphic versions for the
more important element data types,
and they are faster in just they
way you emphasized. Yup, been
there, done that.
So, I, too, was concerned about actual
running time of actual data. But
for the algorithm itself, I
was still using just heap sort
and didn't try to write a
hybrid routine that sometimes
might exploit, say, radix sort.
On the pros and cons of using
big-O for the only criterion,
there's a big example in
linear programming:
In practice, the simplex
algorithm is fantastically
fast. Intuitively, in practice,
the early
iterations in effect focus
what to do to get to the
optimal solution (assuming feasibility,
no unboundedness, etc.).
But due to some work
of Klee and Minty, the worst
case of simplex is exponential
and just awful. Intuitively
the Klee and Minty examples
trick simplex into making
really bad choices!
So, there
was research to find a polynomial
algorithm for linear programming.
Eventually there was
an ellipsoid algorithm that
was polynomial but the constant
was so high that whenever
in practice ellipsoid beat
simplex both ran too long
to be practical.
So, really, the concern about simplex
and the effort for ellipsoid was
as progress just in algorithms and
in the big-O criterion. And in practice,
right away, the constant was
so large that ellipsoid was a flop.
Still, in algorithms, people
are straining to do better in
the sense of
the big-O criterion. Of course
the biggest effort here is
to find an algorithm that shows
that P = NP,
maybe the one of the most important
problems in both math and computer
science,
based on big-O for some polynomial.
With all of that, I decided in
my own work, for something
as simple as sorting, just to
go with heap sort, maybe even
a polymorphic heap sort.
Indeed, with all the emphasis on
polymorphism, people can't be
very concerned about constants! :-)!
- nightcracker 11y agoI'm impressed you can write such a long story yet address none of my points. You should consider becoming a politician. The only relevant piece of information I could find in your anecdote is this: > I assumed that we were talking about in place sorting. If we limit ourselves to in-place sorting algorithms then we still are left with block sort, pdqsort, introsort and heap sort, all of which run in O(n log n). My argument still holds.
- graycat 11y agoI wrote that long post trying to explain my attitude on sorting software from 50,000 or so feet up and, in particular, why I have been happy just to use heap sort and forget about looking for anything faster on average. I was reluctant to read the documentation in your GitHub submission if only because in my software development I have never needed or used GitHub. Also, I was reluctant to believe that there could be any version of quicksort that would have worst case running time of O( n ln(n) ). Why reluctant to believe? First, sure, the usual version of quicksort has us select a pivot value for a partition from a "median of three" keys from that partition. Second, sure: For a good pivot value, we want essentially the median of the keys in the partition we are trying to split into two partitions. And, sure, the median of three approach to selecting a pivot value is an approximation to the median we really want and, thus, a relatively good pivot value. So this approach to selecting a pivot value promises on average to get faster average running times. Fine. Faster on average is good. Terrific. But this pivot selection rule says next to nothing about worst case running time, and, for claiming worst case running time of O( n ln(n) ), this is a biggie issue. Third, with this median of three approach to selecting pivot values, we can think of bad arrays of input keys that will cause quicksort to run in time no faster than O( n^2 ). Then maybe we can think of other ways to select pivot values that result in a new version of quicksort, a version that runs in O( n ln(n) ) again on the bad arrays. But with this change, here's the problem: We are lacking a proof that the worst case running time of the new version of quicksort has worst case running time of O( n ln(n) ). That is, our new version of quicksort is good for some old cases of bad arrays but may have created for itself some new cases of bad arrays. So, with no proof, the new version of quicksort may also have worst case running time of O( n^2 ) like the old version did. The main difference might be that the bad arrays for the new version are more complicated to construct than the bad arrays for the old case. But I just looked at the file README.MD you suggested. I see two issues: First Issue: > When a new pivot is chosen it's compared to the greatest element in the partition before it. If they compare equal we can derive that there are no elements smaller than the chosen pivot. When this happens we switch strategy for this partition, and filter out all elements equal to the pivot. If I understand this statement correctly, I suspect that for some arrays of input keys, the resulting algorithm won't actually sort the keys. That is, we won't have a sorting algorithm. Second Issue: > While this technically still allows for a quadratic worst case, the chances of it happening are astronomically small. For proving worst case running time of O( n ln(n) ), "astronomically small" is irrelevant. Maybe your remark "astronomically small" is just a side comment and not relevant to PDQSORT having worst case running time of O( n ln(n) ), but from your documentation I can't easily tell. > A bad partition occurs when the position of the pivot after partitioning is under 12.5% (1/8th) percentile or over 87,5% percentile - the partition is highly unbalanced. When this happens we will shuffle four elements at fixed locations for both partitions. This effectively breaks up many patterns. If we encounter more than log(n) bad partitions we will switch to heap sort. For this and the associated documentation, I don't see a solid argument that PDQSORT has worst case running time of O( n ln(n) ). For some work, a big-O guarantee on worst case performance is a biggie, as in crucial to human life. Such a guarantee is essentially a theorem in applied math called computer science. A good example of how to prove such theorems is, of course, in D. Knuth, The Art of Computer Programming, Volume 3, Sorting and Searching. Again, I have been happy just to use heap sort and forget about looking for anything faster on average.