\input{../../../texPrelude}

\fancyhead[CO,CE]{Übungsaufgabe {\tt HeapSort}: Musterlösung}
\fancyhead[LO,LE]{Jost Berthold}

\begin{code}
module HeapSort 
  where

\end{code}
{\bf Definitionen:}
Sei H ein Binärbaum, dann sei $\# H$ die Zahl seiner 
inneren Knoten. Ein {\em B-Heap} ist ein Binärbaum mit 
Elementen in den inneren Knoten, in dem gilt: 
\begin{itemize}
  \item[\bf Balance] An jedem inneren Knoten (Kn a h1 h2) gilt für 
        die Sub-Heaps: $\# h1 - \# h2 \in \{ 0,1 \}$, d.h. ihre 
        Knotenzahl unterscheidet sich maximal um 1 und der linke 
        Teilbaum ist größer.
  \item[\bf Ordnung] Das enthaltene Element in einem Knoten ist stets 
        kleiner (oder allenfalls gleich) als die Elemente in beiden 
        Sub-Heaps des Knotens.
\end{itemize}

\begin{code}
data BHeap a = Empty 
              | Kn a (BHeap a) (BHeap a) 
        deriving Show -- Anzeigefunktion automatisch erstellen lassen

\end{code}
Nun können wir mit Hilfe des Heaps eine Liste sortieren: 
alle Elemente der Liste werden in einen leeren BHeap eingefügt und 
(sortiert) wieder herausgenommen.
\begin{code}
-- sortieren mit Heap: alles einfügen und sortiert wieder herausnehmen
heapsort :: Ord a => [a] -> [a]
heapsort [] = []
heapsort xs = fromHeap ( toHeap xs)

-- Herausnehmen
fromHeap :: Ord a => BHeap a -> [a]
fromHeap Empty = []
fromHeap heap  = a:(fromHeap h')
        where (a,h') = removeTop heap

-- Einfügen
toHeap :: Ord a => [a] -> BHeap a
toHeap [] = Empty
toHeap xs = let insertAll :: Ord a => BHeap a -> [a] -> BHeap a
                insertAll h   []  = h
                insertAll h (a:as)= insertAll (insertHeap h a) as
            in insertAll Empty xs
-- dies ist eine einfache Faltungsfunktion (von links falten, Start mit leerem BHeap:
      --  = foldl insertHeap Empty xs
            
-- Spitze des Heap (kleinstes Element) entfernen, Heap-Eigenschaft muss erhalten bleiben
removeTop :: Ord a => BHeap a -> (a,BHeap a)
removeTop Empty = error "removeTop: Heap empty"
removeTop (Kn a Empty Empty) = (a,Empty)
removeTop (Kn a   lh    rh ) = (a, mergeHeaps lh rh)

-- neues Element in einen Heap einfügen, Heap-Eigenschaft erhalten
-- Trick für Balance-Bedingung: immer in rechten Teilbaum einfügen, austauschen
insertHeap :: Ord a => BHeap a -> a -> BHeap a
insertHeap   Empty a          = Kn a Empty Empty
insertHeap (Kn x left right) a | a < x     = Kn a (insertHeap right x) left
                               | otherwise = Kn x (insertHeap right a) left

\end{code}

Hier war ein Denkfehler drin: wir benutzen \cd{mergeHeaps} ja immer
nur für den Fall, dass beide Heaps (fast!) gleichgroß sind (in
\cd{removeTop} und rekursiv). Daher wesentlich einfacher als die ursprüngliche Lösung

{\bf Ursprünglich als Lösung angegeben:}
\begin{xcode}
mergeHeaps :: Ord a => BHeap a -> BHeap a -> BHeap a
mergeHeaps Empty x = x
mergeHeaps x Empty = x
mergeHeaps (Kn l l1 l2) (Kn r r1 r2) 
        -- es gilt: #l1 + #r1 >= #l2 + #r2 >= #l1 + #r1 - 2
        --      ein Element nach oben, anderes in "kleineren" einfügen, auswählen
         | countleft heap2 > countright heap1  = Kn mi heap2 heap1
	 | otherwise                           = Kn mi heap1 heap2 
      where (mi, ma) = (min l r, max l r)
	    heap2      = insertHeap (mergeHeaps l2 r2) ma -- #l2 + #r2 + 1
	    heap1      = (mergeHeaps l1 r1)                -- #l1 + #r1

countleft,countright :: Ord a => BHeap a -> Int
countleft Empty          = 0
countleft (Kn _ Empty _) = 1
countleft (Kn _ l _)     = 1 + countleft l

countright Empty          = 0
countright (Kn _ _ Empty) = 1
countright (Kn _ _ r)     = 1 + countright r


\end{xcode}

{\bf Einfachere Lösung für diesen Fall:}
\begin{code}
mergeHeaps :: Ord a => BHeap a -> BHeap a -> BHeap a
mergeHeaps Empty x = x
mergeHeaps x Empty = x 
mergeHeaps (Kn l l1 l2) (Kn r r1 r2) 
        -- es gilt: #l1 + #r1 >= #l2 + #r2 >= #l1 + #r1 - 2 
	--     UND: #l1 + #l2 >= #r1 + #r2 >= #l1 + #l2 -1 !!!
        --      ein Element nach oben, anderes in "rechten Teil" einfügen, Seiten tauschen
	   = Kn mi (insertHeap right ma) left
    where mi   = min l r
	  ma   = max l r
	  left = mergeHeaps l1 l2
	  right= mergeHeaps r1 r2
\end{code}

Ein einfaches Quicksort ist langsamer und verbraucht mehr Speicher (testen!), 
lässt sich aber wesentlich kürzer (mit ``List-Comprehensions'') formulieren:
\begin{code}
quicksort :: Ord a => [a] -> [a]
quicksort []     = []
quicksort (x:xs) = quicksort [l | l <- xs, l <= x]
                        ++ [x] ++
                   quicksort [h | h <- xs, h > x]
\end{code}
\end{document}