packages feed

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

%-------------------------------=  --------------------------------------------
\chapter{Red-black trees}
%-------------------------------=  --------------------------------------------

%align

> module Swish.HaskellRDF.Sort.RedBlackTree 
> where
> import Swish.HaskellRDF.Sort.LibBase

%align 33

Every search tree scheme can be used for sorting: the elements are
repeatedly inserted into an empty initial tree; an inorder traversal of
the final tree yields the desired ordered permutation of the input.
Here we use red-black trees as described by Chris Okasaki
\cite[p.~24]{Oka98Pur}.

Sorting on the basis of red-black trees is asymptotically optimal and
stable but neither adpative nor lazy.

> data RedBlackTree a		=  Empty
>				|  Red (RedBlackTree a) a (RedBlackTree a)
>				|  Black (RedBlackTree a) a (RedBlackTree a)

\NB For reasons of efficiency nodes do not have a separate color field,
instead the color is coded into the constructor.

> insertBy			:: Rel a -> a -> RedBlackTree a -> RedBlackTree a
> insertBy (<=) a t		=  blacken (ins t)
>   where ins Empty		=  Red Empty a Empty
>         ins (Red l b r)
>             | a <= b		=  Red (ins l) b r
>             | otherwise	=  Red l b (ins r)
>         ins (Black l b r)
>             | a <= b		=  lblack (ins l) b r
>             | otherwise	=  rblack l b (ins r)
>
> blacken			:: RedBlackTree a -> RedBlackTree a
> blacken (Red l a r)		=  Black l a r
> blacken t			=  t

%align 49
{\setlength{\lwidth}{\lwidth + 1cm}

> lblack					:: RedBlackTree a -> a -> RedBlackTree a -> RedBlackTree a
> lblack (Red (Red t1 a1 t2) a2 t3) a3 t4	=  Red (Black t1 a1 t2) a2 (Black t3 a3 t4)
> lblack (Red t1 a1 (Red t2 a2 t3)) a3 t4	=  Red (Black t1 a1 t2) a2 (Black t3 a3 t4)
> lblack l a r					=  Black l a r
>
> rblack					:: RedBlackTree a -> a -> RedBlackTree a -> RedBlackTree a
> rblack t1 a1 (Red (Red t2 a2 t3) a3 t4)	=  Red (Black t1 a1 t2) a2 (Black t3 a3 t4)
> rblack t1 a1 (Red t2 a2 (Red t3 a3 t4))	=  Red (Black t1 a1 t2) a2 (Black t3 a3 t4)
> rblack l a r					=  Black l a r

}
%align 33

> inorder			:: RedBlackTree a -> [a]
> inorder t			=  traverse t []
>     where
>     traverse Empty x		=  x
>     traverse (Red l a r) x	=  traverse l (a : traverse r x)
>     traverse (Black l a r) x	=  traverse l (a : traverse r x)
>
> redBlackSort			:: (Ord a) => [a] -> [a]
> redBlackSort			=  redBlackSortBy (<=)
>
> redBlackSortBy		:: Rel a -> [a] -> [a]
> redBlackSortBy (<=)		=  inorder . foldr (insertBy (<=)) Empty


Note that |lblack| and |rblack| sometimes perform an unnecessary test
since both subtrees are tested for red-red violations. Here is a
variant of |insertBy| which remedies this shortcoming, albeit at the
expense of readability (this solves exercise~3.10(b) in
\cite{Oka98Pur}, the original version already solves
exercise~3.10(a)).

> insertBy'			:: Rel a -> a -> RedBlackTree a -> RedBlackTree a
> insertBy' (<=) a t		=  blacken (ins t)
>     where
>     ins Empty			=  Red Empty a Empty
>     ins (Red l b r)		=  error "red node"
>     ins (Black l b r)		=  black l b r
>
>     black l b r
>       | a <= b		=  case l of
>         Empty			-> Black (Red Empty a Empty) b r
>         Red ll lb lr
>           | a <= lb		-> case ins ll of
>             Red lll llb llr	-> Red (Black lll llb llr) lb (Black lr b r)
>             ll'		-> Black (Red ll' lb lr) b r
>           | otherwise		-> case ins lr of
>             Red lrl lrb lrr	-> Red (Black ll lb lrl) lrb (Black lrr b r)
>             lr'		-> Black (Red ll lb lr') b r
>         Black ll lb lr	-> Black (black ll lb lr) b r
>       | otherwise		=  case r of
>         Empty			-> Black l b (Red Empty a Empty)
>         Red rl rb rr
>           | a <= rb		-> case ins rl of
>             Red rll rlb rlr	-> Red (Black l b rll) rlb (Black rlr rb rr)
>             rl'		-> Black l b (Red rl' rb rr)
>           | otherwise		-> case ins rr of
>             Red rrl rrb rrr	-> Red (Black l a rl) rb (Black rrl rrb rrr)
>             rr'		-> Black l b (Red rl rb rr')
>         Black ll lb lr	-> Black l b (black ll lb lr)
>
> redBlackSort'			:: (Ord a) => [a] -> [a]
> redBlackSort'			=  redBlackSortBy (<=)
>
> redBlackSortBy'		:: Rel a -> [a] -> [a]
> redBlackSortBy' (<=)		=  inorder . foldr (insertBy' (<=)) Empty

Empirical tests show that the decrease in readability is not justified
or counterbalanced by an increase in speed: |redBlackSort'| is
sometimes marginally faster, and sometimes marginally slower.
\Todo{Curiously, |redBlackSort'| outperforms |redBlackSort| on strictly
increasing sequences thereby contradicting the theory.}