Fun with binary heap trees


... [Seminar "Haskell"] ... [Inhaltsübersicht] ... [zurück] ... [weiter] ...

Lazy Evaluation


Problemstellung

Der Laufzeitbeweis für Skew Heaps hinkt ein wenig, wenn man von den bei Haskell üblichen persistenten Datenstrukturen ausgeht. Wir betrachten dazu folgendes Beispiel:
Aus dem Baum T werden 100 neue Bäume erzeugt (T100 bis T199), die jeweils das zusätzliche Element i enthalten. Wobei i irgendein beliebiger sehr hoher Wert ist.

T            T100 ... T199
        1      1
       / \    / \
      2   4  2   3
     / \        / \
    5   3      4   5
   . \            / .
  .  6           6   .
 97                  97
 / \                 / \
99 98               98 99
                         \
                          i

In diesem Falle können die Schritte zur Einfügung des i in den neuen Bäumen nicht mehr mit der Zeit gemittelt werden, die notwendig war, um den Ausgangsbaum zu erzeugen, da dieser Baum ja nur einmal aufgebaut wurde und während dieser Prozedur nur einmal Zeit gespart wurde. In diesem extremen Fall bekommen wir sogar eine Laufzeitkomplexität von O(n²), da jeder Einfügevorgang von i n Schritte benötigt.


Potentielle Knoten

Die Lösung für unser Problem ist eine andere Eigenart von Haskell. Die Lazy Evaluation. Durch sie benötigt der Aufruf von insert i nur noch konstante Zeit, da i nicht vollständig eingefügt wird. Der Einfügevorgang wird nur soweit ausgeführt, wie es im Moment benötigt wird. An der Stelle, wo dieser abgebrochen wird, entsteht ein so genannter potentieller Knoten, der erst bei einem Zugriff auf diesen ausgewertet wird. Das ganze sieht dann so aus, wobei das '+' der potentielle Knoten ist.

       1
      / \
     2   +
        / \
       3   i
      / \
     5   3
    . \
   .   6
  97
 / \
99 98

Wird jetzt zum Beispiel deleteMin ausgeführt, so muss dieser Knoten ausgewertet werden. Damit verlagert sich der Rechenaufwand in die Funktion deleteMin. Aber auch dann wird nur wieder ein Schritt ausgewertet.
Aber das alleine reicht noch nicht ganz. Es gibt noch ein weiteres Optimierungfeature in Haskell. Wenn einmal ein potentieller Knoten ausgewertet wurde, wird dieses Ergebnis wiederverwendet, sollte es durch einen anderen Aufruf noch einmal benötigt werden. Das heisst, dass selbst bei vollständiger Auswertung aller 100 neuen Bäume bis zum Schluss nur einmal alle Auswertungsaktionen durchgeführt wurden, da sie bei allen 100 Bäumen identisch sind.