27.12.2012 Aufrufe

3.6.4 Rekursive Funktionen

3.6.4 Rekursive Funktionen

3.6.4 Rekursive Funktionen

MEHR ANZEIGEN
WENIGER ANZEIGEN

Erfolgreiche ePaper selbst erstellen

Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.

<strong>3.6.4</strong> <strong>Rekursive</strong> <strong>Funktionen</strong><br />

Beispiel: Keine Einführung in die Informatik ohne die Türme von Hanoi.<br />

Das Spiel wurde 1883 von dem französischen Mathematiker Edouard<br />

Lucas erfunden.<br />

Die Scheiben auf Stab A (links) sollen nach Stab C (rechts) gebracht<br />

werden, ohne dass jemals eine größere Scheibe auf einer kleineren<br />

liegt. Es darf immer nur die oberste Scheibe eines Stapels bewegt<br />

werden; ablegen neben den Stäben ist verboten.<br />

© Xiaoyi Jiang Informatik I – Grundlagen der Programmierung<br />

148


<strong>3.6.4</strong> <strong>Rekursive</strong> <strong>Funktionen</strong><br />

Wenn das Universum sein Ende finden wird …<br />

Legende über ein buddhistisches Kloster bei Hanoi, in dem sich ein<br />

riesiger Raum mit drei abgenutzten Pfosten befindet, die von 64<br />

goldenen Scheiben umgeben waren<br />

� Seit der Gründung des Klosters vor über tausend Jahren führen<br />

Mönche die Anordnung einer alten Prophezeiung aus:<br />

• Sie bewegen die Scheiben, in Übereinstimmung mit den Regeln<br />

des Puzzles<br />

• Jeden Tag eine Scheibe<br />

� Sie glauben, wenn die letzte Bewegung ausgeführt würde, wird die<br />

Welt mit einem Donnerschlag untergehen<br />

© Xiaoyi Jiang Informatik I – Grundlagen der Programmierung<br />

149


<strong>3.6.4</strong> <strong>Rekursive</strong> <strong>Funktionen</strong><br />

Algorithmus in rekursiver Formulierung:<br />

n Scheiben von A unter Zuhilfenahme des Stabes<br />

B nach C bringen<br />

� n − 1 Scheiben von A unter Zuhilfenahme des<br />

Stabes C nach B bringen<br />

� Bewege eine Scheibe von A nach C<br />

� n − 1 Scheiben von B unter Zuhilfenahme des<br />

Stabes A nach C bringen<br />

(define(move T1 T2 T3 n)<br />

(cond [(= n 0) empty]<br />

[else (append<br />

(move T1 T3 T2 (- n 1))<br />

(list (list T1 T2))<br />

(move T3 T2 T1 (- n 1)))]))<br />

© Xiaoyi Jiang Informatik I – Grundlagen der Programmierung<br />

