packages feed

swish-0.2.1: Swish/HaskellRDF/Sort/QuickSort.lhs

%-------------------------------=  --------------------------------------------
\chapter{Partioning sort}
%-------------------------------=  --------------------------------------------

%align

> module Swish.HaskellRDF.Sort.QuickSort
> where
> import Swish.HaskellRDF.Sort.MergeSort (mergeSortBy)
> import Data.List
> import Swish.HaskellRDF.Sort.LibBase

%align 33

%-------------------------------=  --------------------------------------------
\section{Some variations of quick sort}
%-------------------------------=  --------------------------------------------
%format qsort1			=  qsort "_1"
%format qsort2			=  qsort "_2"
%format qsort3			=  qsort "_3"

This section lists some variants of quick sort which \emph{cannot be
recommended} for use. The first and probably the most popular of all
appears in \cite[p.~154]{BiW88}, \cite[p.~128]{HinFun92},
\cite[p.~433]{Tho96Has}, and \cite[p.~9]{HPF97Gen}.

> qsort1			:: (Ord a) => [a] -> [a]
> qsort1 []			=  []
> qsort1 (p : as)		=  qsort1 [ a | a <- as, a < p ]
>				++ p : qsort1 [ a | a <- as, p <= a ]

Its main purpose is probably to demonstrate the use and elegancy of
list comprehensions. The definition has, however, several drawbacks
part from the fact that quick sort is not asymptotically optimal.
%
\begin{enumerate}
\item
it traverses the list twice,

\item
it has a space leak: |as| is alive until the second traversal is done
\cite[p.~18]{Bir96Fun},

\item
it uses |(++)| to catenate the results of the recursive calls (note that
|(++)| takes time proportional to the length of its first argument).
\end{enumerate}

The second variant uses the library function |partition| to avoid the
double traversal.

> qsort2			:: (Ord a) => [a] -> [a]
> qsort2 []			=  []
> qsort2 (p : as)		=  qsort2 l ++ p : qsort2 r
>     where (l, r)		=  partition (< p) as

And finally, here is a variant which employs a tail recursive partition
function.

> qsort3			:: (Ord a) => [a] -> [a]
> qsort3 []			=  []
> qsort3 [a]			=  [a]
> qsort3 (p : as)		=  partition [] [] as
>     where
>     partition l r []		=  qsort3 l ++ p : qsort3 r
>     partition l r (a : as)
>         | p <= a 		=  partition l (a : r) as
>	  | otherwise		=  partition (a : l) r as

Note however that |qsort3| is no longer stable since |l| and |r| are in
reverse order.

%-------------------------------=  --------------------------------------------
\section{Paulson's quicksort}
%-------------------------------=  --------------------------------------------

The following code is adpated from \cite[p.~110]{Pau96ML}. The main
difference to |qsort3| is that it uses an accumulating argument to
eliminate the calls to |(++)|.

> quickSort			:: (Ord a) => [a] -> [a]
> quickSort			=  quickSortBy (<=)
>
> quickSortBy			:: Rel a -> [a] -> [a]
> quickSortBy (<=) as		=  qsort as []
>     where
>     qsort []       x		=  x
>     qsort [a]      x		=  a : x
>     qsort (p : as) x		=  partition [] [] as
>         where
>         partition l r []	=  qsort l (p : qsort r x)
>         partition l r (a : as)
>             | p <= a 		=  partition l (a : r) as
>	      | otherwise	=  partition (a : l) r as

%-------------------------------=  --------------------------------------------
\section{Augustsson's stable quicksort}
%-------------------------------=  --------------------------------------------

Neither |qsort3| nor |quickSort| is stable. This is due to the fact
that partition reverses the order of elements (the lists |l| and |r|
are used as a stack). By duplicating the code into a stable and an
anti-stable variant we obtain stability.

> stableQuickSort		:: (Ord a) => [a] -> [a]
> stableQuickSort		=  stableQuickSortBy (<=)
>
> stableQuickSortBy		:: Rel a -> [a] -> [a]
> stableQuickSortBy (<=) as	=  qsortBy (<=) as []
>
> qsortBy			:: Rel a -> [a] -> [a] -> [a]
> qsortBy (<=) []       x 	=  x
> qsortBy (<=) [a]      x	=  a : x
> qsortBy (<=) (p : as) x	=  partition [] [] as
>     where

The function |partition| partitions and sorts the sublists. Note that
|l| and |r| are in reverse order and must be sorted with an anti-stable
sorting.

>     partition l r []		=  rqsortBy (<=) l (p : rqsortBy (<=) r x)
>     partition l r (a : as)
>         | p <= a 		=  partition l (a : r) as
>	  | otherwise		=  partition (a : l) r as

The function |rqsortBy| is as |qsort| but anti-stable, ie it reverses
equal elements (compare the last two equations of |partition|).

> rqsortBy			:: Rel a -> [a] -> [a] -> [a]
> rqsortBy (<=) []       x 	=  x
> rqsortBy (<=) [a]      x	=  a : x
> rqsortBy (<=) (p : as) x	=  partition [] [] as
>     where
>     partition l r []		=  qsortBy (<=) l (p : qsortBy (<=) r x)
>     partition l r (a : as)
>         | a <= p 		=  partition (a : l) r as
>	  | otherwise		=  partition l (a : r) as

%-------------------------------=  --------------------------------------------
\section{Introspective quicksort}
%-------------------------------=  --------------------------------------------

One of the big disadvantages of quick sort is its quadratic worst-case
behaviour. It turns out that it is quite easy to solve this dilemma:
simply limit the recursion depth to a fixed bound, and for subproblems
which exceed the limit use an $\Theta(n\lg n)$ algorithm
\cite{Mus97Int}.

> introSort			:: (Ord a) => [a] -> [a]
> introSort			=  introSortBy (<=)

> introSortBy			:: Rel a -> [a] -> [a]
> introSortBy (<=) as		=  isort (2 * floorLg (length as)) as []
>   where
>   isort 0       as       x	=  mergeSortBy (<=) as ++ x
>   isort (d + 1) []       x	=  x
>   isort (d + 1) [a]      x	=  a : x
>   isort (d + 1) (p : as) x	=  partition [] [] as
>     where
>     partition l r []		=  isort d l (p : isort d r x)
>     partition l r (a : as)
>         | p <= a 		=  partition l (a : r) as
>         | otherwise		=  partition (a : l) r as

Musser recommends a depth bound of |2*floor (lg n)|.

> floorLg                       :: Int -> Int
> floorLg n
>     | n == 1                  =  0
>     | otherwise               =  1 + floorLg (n `div` 2)