Bergervei/GnomSort
Aus ProgrammingWiki
Wiki-Quelltext dieser Seite.
<language>Scheme</language>
<code id="4db9d77895031" hidden>
(define listequal?
(lambda (ls1 ls2)
(cond
[(and (null? ls1) (null? ls2)) #t]
[(or (null? ls1) (null? ls2)) #f]
[(and (list? (car ls1)) (list? (car ls1)))
(and (listequal? (car ls1) (car ls2)) (listequal? (cdr ls1) (cdr ls2)))]
[else
(and (equal? (car ls1) (car ls2)) (listequal? (cdr ls1) (cdr ls2)))])))
(define rand 50)
(define maxbreite (- 400 (* 2 rand)))
(define maxhoehe (- 300 (* 2 rand)))
(define minmax
(lambda (ls)
(letrec
([extremsuche
(lambda (extrem ls op)
(cond
[(null? ls) extrem]
[(op extrem (car ls)) (extremsuche (car ls) (cdr ls) op)]
[else
(extremsuche extrem (cdr ls) op)]))])
(cons (extremsuche 0 ls >)
(extremsuche 0 ls <)))))
(define balken
(lambda (hoehe breite turtle)
(forward turtle breite) (left turtle 90)
(forward turtle hoehe) (left turtle 90)
(forward turtle breite) (left turtle 90)
(forward turtle hoehe) (left turtle 90)
(forward turtle breite)))
(define balkendiagramm
(lambda (ls nr)
(let* ([breite (/ maxbreite (length ls))]
[extremwerte (minmax ls)]
[min (car extremwerte)] [max (cdr extremwerte)]
[canvas (eval (string->symbol (string-append "canvas" (number->string nr))))]
[turtle (eval (string->symbol (string-append "turtle" (number->string nr))))])
(letrec
([zeichnen
(lambda (min max ls)
(if (null? ls)
'ok
(begin
(balken (floor (* (/ (car ls) (- max min)) maxhoehe)) (floor breite) turtle)
(zeichnen min max (cdr ls)))))])
(clear canvas)
(hide turtle)
(move turtle rand (+ rand (* (/ max (- max min)) maxhoehe))) (right turtle 90)
(zeichnen min max ls)))))
</code>
== Algorithmus ==
In einem Garten sind Bumentöpfe in einer Reihe aufgestellt. Gartenzwerge sollen sie nun der Größe nach aufsteigend sortieren. Sie benutzen dazu den folgenden Algorithmus:<br />
<ul>
<li> Beginne links und vergleiche die beiden ersten Töpfe.</li>
<li> Stehen sie in der richtigen Reihenfolge gehe ein Schritt nach rechts.</li>
<li> Vertausche die beiden Töpfe, wenn sie nicht in der korrekten Reihenfolge stehen, und gehe einen Schritt nach links.<br />
Ist der Schritt nach links nicht möglich, gehe ein Schritt nach rechts.</li>
<li> Wiederhole das Sortieren bis zum letzten Blumentopf.</li>
</ul>
Die Größe der Blumentöpfe wird mit Zufallszahlen festgelegt:
<code id="4dce4f468dc24">
(define zufallszahl
(lambda (min max)
(+ (randomint (- max min -1)) min)))
(define zufallsliste
(lambda (min max anzahl)
(if (= anzahl 0)
()
(cons (zufallszahl min max) (zufallsliste min max (- anzahl 1))))))
</code>
Da keine Indizierung der Listenelemente möglich ist, wird der Algorithmus mit den beiden Listen <tt>ls1</tt> und <tt>ls2</tt> implementiert. Die Liste <tt>ls1</tt> enthält die unsortierte Zahlenliste. Mit <tt>ls2</tt> wird die sortierte Zahlenlise gebildet. Beim Schritt nach rechts wird das erste Listenelement von <tt>ls1</tt> der Liste <tt>ls2</tt> vorangestellt. Analog wird beim Schritt nach links das erste Listenelement von <tt>ls2</tt> zurückgenommen und <tt>ls1</tt> vorangestellt:
<code id="4dce4f468dcc4">
;GnomSort
(define gnomsort
(lambda (ls)
(letrec
([sort
(lambda (ls1 ls2)
...])
(reverse (sort ls ())))))
</code>
<check>
(and (listequal? (gnomsort '(12 4 5 6 33 2 6 7 1 47)) '(1 2 4 5 6 6 7 12 33 47))
(listequal? (gnomsort '(147)) '(147))
(listequal? (gnomsort '()) '()))
</check>
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td align="center">
'''Ungeordnete Zufallszahlen:'''
<canvas w="400" h="300"></canvas>
</td>
<td> </td>
<td align="center">
'''Geordnete Zufallszahlen:'''
<canvas w="400" h="300"></canvas>
</td>
</tr>
</table>
<run id="4db9d8505774c">
(define ls (zufallsliste 1 1000 100))
(balkendiagramm ls 1)
(define ls-sort (gnomsort ls))
(balkendiagramm ls-sort 2)
</run>
'''Hinweis:''' Um mehrere Zeichenflächen nutzen zu können, muss die Prozedur <tt>balkendiagramm</tt> mit einem zusätzlichen Parameter ausgestattet werden, der einer fortlaufenden Nummerierung entspricht:
(balkendiagramm
<ls> ; Liste mit Zahlenwerten
<nr>) ; fortlaufende Nummerierung des Balkendiagramms
== Effizienz von GnomSort ==
=== Average-Case-Experimente ===
Wir definieren unseren GnomSort-Algorithmus mit einem "eingebauten" (Zeittakt-)Zähler:
<code id="4dbdbbbc88f1a">
(define count 0)
(define gnomsort-aufwand
(lambda (ls)
(letrec
([sort
(lambda (ls1 ls2)
(cond
[(null? ls1) ls2]
[(null? (cdr ls1))
(set! count (+ 1 count)) ;gib die einelementige Liste zurück
(cons (car ls1) ls2)]
[(> (car ls1) (cadr ls1))
(set! count (+ 1 count)) ;vertausche das n-te Paar und gehe nach links (bzw. nach rechts)
(if (null? ls2)
(sort (cons (car ls1) (cddr ls1)) (cons (cadr ls1) ls2))
(sort (cons (car ls2) (cons (cadr ls1) (cons (car ls1) (cddr ls1)))) (cdr ls2)))]
[else
(set! count (+ 1 count)) ;gehe nach rechts
(sort (cdr ls1) (cons (car ls1) ls2))]))])
(reverse (sort ls ())))))
(define gnomsort-ausgabe
(lambda (min max anzahl)
(let ([ls (zufallsliste min max anzahl)])
(set! count 0)
(display "Ungeordnete Zufallszahlen:") (newline)
(display ls) (newline)
(display "Geordnete Zufallszahlen:") (newline)
(display (gnomsort-aufwand ls)) (newline)
(display "Anzahl der Vertauschungen bzw. Bewegungen: ") (display count) (newline)
'ok)))
</code>
<run id="4db9d77895339">
(gnomsort-ausgabe 1 1000 20)
</run>
Im Weiteren wollen wir uns nur auf die Zählergebnisse unseres "Zeittaktmessers" konzentrieren.<br />
Dazu sollen die Wiederholungen des Experimentes automatisiert werden:
<code id="4db9d778951bf">
(define gnomsort-mehrfach
(lambda (min max anzahl whlg)
(letrec
([summe
(lambda (n)
(if (= n 0)
0
(begin
(set! count 0)
(gnomsort-aufwand (zufallsliste min max anzahl))
(+ count (summe (- n 1))))))])
(exact->inexact (/ (summe whlg) whlg)))))
</code>
<run id="4db9d778953a3">
(gnomsort-mehrfach 1 1000 20 100)
</run>
Schließlich sollen die Ergebnisse unserer empirischen Untersuchung wieder im Balkendiagramm visualisiert werden.
<code id="4db9d77895234">
(define experiment
(lambda (min max anzahl schrittweite whlg)
(if (< anzahl 1)
()
(append (experiment min max (- anzahl schrittweite) schrittweite whlg)
(list (gnomsort-mehrfach min max anzahl whlg))))))
</code>
'''Achtung!''' Mit kleinen Wiederholraten und etwas Geduld experimentieren!
<canvas w="400" h="300"></canvas>
<run id="4db9d7789541b">
(balkendiagramm (experiment 1 1000 200 5 10) 3)
</run>
Die empirischen Untersuchungen zeigen im Mittel offensichtlich einen ''quadratischen Zeitaufwand'' von GnomSort.<br />
Es gilt:
<table border="0" cellspace="0" cellpadding="4">
<tr valign="top">
<td rowspan="2"><b>average case: </b></td>
<td><math>T(n) = \frac{1}{2} \cdot n^2</math> bzw.</td>
</tr>
<tr>
<td><math>T(n) = \mathcal{O}(n^2)</math></td>
</tr>
</table>
=== Best- und Worst-Case-Experimente ===
Zur Untersuchung dieser Spezialfälle wollen wir die beiden Prozeduren <tt>gnomsort-mehrfach</tt> und <tt>experiment</tt> ''variabelstellig'' erweitern. ''Zusätzlich'' können nun beliebig viele Prozeduren übergeben, die sich auf Listen anwenden lassen. Damit kann beispielsweise die zufällige Zahlenliste "vorsortiert" oder vor dem Sortiervorgang ungekehrt werden:
<code id="4dce4f468dd58">
(define gnomsort-spezial
(lambda (min max anzahl whlg . proz-ls)
(letrec
([anwenden
(lambda (zuf-ls proz-ls)
(if (null? proz-ls)
zuf-ls
(apply (car proz-ls) (list (anwenden zuf-ls (cdr proz-ls))))))]
[summe
(lambda (n)
(if (= n 0)
0
(begin
(set! count 0)
(gnomsort-aufwand (anwenden (zufallsliste min max anzahl) proz-ls))
(+ count (summe (- n 1))))))])
(exact->inexact (/ (summe whlg) whlg)))))
(define experiment-spezial
(lambda (min max anzahl schrittweite whlg . proz-ls)
(if (< anzahl 1)
()
(append (apply experiment-spezial min max (- anzahl schrittweite) schrittweite whlg proz-ls)
(list (apply gnomsort-spezial min max anzahl whlg proz-ls))))))
</code>
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td align="center">
'''GnomSort: best-case'''
<canvas w="400" h="300"></canvas>
</td>
<td> </td>
<td align="center">
'''GnomSort: worst-case'''
<canvas w="400" h="300"></canvas>
</td>
</tr>
</table>
<run id="4dbbdcbaddc87">
(balkendiagramm (experiment-spezial 1 1000 200 5 1 gnomsort) 4)
(balkendiagramm (experiment-spezial 1 1000 200 5 5 reverse gnomsort) 5)
</run>
Für die beiden Spezialfälle gilt:
<table border="0" cellspace="0" cellpadding="4">
<tr valign="top">
<td rowspan="2"><b>best case: </b></td>
<td><math>T(n) = n</math> bzw.</td>
</tr>
<tr>
<td><math>T(n) = \mathcal{O}(n)</math></td>
</tr>
<tr valign="top">
<td rowspan="2"><b>worst case: </b></td>
<td><math>T(n) = (n-1)^2 + 1</math> bzw.</td>
</tr>
<tr>
<td><math>T(n) = \mathcal{O}(n^2)</math></td>
</tr>
</table>
Die Effizienz von GnomSort verbessert sich also, wenn schon eine gewisse Ordnung ("Vorsortierung") vorliegt.<br />
Im ungünstigsten Fall ist GnomSort etwas uneffizienter als MinSort.