> (move 'A 'B 'C 4)<br />

(list<br />

(list 'A 'C)<br />

(list 'A 'B)<br />

(list 'C 'B)<br />

(list 'A 'C)<br />

(list 'B 'A)<br />

(list 'B 'C)<br />

(list 'A 'C)<br />

(list 'A 'B)<br />

(list 'C 'B)<br />

(list 'C 'A)<br />

(list 'B 'A)<br />

(list 'C 'B)<br />

(list 'A 'C)<br />

(list 'A 'B)<br />

(list 'C 'B))<br />

150


<strong>3.6.4</strong> <strong>Rekursive</strong> <strong>Funktionen</strong><br />

Spielzüge mit drei Scheiben:<br />

Spielzüge mit vier Scheiben:<br />

© Xiaoyi Jiang Informatik I – Grundlagen der Programmierung<br />

Quelle: Wikipedia<br />

151


<strong>3.6.4</strong> <strong>Rekursive</strong> <strong>Funktionen</strong><br />

Wenn t n die Rechenzeit für (move X Y Z n) bezeichnet, dann gilt:<br />

Die Anzahl der Bewegungen zur Lösung des Problems mit n Scheiben ist<br />

bei diesem Verfahren 2 n − 1. Man kann beweisen, dass das optimal ist.<br />

© Xiaoyi Jiang Informatik I – Grundlagen der Programmierung<br />

152


Exkurs: Wachstum von Zweierpotenzen<br />

Eine praktische Umsetzung einer Lösung<br />

mit exponentieller Komplexität ist nur für<br />

kleine n möglich. Die nebenstehende<br />

Tabelle zeigt die Dauer unter der Annahme,<br />

dass eine Scheibe pro Sekunde<br />

verschoben wird.<br />

Zum Glück sind die Mönche nicht mal annäherungsweise fertig!<br />

� Sie bräuchten 264−1 Sekunden oder ungefähr 585 Milliarden Jahre<br />

� Unser Universum ist zur Zeit ungefähr 13,7 Milliarden Jahre alt<br />

© Xiaoyi Jiang Informatik I – Grundlagen der Programmierung<br />

153


Exkurs: Wachstum von Zweierpotenzen<br />

Weizenkornlegende: Sissa (auch: Sessa) lebte<br />

angeblich im dritten oder vierten Jahrhundert<br />

n.Chr. in Indien und gilt Legenden zufolge als<br />

der Erfinder des Schachspiels bzw. seiner<br />

indischen Urform Tschaturanga.<br />

Der indische Herrscher Shihram tyrannisierte seine<br />

Untertanen und stürzte sein Land in Not und Elend.<br />

Um die Aufmerksamkeit des Königs, ohne seinen Zorn zu entfachen, auf seine<br />

Fehler zu lenken, schuf der weise Sissa ein Spiel, in dem die wichtigste Figur,<br />

der König, ohne Hilfe anderer Figuren und Bauern nichts ausrichten kann.<br />

Der Unterricht im Schachspiel hat auf Shihram einen starken Eindruck gemacht.<br />

Er wurde milder und ließ das Schachspiel verbreiten, damit alle davon Kenntnis<br />

nähmen. Um sich für die anschauliche Lehre von Lebensweisheit und zugleich<br />

Unterhaltung zu bedanken, gewährte er Sissa einen freien Wunsch. Dieser<br />

wünschte sich Weizenkörner: Auf das erste Feld eines Schachbretts wollte er ein<br />

Korn, auf das zweite Feld die doppelte Menge, also zwei, auf das dritte wiederum<br />

doppelt so viele, also vier und so weiter. Quelle: Wikipedia<br />

© Xiaoyi Jiang Informatik I – Grundlagen der Programmierung<br />

154


Exkurs: Wachstum von Zweierpotenzen<br />

Der König lachte und war gleichzeitig erbost ob der vermeintlichen<br />

Bescheidenheit des Brahmanen. Als sich Shihram einige Tage später<br />

erkundigte, ob Sissa seine Belohnung in Empfang genommen habe,<br />

musste er hören, dass die Rechenmeister die Menge der Weizenkörner<br />

noch nicht berechnet hatten. Nach mehreren Tagen ununterbrochener<br />

Arbeit meldete der Vorsteher der Kornkammer, dass er die Menge<br />

Getreidekörner im ganzen Reich nicht aufbringen könne. Auf allen<br />

Feldern zusammen wären es 2 64 −1 oder 18.446.744.073.709.551.615<br />

Weizenkörner (eine Wagenkolonne mit dieser Menge würde<br />

231666mal um die Erde reichen).<br />

Nun stellte sich die Frage, wie das Versprechen eingelöst werden<br />

könne. Der Rechenmeister half dem Herrscher aus der Verlegenheit,<br />

indem er ihm empfahl, er solle Sissa ganz einfach das Getreide Korn<br />

für Korn zählen lassen.<br />

© Xiaoyi Jiang Informatik I – Grundlagen der Programmierung<br />

155


<strong>3.6.4</strong> <strong>Rekursive</strong> <strong>Funktionen</strong><br />

Linear rekursiver Prozess:<br />

(define (fak n) (if (= n 0) 1 (∗ n (fak (- n 1)))))<br />

Auswertung im Substitutionsmodell:<br />

(fak 4) →<br />

(∗ 4 (fak 3)) → Expansion<br />

(∗ 4 (∗ 3 (fak 2))) →<br />

(∗ 4 (∗ 3 (∗ 2 (fak 1)))) →<br />

(∗ 4 (∗ 3 (∗ 2 (∗ 1 (fak 0))))) →<br />

(∗ 4 (∗ 3 (∗ 2 (∗ 1 1)))) →<br />

(∗ 4 (∗ 3 (∗ 2 1))) →<br />

(∗ 4 (∗ 3 2)) → Kontraktion<br />

(∗ 4 6) →<br />

24<br />

© Xiaoyi Jiang Informatik I – Grundlagen der Programmierung<br />

Interpreter muss über die<br />

später durchzuführenden<br />

Operationen Buch führen.<br />

Anzahl aufgeschobener<br />

Operationen wächst linear<br />

mit n � Zeit- und Speicherplatzbedarf<br />

wächst linear<br />

mit n<br />

� <strong>Rekursive</strong>r Prozess: Operationen werden aufgeschoben und erst nach<br />

Beenden der rekursiven Aufrufe durchgeführt<br />

� fak erzeugt einen linear rekursiven Prozess<br />

156


<strong>3.6.4</strong> <strong>Rekursive</strong> <strong>Funktionen</strong><br />

Linear iterativer Prozess:<br />

Berechne n! durch 1 → 1 ∗ 1 → 2 ∗ 1 → 3 ∗ 2 → 4 ∗ 6 → 5 ∗ 24 → …<br />

d.h. verwalte ein Zwischenprodukt p, das mit 1 initialisiert wird, und<br />

einen Zähler c, der von 1 bis n zählt. p und c ändern sich in jedem Schritt<br />

simultan gemäß<br />

p ← c ∗ p c ← c + 1<br />

(define (fak n) (ifak 1 1 n))<br />

(define (ifak p c max)<br />

(if (> c max) p (ifak (∗ c p) (+ c 1) max)))<br />

Auswertung im Substitutionsmodell:<br />

(fak 5) →<br />

(ifak 1 1 5) → (ifak 1 2 5) → (ifak 2 3 5) →<br />

(ifak 6 4 5) → (ifak 24 5 5) → (ifak 120 6 5) → 120<br />

Variablen p, c und max beschreiben zu jedem Zeitpunkt den<br />

Berechnungsprozess vollständig<br />

© Xiaoyi Jiang Informatik I – Grundlagen der Programmierung<br />

157


<strong>3.6.4</strong> <strong>Rekursive</strong> <strong>Funktionen</strong><br />

Linear iterativer Prozess:<br />

� Zustand des Prozesses wird durch eine feste, von der Eingabe<br />

unabhängige Anzahl von Zustandsvariablen beschrieben<br />

• Fixe Regel beschreibt, wie die Zustandsvariablen aktualisiert<br />

werden, wenn der Prozess von einem Zustand in den nächsten<br />

Zustand übergeht<br />

• (Optionaler) Test, der die Bedingungen spezifiziert, unter denen<br />

der Prozess terminiert<br />

� Interpreter muss sich einzig die Zwischenwerte der Zustandsvariablen<br />

merken � Speicherplatzbedarf ist konstant<br />

� Zeitbedarf ist bestimmt durch Anzahl der rekursiven Aufrufe, die linear<br />

mit n wächst<br />

© Xiaoyi Jiang Informatik I – Grundlagen der Programmierung<br />

158


<strong>3.6.4</strong> <strong>Rekursive</strong> <strong>Funktionen</strong><br />

Beispiel: Fibonacci-Zahlen<br />

� Folgen lassen sich in vielen Fällen durch eine iterative Funktion<br />

beschreiben<br />

� Die Fibonacci-Folge kann man fortsetzen, wenn man jeweils die<br />

beiden letzten Elemente kennt:<br />

0 1 � 1 1 � 1 2 � 2 3 � 3 5 � 5 8 � 8 13 � ……<br />

(define (fib n) (fib-iter 0 1 n))<br />

(define (fib-iter a b n)<br />

(cond [(= n 0) a]<br />

[else (fib-iter b (+ a b) (- n 1))]))<br />

> (fib 10)<br />

55<br />

> (map fib (list 0 1 2 3 4 5 6 7 8 9 10))<br />

(list 0 1 1 2 3 5 8 13 21 34 55)<br />

© Xiaoyi Jiang Informatik I – Grundlagen der Programmierung<br />

159


<strong>3.6.4</strong> <strong>Rekursive</strong> <strong>Funktionen</strong><br />

Endrekursion:<br />

Erzeugt eine linear rekursive Funktion bei ihrer Auswertung einen<br />

iterativen Prozess, d.h. das Ergebnis eines rekursiven Aufrufs wird nicht<br />

im Rumpf der rekursiven Funktion weiterverarbeitet und mit dem<br />

Beenden des letzten rekursiven Aufrufs liegt auch das Ergebnis der<br />

Berechnung vor, so nennt man die Funktion endrekursiv bzw.<br />

endrekursion (tail recursion).<br />

� Weniger Platzbedarf bei der Abarbeitung, da die Größe des auszuwertenden<br />

Ausdrucks nicht mit jedem rekursiven Aufruf wächst<br />

© Xiaoyi Jiang Informatik I – Grundlagen der Programmierung<br />

160


<strong>3.6.4</strong> <strong>Rekursive</strong> <strong>Funktionen</strong><br />

Beispiel: Berechnung von<br />

(define (sum n)<br />

(if (= n 0) 0 (+ n (sum (- n 1)))))<br />

sum ist nicht endrekursiv<br />

(define (sum n) (isum n 0))<br />

(define (isum n result)<br />

(if (= n 0) result (isum (- n 1) (+ n result))))<br />

isum ist endrekursiv<br />

© Xiaoyi Jiang Informatik I – Grundlagen der Programmierung<br />

n<br />

∑<br />

i =0<br />

i<br />

(isum 5 0) � (isum 4 5) � (isum 3 9) � (isum 2 12) � (isum 1 14) �<br />

(isum 0 15) � 15<br />

161


<strong>3.6.4</strong> <strong>Rekursive</strong> <strong>Funktionen</strong><br />

Beispiel: Länge einer Liste (vgl. Folie 112, Kap.3)<br />

Endrekursion:<br />

(define (len list-of-anything)<br />

(cond [(empty? list-of-anything) 0]<br />

[else (+ 1 (len (rest list-of-anything)))]))<br />

(define (len list-of-anything) (len-iter list-of-anything 0))<br />

(define (len-iter l n)<br />

(cond [(empty? l) n]<br />

[else (len-iter (rest l) (+ n 1))]))<br />

(len-iter (list 1 2 3) 0) � (len-iter (list 2 3) 1) � (len-iter (list 3) 2) �<br />

(len-iter empty, 3) � 3<br />

© Xiaoyi Jiang Informatik I – Grundlagen der Programmierung<br />

162


<strong>3.6.4</strong> <strong>Rekursive</strong> <strong>Funktionen</strong><br />

Eine Funktion kann rekursiv in der syntaktischen Definition, aber iterativ<br />

in der effektiven Ausführung sein<br />

� Funktion ist rekursiv � syntaktische Eigenschaft<br />

� Prozess ist linear rekursiv oder linear iterativ � Aussage über die<br />

Entwicklung des Berechnungsprozesses, nicht aber über die Syntax<br />

der Funktion<br />

Für den Speicher- und Zeitbedarf ist das Prozessverhalten, nicht die<br />

syntaktische Definition, entscheidend<br />

© Xiaoyi Jiang Informatik I – Grundlagen der Programmierung<br />

163


<strong>3.6.4</strong> <strong>Rekursive</strong> <strong>Funktionen</strong><br />

Terminierung bei rekursiven <strong>Funktionen</strong>:<br />

� Terminierung bei nicht-rekursiven <strong>Funktionen</strong><br />

Komplexe <strong>Funktionen</strong> stützen sich auf einfachere <strong>Funktionen</strong> ab,<br />

dieser Abstützprozess endet nach endlich vielen Schritten bei den<br />

vordefinierten <strong>Funktionen</strong> von Scheme<br />

� Versuche, die Vorgehensweise auf die Kombination aus<br />

Funktionsaufrufen und Parametern zu übertragen. Gelingt es also,<br />

für eine Funktion nachzuweisen, dass die aufgerufenen <strong>Funktionen</strong><br />

und ihre Parameter als Ganzes in dem Sinne einfacher werden, dass<br />

sie nach endlich vielen Schritten die Abbruchbedingung erfüllen, so<br />

kann daraus die Terminierung der (rekursiven) Funktion gefolgert<br />

bzw. nachgewiesen werden.<br />

© Xiaoyi Jiang Informatik I – Grundlagen der Programmierung<br />

164


<strong>3.6.4</strong> <strong>Rekursive</strong> <strong>Funktionen</strong><br />

Beispiel:<br />

(define (fak n) (if (= n 0) 1 (∗ n (fak (- n 1)))))<br />

� fak ist linear rekursive Funktion mit einem Parameter; Definition<br />

stützt sich auf { ∗ , - , = } und auf fak selbst ab<br />

� Rekursion terminiert genau dann, wenn das Abbruchkriterium (= n 0)<br />

erfüllt ist<br />

� In jedem Aufruf von fak wird das Argument um 1 dekrementiert<br />

• Für Startwerte n∈ N ist (fak n) bzgl. des Abbruchkriteriums<br />

komplexer als (fak (- n 1)) � Funktion terminiert nach n<br />

rekursiven Aufrufen<br />

• Für Startwerte n


<strong>3.6.4</strong> <strong>Rekursive</strong> <strong>Funktionen</strong><br />

� Wichtig: kombinierte Betrachtung der Parameter der rekursiven<br />

Aufrufe, der Abbruchbedingung und der erlaubten Eingabewerte<br />

� Dabei sind nicht unbedingt alle Parameter relevant, sondern nur die,<br />

die in den Abbruchbedingungen berücksichtigt werden. Man sagt<br />

dann, dass bestimmte Parameter die Rekursion steuern.<br />

Beispiel:<br />

(define (fak n) (ifak 1 1 n))<br />

(define (ifak p c max)<br />

(if (> c max) p (ifak (∗ c p) (+ c 1) max)))<br />

Nur c und max steuern die Rekursion; p dient lediglich zur<br />

Speicherung des Zwischenprodukts<br />

© Xiaoyi Jiang Informatik I – Grundlagen der Programmierung<br />

166


<strong>3.6.4</strong> <strong>Rekursive</strong> <strong>Funktionen</strong><br />

Beispiel: Linear rekursive Funktion t mit zwei Parametern<br />

(define (t x y) (cond [(< y 0) x]<br />

[(


<strong>3.6.4</strong> <strong>Rekursive</strong> <strong>Funktionen</strong><br />

Bei nicht-linearer Rekursion muss man das Terminieren aller rekursiven<br />

Aufrufe zeigen, d.h. dass jede der auftretenden Rekursionen eine<br />

Abbruchbedingung erreicht<br />

Beispiel:<br />

(define (fib n)<br />

(cond [(= n 0) 0]<br />

[(= n 1) 1]<br />

[else (+ (fib (- n 1)) (fib (- n 2)))]))<br />

Die baumrekursive Fibonacci-Funktion terminiert für alle n ∈ N 0, da<br />

beide auftretenden rekursiven Aufrufe in jedem Schritt zu um 1 oder 2<br />

kleineren Parameterwerten führen und die Abbruchbedingungen<br />

(= n 0) bzw. (= n 1) immer erreicht werden<br />

© Xiaoyi Jiang Informatik I – Grundlagen der Programmierung<br />

168


<strong>3.6.4</strong> <strong>Rekursive</strong> <strong>Funktionen</strong><br />

Vorgehen zum Sicherstellen von Terminierung:<br />

� Welche Abbruchbedingungen sind für welche Parameter relevant?<br />

� Wie werden die relevanten Parameter in jedem Rekursionsschritt<br />

verändert?<br />

� Auf Grundlage dieses Wissens muss man versuchen,<br />

sicherzustellen (nachzuweisen), dass<br />

• alle vorkommenden rekursiven Aufrufe<br />

• für mindestens einen relevanten Parameter<br />

• nach endlich vielen Schritten<br />

• eine der Abbruchbedingungen erreichen durch Zuordnen einer<br />

Reihenfolge von komplexen zu einfacheren Aufrufen<br />

Berücksichtige diese Überlegungen nicht erst am (scheinbar) fertigen<br />

Programm, sondern bei der Entwicklung des Programms.<br />

© Xiaoyi Jiang Informatik I – Grundlagen der Programmierung<br />

169


<strong>3.6.4</strong> <strong>Rekursive</strong> <strong>Funktionen</strong><br />

Korrektheitsbeweis:<br />

Man kann für eine Funktion eine Zusicherung ihrer Eigenschaften<br />

geben. Zusicherungen für <strong>Funktionen</strong>, die mit Listen arbeiten, werden<br />

oft mit vollständiger Induktion über die Länge der beteiligten Listen<br />

bewiesen.<br />

Beispiel: Konkatenieren zweier Listen (vgl. Folie 114, Kap.3)<br />

(define (concat l1 l2)<br />

(cond [(empty? l1) l2]<br />

[else (cons (first l1) (concat (rest l1) l2))]))<br />

Zusicherung: Ist l1 = (x1 . . . xn) eine Liste der Länge n und l2 = (y1 . . .<br />

ym) eine Liste der Länge m, so ist der Wert von (concat l1 l2) die Liste<br />

(x1 . . . xn y1 . . . ym) der Länge n + m.<br />

© Xiaoyi Jiang Informatik I – Grundlagen der Programmierung<br />

170


<strong>3.6.4</strong> <strong>Rekursive</strong> <strong>Funktionen</strong><br />

(define (concat l1 l2)<br />

(cond [(empty? l1) l2] ; Zeile 1<br />

[else (cons (first l1) (concat (rest l1) l2))])) ; Zeile 2<br />

Beweis mit Induktion über n (Länge der Liste l1):<br />

Induktionsanfang: Für n = 0 folgt die Behauptung aus Zeile 1<br />

Induktionsschritt: Ist n > 0 und die Zusicherung für Listen der Länge n − 1<br />

richtig, dann gilt sie für Listen der Länge n: Nach Induktionsvoraussetzung<br />

ist der Wert von (concat (rest l1) l2) die Liste (x2 . . . xn y1 . . . ym) der<br />

Länge n − 1 + m, daraus und aus Zeile 2 folgt, dass (cons (first l1) ...) und<br />

damit das Resultat die Liste (x1 . . . xn y1 . . . ym) der Länge n + m ist.<br />

© Xiaoyi Jiang Informatik I – Grundlagen der Programmierung<br />

171


Exkurs: Beweis durch Induktion<br />

Wir wollen zeigen, dass eine Aussage P(n) für alle natürlichen Zahlen<br />

n ≥ k gilt<br />

Beweismethode der vollständigen Induktion:<br />

� Induktionsanfang (IA, auch Anker genannt): Zeige P(k)<br />

� Induktionsschritt (IS): Zeige, dass für alle natürlichen Zahlen n > k gilt:<br />

P(n) � P(n + 1)<br />

P(n) heisst auch Induktionsannahme oder Induktionsvoraussetzung (IV)<br />

© Xiaoyi Jiang Informatik I – Grundlagen der Programmierung<br />

172


Exkurs: Beweis durch Induktion<br />

Der folgende Satz ist benannt nach Carl Friedrich Gauss (1777–1855),<br />

der ihn bereits während seiner Grundschulzeit bewiesen hat<br />

Theorem: Für alle natürlichen Zahlen n gilt: Die Summe der ersten n<br />

natürlichen Zahlen ist gleich n(n + 1)/2<br />

Beweis:<br />

Der Induktionsanfang mit P(1) = 1 ist klar.<br />

IV: P(n) stimmt, d.h.<br />

Nun zeigen wir, dass auch P(n + 1) stimmt:<br />

© Xiaoyi Jiang Informatik I – Grundlagen der Programmierung<br />

173


Exkurs: Beweis durch Induktion<br />

Manchmal gelingt es nicht, aus der Gültigkeit der Eigenschaft P(n) auf<br />

die Gültigkeit von P(n + 1) zu schliessen. Hier hilft zuweilen das<br />

allgemeine Induktionsprinzip, bei dem man im Induktionsschritt zeigt:<br />

Wenn P(n′), k ≤ n′ ≤n, gilt, dann gilt auch P(n + 1)<br />

Beweismethode der allgemeinen Induktion:<br />

� Induktionsanfang (IA, auch Anker genannt): Zeige P(k)<br />

� Induktionsschritt (IS): Zeige, dass für alle natürlichen Zahlen n > k gilt:<br />

P(k) ∧ P(k + 1) ∧ ···∧ P(n) � P(n + 1)<br />

© Xiaoyi Jiang Informatik I – Grundlagen der Programmierung<br />

174

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!