So, einen schönen Tag. Andererseits eine Verpflichtung habe ich offensichtlich noch.
Das ist im Zusammenhang mit der Übung und zumindest ein Übungsgruppenleiter hat angekündigt,
dass ich mich hier in der Vorlesung nochmal drauf einlasse.
Das ist die Frage der Memorisierung. Die wird heute
gleich an einer anderen Stelle nochmal eine Rolle spielen.
Wir hatten ja die verzweigende Rekursion bei der Berechnung von Vinomyalkoeffizienten
und da war zum Schluss die Frage zu beantworten,
wie man das Ganze denn effizienter gestalten könnte.
Und da gibt es eine ganze Reihe von Möglichkeiten,
die Symmetrie ausnutzen und so weiter. Und das bei weitaus
effizientes oder wirksamste Mittel zur Beschleunigung des Ganzen ist die Mimusierung.
Also die Kunst, Zwischenergebnisse aufzubewahren. Das hier ist die,
wir hatten drei verschiedene Definitionen, das ist die erste für
die Bindung, jakoffizienten, diese typische verzweigende Rekursion hier.
Ich berechne den linken Vorgänger, ich berechne den rechten Vorgänge,
ich addiere die beiden und habe den neuen Bindung, jakoffizienten.
Der Rest ist nur immer den entsprechenden Rekursionsfortschritt berechnen.
Und das Ganze ist eben so sowohl,
wenn der eine null ist, als auch wenn die beiden gleich sind,
ist der Venuumialkoeffizient 1. Und wenn man das also jetzt benutzt,
ist das zu erkennen?
Ja, das ist groß genug. Und klein anfängt,
die kritischen Fälle sind immer die,
die in der Mitte des Dreiecks liegen.
Dann fängt das relativ, ach so, wir wollen mal
gleich die Zeitmessung einschalten, damit es interessanter wird.
Wenn man da klein anfängt, da passiert erst mal nicht
sehr viel. Der Zeitaufwand ist nicht nachweisbar,
auch in der dritten Stelle hinterm Komma sehen wir nichts.
Ups, wir können also das nach oben treiben.
Auch da sehen wir noch nichts und na no.
Und das ist das typische Verhalten bei exponentiellem Wachstum eine ganze
Zeit sieht man überhaupt nichts, das irgendwie nennenswerter Aufwand damit verbunden ist.
Und irgendwann geht es dann plötzlich los,
dann aber richtig
Ja, also selbst hier sind wir immer noch an der Stelle,
wo kein nennenswerter Zeitaufwand entsteht. Jetzt wird es langsam ein
bisschen mehr, aber immer noch ist es vernachlässigbar gering.
Ich mache mal jetzt ein paar Schritte. Hier wird es
langsam interessant. Und das ist das Typische bei exponentiellem Wachstum.
Der nächste Schritt ist dann immer der Entscheidende, der nicht mehr abtraktabel ist.
Bis zu einem bestimmten Zeitpunkt geht das alles noch ganz gut.
Und dann ist der Anstieg so gewaltig, dass man nicht
mehr vernünftig damit arbeiten kann. Also hier 13 Sekunden,
es ist hier auch so, dass die Zeitangaben extrem schwanken,
weil bereits kleinste Veränderungen am Anfang der Rekursion dazu führen können,
dass am Ende gewaltige Unterschiede auftauchen.
Richtig problematisch wird es dann im Bereich, wenn wir hier hingehen,
dann können wir schon eine ganze Weile warten,
bis wir tatsächlich mal ein Ergebnis zu sehen bekommen.
Und die Alternative dazu ist eben, dass man sich,
das sind die verschiedenen anderen Varianten,
sie erinnern sich vielleicht noch mit Rückgriff auf die Fakultät.
Das, wofür wir uns jetzt hier interessieren sollten,
ist die Immuisierungsvariante, ich führe ein neues Prädikat ein,
was hier Binom Old heißt und in Binom Olds,
speichere ich all die Fakten ab, die ich schon mal berechnet habe.
Das Ganze wird eingekleidet in einen Frepper und der macht eigentlich nichts anderes,
als dass er ein bisschen Housekeeping betreibt, dass er alle diese Fakten,
die ich zu Dibinom Olds schon jemals da reingeschrieben habe,
dass er die also beseitigt über dieses Rectract All.
Und ansonsten ist es die gleiche Struktur,
die wir auch vorher schon gesehen hatten,
hier mit den zwei Rekursionsabschlüssen für den Gleichheitsfall und für den Fall gleich k gleich null.
Dann der Rekursionsschritt mit der verzweigenden Rekursion hier und anschließender Addition,
um das Ergebnis zu berechnen. Und der einzige Unterschied ist,
dass ich jetzt hier noch mal eine Prädikat aufrufe,
sagt, speicher mir doch mal bitte die Merkmalsbelegung,
die du jetzt gerade verwendet hast, ab,
um sie zu merken.
Und die Definition kann man hier auf verschiedene Art und Weise
Ach du Schreck, das hätte ich nicht tun sollen.
Da ist sie wieder. Das macht nichts anderes dann,
wenn es diese Informationen bereits in der Datenbank findet, dann tut es nichts.
Und wenn es diese Informationen nicht in der Datenbank findet,
dann schreibt es sie in die Datenbank rein.
Im Übrigen ist jetzt unsere Berechnung hier soweit, dass es
für die Parameterbelegung den Pinomialkomplizenten ermittelt hat. Also in dem Augenblick,
wo ich eine neue Variante von N&K oder eine Kombination von N&K vorliegen habe,
die in der Datenbank noch nicht drinsteht, dann wird sie hier mit reingeschrieben.
Und der entscheidende Punkt ist hier oben,
und das ist ein kleiner Vorgriff auf den Abschnitt Default schließen,
den wir noch machen werden,
das ist tatsächlich eine der wenigen sinnvollen Verwendungsweisen für den Cut,
dass ich nämlich sage, standardmäßig berechne mir bitte,
die binomialkoeffizienten über diese rekursive Vorschrift.
Aber wenn du als Ausnahme bereits das Ergebnis kennst,
dann nimm das Ergebnis und mach nichts weiteres.
Schneide also alle Alternativen ab. Berufe dieses Ergebnis aus der Datenbank ab,
schneide alle Alternativen ab und damit wird diese rekursive Berechnung niemals aufgerufen.
Das ist der ganze Trick, der hier passiert.
Also als allererstes prüfe mal,
ob du bereits den Wert kennst, wenn ja,
dann brauchst du diese Berechnung nichts zu machen.
Und wenn du einen neuen Wert berechnet hast,
dann schreib dir bitte in die Datenbank rein.
Das ist alles. Also die drei Zeilen kommen hinzu,
mehr muss nicht
gemacht werden,
Glauben, das ist sinnvoll, dass man den Betriebsmärkten auch fordert,
die Werte, die da gelöscht werden,
bevor er hier im New York... Das kann man handhaben,
wie man möchte.
Wenn man die aus früheren Berechnungen wieder recyceln möchte,
dann lässt man sie natürlich drinstehen. Für Testzwecke ist es sehr hilfreich,
wenn man die Alten mal raus... Insbesondere,
wenn wir jetzt rangehen und den Zeitbedarf ermitteln wollen,
dann verzerrt es das Bild sehr, wenn da bereits alte Ergebnisse drinstehen.
Deshalb habe ich es rausgelöscht. Das ist richtig,
das muss man nicht machen, das kann man hier oben auch streichen,
je nachdem, wie stark man mit der Speicherbelastung leben möchte.
Denn mit zunehmender Berechnung wird der Speicher gefüllt mit Fakten zu Benom,
Ult und ob man die noch drin haben möchte oder nicht,
das muss man anwendungsabhängig entscheiden.
Ich habe hier noch mal eine andere Alternative angegeben,
wem das zu kompliziert ist. Der kann es auch
kürzer machen. Dies ist auch ein Vorgriff auf eine der
sinnvollen Verwendungsweisen des Cuts, nämlich nicht monotonisch schließen im Allgemeinen.
Also in diesem Fall ist diese Closed World Assumption der Negation.
Das ist nämlich nichts anderes als unser bekanntes If-Wen.
Falls also nicht noch ein,
falls noch nicht ein Wert für die Parameterkombination vorliegt,
dann trage ihn in die Datenbank ein
Das ist also nichts anderes als das FWN in anderen,
nein, halt, das ist etwas anderes als das FWN
in anderen Programmiersprachen, weil dies auch scheitern kann.
Aber das gucken wir uns an der entsprechenden Stelle an. Hier macht es nichts,
wenn es scheitert, es ist genau der gewünschte Effekt,
ist der letzte, das letzte Teilziel in der Klausel,
das ist nur interessant, wenn danach noch weitere Teilziele folgen.
Wer da auf Nummer sicher gehen will,
muss nicht das FWN verwenden, sondern das ÖVNLs,
das FWNLs stellt sicher,
das auch im Alternativfall die Berechnung weitergeht und das Ganze nicht
mit einem Fehl scheitert
Gut, also ich wollte dich nur mal zeigen,
es ist relativ geringer Aufwand, so eine Memorisierung hier mit einzubauen.
Es sind also nur zwei Zeilen, die ich,
oder nein, drei Zeilen, die ich ergänzen muss.
Und wir können uns jetzt mal angucken, was bringt es?
Wir haben hier also das Ergebnis für die Nicht-Memuisierungsvariante,
der einzige Unterschied, den wir jetzt machen müssen, ist,
dass wir eben die Memorisierungsvariante aufrufen und dann sehen wir,
wir haben das Ergebnis mit dem Fingerschnipp.
Und wir können kühn werden, wir können hier richtig kühn werden,
indem wir jetzt eben sagen, das kommt immer noch,
Fingerschnipp und wir können das also auch in die Höhe treiben,
womit wir gleich mal schön sehen, wie man Inditorzahlen Na,
mal sehen, ob das zu kühn ist.
Nein, es ist nicht zu kühn. Es dauert inzwischen erheblich länger,
ja, aber wir sind bereits um Größenordnung über dem,
was wir über die rekursive Variante ohne, also,
ja, die rekursive Variante ohne Mimuisierung uns leisten konnten.
Also, das ist eine relativ einfache Möglichkeit und ist es nicht,
dass sie den Eindruck erwecken möchte, ich habe das von ihnen erwartet.
Ich wollte ihnen nur mal zeigen,
es ist immer sinnvoll, darüber nachzudenken,
wenn ich also nicht in der Lage bin oder wenn ich sehr oft auf alte Rekursionsergebnisse zurückgreifen möchte,
dann ist es immer sinnvoll, darüber nachzudenken,
ob man nicht über das Speichern dieser Zwischenergebnisse, Effizienzgewinne erzielen kann.
Und das ist hier tatsächlich der Fall. Wir könnten jetzt natürlich
mal gucken, wie viele Fakten er abgespeichert hat dafür.
Jemand eine Idee, wie man das machen kann?
Es gibt durch die digitale Listik, mit dem man die Tatphase ansehen kann,
kann man da auch nicht einen kleinen Ort anwerfen oder die Menge für das Ding stimmen.
Ja, aber im Prinzip ist es genau das,
also das würde ich jetzt machen, ich weiß nicht,
ob wir damit, das habe ich nicht vorher probiert,
wir machen jetzt also ungeschütztes Ausprobieren,
ich würde ein Find All über über dieses Prädikat machen,
also, gib mir alle X, sodass,
oder was weiß ich, es reicht auch da einzeln reinzuschreiben.
Ja, ich rufe einfach Binom Old auf,
lasse mir für jedes Ding, was ich finde,
eine Eins in eine Liste schreiben,
habe dann die Liste und will die Länge der Liste wissen,
wie rum ist es, erst die Liste dann,
dass die Länge,
ich weiß es immer nicht,
aber wir werden es gleich mitkriegen
Das war's. Das ist natürlich dumm gelaufen. Ja,
früher könnte man das unterdrücken,
es geht inzwischen nicht mehr, das ist bedauerlich.
Da müssen wir einen Trapper drumherum schreiben, um das L wegzukriegen.
Aber wahrscheinlich wird es nicht mehr gehen.
Früher könnte man das durch so einen einfachen Trick unterbinden,
aber das geht leider nicht mehr.
Gut, dann müssen wir noch ein,
bist du nicht willig,
so brauche ich Gewalt
Nein, ach, das schreibt er ja erst aus,
dann kommen wir nicht weiter. Okay, ähm,
hat jemand eine nette Idee, wie man das, äh, oder?
Man muss, glaube ich, bei den Hunden zuerst lösen.
Ist es doch verkehrt drum, ja? Ich weiß es immer nicht,
das ist, äh, tut mir leid, na,
im Zweifelsfall guckt man immer nach, ähm,
es hat sich nämlich mit der Zeit auch immer geändert und irgendwann gebe ich das mal auf,
mir zu merken, wie rum es ist, äh,
also sie waren der Meinung, erst die, erst die Liste, Sehr gut, ja, okay.
Dann müssen wir das hier umdrehen. Ah, okay.
Also, 62.000 Fakten stehen jetzt in der Datenbank drin.
Das ist nicht so schrecklich viel. Wir können auch noch gucken,
ob das irgendwie korrespondiert mit den Inferenzschritten,
die er gemacht hat, oh nein, das ist,
also er hat 600.000 Regeln abgearbeitet, um die zu berechnen.
Und daraus sind 62.000 Fakten entstanden. Das ist doch
erheblich weniger
Gut, das soll dafür erst mal reichen. Dann machen wir jetzt weiter mit unserem Vorlesungskanon.
Wir hatten uns ja verschiedene Beispiele für Prädikate zur Listenverarbeitung angeguckt. Wir hatten dabei verschiedene Varianten, also verschiedene Rekursionsschemata uns angeguckt. Lineare Rekursion,
die entweder beim rekursiven Aufstieg berechnet oder beim rekursiven Abstieg berechnet oder auf beiden Zweigen der Rekursion berechnet oder verzweigende Rekursion,
so wie wir das eben jetzt hier bei dem Bei dem Benumialkoffizienten hatten und das Beispiel in der Welt der Listenverarbeitung war,
also das Flatten, wo wir eine strukturierte Liste,
die auch widerlisten als Elemente enthalten kann,
in eine unstrukturierte, also lineare Liste umwandeln wollen,
was immer darin besteht,
dass wir den Kopf der Liste nehmen und die Struktur darin beseitigen und dass wir den West-Day-Liste nehmen und die Struktur darin beseitigen.
Und dadurch haben wir eine verzweigende Rekursion,
denn wir müssen zum Schluss die beiden Ergebnisse wieder per Append zusammenfassen.
Das wird also ein Punkt sein,
den wir uns nochmal genauer im Zusammenhang mit den Differenzlisten
angucken müssen
Im Hinblick darauf, ob auch in diesem Fall endrekusiv programmiert werden kann.
Wir hatten ja eben gesehen, bei dem Binomialkoeffizienten,
dass man dann zum Schluss nochmal addieren muss,
die beiden Berechnungsergebnisse und auf die Art und Weise nicht mehr in der Lage ist, das ganze Endrekusiv aufzubauen.
Und wir müssen dann sogar so weit gehen,
ob unser Erkennungsmerkmal, was wir bisher hatten,
das letzte Teilziel der Klausel muss der rekursive Aufruf sein,
ob das überhaupt noch für Enddruck Rekursivität das entscheidende Merkmal ist.
Aber das ist jetzt ein Vorausgriff auf einen Teil, den ich mache,
nachdem wir uns erstmal mit der funktionalen Programmierung befasst haben.
Der weitere Gang der Dinge wird dann also sein,
wir gucken uns erst noch all die Sachen an,
die wir insbesondere für die Meta-Programmierung brauchen,
dann gehen wir in die funktionale Programmierung und zum Schluss komme ich nochmal auf die Frage zurück,
ob wir eigentlich das Potenzial der Unifikation schon ausgeschöpft haben im Bereich der Listenverarbeitung und da muss man sagen,
nein, wir haben es noch nicht,
da gibt es noch erhebliche Tricks,
die man
sich angucken kann
So, also hier jetzt Anwendungsbeispiele für Listenverarbeitung. Wir fangen an
mit etwas, was in der Informatik allgemein viel verbreitet ist.
Es geht jetzt nicht so sehr darum,
dass ich Ihnen erklären will, wie die verschiedenen Verfahren funktionieren.
Da gehe ich über weite Strecken davon aus,
dass Ihnen das schon mal irgendwo über den Weg gelaufen ist.
Wir wollen uns im Wesentlichen darauf konzentrieren,
wie wird das jetzt in ein Logikprogramm umgesetzt?
Und das ist also der Kanon, den ich hier abarbeiten will.
Einige Sachen davon sollten Ihnen bekannt vorkommen. Gut,
machen wir gleich mal die Frage, sowas wie Bubblesword und Quicksorte
ist schon mal irgendwo aufgetaucht oder ist Ihnen das völlig neu?
Gut, da muss ich also, da kann ich
darauf zurückgreifen
Also das einfachste, einfachste Suchverfahren, was ich habe,
ist,
ich sortiere eine Liste und suche in der sortierten Liste,
wodurch ich zumindest den Vorteil habe,
dass ich im Mittel den Suchaufwand auf die Hälfte reduzieren kann,
wenn ich nämlich die Suche abbreche,
nachdem ich das gewünschte Element gefunden habe oder soweit in der Liste fortgeschritten bin,
dass ich weiß, es kann jetzt nicht mehr kommen.
Also das erste Mal, wenn ich ein Element gefunden habe,
was größer ist als das, was ich suche,
weiß ich, ich werde es in der Liste nicht finden.
Und auf die Art und Weise kann ich also
den Aufwand für die negativen Fälle deutlich verbessern,
alle deutlich verringern
Und das ist der Gewinn von einer solchen Sucherei.
Und das ist der Trick, ist also relativ simpel.
Es ist wieder ein rekrusiver Abbau der Liste.
Der rekrusive Abbau passiert auf der zweiten Argumentstelle. Hier,
da wird immer das erste Element angeguckt. Und wenn das
das gesuchte Element ist, dann bin ich fertig mit der Suche.
Dann habe ich gefunden, was ich suchen wollte. Andernfalls,
wenn denn das gesuchte Element größer ist als das erste Element dieser Liste,
dann muss ich in der Restliste gucken,
ob ich dort das Element finden kann
Und der interessante Fall, der ist eben genau hier nicht aufgelistet,
das ist der, wenn weder das erste noch das zweite gilt,
dann ist eben das Element weiter hinten in der Liste und dann kann ich abbrechen,
dann muss ich gar nichts mehr tun.
Und deshalb gibt es auch keine Klausel dafür, für den dritten Fall. Das ist also ein einfacher Guard,
der hier eingebaut ist und dem werden wir im Weiteren des Öfteren wiederfinden,
dass wir eben immer gucken,
wo befinden wir uns in der Liste eigentlich,
an welcher Stelle und so etwas wie Quicksort und so weiter,
basiert ja genau auf dieser Idee,
dass ich eben eine Liste,
eine sortierte Liste aufteile in die Elemente,
die kleiner sind als ein Split-Element und die Elemente,
die größer sind
als ein
Also die Idee werden wir des Öfteren wieder gebrauchen.
Eine Brutfonds-Variante des Sortierens ist durch Permutieren. Das ist hier nicht so sehr drin,
um zu zeigen, dass man damit effizient sortieren kann.
Es ist im Gegenteil hochgradig unökonomisch, was hier passiert.
Es ist mehr oder weniger, um zu zeigen,
wie einfach ich eigentlich so etwas wie,
gib mir alle paar Mutationsvarianten einer Liste programmieren kann.
Es ist eher eine Übung, um zu zeigen,
was es das Ganze kann. Also es ist ein reiner Generator-Test-Algorithmus,
der erstmal alle Permutationsvarianten erzeugt und dann überprüft,
ob nicht zufällig eine davon der Richter ins Ortierreihenfolge ist.
Irgendwann muss sie auch mal vorbeikommen. Der Raum der möglichen
paar Mutationen ist endlich, wenn auch sehr groß.
Gut, auch hier wieder eine sinnvolle Verwendungsweise des Cuts,
werde ich die erste sortierte Variante gefunden habe, dann muss ich nicht weitersuchen.
Eine zweite sortierte Variante wird es nicht geben.
Das kann ich schon vorher wissen. Zu testen,
ob eine Liste sortiert ist, basiert eben einfach darauf,
dass ich sage, eine einer Liste ist immer sortiert.
Das ist der Trivialfall und unser Rekursionsabschluss.
Ansonsten gucke ich mir die ersten beiden Elemente der Liste an,
schaue nach, ob die in der richtigen Sortierreihenfolge sind
Und wenn ja, dann mache ich mit,
der Restliste und zwar nehme ich das erste Element entferne und
die Restliste Y gefolgt von R, dem der Überprüfung und der Ziehe.
Und auf die Art und Weise arbeite ich die Liste Stück für Stück
ab und prüfe immer für jedes Paar, ob diese Bedingung erfüllt ist.
Und wenn ja, dann ist die Liste sortiert,
wenn irgendwo die Belegung mal nicht erfüllt ist, dann ist sie nicht sortiert.
Der Kern des Ganzen ist also diese Permutationsvariante und dann sieht man eben,
wie schön einfach und elegant. Das Ganze wird,
ich habe also die leere Liste permutiert, ist wieder die leere Liste.
Ansonsten nehme ich mir über aus der Liste,
die ich permutieren soll, über dieses Select-Element,
das hatten wir uns vor 14 Tagen angeguckt,
nehme ich mir das ein Element heraus und bekomme eine reduzierte Liste.
Und diese reduzierte Liste muss ich natürlich wieder permutieren.
Und anschließend, indem ich das ausgewählte Element und setze es
vor die permutierte Liste wieder davor. Also auch hier wieder,
es passiert nicht anschließend,
sondern als allererstes wird dieses Pattern erzeugt und dann wird für E hineinunifiziert,
was immer hier gewählt worden ist und für R wird hineinunifiziert,
was immer hier gewählt worden ist
Wir müssen uns ja immer an die deklarative Sicht erinnern.
Das ist ja nicht, dass hier irgendwo ein Ergebnis
erzeugt wird, mit dem ich noch etwas mache.
Das Ergebnis steht hier bereits fest. Egal, was immer
hier und was hier passiert. Es wird so aussehen.
Und über den Backtracking-Mechanismus von Select Element,
was also jetzt jedes Element aus der Liste nimmt,
wenn ich das über das Backtracking anfordere, wird jetzt erreicht,
dass alle Elemente mal als erstes Element auftauchen dürfen.
Und für dieses, für jede Variante,
erstes Element ist eines der Listenelemente,
wird die Rest oder die übrigbleibende Liste nach entfernen dieses Elements,
wieder der Permutation unterworfen
Und dann sehen wir eben, dass genau das passiert.
Als allererstes wird A durchs Select-Element herausgeholt und die Liste BC wird permutiert.
Und damit kriegen wir diese beiden Varianten. Dann wird B
über das Backtracking hier herausgeholt und die Liste AC wird,
das ist nämlich das, was übrig bleibt,
wenn ich aus dieser Liste das B entfernt habe,
das wird permutiert und wir kriegen wieder zwei Varianten.
Und als letztes wird C aus der Liste herausgeholt,
übrig bleibt die Liste AB und die wird wieder durch Parmutieren weiterverarbeitet.
Gut, also das nur ein kleiner Fingerübung in Listenverarbeitung
Wenn ich mit sortierten Listen arbeiten will, ist natürlich auch
immer die Frage des Einfügens in eine sortierte Liste entscheidend.
Und auf die Art und Weise entsteht eine sortierte Liste,
indem ich immer das neu hinzukommende Element auf an die richtige Stelle einfüge.
Und der Kern hier ist also dieses Insert in eine Liste,
in eine sortierte Liste, dieses Prädikat,
was eben jetzt hier eingebaut wird,
indem ich eine Liste rekursiv abarbeite und immer das erste Element abtrenne,
die Restliste wieder sortiere und anschließend das abgetrennte Element in die sortierte Liste einfüge.
Der ganze Sortier, Aufwand, passiert also in diesem Instant Earl,
in dem ich also die Liste durchgehe und das Element an
der Stelle fallen lasse, wo es nach dem Sortierreihenfolge hingehört.
Und das Ganze mache ich eben, bis diese Liste leer ist.
Also der entscheidende Punkt ist hier wieder,
was passiert in der eingebetteten, in dem eingebetteten Prädikat.
Und hier haben wir jetzt tatsächlich diese Dreierstruktur,
die typisch ist für alle Sortieraufgaben. Wir haben die Frage,
das Ding ist gleich, das Ding ist größer oder
das Ding ist kleiner
Achso, nein, sorry, das ist der, hier,
das Ding ist entweder größer oder es ist kleiner gleich.
Diese beiden Fälle werden hier nur unterschieden,
das hier ist der Rekursionsabschluss für das Durchgehen der Liste.
Also ich habe hier eine Liste gegeben,
auf der zweiten Argumentstelle, wenn die leer ist,
dann ist die erweiterte Liste durch Nacheinfügen dieses Elements die Liste,
die nur eh enthält.
Das ist der Rekursionsabschluss. Und ansonsten wird eben hier
immer ein Element abgetrennt und es wird geguckt,
ob das einzufügende Element größer ist,
als das erste Element der Liste.
Dann gehört das einzufügende Element in die Restliste hinein
DSR und es wird eine neue Restliste erzeugt,
die dann wieder mit dem hier abzutrennenden Element als erstes auftritt.
Also ist das einzufügende Element größer als das erste Element.
Dann sieht die Ergebnisliste so aus,
dass das erste Element wieder das hier vorgefundene Element ist und
eine Westliste hier eingefügt wird und in dieser Restliste muss das einzufügende Element drinstehen.
Das wird dadurch erledigt. Und im umgekehrten Falle,
wenn es denn kleiner oder gleich ist als das erste Element der Liste,
dann wird es eben an diese Position in die Liste eingefügt
Und die Verarbeitung ist beendet.
Es verfolgt keinen Rekurs, kein rekursiver Aufruf mehr.
Also, wir finden ein Element vor. Wir stellen fest,
das einzufügende Element ist kleiner oder gleich als das erste Element hier.
Also muss das Element vor, das hier vorgefundene Element davor.
Also das Listenpad, dann schiebe es als erstes Element in die Ergebnisliste hinein.
Und du bist fertig. Ich kann auch natürlich immer umgekehrt vorgehen.
Ich gehe davon aus, ich habe eine unsortierte Liste.
Ich suche mir immer das minimale Element dieser Liste
heraus und schiebe es an den Anfang der Liste
Das ist auch eine Möglichkeit zu sortieren. Also zum Sortieren kann man
unterschiedlichste Ideen umsetzen. Das sind alles nicht die effizientesten,
zu den effizientesten kommen wir natürlich zum Schluss. Das kann man sich leicht ausrechnen,
wie viel Aufwand hier getrieben werden muss,
indem ich also immer durch die Liste durchgehe,
wieder das minimale Element heraussuche,
es an den Anfang der Liste stelle,
auch das Herausnehmen aus der Liste des minimalen Elements ist ja bereits wieder mit rekursivem Aufwand verbunden.
Und ja, in diesem Falle brauche ich also ein Prädikat,
was mir das minimale Element einer Liste liefert.
Ich selekt soll andeuten, es wird dort herausgenommen aus der Liste.
Und dann muss die Restliste, die dadurch übrig bleibt,
natürlich sortiert werden. Und die Ergebnisliste setzt sich jetzt aus
dem minimalen Element als erstem und der sortierten Restliste zusammen.
Gut, und das wird wieder gemacht,
bis sie bei der einer Liste angekommen,
wenn die einer Liste kann ich nicht weiter sortieren.
Also ist das ein vernünftiger rekursiver Abschluss. Nachteil dieser Lösung
ist natürlich, dass ich eine leere Liste nicht sortieren kann.
Wer immer eine leere Liste sortieren möchte,
der muss den Rekursionsabschluss hier entsprechend anpassen
Aber das ist wieder aufgabenspezifisch, wie man das definieren will.
Macht es Sinn, eine leere Liste zu sortieren oder nicht?
Das hängt von der Verwendungsumgebung dieses Prädikats ab.
Und nicht von dem. Da muss man einmal
eine Festlegung treffen und dann dabei der bleiben. Gut,
also dieses Auswahl des Minimums ist eben, suche erstmal das
Minimum in der Liste und dann wähle dieses Element aus.
Und dieses Auswählen des Elementes ist das,
was wir schon bisher hatten, das Select-Element recycelt.
Wir könnten natürlich auch diese beiden Dinge in eins zusammenfassen und
nur einen rücken
Könnten wir? Nein, könnten wir nicht.
Wir wissen ja, an der Stelle noch gar nicht,
ob es jemals das minimale Element sein wird.
Dieses Minimum hier ist nichts anderes als das, was heutzutage Mindelist in SPI-Prolog heißt,
ich habe es hier nochmal auf der Folie drauf.
Das macht aber nichts anderes als dieses, also das minimale Element,
eine eine Liste ist immer das einzige Element dieser Liste.
Und ansonsten gehe ich wieder mit diesen beiden Fällen durch,
bis ich dann sagen kann, welches tatsächlich das minimale Element ist.
Das heißt also, ich nehme immer das ein,
Habe eine Hypothese über das Minimum, nein, umgekehrt.
Ich nehme das erste Element dieser Liste ab. Ich trenne es ab,
ich berechne das Minimum rekursiv der Restliste und schaue dann anschließend,
ob das Minimum, was sich in der Restliste gefunden hat,
kleiner ist als dieses erste Element hier.
Wenn das der Fall ist, dann ist dieses Minimum,
was ich hier gefunden habe, das gesuchte Minimum und im anderen Falle,
wenn das nicht der Fall ist, ist dieses erste Element das Minimum.
Das sind die beiden Fälle, die ich unterscheiden muss.
Gut, das ist, wie gesagt, eingebaut typischerweise,
da muss man nicht selbst dran
Bubbles Hot ist also die Sortieridee immer,
ich gucke mir die ersten beiden, nein,
ich gucke eine zwei benachbarte Elemente in der Liste an und schaue,
ob sie in der richtigen Sortierreihenfolge sind, wenn nein,
wir vertauschen sie ihre Plätze und das Ganze wird Recosin wieder aufgerufen.
Das heißt also, ich muss erst mal die Liste zerlegen in ein Anfangsstück,
in ein Endstück und zwei Elemente irgendwo mitten raus.
Natürlich kann das anfangs oder das Endstück auch leer sein und dann habe ich
die Elemente eben am Anfang oder am Ende der Liste gegriffen.
Und so dieses Zerlegen, und das ist eine schöne Verwendungsweise des Appens,
da kann ich jetzt, das Appent nämlich benutzen,
für eine gegebene Liste und gib mir bitte alle diese Zerlegungsvarianten.
Das ist die Verwendungsweise, wo wir bei der Behandlung des Sepens noch
ein bisschen ratlos davor gestanden haben, wofür brauche ich das eigentlich jemals.
Hier sieht man, wofür man es braucht. Da sieht man erstmalig,
dass, ja, wahrscheinlich nicht erstmalig, aber hier sieht man,
dass die richtungsunabhängigkeit durchaus mit Vorteilen verbunden ist. Ich rufe das,
erpennt also mit einem Instanzierten L auf, das ist die Liste,
die zerlegt werden soll und ich kriege dann ein Anfangsstück,
ich kriege ein R,
das ist die Restliste und ich kriege zwei Elemente
X und Y,
die ich jetzt auf Sortierreihenfolge vergleichen kann
Und da schaue ich eben nach, ob die in der richtigen Sortierreihenfolge stehen.
Und wenn X größer ist als Y, dann sind sie es offensichtlich nicht.
Dann muss ich sie in der umgekehrten Reihenfolge wieder in die Liste einfügen. Und das ist,
wir sehen, der Aufruf ist praktisch identisch,
nur dass ich hier eben mal ein 11 vorgegeben habe,
dann alles, was ich hier daraus selektiert habe aus dem,
das ist ein Selektoraufrufe, die ich hier mache,
die werden hier als Konstruktoraufrufe wieder verwendet.
Ich nehme dieses Anfangsstück, ich nehme diese Restliste,
die hier übrig bleibt und ich nehme die beiden Elemente nur,
dass ich eben jetzt ihre Reihenfolge vertauscht habe.
Und da sieht man eben sehr schön,
wie durch diese Pattern-Darstellung glasklar wird, was ja eigentlich passiert.
Dass man so schon grafisch erkennen kann, was ist eigentlich
die zugrunde liegende Implementierungsidee. Nur der einzige Unterschied ist,
dass ihr jetzt eben hier bei dem Konstrukt dann natürlich eine andere Liste rauskommen soll,
als das, was ich hier ursprünglich mal reingesteckt habe.
Und diese neue Liste muss ich jetzt eben wieder über
die Regussion dem Bubble unterwerfen. Was würde passieren,
wenn man dieses Sword Stree mit unterstützifizierter Liste.
Gute Frage. Also beides unterspezifiziert, ja?
Zum Beispiel, oder jedenfalls erste,
wo das Plus angeht
Das würde, glaube ich, hier schon scheitern.
Er muss ja immer überprüfen, ob die Liste,
die durch Bubblen entstanden ist, bereits sortiert ist oder nicht.
Das passiert hier an dieser Stelle. Und wenn,
wenn sie denn sortiert ist,
dann wird per Co-Referenz auf die zweite Argumentstelle das Ergebnis übergeben.
Wenn nicht, dann wird das Ganze hier legosiv aufgerufen.
Und er würde hier bereits versacken,
denn an irgendeiner Stelle muss er diesen Größenvergleich...
Achso, in der Erhalt der Größenvergleich,
der sollte auch nicht uninstanziert sein.
Irgendwann läuft er in einen Größenvergleich hinein und
an dieser Stelle würde er aussteigen mit Fehlern
Ja, der große vielleicht auch variabler. Ah, ja,
natürlich, das ist ja, ja, guter Hinweis.
Da habe ich nicht dran gedacht, ja. Wie immer
, ja. Sollte man durch Zusicherung ausschließen, ja,
das ist der generalisierte Vergleich, der auch uninstallziierte Variablen in die Sortierreihenfolge einbaut.
Und dann würde er an dieser Stelle diese beiden Bayablen miteinander vergleichen.
Das hängt auch noch davon ab, ob es installziiert ist
oder nicht installsiert ist. Hochgradig spannend. Okay,
da wage ich jetzt nicht irgendwie eine Hypothese abzugeben,
Also hier ist unser Sortiertprädikat, das habe ich nur noch mal gesucht,
was wir vorhin benutzt haben.
Und da würde er eben jetzt hier auch in diesem Vergleich hineinlaufen.
Für, da er die Liste nicht kennt,
kann er auch X und Y nicht kennen,
dann würde er also dieses immer mit Ja beantworten,
denn die Vayable X ist natürlich kleiner als die Vayable Y.
Er würde sagen, das Ding ist sortiert und würde
das Ganze mit einem uninstanztierten Pattern dieser Art aufrufen
Das unifiziert vermutlich mit diesem hier, da spricht nichts dagegen.
Das kann ja auch die Einerliste sein. Damit würde er
in jedem Falle sagen, das Ding ist sortiert.
Und, warte? Wegschrecken wir das zweite Mal,
wo ich laute, kommt wahrscheinlich längere Liste. Ja,
also ich meine, wir können ja mal,
wenn wir schon beim Experimentieren sind, ach so,
jetzt brauchen wir das sortiert natürlich irgendwo her,
da bin ich mir jetzt nicht sicher,
ob ich das finden kann.
Das lassen wir
Aha. Nein, leider nicht. Gut, das lassen wir jetzt mal lieber.
Aber das können wir mal ausprobieren, was da passiert.
Das ist ein interessanter Fall. An den habe ich überhaupt
nicht gedacht. Aber ich bin wenigstens so schlau gewesen,
dass ich hier bei dem sortiert hingeschrieben habe,
dass es auch bitte schön instanziert sein möchte.
Okay, wenigstens das kann ich zu meiner Entschuldigung anbringen. Hups,
die Frage, ob ich das auch konsequenterweise hier gemacht habe,
ja, ich habe auch ein Plus hingeschrieben,
Aber die Frage, was passiert eigentlich, wenn die Zusicherung
verletzt sind? Die ist natürlich immer interessant. Okay,
ich gucke mir das mal in Ruhe an und
dann können wir uns vielleicht noch mal darüber unterhalten.
Ja, so ein Ideen muss man erstmal kommen, ja.
Ich habe es ja auch an der Stelle gesagt,
die einzig sinnvolle Verwendungsweise, die ich kenne für diesen generalisierten Vergleich,
ist lexikalische Sortierung überprüfen.
Und das hatte ich hier im Sinn, ja. Also auf die Idee,
dass das natürlich auch bei uninstanzierten Bajablen problemlos Yes sagt,
dass auf die Idee bin ich nicht gekommen. Gut,
Quicksort, dann als das als Beispiel für das,
was wirklich effizient funktioniert,
wo man also mit Logarithmischem Aufwand über die Bühne kommt,
Die Idee ist ja immer, ich zerlege eine Liste in zwei Teile.
Ich sortiere die beiden Teile separat und baue sie anschließend wieder zusammen.
Und jetzt steigt er aus. Gut, also,
das sieht man hier, ich splitte eine Liste auf in
einen vorderen Teil und einen hinteren Teil und ein Mittenelement.
Das sind die vier Argumente, also gegeben eine Liste
Gib mir zurück, ein vorderes Element, ein Mittenelement,
gib mir denen vordere Teilliste zurück, gib mir die hintere Teilliste zurück.
Und dann sortiere ich eben rekursiv mit dem gleichen Ding wieder
den vorderen Teil und kriege einen sortierten vorderen Teil.
Ich sortiere den hinteren Teil und kriege einen hinteren Teil,
also wem es besser gefällt,
der nennt es linke oder rechten Teil,
aber ich bin von der Liste ausgegangen,
dann ist es eben der vordere Teil der Liste oder der hintere Teil.
Und anschließend muss ich die Ergebnisse zusammenbauen,
indem ich also den vorderen sortierten Teil mit dem hinteren sortierten Teil verkette,
aber in der Mitte noch dieses Mittenelement wieder hinzufüge.
Und damit kriege ich diese sortierte Liste,
die ich als Ergebnis haben möchte
Auch hier wieder ein Beispiel dafür, verzweigende Rekursion,
ja, dieses Sword-Coup verzweigt einerseits in den vorderen Teil,
andererseits in den hinteren Teil, einen Pattern,
was wir bei Baumsuche immer wieder finden werden.
Wir müssen irgendwas mit dem linken Teilbaum machen,
wir müssen irgendetwas mit dem rechten Teilbaum machen und deshalb verzweigt die Rekursion.
Und anschließend muss ich noch eine Berechnung durchführen.
So wie es hier implementiert ist, klarer Fall von Nicht-Entrekursiv.
Aber auch das wird ein Beispiel sein,
was wir uns da noch angucken müssen,
ob wir da nicht das auch Ad-Rekursiv hinbekommen können.
Gut, das war die Rekursionsschritt,
der Rick und Abschluss ist natürlich klar,
wenn ich die leere Liste sortieren soll,
dann ist es immer wieder
die leere Liste
Ja, wir brauchen einen Split-Prädikat,
was uns die Liste zerlegt und auch hier wieder der triviale Fall,
also eine leere Liste aufgeteilt,
ist eben für ein beliebiges Mittenelement wieder zwei leere Listen,
da kann man nicht viel dazu sagen.
Ansonsten müssen wir eben immer die Elemente in die beiden Varianten sortieren,
sind sie kleiner oder sind sie größer als das Split-Element,
wenn sie denn kleiner als das Split-Element sind,
dann kommen sie in die vordere Liste hinein,
wenn sie denn größer sind,
kommen sie in die hintere Liste hinein
Also, ich nehme immer das erste Element der Liste.
Ich habe das Mittenelement gegeben. Ich mache den Größenvergleich mit
dem Mittenelement und rufe das Ganze eben rekursiv auf,
um die dann hier übrig bleibende Restliste genauso zu behandeln.
Und was immer, vordere Liste und hintere Liste
hier aus dieser Rekursion herauskommt, wird hier weiterverwendet.
Die hintere Liste wird unverändert übernommen. Die vordere Liste wird ergänzt,
um das einzufügende Element, weil es eben kleiner ist als das Mittenelement,
drin in die vordere Liste.
Und das ist der umgekehrte Fall. Hier wird eben,
ist das einzufügende Element größer, als das Mittenelement.
Also kommt es in die hintere Liste und wird hier mit dem,
was aus der Rekursion herauskommt,
verbunden als Ergebnis präsentiert
Gut, und das kann man jetzt eben testen,
splitte mir diese Liste hier auf mit dem gegebenen Mitten-Element D
und dann kriegt man eben alle kleineren Elemente,
die hier vorn angegeben sind und alle größeren Elemente,
die da hinten als in der Liste aufgesammelt sind.
Und auf die Art und Weise habe ich also meinen Quicksort.
Wie gesagt, wir gucken uns das dann nochmal mit einer alternativen Implementation als Differenzlisten an.
Man kann natürlich auch sortieren, indem man einen Baum aufbaut,
einen sortierten Baum, wo die Verzweigung immer ein Binärbaum,
wo immer der linke und der rechte Teil, warum sich dahingehend unterscheiden,
ob sie denn größere oder kleinere Elemente als das Mitten-Element enthalten
Und ich wandle also eine Liste auf die Art und Weise
in einen binären Suchbaum um und anschließend klopfe ich diesen Suchbaum
wieder platt auf eine sequenzielle Liste und dann wird diese Liste sortiert sein.
Hört sich ein bisschen aufwendig an, ist aber nicht viel
aufwendiger als das Quicksort auch und ist ein schöner Einstieg dahin, dass wir jetzt mit Suchbäumen umgehen können.
Etwas, was man in vielen Anwendungen tatsächlich braucht. Also erster Schritt ist,
ich wandle eine Liste in einen Baum um und da sehen wir schon jetzt die Idee,
sehr, sehr ähnlich zu dem ist,
was wir beim Quicksort schon gesehen haben,
wir spalten die Liste auf mit einem mit einem Mittenelement und erhalten einen vorderen Teil,
nein Quatsch, doch wir spalten das auf,
wir mit einem Mittenelement, wir kriegen einen vorderen Listenteil,
einen hinteren Listenteil.
Und dann müssen wir eben den vorderen Listenteil in einen Baum
umwandeln und den hinteren Listenteil in einen Baum umwandeln.
Und jetzt müssen wir den aus diesen Bäumen, die hier rauskommen,
einen Baum konstruieren. Und das ist der entscheidende Punkt.
Dafür brauchen wir eine Baumrepräsentation. Und ich habe als Baumrepräsentation eben diese dreistellige Struktur gewählt,
die mir immer sagt, ich bin ein Baumknoten,
ich habe Element, was an der einen Spitzenknoten,
der ein Label trägt, das ist der Wert, der Variablen E.
Und ich habe einen linken Teilbaum und einen rechten Teilbaum hinter mir unter mir.
Und genau das ist das, was hier ausgedrückt ist.
Ich bin das, die Knotenbeschriftung am Spitzenknoten.
Ich bin der linke Teilbaum unterhalb dieses Knotens.
Ich bin der rechte Teilbaum unterhalb dieses Knotens. Und dann sehen wir eben,
dass der linke Teilbaum,
der hier erzeugt worden ist als linker Teilbaum hier eingebaut wird und der rechte Teilbaum,
der hier oder der hintere Teil, warum der hier erzeugt worden ist,
wird hier als hinter, wird hier in diesen Knoten eingebaut.
Gut, mehr ist damit eigentlich nicht zu sagen.
Das Wesentliche ist, dass hier,
wenn ich denn hingehe und diese Liste umwandeln soll,
wird das erste, das erste Element der Liste als Mittenelement gewählt.
Das hat wesentlichen Einfluss auf den Baum, der daraus entsteht.
Nämlich in welcher Hinsicht. Also was sind die Extremfälle,
die ich hier betrachten muss?
Die Liste bereits sortiert ist dafür, dass das der
linke Teilpunkt
immer leer ist
Genau, ja, das ist genauso, das hängt,
ist beim Quickshot in ähnlicher Weise gegeben,
wenn ich mich genauso unintelligent anstelle wie hier.
Man kann das natürlich anders machen. Aber wenn die Liste bereits sortiert ist,
dann wird das erste Element immer kleiner sein als alle anderen Elemente.
Das heißt, alle anderen Elemente kommen in den rechten Teilbaum
hinein. Dann kriegen wir einen entarteten Baum,
der dann eben so aussieht und so weiter.
Und der hat natürlich bezüglich des Suchverhaltens keinerlei Gewinn mehr.
Der ist nicht balanciert, wenn er den als Suchbaum verwenden wollten
, dann können wir genauso gut Das ist vollständig äquivalent,
linear durch diese Liste durchgehen, bis wir das gesuchte Element gefunden haben.
Was wir haben wollen, ist natürlich ein balancierter Baum,
wo die Zweige einigermaßen gut mit ähnlich vielen Elementen belegt sind.
Und wann kriegen wir einen balancierten Baum? Was bei der Teleform läuft,
das heißt ja eigentlich,
dass sich die Duell des Links und des rechten Teilbaus jetzt mal eins unterscheiden,
das heißt, wo es nun ein geschicktes Element in die Mitte wählen.
Okay, also aus allen Gründen und Datenstrukturen weiß ich jetzt,
dass es verschiedene Methoden gibt.
Ja, und was wir uns machen?
Eine wäre zum Beispiel
das Perzuper auszuwählen
Der andere wäre jedes Mal, wenn man den Baum zu balancieren.
Okay, ja gut, dritte Variante, die kommt,
glaube ich, auch noch irgendwo, ist,
nee, die habe ich in der Übung,
in der Musterlösung für die Übung verwendet.
Dritte Variante ist, ich sortiere, die, das macht hier natürlich keinen Sinn.
Gut, die dritte Variante gibt es in diesem Fall nicht,
weil ich ja gerade sortieren will. Dritte Variante wäre,
wenn ich an dem Baum tatsächlich interessiert bin,
ich sortiere den Baum und teile in den zwei Hälften auf.
Aber das macht hier keinen Sinn, weil ich mich dann im Kreise drehe.
Um zu sortieren, sortiere ich und das will man natürlich nicht.
Aber es ist richtig und eine Antwort zufällige Auswahl ist natürlich auch,
wenn ich davon ausgehen kann, dass die Liste zufällig ist, sortiert ist.
Also die aus einer Quelle bekomme, wo einigermaßen begründet ist,
dass dies eine zufällige Auswahl ist oder eine zufällige Reihenfolge ist,
dann kann ich eben so simpel vorgehen,
dass ich das erste Element nehme und als Mittelelement etabliere.
Aber da muss ich mir relativ sicher sein,
dass da nicht irgendwelche verborgenen Gesetzmäßigkeiten drin sind,
die auch dazu führen,
dass tendenziell die kleinen Elemente immer vorn stehen und tendenziell
die großen Elemente immer hinten stehen oder umgekehrt
Gut, ja, wir brauchen noch einen speziellen Ende-Marker in unserem Knoten,
wo wir sagen können, hier gibt es keine Unterknoten mehr.
Das ist ja immer ein Baumknoten, der eine Verzweigung ankündigt.
Ich bin ein Knoten mit einem linken Teilbaum unter mir,
mit einem rechten Teilbaum unter mir.
Wir brauchen noch einen expliziten Ende-Marker, der sagt,
hier ist der Baum zu Ende, da kann man irgendetwas nehmen.
Ich habe einfach dieses Schlüsselwort End jetzt genommen. Das heißt also,
wenn jetzt hier irgendwo End drin steht, heißt das,
es gibt keine weiteren Verzweigungen mehr unterhalb hier
des Knotens
Gut, und damit ist auch klar,
wenn ich eine leere Liste umzuwandeln habe,
dann ist der Baum, der dem entspricht,
einfach der Marker, ich bin am Ende,
da gibt es keine Knoten, die weiter da sind.
Und alles andere ergibt sich dann aus diesem Umwandlungsprozess hier.
Das eben für jeden neu hinzukommenden Knoten,
allen solcher Knoten in den Baum eingefügt wird.
Gut, das können wir uns hier angucken. Im Beispiel,
wir geben, haben eine Liste gegeben,
wir sehen, sie ist schön unsortiert,
wir kriegen also als Spitzelelement des Baumes
den Knoten D heraus
Und müssen dann die restlichen Knoten einsortieren,
ob sie nach vorn oder nach hinten kommen,
also in den linken Teilbaum oder in den rechten Teilbaum,
C-A und B werden in den linken Teilbaum kommen,
E wird in den rechten Teilbaum kommen.
Wir können natürlich bei so einem kleinen,
kleinen Sample nicht erwarten, dass der Baum perfekt balanciert ist.
Das wäre ein Wunder, wenn das passiert. Also wir kriegen einen Baum,
sorry, der hat D als Spitzenknoten,
darunter verzweigt es sich einerseits in diesen Teilbaum hier und andererseits in diesen Teilbaum hier der Teilbaum links enthält,
ein C als Spitzenknoten und verzweigt sich darunter weiter in ein A als Spitzenknoten und einen,
unterhalb des As ist es,
der Ende-Marke erreicht unter rechtsunterhalb des As gibt es ein B zu finden.
Rupst? Moment, jetzt habe ich mich, das sollte
eigentlich nicht sein. Da habe ich mich also D,
dann kommt C, unterhalb von C haben wir
ein A und rechtsunterhalb
des As
hängt das
Das war eine Etage zu weit oben eingetragen. Denn das
wäre ja auch kein schöner sortierter Baum, der bei rauskommt.
Das wollen wir nicht haben. Und unterhalb des Bs geht es nicht weiter.
Das ist eigentlich Blattknoten. Unterhalb des As ist dir das B,
dann das C, da gehört das End dazu.
Da geht es auch nicht weiter. Und unterhalb der SDs
haben wir noch einen rechten Teil, warum da steht ein E drin,
der dann sich nicht weiter verzweigt.
Das ist der Baum, der dabei herauskommt. Und wir sehen,
dieser Baum ist also schön sortiert, wenn wir da Ihnen mit Links-Order traversieren,
dann kriegen wir tatsächlich die Sortierreihenfolge a, b, c.de heraus.
Und genau das müssen wir jetzt noch implementieren, dass wir
diesen Baum nehmen und jetzt wieder in eine Liste umwandeln.
Ach so, sorry, wir sind ja noch gar nicht am Ende.
Wir haben als Alternative dazu Ja, gucken uns erstmal,
erstmal an, wie wir den Baum wieder in eine Liste umwandeln können.
Und da haben wir wieder dieses einfache Pattern,
wenn denn wir am Ende Marke angekommen sind, ist es die Lehreliste.
Wenn wir einen Knoten haben, der verzweigt,
dann müssen wir eben den linken Teilbaum in die Liste umwandeln,
den rechten Teilbaum in die Liste umwandeln, das Ganze zusammenfügen,
mit Einbau des Mittenelementes, was am Spitzenknoten hängt.
Und damit haben wir wieder dieses klassische Rekursionsschema.
Und dann können wir also den Baum auf die eine Weise
erzeugen und auf die andere Weise verarbeiten.
Ich gehe jetzt nochmal kurz zurück, wir hatten dies zum Baum erzeugen.
Wir gucken uns jetzt eine Alternative Variante zum Baum erzeugen an,
wo wir eine Verarbeitungsmodus uns ansehen,
der für viele Zwecke sehr nützlich ist,
die ja das vorherige Beispiel zum Baum aufbauen,
basiert ihr immer darauf,
dass sich eine Liste gegeben habe und die Liste vollständig in den Baum umwandle,
indem ich also die Listenelemente rekursiv abarbeite,
immer nimm das erste Listenelement und verarbeite das Ganze dann entsprechend
Ich kann hier also nur in einer Art Batch-Prozedur aus der Liste den Baum erzeugen.
Für viele Zwecke hat man aber einen anderen Verarbeitungsmodus gewünscht,
wo man eine Datenstruktur haben möchte, die man inkrementell erweitern kann,
wo zusätzliche Elemente zu jedem beliebigen Zeitpunkt hinzukommen können und
die möchte ich dann in den Baum eingebaut haben.
Das ist die Basis für die zweite Verarbeitungsweise.
Hier gibt es also jetzt eine Prozedur, die heißt Intri,
die nimmt immer einen alten Baum und wandelt ihn in einen neuen Baum um.
Das ist immer die Idee. Ich habe bereits
einen Baum und ich möchte einen neuen Baum daraus erzeugen.
Das ist etwas, was wir in der Übung
auch benutzen wollen
Wenn wir jetzt in den Übungen dazu kommen,
dass wir mal ein einfaches Dokumentenretriebel-System uns bauen.
Dann wollen wir also den Index, der den Zugriff zu
den Dokumenten ermöglicht, jederzeit durch neue Texte erweitern können.
Und das ist hier der ähnliche, hier will ich also
jederzeit neue Knoten in einen existierenden Baum einbauen können.
Und dazu braucht man also ein bisschen anderen Ansatz.
Hier wird also jetzt die Liste erst beim rekursiven Aufstieg umgewandelt.
Ich gehe also rekursiv durch die Liste durch und baue die Elemente ab.
Und für jedes Element, was ich abgebaut habe,
rufe ich diese Prozedur Intri auf,
den gegebenen Baum nimmt und das neue Element in diesen gegebenen Baum einbaut.
Und das heißt natürlich, nur noch,
wenn ich ein Element in der Liste nur habe,
dann muss ich daraus einen trivialen Baum erzeugen,
der nämlich genau dieses Element als Spitzenknoten hat und darunter zwei Endemarker und sagt hier,
dass ich bin der Einzelknoten dieses Baumes.
Und dann habe ich einen solchen Baum und kann jetzt hier im rekursiven Aufstieg das nächste Element,
was davor gestanden hat, in diesen Baum einbauen,
indem ich diesen Baum zerlege,
in einen Spitzenknoten,
einen voren Teil und einen hinteren Teil und über diese Prozedur,
dann einen
neu strukturierten Baum erzeugen kann
Wir gucken uns Intri, da passiert natürlich,
dass die ganze Magie, das gucken wir zum Wesentlichen an.
Erstmal illustrieren, wie es tatsächlich funktioniert. Ich nehme jetzt also,
ich baue diese Liste rekursiv ab, bis ich bei dem B angekommen bin.
Und wenn ich bei dem B angekommen bin, erzeuge ich einen Teilbaum,
der B zum spitzen Knoten macht und zwei Endemarker unten drunter hat.
Also die Welt sieht jetzt ein bisschen anders aus.
Wir erzeugen einen Baum,
der so aussieht
Hier habe ich Striche gemacht, aber das soll äquivalent sein zu den Strichen.
Damit habe ich einen Baum gegeben. Damit habe ich
einen Baum gegeben. Ich muss jetzt das Element,
was vor dem B gestanden hat, in diesen Baum einfügen,
über diese Prozedur. Und dazu muss ich gucken,
ob es kleiner oder größer ist als das B.
Es ist ein E, es ist größer als diese Prozedur,
eher als das B, als der Spitzenknoten.
Und deshalb wird dieser Endknoten jetzt hier ersetzt durch das neue,
das Element, was davor steht,
das ist das
Dann komme ich zu dem Element, was davor gestanden hat,
das ist ein A und an der Stelle wird jetzt dieser Teilbaum ersetzt.
Was soll ein A sein? Davor stand ein C und
das C wird jetzt, ist größer als dieser Knoten,
kommt also in den rechten Teilbaum, ist kleiner als dieser Knoten,
kommt also in den linken Teilbaum. Das heißt,
jetzt wird dieser Knoten ersetzt durch den Knoten C,
davor kommt der Knoten D, der ist größer als B,
er ist kleiner als E,
aber größer als C,
also gehört er in diesen rechten Teilbaum
unterhalb von 10 hinein
Und das sollte jetzt tatsächlich der Baum sein, der hier unten angegeben ist.
Wir haben den Spitzenknoten B, wir haben einen linken Zweig,
der enthält nur A und keine weiteren Teilbäume darunter. Der hat einen rechten Zweig,
der enthält E und der rechte Zweig verzweigt sich wieder nach links mit dem Knoten C,
nach rechts unter dem Knoten nach links,
unter dem Knoten C ist Schluss,
aber nach rechts ist unter dem C ein D-Knoten noch drunter.
Das ist der hier unten. Und der Knoten E,
der hier aufgemacht wird, der hat hier seinen rechten Teilbaum,
der darunter steht,
das ist auch der leere Teilbaum
Also auf diese Art und Weise sehen wir, dass der Baum ein bisschen anders aussieht,
weil er jetzt eben sozusagen die Liste von hinten nach vorn abarbeitet.
Er geht erst rekursiv in die Tiefe,
bis er am Rekursionsabschluss angekommen ist, beim letzten Element.
Und von dort aus baut er den Baum sozusagen Top-Down nach unten auf.
Und dieses Intry müssen wir jetzt also implementieren. Wir kriegen,
wir sollen einen, im einfachsten Fall haben wir,
das haben wir ja am Anfang gesehen,
wir sollen ein Element in einen leeren Baum einfügen,
also der Baum, den wir bekommen, ist leer,
der enthält nur diesen Endemager,
wir kriegen ein Element und wir sollen daraus einen neuen Baum produzieren und das ist trivial,
indem wir einfach einen Knoten aufbauen und sagen,
du bist mein Spitzenelement und unten drunter steht nichts.
Wenn wir denn bereits einen Baum vorliegen haben mit einem linken und einem rechten Teilbaum,
dann müssen wir gucken, ob der Knoten,
den wir einfügen soll,
kleiner oder größer als der jetzige Spitzenknoten ist und wenn er denn kleiner ist als der Spitzenknoten,
dann kommt der in den vorderen Teilbaum hinein.
Es wird ein neuer vorderer Teilbaum aus dem Alten,
der noch das End war,
aus dem Alten wird ein neuer Teilbaum erzeugt und dieser neue
Teilbaum wird in den in das Ergebnis hier hinein udifiziert.
Das ist genau diese Operation gewesen, nimm diesen Knoten hier
raus und ersetze ihn durch diesen Teilbaum. Und umgekehrt eben,
wenn denn das einzufügende Element größer ist als der Spitzenknoten,
dann kommt es in den hinteren Teilbaum oder in den rechten Teilbaum hinein.
Und dann wird der hier erweiterte rechte Teilbaum Teil des Gesamtergebnisses.
Während der vordere Teilbaum der Linke unverändert übernommen wird von dem Alten.
Und der Spitzenknoten natürlich auch, das haben sie gesehen,
wir haben an keiner Stelle irgendwann mal den Spitzenknoten verändert.
Der ist immer gleich geblieben. Alles, was wir gemacht
haben, sind Blattknoten auszuzutauschen. Und zwar so,
dass die entsprechenden Elemente an der richtigen Stelle im Baum
platziert werden
Also die ist eine Alternative, Sichtweise, die eben immer,
und das ist das Muster, was da zugrunde liegt,
einen alten Baum nimmt und diesen alten Baum an irgendeiner Stelle,
oder nehmen wir eigentlich eine Datenstruktur allgemein,
irgendeine Datenstruktur nimmt,
diese Datenstruktur manipuliert und daraus eine neue Datenstruktur errechnet.
Und diese neue Datenstruktur steht dann wieder zur Verfügung,
um weitere Elemente hineinzufügen. Das ist also eine Art inkrementelle Herangehensweise,
wo ich eine gegebene Datenstruktur immer erweitere, je nachdem,
ob neue Elemente mit aufgenommen werden sollen oder nicht
Gut, das ist also, ach so,
nur der Beispiel dafür für diesen Einfügen in den Baum,
ich habe hier also ein neues Element und das soll jetzt in diesen Baum hier eingefügt werden,
das ist, glaube ich,
wenn wir das mal vergleichen, dieser Baum,
den wir hier haben und dann wird eben dieses Element hier eingefügt unterhalb des Knotens eh nach rechts.
Also der Algorithmus ist das F, ist größer als unser Spitzenknoten,
deshalb gehört es hier in den rechten Teil, Baum,
es ist auch noch größer als das E,
deshalb gehört es hier in den rechten Teil,
Baum,
dieser Teilknoten hier wird ersetzt durch den neuen Knoten und der
steht hier
Gut, und das Spiel können wir also Ad Infinitum fortsetzen,
dass der Baum kann ja beliebig viele Elemente aufnehmen.
Das hat man schon angeguckt, das ist der umgekehrte Weg,
wie wir jetzt aus dem Baum wieder die Liste organisieren.
Und wenn wir das also jetzt insgesamt,
das ist auch wieder dieser Baum, den wir hier haben,
wenn wir dies jetzt also traversieren,
wir gehen immer in den linken Teilbaum und versuchen daraus eine Liste zu bauen,
das ist hier noch ein Überbleibsel aus früheren Zeiten,
das nehmen wir mal weg.
Wir gehen immer in den linken Teilbaum und versuchen daraus
eine Liste zu bauen und das müssen wir rekursiv machen,
bis wir an den Blattknoten angekommen sind.
Das ist auch noch ein Überbleibsel aus vergangenen Zeiten.
So, jetzt ist es der wirkliche Baum.
Und das ist also hier erstmalig erreicht. Das heißt,
dieses Element kommt als erstes hinein,
ehe wir dann in den rechten Teilbaum mitgehen und daraus eine
Liste machen und aus linkem Teilbaum plus Mittenelement plus rechten Teilbaum
Listen durch Appent eine Teilliste erzeugen, die genau A und B enthält.
Und diese Liste muss dann mit Mittenelement C und der Lehrenliste
per Appent verknüpft werden zu der Liste A, B,
C. Und diese Liste ABC wird dann mit Mittenelement und rechter Teilliste,
die nur E enthält zur Liste A, B,
C, D,
E,
zusammen verkettet
Und hier, das ist also die erweiterte Struktur gewesen,
das habe ich jetzt nicht mehr eingetragen gehabt,
wenn wir also diesen Baum hier, was wir eben gemacht haben,
hier noch durch einen Kloten F ergänzt hatten.
Dann kommt genau das, dann ist das der Baum,
der hier oben steht, der hier unten diese Verzweigung hat,
dass unterhalb des Es im rechten Teilbaum noch ein App steht.
Und wenn wir den reinstecken in die Prozedur,
dann kriegen wir eben genau diese sortierte Liste heraus.
Das ist also eine Linksordertraversierung des Baumes,
die hier gemacht wird
Das war ein Anwendungsbereich, der in der Informatik immer
eine zentrale Rolle spielt, sortieren und suchen.
Jetzt kommen wir zur Mimuisierung. Mimuisierung hatten wir ja vorhin
schon gesehen bei dem Beispiel mit der, wie nominalkoeffizienten Berechnung.
Wir gucken uns jetzt einen anderen Anwendungskontext an, und zwar im Anwendungskontext,
den wir schon hatten, nämlich die Suche in einem Labyrinth.
Und wir können in einem Labyrinth,
wir können ein Labyrinth auch als endlichen Automaten darstellen und der uns sagt,
wie wir von einem Punkt, im Labyrinth zum nächsten Labyrinthen kommen,
zum nächsten Punkt im Labyrinth kommen.
Und wir können Memoisierung verwenden, wenn wir etwa uns nicht nur,
wenn wir uns die Zustandsfolge ausgeben lassen wollen,
wie man von einem Punkt zu dem anderen Punkt kommt.
Und wenn man die Zustandsfolge hat, dann kann man die natürlich sehr schön benutzen,
um zu gucken, ob man sich möglicherweise im Kreise bewegt.
Also Zyklenüberwachung wird dann eines der Themen sein, was wir
hier diskutieren wollen. Wir gucken uns an ganz einfachen,
endlichen Automaten an und der Seiteneffekt, den wir haben,
ist zu sehen, wie simpel es ist,
elementare Strukturen der theoretischen Informatik jetzt hier mit dem Mittel der Logikprogrammierung zu implementieren und damit dann auch spielerisch zur Verfügung zu haben,
wo ich am Programm tatsächlich mir angucken kann,
wie verhält sich das, warum verhält sich das so,
warum verhält es sich nicht anders.
Also endlicher Automat, wenn Sie sich erinnern,
besteht im Prinzip aus Startzuständen, das können mehrere sein,
in unserem Beispiel haben wir nur einen,
markiert durch diesen Kleinen hineingehenden Feier.
Hier, wir haben nur einen Zielzustand markiert durch diesen Doppelkreis
und wir haben Zustandsübergänge, die von einem Zustand zum anderen führen.
Wir müssen das irgendwie mit unseren Mitteln repräsentieren.
Und natürlich werden wir Strukturen dafür verwenden. Wir sagen also,
eine Struktur a von einem Zustand heißt, der Zustand ist Anfangszustand.
e von einem Zustand heißt,
dieser Zustand hier ist ein Endzustand und ein Tee,
das heißt jetzt zufälligerweise auch Tee,
ob wir was eben für Bäume für Treebes benutzt haben.
Hier heißt es jetzt Transitions. Wir haben einen Übergang von
einem alten Zustand zu einem neuen Zustand und am Imitieren eines Symbols.
Das sind also endliche Automaten, wo die Symbole nicht an
den Zuständen dranhängen, sondern an den Übergängen.
Sie wissen ja, es gibt zwei Sorten,
Moor und Mili-Automaten, die aber von ihrer Darstellungsmächtigkeit völlig äquivalent sind.
Wir gucken uns also nur die Variante an wo die Emissionen an den Übergängen passiert.
Man könnte das auch anders implementieren, wenn man es möchte.
Sie wissen vielleicht auch, dass jedem endlichen Automaten ein regulärer Ausdruck entspricht,
der reguläre Ausdruck für diesen Automaten ist hier angegeben.
Also es ist immer ein X,
gefolgt von beliebig vielen Ys oder beliebig vielen Sets und wieder gefolgt von einem X.
Also ich kann über diesen Weg hier ein X emittieren,
dann beliebig viele Sets, inklusive null Sets,
muss wieder ein X emittieren, um in den Endzustand zu kommen.
Und symmetrisch, dazu haben wir hier den Weg über S1,
der also unter Emissionen von beliebigen Pinnen beliebig vielen Ys
beschritten werden kann
Diesen Beispielautomaten können wir jetzt als Faktensammlung darstellen,
indem wir sagen, S-0 ist unser Staatszustand,
S3 ist unser Zielzustand und wir haben diese sechs Zustandsübergänge von S-0
zu X1 und der Emissionen von X und von S-0 zu S2 unter Emissionen von X. Wenn wir denn in S-1 bleiben,
emittieren wir Ys,
wenn wir denn in S2 bleiben,
emittieren wir Z und wir kommen natürlich aus S-1 durch Emittieren
von X wieder in den Zielzustand und von S2 ebenfalls
Und was wir haben wollen, ist jetzt,
dass wir die Zeichenfolge, die dieser Automat erzeugt oder auch akzeptiert,
auch ein endlicher Automat ist, nur eine dikterative Datenstruktur,
die wir sowohl generativ als auch analytisch verwenden können.
Und das ist genau wieder unsere Richtungsunabhängigkeit in der Logikprogrammierung.
Wir können den Automaten oder wir wollen ihn verwenden,
sowohl, dass er uns sagt, ob diese Zeichenfolge,
die wir hier haben, durch den Automaten lizenziert ist.
Oder wir wollen sagen, generier uns doch mal bitte
alle Zeichenfolgen, die du generieren kannst.
Und das repräsentieren wir jedem als Listen.
Und insofern ist es ein Anwendungsbereich für die Listenverarbeitung
Der, das Programm, der Automat, den wir brauchen,
oder das Programm, was wir brauchen,
um den Automaten zu simulieren, ist relativ simpel wieder.
Ich habe es generate genommen, genannt, weil es immer
bei den Automaten eine generative Sicht typischerweise zugrunde gelegt wird.
Aber wie gesagt, wir gucken uns gleich an,
wir können es auch analytisch verwenden. Das ist vollkommen,
ist nur die andere Instanzierungsvariante. Also wenn wir ein Wort generieren wollen,
dann müssen wir beim Staatszustand anfangen, gib mir irgendeinen Staatszustand übers Backtracking,
gib mir alle Staatszustände, generiere dann ein Wort,
was bei diesem Staatszustand anfängt und zu irgendeinem zweiten Zustand führt,
Aber dieser zweite Zustand muss ein Zielzustand sein.
Und dann bin ich fertig. Die ganze Magie passiert jetzt
wieder in diesem Generierungsteil hier. Und das ist etwas,
was wir schon gesehen haben,
das ist nichts anderes als die transitive Hülle über der Erreichbarkeitsrelation,
die wir schon bei der Wegplanung gesehen hatten.
Also, wenn du bereits am Startzustand bist,
wenn der Zielzustand gleich dem Strap-Zustand ist, dann bist du fertig.
Andernfalls suche einen Übergang vom Zielzustand zu einem Zwischenzustand
und versuche von dem Zwischenzustand deinen Zielzustand zu erreichen,
durch rekursiven Aufruf dieses Generierungsprädikats
Der Mehrwert, der jetzt hinzukommt, also das haben wir alle schon gesehen,
das ist nichts Neues, das ist genau das Gleiche,
was wir bei der Wegplanung gemacht haben.
Was hinzukommt, ist jetzt, dass wir hier
auch immer noch auf diesem Argument hier vorn aufsammeln wollen,
auf welchen Zwischenzuständen bist du eigentlich jemals gewesen?
Und wenn wir denn am Ziel ankommen, dann müssen wir
gar nichts machen, dann ist die Liste leer.
Wir haben keinen weiteren Zustand besucht, wir sind bereits am Ziel angekommen.
Und ansonsten nehmen wir immer das emittierte Symbol,
ach so, sorry, das ist nicht die Zustandsliste,
die wir hier sammeln, sondern die emittierten Symbole.
Entschuldigung, war kleiner Aussetzer,
wir nehmen immer das emittierte Symbol an einer Transition
und tragen es als erstes Element in diese Liste ein
Und wenn wir dann diese transitive Hülle bilden, hier den Rekursionsschritt machen,
dann erzeugen wir dabei natürlich eine Liste von emittierten Symbolen.
Und das ist diese Restliste hier,
an die wir jetzt das neue Symbol vorn dran fügen.
Das ist alles, was wir brauchen, dann kann es schon losgehen.
Wir rufen das Ganze jetzt auf und erst mal vollständig instanziert.
Das heißt, wir fragen, ist die Symbolfolge XYX
eine Symbolfolge, die von diesem Automaten erzeugt werden könnte?
Und da kriegen wir eben ein Truh heraus,
es ist der Fall. Wir starten beim Startzustand,
wir nehmen eine Ex-Transition, wir könnten beliebig viele Ys erzeugen,
wir haben hier nur eins erzeugt und wir nehmen wieder eine Ex-Transition,
um im Zielzustand
zu landen
Den letzten Übergang weglassen. Wenn wir uns also noch mal
den Automaten angucken, dann sollten wir sehen,
das ist nicht mehr durch den Automaten abgedeckt.
Nennen wir nur ein X, ein Y erzeugen,
dann sind wir im Zustand S1, das ist aber kein Zielzustand.
Wir müssen noch in den Zielzustand und das geht nur,
wenn danach noch ein X folgt.
Das ist aber hier nicht drin, das heißt,
das Ganze scheitert und zurecht. Genau das Gleiche scheitert auch,
wenn wir von einem Folge Y Z haben.
Das ist praktisch nicht möglich, weil die Y sind
die Z in alternativen Zweigen des Automaten
erzeugt werden
Unten, die Ys hier oben, die Z. Es gibt
keine Möglichkeit, beide in einer Folge miteinander zu kombinieren.
Auch deshalb ein False angebracht. Oder wir können jetzt also auch sagen,
wir generieren ja bitte mal alle Zustand, nein, alle Symbolfolgen,
die du generieren kannst und wir kriegen dieses Ergebnis hier.
Ich habe hier abgebrochen, nachdem ich mich dreimal Ys gesehen
habe, das geht also so weiter.
Das nächste wäre mit viermal Y, fünfmal, sechsmal und so weiter.
Was fällt uns auf? Ja, ich möchte jetzt mal
alle dabei haben
Genau. Das ist unsere Tiefensuche, die das Prolog-System macht.
Es sind die erste vaiablen Bedingungen,
die es findet und die erste Variablenbio, die es findet,
ist eben, dass es den Übergang in den Zustand S1
macht und im Zustand S1 kann es nur noch Y zerzeugen.
Das ist der Grund. Ich habe hier einmal festgelegt und
ich müsste jetzt über das Backtracking zu der zweiten Alternative kommen,
dass ich hier in den Zustand S2 komme.
Zu der zweiten Alternative komme ich aber nie,
weil ich bereits unendlich viele Lösungen für die erste erzeugen kann.
Also die Menge der Lösung für die erste Alternative ist nie erschöpft,
sodass ich jemals in die Chance hätte, die zweite Alternative überhaupt nur anzufassen.
Das ist der Grund, weshalb ich also jetzt hier mich damit verausgabe,
beliebig viele Y zu erzeugen, aber den zweiten Weg nie zu sehen bekomme.
Was könnte man tun, um das zu ändern?
Was müsste man an dem Grundansatz ändern,
den wir hier haben, damit man auch die Z-Alternativen mal
zu Gesicht bekommt
Ja. Ich habe ja schon gesagt, der Grund liegt in
der Tiefensuche. Was wäre die Alternative zu der Tiefensuche?
Ist das jetzt so publik oder ist es nicht klar,
was man tun müsste? Also der Grund für das Verhalten ist,
ich bin in dem Zustand erst null und mein Staatszustand,
ich habe zwei Alternativen.
Ich nehme die erste Alternative und lande im Zustand S2.
Da habe ich wieder zwei Alternativen. Entweder,
sorry,
im Zustand
Da habe ich zwei Alternativen. Entweder ich erzeuge ein Y
oder ich nehme diese Transition. Im ersten Fall hatte er
sich dafür entschieden, übers Backtracking nimmt er diese Alternative.
Damit hatte er wieder zwei Alternativen. Entweder ich mache Schluss
oder ich mache weiter. Und da ist,
ich mache weiter und endlich viele Varianten gibt,
komme ich nie dazu, hier jemals die zweite Alternative zu betrachten.
Also was müsste ich machen? Das ist eine Freiheit,
die wir so nennen. Das ist,
die Memoisierung braucht man hier für die Zyklenüberwachung,
aber erst mal Breitensuche ist genau der springende Punkt.
Ehe ich vom Zustand S1, entscheide mich,
gehe ich hier weiter oder gehe ich da weiter?
Sollte ich erst mal alle Alternativen betrachten, die ich noch habe?
Und dann sollte ich erst mal die Alternative bemühen, sodass ich jetzt weiß,
ich kann voll S0 sowohl nach S1 als auch nach S2 kommen.
Mit der tiefen Suche habe ich nur die Variante S1 gesehen.
Die Variante S2 habe ich nie zu Gesicht bekommen.
Breitensuche garantiert mir, dass ich alle Varianten auch mal tatsächlich betrachte.
Und das ist etwas, was wir dann auch anstreben wollten.
Aber so weit sind wir
noch nicht
Eva dazu kommen, gucken wir uns mal Labyrinth-Problem an.
Labyrinth-Problem ist sehr ähnlich, nur dass wir die Emissionen hier nicht haben.
Also die Symbole, die an den Kanten des Automaten erzeugt werden,
sondern wir haben immer nur, wie ich komme von A nach B,
ich komme von B nach C,
von A nach D und so weiter.
Und wir haben diese Prädikate, die sie alle schon,
diese Klauseln, die sie alle schon kennen,
die also zum einen dazu dienen,
die transitive Hülle über der Erreichbarkeit herzustellen und die andererseits dazu dienen,
die Symmetrie herzustellen.
Also wenn ich eine Verbindung von A nach B habe,
soll das gleichbedeutend sein damit,
dass ich auch eine Verbindung von B
nach A habe
Das kennen Sie alle schon und das Verhalten kennen Sie auch,
das haben wir alles schon mal gesehen. Wenn ich das jetzt aufrufe,
wo kann ich von dem aus überall hinkommen,
dann sehen Sie nach A, B, C,
nach A nach B, nach C, nach B,
nach D und dann werden Sie irgendwann mal feststellen,
das wiederholt sich dieses Pattern.
Das wird immer wieder eine Aufzählung dieser gleichen fünf Knoten,
die ich betreten kann, sein.
Und ich habe hier also aufgehört und das Ganze einfach noch
nicht weiter dargestellt. Wenn man jetzt Zyklenvermeidung machen will,
was ist der Trick, den man anwendet dabei?
Man soll sich in einem Labyrinth bewegen,
man möchte den Ausgang wiederfinden und nicht ständig im Kreise laufen,
sonst zählt man zu den Grippen,
die irgendwann mal in der Ecke liegen
Alle, noch nie in einem Maislabyrinth gewesen. Es ist
der Faden der Ariadne, in dem sie auslegen müssen,
das, was sie kluge Ariadne ihrem Herkules mitgegeben hat,
als er das Labyrinth betreten sollte, um den Minotaurus zu besiegen.
Eine Wollknäuel. Und das Wollknäuel wird ausgerollt und dadurch weiß ich,
bin ich an dieser Stelle schon mal gewesen, ja oder nein?
Wenn ich irgendwann mal wieder auf den Faden stoße,
weiß ich, dass ich bin im Kreise gelaufen.
So komme ich nie wieder raus aus dem Labyrinth
Und dass dieses Wollknäuel müssen wir irgendwie repräsentieren und wir repräsentieren es
durch eine Liste der Punkte, die wir schon besichtigt haben.
Und dazu brauchen wir ein zusätzliches Argument, auf dem wir diese Liste verwalten können.
Genauso wie wir vorhin eben die emittierten Symbole des endlichen Automaten
da gesammelt haben, sammeln wir jetzt eben tatsächlich die Zustände,
die wir schon mal besichtigt haben.
Initial ist das natürlich der Ausgangsort, wo wir waren. Und wenn wir finden,
dass wir bereits auf der Liste einen Knoten oder ein Symbol haben,
dass wenn wir in einen Zustand kommen,
der bereits auf der Liste steht, dann können wir abbrechen,
dann sind wir im Zyklus drin,
dann sollten wir nicht weitermachen
Gut, also dazu müssen wir jetzt das Ganze ein bisschen erweitern.
Wir müssen also in jedem Falle schauen, ehe wir diese
Symmetriebetrachtung machen, also immer eh wie einen Schritt machen.
Immer dann, wenn wir dabei sind, den nächsten Schritt
zu tun. Das ist diese C-Struktur oder das C-Prädikat,
eine Verbindung zwischen A und B. Dann müssen wir gucken,
ob das Ziel dieses Schrittes liegt bereits auf der Liste der bisher besuchten Punkte drauf ist.
Und das gilt in jedem Falle,
wenn wir von B nach A gehen,
dann müssen wir eben gucken, ach so,
wegen der Symmetrie heißt das ja,
wir gehen von A nach B,
wenn es eine Verbindung von B nach A gibt.
Also das heißt,
unser Zielpunkt ist
Und dann müssen wir eben wieder gucken, ob B bereits auf der Liste draufsteht.
Und wenn wir die transitive Hülle hier nehmen,
wir wollen von A nach C gehen, das heißt,
wir müssen von A nach B gehen, das sollten wir nur dann tun,
wenn wir bisher noch nicht bei B gewesen sind.
Und umgekehrt eben hier, wenn wir von A nach C wollen,
dann können wir auch diese Verbindung von B nach A benutzen,
dann ist aber wieder B unser Zielpunkt,
wir wollen von A einen Schritt gehen,
also wir gehen von A nach B, dann müssen wir auch gucken,
ob das noch nicht auf der Liste draufsteht.
Und wir nehmen eben immer auf der dritten Argumentstelle initial,
heißt es, wir fangen hier an zu laufen und in jedem Schritt,
den wir machen, schreiben wir jetzt den Punkt,
den wir gerade absolviert haben.
Wir haben hier also geguckt, ob wir bei B noch nicht waren.
Das ist nicht der Fall. Dann wird dieses B
auf die Liste draufgesetzt. Also hier merken wir uns,
wo waren wir noch nicht? Wo waren wir, sorry,
wo waren wir bereits? Und hier jeweils überprüfen wir,
auch bei dem Ziel des nächsten Teilschrittes,
nicht möglicherweise einen Punkt besucht wird, an dem wir schon mal waren.
Das ist alles, was wir brauchen. Und wenn wir das jetzt benutzen,
dann können wir tatsächlich sehen,
dass wir in diesem Labyrinth herumlaufen können,
aber er kommt irgendwann zu einem natürlichen Ende
Es gibt nur noch endlich viele Lösungen.
Im Kreise können wir nicht mehr laufen. Das ist dadurch
ausgeschlossen. Ganz einfach dadurch, dass wir testen,
waren wir hier schon oder waren wir hier noch nicht.
Ja, möglicherweise möchte man auch die zurückgelegte Wegstrecke noch ausgeben.
Beispielsweise, wenn man das Ganze als Fahrplanproblem betrachtet.
Ich möchte von A nach B kommen, über welche Zwischenstationen komme ich dorthin,
wo muss ich umsteigen oder welche Landmarken muss ich partizisieren.
Mit dem Auto beispielsweise, welche Städte muss ich passieren,
um mein Ziel zu erreichen.
Dann haben wir hier ein kleines Problem. In der bisherigen
Programmstruktur Tragen wir hier zwar immer das Ergebnis ein in diese Zwischenergebnisliste,
aber wenn wir am Zielzustand ankommen, Moment,
wo sieht man das jetzt? Also wir wollen nach B,
das ist unser Ziel und wir müssen also hier immer schauen,
ob uns diese Transition tatsächlich in den, in diesem Zielzustand bringt.
Dann sind wir zwar im Zielzustand und wir wissen auch,
welche Wegstrecke wir bis dahin schon zurückgelegt haben,
aber wir können sie nicht mehr
nach außen mitteilen
Wir haben keine Möglichkeit, der Außenwelt mitzuteilen, welcher Wegstrecke wir zurückgelegt haben.
Denn alles, was wir hier eingerichtet haben, ist ein Akkumulator.
Auf dem Akkumulator sammeln wir auf, was haben wir bisher gemacht.
Aber wir haben keine Argumentstelle, wo wir jetzt nach draußen
sagen könnten, schaut her, das haben wir tatsächlich gemacht.
Wir müssen also beim rekrusiven Abstieg, den wir hier machen,
sammeln wir hier auf dem Akkulator über die Zwischenstation.
Wir müssen jetzt dafür sorgen, dass wir das Gesammelte,
in dem Augenblick, wo wir am Zielzustand angekommen sind,
auch der Außenwelt mitteilen können.
Dieses Schema haben wir schon oft genug gesehen,
hier sehen wir es aus einer kleinen, anderen Perspektive.
Wir haben erstmal nur den Akkumulator eingefügt,
aber das Resultatsargument fehlt uns
immer noch
Das haben wir beim Reverse zum Beispiel schon mal gesehen.
Und auch da hilft wieder ein zusätzliches Argument her.
Das ist dann die Argumentstelle, auf der wir der Welt
nach draußen mitteilen können, was haben wir tatsächlich getan?
Und die wird, wird wird hier an dieser Stelle
mit einem Wert versehen, in dem Moment.
Hier wird es immer nur durchgereicht, hier wird es nur durchgereicht.
Bitte? Ja, bei den ersten Weisen kommen wir
nur das Pederbogen
Ja, ja, ja, ja,
ich habe jetzt im Augenblick den Punkt verloren,
der Moderator und mit der Kommunikation der Beeren.
Ja, das ist also hier, hier machen wir ja
keinen weiteren Schritt mehr. Wir hören auf, die Schritte,
wo wir weiterschreiten, das sind ja diese Transitiven hier unten.
Hier machen wir heute den nächsten Schritt. Und entweder der
nächste Schritt führt uns in den Zielzustand ja oder nein.
Und an der Stelle wird eben jetzt das letzte,
der letzte, der letzte Ort, den wir besuchen,
noch vor die bisher zurückgelegte Wegstadt gehangen und Ja,
auf der dritten Argumentstelle wird das initialisiert mit den Punkten und es wird immer nach unten gereicht.
Und es wird dann auf der letzten Argumentstelle nach außen gegeben.
Mit dem letzten Schritt, der gemacht worden ist.
Und hier wird also die bisher zurückgelegte Weckstrecke gesammelt.
Und sie wird erweitert, um den aktuell besuchten Punkt und
weiter nach unten gereicht in der Rekursion.
Bis es irgendwann mal hier, das ist sozusagen der Rekursionsabschluss,
ankommt, weil die einer dieser beiden Klauseln.
Und hier wird das endgültige Ergebnis erzeugt und auf die dritte
Argumentstelle übergeben. Ey, sorry, auf die vierte.
Auf der dritten wird es nach unten gereicht.
Auf der vierten wird es wieder nach oben gereicht
Im Rekursionsschema. Und auf die Art und Weise habe ich jetzt tatsächlich die Wegstrecke aufgesammelt,
auf der dritten Argumentstelle nur einen kleinen Schönheitsfehler hat es noch,
weil ich natürlich beim Startpunkt anfange und dann immer alle weiteren Stationen vorn dran hänge,
weil sie das einfach bequem machen lässt für Listen,
ist die Liste in der umgekehrten Reihenfolge sortiert.
Der Startpunkt steht ganz hinten, der Zielpunkt ist als letztes,
nämlich genau hier, in die Liste hineingefügt worden.
Sorry, nicht hier, wo zeige ich denn hin,
hier oder da? Das ist der Zielpunkt, an dem
ich angekommen bin. Deshalb müssen wir das Ganze nochmal umdrehen,
damit es in der richtigen Reihenfolge ist,
so wie wir die Suche tatsächlich auch gemacht haben.
Und dann können wir es als die Gesamtwegstrecke
nach außen mitteilen
Und das ist dann das Ergebnis, was wir haben.
Wir haben also verschiedene Zielpunkte, die wir von D aus erreichen können.
Und dann sehen wir auch, wie wir laufen müssen.
Also von D direkt nach A, von D direkt nach B,
von D direkt nach C. Das sind immer die unmittelbaren Verbindungen.
Wobei interessanter, keines dieser Dinge ist,
in der richtigen Reihenfolge in der Datenbank drin.
In der Datenbank stand immer nur, du kommst von A nach D,
du kommst von B nach D, du kommst von C nach D.
Das ist über die Symmetrie bereits hergestellt worden.
Das ist auch in der umgekehrten Richtung geht.
Und dann kommen die transitiven Hülden dazu. Ja,
ich kann aber auch von D nach B kommen,
indem ich über A gehe oder von D nach C komme,
indem ich über A, B gehe und so weiter und so fort.
Und das sind wieder endlich viele Lösungen,
die ich hier rausbekomme
Also durch die Mimuisierung sind wir jetzt tatsächlich in der Lage,
Zyklen zu vermeiden und ja,
auf endlich viele Ergebnisse in einem solchen endlichen Automaten zu kommen.
Haben wir damit das Halteproblem der Touring-Maschine gelöst?
Weiß jemand, was das Halteproblem der Touring-Maschine ist?
Okay, erklären Sie es uns. Also das Halteproblem ist plötzlich,
da geht es im Prinzip um die Frage,
ob man mit einem generellen Verfahren an allgemeinen Verfahren bei dem Leben getroffen wo die Maschine feststellen kann,
ob sie auf einer bestimmten Eingabe auch anhalten wird oder ob sie irgendwo eine endlich aufläuft.
Da kann man beweisen, dass es unentscheidbar ist,
dass man kein allgemeines Verrat ist, dass es feststellt.
Und in diesem Fall geht das jetzt schon, dass man diese Zyklen vermeidet,
aber ein endlicher Automat ist auch deutlich allenfalls zu tun.
Richtig, wir reden nicht über Touring-Maschinen,
wir reden über endliche Automaten, wir gucken uns das gleiche Thema an,
auch mal im Rahmen der Meta-Programmierung an,
wo wir Zyklenvermeidung für allgemeine Prolog-Programme uns angucken.
Die sind Touring-Äquivalent. Dann wird die Frage wieder, welche die nochmal stellen,
ob wir dann das heute Problem der Touring-Maschinen gelöst haben.
Hier können wir also die einfache Antwort geben,
wir reden gar nicht über Touring-Maschinen, sondern wir reden über endlich Automaten.
Das ist also drei Automatenklassen tiefer unten, als die Touring-Maschine.
Letzter Punkt ist die Suchraumverwaltung und jetzt kommen wir tatsächlich zu dem Punkt,
wo wir unseren endlichen Automaten dazu bringen wollen,
alle Alternativen zu zeigen und nicht nur in der Tiefensuche einen dieser Wege.
Also Prolog realisiert standardmäßig Tiefe zuerst Suche und das war genau der Grund,
weshalb wir immer nur die eine Ableitungsgeschichte gesehen haben und nicht die zweite.
Wir können, aber wenn wir die bereit sind,
den Aufwand zu tragen, können wir die Suchstrategie selbst in die Hand nehmen.
Wir sind nicht daran gezwungen, die Suchstrategie des Prolog-Systems zu benutzen
Es selbst programmieren. Und das ist also ein erster Schritt in die Richtung,
dass wir anfangen, über die Unzulänglichkeiten der Standardabarbeitung im Prolog-System nachzudenken.
Und das werden wir bei der Metaprogrammierung in extremer Weise nachvollziehen,
dass wir bis dahin gehen, dass wir sagen,
diese ganze Logikprogrammierung gefällt uns nicht.
Wir wollen diese Mantik umdrehen zur funktionalen Programmierung.
Das ist der Extremfall, den wir uns angucken werden.
Das ist ein ganz einfacher Fall, den wir hier machen,
indem wir sagen, diese Tiefensuche gefällt uns nicht,
wir wollen Breitensuche realisieren,
damit wir eben in unserem endlichen Automat Alternativen zu Gesicht
bekommen,
die wir sonst nicht sehen können
Was wir dann machen müssen, ist,
dass wir alle Suchzustände uns tatsächlich verwalten müssen und abspeichern müssen.
Und das nennt man dann eine Agenda. Das sind immer
die Sachen, die noch zu tun sind.
Und wir verwalten jetzt also eine Agenda.
Und wir müssen die Agenda-Einträge sehen immer so aus,
ich bin im Zustand sowieso, also hier S1.
Und ich habe bisher das Wort XY, Y erzeugt.
Auch hier wieder rückwärts, damit wir immer die neuen Elemente vorne dranfügen können.
Und wir arbeiten dann die Agenda eben vom Listenanfang ab,
nehmen immer neue Elemente von der Agenda runter
Versuchen, den nächsten Schritt zu tun in unserem Automat.
Und der Unterschied zwischen den beiden Suchverfahren ist jetzt einzig und allein,
wenn wir die neuen Agenda-Elemente am Listen anfangen hinzufügen,
dann haben wir dort einen Kellerspeicher.
Wenn wir sie am Listen Ende hinzufügen, haben wir
eine Warteschlange. Und wenn wir einen Kellerspeicher haben,
dann ist es genau das, was wir bisher immer hatten.
Das hat nämlich das Produktsystem auch. Dann ist es eine Tiefensuche.
Und wenn wir eine Warteschlange realisieren, dann ist es eine Breitensuche.
Das ist alles. Und deshalb kann man hier das umschalten zwischen diesen beiden Suchmodi,
ganz einfach dadurch erreichen, dass man Eine Einzelzeile ändert,
füge ich die neuen Elemente am Anfang hinzu oder am Ende.
Unser Generierungsapparat sieht jetzt etwas anders aus. Er sagt nicht mehr,
nimm den ersten besten Startzustand, den wir bisher hatten,
sondern nimm alle Startzustände, die du finden kannst.
Und baue mir daraus eine Agenda. Und die Agenda sieht immer so aus,
schreibe hier die Fußstände hinein und schreibe hier die Historie hinein.
Also, ich bin im Startzustand und habe bisher noch nichts getan.
Deshalb die leere Liste. Und der Rest,
den kennen wir bereits wieder. Also, nimm die Agenda
und generiere mir ein Wort damit. Und zum Schluss,
weil wir die Dinge wieder in der falschen Reihenfolge aufsammeln,
drehe das Wort um und damit hast du das,
gibt das erzeugte Wort nach draußen aus
Und hier ist der Kern des Ganzen, auch der Generierungsschritt,
sagt jetzt eben, wenn du im Endzustand angekommen bist,
dann, Moment, machen wir erstmal den Rekursionsschritt.
Wir sind in einem bestimmten Zustand.
Wir nehmen den ersten Agenda-Eintracht von der Agenda herunter.
Wir sind im Zustand Z und wir haben bisher ein Wort erzeugt.
Das ist das vorher rangegangene Wort,
also Previous Word
Und natürlich haben wir noch weitere Agenda-Einträge, über die wir im Augenblick nicht reden.
Wir kümmern uns nur um den allerersten Eintracht.
Was weiter passiert, das muss in der Rekussion geregelt werden.
Und dann suchen wir uns alle Transitionen,
die von diesem Zustand Z ausgehen, und zwar alle wieder.
Es ist ein Find-All. Und tragen, die führen natürlich
auf einen nächsten Zustand und sie emittieren ein Symbol.
Und wir sammeln jetzt Strukturen der Art,
ich bin dann im nächsten Zustand,
nachdem ich diese Transition gemacht habe, bin ich im nächsten Zustand.
Und zu dem, was ich bisher schon getan habe,
kommt dieses neue Element eh noch hinzu
Ich generiere hier einen neuen Agenda-Eintrag, der sagt,
ich bin der Zustand, in dem ich bin und das sind die,
ist das Wort, was ich bisher erzeugt habe.
Und das mache ich für alle Transitionen, die vom Zustand Z wegführen.
Und damit kriege ich eine Liste neuer Items. Das sind
die Suchzustände, die von einem Zustand aus besichtigen kann.
Und jetzt kommt der entscheidende Schritt,
wenn ich denn diese neuen Items hinten dran schreibe an die Agenda,
die ich habe, das ist die Restagenda hier,
dann kriege ich eine neue Agenda und dann ist das eine Warteschlange.
Dann müssen sich alle hinten anstellen. Sie können dieses auskommentierte
Ding nach da verschieben und dann schreiben sie die neuen Items
vorne an die Agenda dran.
Dann drängeln die sich vor und dann haben wir das,
was wir bisher auch hatten,
nämlich Tiefensuche
Also das Umschalten zwischen Breiten- und Tiefensuche ist hier nichts anderes,
als dass die neuen Suchzustände, die ich erzeugt habe,
entweder vorn dran geschrieben werden an die Agenda,
dann habe ich Tiefensuche oder hinten sich in der Agenda anstellen,
dann habe ich Breitensuche.
Denn dann muss ich erst die alten Sachen,
die in der Agenda drinstehen, die zuerst anfassen,
ich dazu komme, diese neuen Sachen mal anzufassen.
Umgekehrt gucke ich mir immer die neuesten Sachen als erstes an und zu
den alten Sachen komme ich als letztes, möglicherweise nie,
wenn ich mit den neuen Sachen unendlich lange beschäftigt bin.
Das ist genau der Effekt, den wir gesehen haben
Okay, und nachdem wir das gemacht haben,
haben wir eine neue Agenda mit der neuen Agenda,
schicken wir das Ganze wieder ins Rennen und lassen das Wort generieren,
was wir dann nach draußen übergeben wollen.
Hier an die Stelle. Und wenn denn der Eintrag auf
der Agenda einen Zustand betrifft, von dem wir wissen,
dass es ein Endzustand ist, dann sind wir fertig.
Dann brauchen wir nichts weiter zu machen, wir sind am Ziel angekommen.
Gut, und dann sehen wir jetzt hier,
Aufruf mit Kellerspeicher, das heißt also, die neuen Items kommen
Als erstes in die Liste hinein und das ist genau
das Verhalten, was wir für ihn auch hatten.
Wir kriegen lauter Ys generiert, aber wir kommen nie zu den Zs.
Und wenn wir die Breitensuche einschalten, dann wird immer abwechselnd mal die X-Vayant,
sorry mal die Y, mal die Z-Variante,
die Y-Variante, wie Z-Variante, die Y-DZ-Variante abgesucht.
Also jetzt gucken wir tatsächlich in alle Alternativen,
die wir zur Verfügung haben, hinein und schauen,
arbeiten die der Reihe nach ab, erstmal in der Breite,
ehe wir den nächsten Schritt in die Tiefe des Suchraumes machen.
Natürlich kriegen wir auch hier unendlich viele Ergebnisse.
Aber die Ergebnisse sind balancierter als in der Tiefensuche.
Warum verwendet das Prolog-System dann
keine Breitensuche
Ist das Dummheit? Ich überlege jetzt,
ob anders lieber auf den nächsten Termin verschiebt oder ob er
die Frage jetzt noch klären wollen.
Dann sagen wir es, sorry, nicht gesehen, ja.
Wahrscheinlich wieder performen. Was heißt Performance?
Da müssen wir ja immer unterscheiden, Rechenzeitaufwand,
Wollen wir dann auch jetzt anschauen, dass du uns hier als Tier bewegt werden?
Als sie dann tief war. Und wahrscheinlich läuft das auch länger.
Wenn ich unendlich viele generieren kann, ist länger kein Argument.
Ich bin jenseits von Gut und Böse. Das kann ich
der entscheidende Faktor sein. Ich bin mir nicht sicher. Aber ich kann mir vorstellen,
dass Probleme mit dem definieren, von der Cousinen und Kreditkarten gibt,
wenn nicht erst die Tiefe gegangen bin, sondern er ist geguckt,
was man noch für Alternativen hat, man zu früh.
Nee, wir müssen nur das machen, was wir hier auch gemacht haben.
Wir müssen die vollständige Information über einen Suchzustand abspeichern.
Das passiert üblicherweise auf einem Stack. Und das hat gute Gründe,
dass er auf einem Stack passiert und nicht in einer Warteschlange.
Also das Produkt-System hat einen Stack, wo es nämlich genau
diese alternativen Klauseln, die es probieren könnte, erstmal draufpacken.
Und immer die erste wieder runternimmt und damit mache ich weiter.
Es könnte sie auch in eine Warteschlange stellen, aber warum
tut es das nicht? Wenn man eine Warteschlange effizient bauen will,
da muss man das mit einer Doppelweckkette,
wie das zu machen,
ist deutlich schwerer zu verwalten
Ach so, ja, weil ich immer hinten ran muss,
ja. Gut, ja. Zweckwerte ist meistens wie wir
schon auf den Brecher-Architekturen, minimativ irgendwie vorhanden,
weil den aller anderen, wenn man den ständig braucht.
Ja. Und ich wäre das in Speicherverwaltung. Gut,
also wir machen es mal kurz, sonst läuft uns die Zeit weg.
Das entscheidende Argument ist, die Agenda wächst in der breiten Suche exponentiell.
Und die ist immer mit einem exponentiell großen Speicherplatz wieder verbunden.
Der Stack wächst nur linear mit der Einbettungstiefe. Und das ist
der Grund, weshalb also das Produktsystem standardmäßig mit der Tiefensuche arbeitet.
Ansonsten würden selbst elementarste Probleme, und wir haben das vorhin gesehen,
wenn wir dieses Bindungialproblem jetzt mit einer Breitensuche machen wollten.
Dann würde nämlich unser Agenda exponentiell anwachsen,
genau wie der Zeitbedarf bei der Berechnung exponentiell angewachsen ist.
Und dann könnten wir auch einfachste Probleme nicht mehr lösen.
Dadurch, dass wir Tiefensuche machen, haben wir es gesehen.
Wir konnten uns bis 500 treiben und es hat trotzdem
immer noch klaglos Ergebnisse produziert. Das ist der Grund,
der dazu führt, weshalb standardmäßig die Tiefensuche verwendet wird.
Okay, soviel für heute. Schönen Dank für die Aufmerksamkeit
und ich hoffe, dass nächste Woche auch noch jemand da ist.