Lässt es sich mit cremefarbenem Papier, einer Tönung, mehr Kontrast oder größerer Schrift besser lesen? Wählen Sie Ihre Farben und Ihre Schrift, einmalig für alle Seiten.

Kostenlose Lektion

Memoisation und dynamische Programmierung

Nach dieser Lektion: Ich kann eine langsame Rekursion mit einer Merktabelle beschleunigen.

Lektion, 65 Min.2 BefehleLab „Linux im Alltag“: die dreißig Befehle, die Entwickelnde auswendig tippen

Wie man eine langsame Rekursion schnell macht

Memoisation bedeutet, das Ergebnis eines Funktionsaufrufs zu merken, damit derselbe Aufruf nie ein zweites Mal gerechnet wird. Eine rekursive Funktion, die Millionen Aufrufe braucht, kommt dann mit einigen Dutzend aus.

Ein Getränkeautomat soll einen Betrag in möglichst wenige Wertmarken wechseln. Ohne Merken rechnet er dieselben Teilbeträge immer wieder.

Am Ende dieser Lektion

Notiz

Ich kann eine langsame Rekursion mit einer Merktabelle beschleunigen.

Schlüsselwörter

BegriffWas er bedeutetBeispiel
MemoisationErgebnisse von Aufrufen merken und wiederverwenden
Merktabelleein Wörterbuch, das Eingabe und Ergebnis speichertmerke[10] = 55
Teilproblemein kleineres Problem, das zum großen gehört
Dynamische Programmierungein großes Problem aus gemerkten Teilproblemen lösen
Gieriges Verfahrenimmer die jetzt beste Wahl treffen

Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.

Dieselbe Arbeit wird immer wieder getan

Wenn fib(6) gerechnet wird, ruft es fib(5) und fib(4) auf. Auch fib(5) ruft wieder fib(4) auf, und das rechnet alles noch einmal.

Der Code zählt, wie oft jedes fib(k) aufgerufen wird. Diese Wiederholung ist die Verschwendung. Genau sie lässt sich vermeiden.

Wie oft jedes fib(k) vorkommt

Code: python
# Wie oft wird jedes fib(k) gerechnet, wenn wir fib(6) wollen?
anzahl = {}

def fib(n):
    if n in anzahl:
        anzahl[n] = anzahl[n] + 1
    else:
        anzahl[n] = 1
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

print("fib(6) =", fib(6))
for k in range(7):
    print("fib(", k, ") wurde", anzahl[k], "mal aufgerufen")
# Aendern Sie 6 auf 8. Welche Zeile waechst am staerksten?

Die Aufrufe bei fib(6)

AufrufWie oft gerechnetWie oft nötig
fib(6)11
fib(5)11
fib(4)21
fib(3)31
fib(2)51
fib(1)81
fib(0)51

Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.

Eine Merktabelle schlägt nach, statt neu zu rechnen

Die Merktabelle ist ein Wörterbuch. Der Schlüssel ist die Eingabe, der Wert das Ergebnis. Die Funktion schaut zuerst nach. Steht die Eingabe schon darin, gibt sie das Ergebnis sofort zurück.

Sonst rechnet sie, trägt das Ergebnis ein und gibt es zurück. Jedes Teilproblem wird so nur einmal gerechnet. Aus 2.692.537 Aufrufen bei fib(30) werden 59.

fib mit Merktabelle

Code: python
# Dieselbe Funktion, aber mit einer Merktabelle (einem Woerterbuch).
merke = {}
aufrufe = [0]

def fib(n):
    aufrufe[0] = aufrufe[0] + 1
    if n in merke:
        return merke[n]  # schon gerechnet: nur nachschlagen
    if n < 2:
        ergebnis = n
    else:
        ergebnis = fib(n - 1) + fib(n - 2)
    merke[n] = ergebnis
    return ergebnis

for n in [10, 20, 30]:
    merke = {}
    aufrufe[0] = 0
    print("fib(", n, ") =", fib(n), "Aufrufe:", aufrufe[0])
# Ohne Merktabelle waren es 177, 21891 und 2692537 Aufrufe.

Im Lab: dieselben Zahlen mit einer Schleife

Man kann die Merktabelle auch von unten füllen: erst fib(0), dann fib(1), und jedes weitere aus den beiden davor. Das ist die dynamische Programmierung.

Die Befehle bauen die Tabelle in einer Schleife und geben einen Wert aus. Für fib(30) sind das nur 29 Additionen.

fib(30) in einer Schleife

Befehl
python3 -c "t = [0, 1]; exec('for i in range(2, 31): t.append(t[i - 1] + t[i - 2])'); print(t[30])"

Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.

fib(90) in einer Schleife

Befehl
python3 -c "t = [0, 1]; exec('for i in range(2, 91): t.append(t[i - 1] + t[i - 2])'); print(t[90])"

Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.

Wechselgeld mit den wenigsten Marken

Ein Automat hat Marken zu 1, 3 und 4. Wie wechselt er 6 in möglichst wenige Marken? Die Teilprobleme heißen: die wenigsten Marken für jeden kleineren Betrag.

Für einen Betrag b probiert er jede Marke aus. Er nimmt die Marke und fragt nach den wenigsten Marken für den Rest. Die beste Wahl plus 1 ist die Antwort.

Wenigste Marken für Beträge 0 bis 8

BetragBeste WahlWenigste Marken
0keine0
111
21 + 12
331
441
54 + 12
63 + 32
73 + 42
84 + 42

Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.

Die wenigsten Marken mit Merktabelle

Code: python
# Wechselgeld: die wenigsten Marken fuer einen Betrag, Marken zu 1, 3 und 4.
marken = [1, 3, 4]
merke = {}

def wenigste(betrag):
    if betrag == 0:
        return 0
    if betrag in merke:
        return merke[betrag]
    beste = 1000
    for wert in marken:
        if wert <= betrag:
            versuch = 1 + wenigste(betrag - wert)
            if versuch < beste:
                beste = versuch
    merke[betrag] = beste
    return beste

for betrag in [6, 7, 8]:
    print("Betrag", betrag, ":", wenigste(betrag), "Marken")
# Aendern Sie die Marken auf [1, 5, 12] und den Betrag auf 15.

Ein häufiger Fehler

Warnung

Viele denken, es genüge, immer die größte passende Marke zu nehmen. Für 6 ergibt das 4, 1 und 1, also drei Marken. Mit 3 und 3 sind es nur zwei. Ein gieriges Verfahren ist nicht immer das beste. Die Tabelle der Teilprobleme probiert alle Wege.

Weiterführend

Ändern Sie wenigste_muenzen so, dass es nicht nur die Zahl zurückgibt, sondern auch die Liste der Münzen. Merken Sie sich dazu in der Tabelle für jeden Betrag die gewählte Münze.

Rechnen Sie dann von Hand nach, ob bei 63 mit den Marken 1, 3 und 4 wirklich 16 Marken nötig sind.

Über dieses Lab und seinen Quellcode: Lab „Linux im Alltag“: die dreißig Befehle, die Entwickelnde auswendig tippen

Sie unterrichten eine Gruppe? Weisen Sie dieses Modul Ihren Studierenden zu, mit Handbuch für Lehrende, automatisch geprüften Aufgaben und einem Lab für jede Person.