1 2.5 verzweigte rekursion und backtracking-verfahren 2.5.1 türme von hanoi

Post on 06-Apr-2015

113 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

1

2.5    Verzweigte Rekursion und Backtracking-Verfahren2.5.1    Türme von Hanoi

2

Türme von Hanoi: Logisches Puzzle

Gegeben: 3 Pflöcke (start, ziel, hilf), verschieden große Scheiben, der Größe nach geordnet, alle auf Pflock start.

Ziel: alle Scheiben auf Pflock ziel bewegen, dort wieder der Größe nach geordnet.

Bedingung: Auch zwischendurch darf niemals eine größere Scheibe auf einer kleineren liegen!

3

Lösungsstrategie: rekursiv!

Sei n die Höhe des jeweils zu versetzenden Turmes.

move(n,start,ziel,hilf) Falls n=1, einzige Scheibe von start nach ziel sonst 1. move(n-1,start,hilf,ziel); 2. letzte Scheibe von start nach ziel; 3. move(n-1,hilf,ziel,start).

4

import java.io.*; public class hanoi {

public static void move(String start, String goal) { System.out.println("Move from "+start+" to "+goal); }

public static void moves(int n, String start, String goal, String aux) { if (n==1) move(start,goal); else { moves(n-1,start,aux,goal); move(start,goal); moves(n-1,aux,goal,start); } }

public static void main(String args[]) { moves(3,"a","c","b"); }}

Implementation in Java

5

2.5.2    Erzeugung fraktaler Muster durch

verzweigt rekursive Turtle-Programme

Beispiel: Koch-Kurve (Schneeflockenkurve)

6

Turtle: virtueller Zeichenroboter

Zustand:• Position• Blickrichtung Grundbefehle: • moveTurtle (Vorwärts- bzw. Rückwärtsschritt)• turnTurtle (Drehung auf der Stelle)

Turtle kann eine Spur hinterlassen und so Figuren zeichnen.

7

Koch-Kurve in Java:

private static void kochstep(TgFrame f, int n, float step)

{ if (n==0) { f.moveTurtle(step); } else { kochstep(f,n-1,step/3); f.turnTurtle(60); kochstep(f,n-1,step/3); f.turnTurtle(-120); kochstep(f,n-1,step/3); f.turnTurtle(60); kochstep(f,n-1,step/3); } }

8

Beispiel: Binär verzweigter Baum

Ein Baum der Verzweigungstiefe n besteht aus einem Stamm, an dem jeweils im 45 Grad-Winkel nach rechts und links ein Teilbaum gleicher Struktur mit Verzweigungstiefe n-1 angesetzt wird.

9

Implementation in Java: private static void tree(TgFrame f, int depth,

double step)

{

if (depth==1)

{ f.moveTurtle(step); f.moveTurtle(-step); }

else

{ f.moveTurtle(step);

f.turnTurtle(-45);

tree(f,depth-1,step/1.75);

f.turnTurtle(90);

tree(f,depth-1,step/1.75);

f.turnTurtle(-45);

f.moveTurtle(-step);

}

}

10

Backtracking: Hofstadters MU-Puzzle

Backtracking-Verfahren als Probieren nach dem Tiefensuche-Prinzip

Beispiel: MU-Puzzle von Hofstadter Gegeben ist folgendes Zeichensystem: Alphabet:     M, I, U Startwort:    "MI" Zielwort:      "MU"Operatoren (x,y stehen als Variablen für Teilstrings): (R1) xI xIU (R2) Mx Mxx (R3) xIIIy xUy (R4) xUUy xyFrage: Ist "MU" aus "MI" ableitbar?

11

2.5.3    Backtracking: Hofstadters MU-Puzzle

Backtracking laut Informatik-Duden:  

Backtracking bezeichnet ein Trial-and-Error-Verfahren, bei dem man versucht, eine Teillösung eines Problems inkrementell zu einer Gesamtlösung auszubauen. Falls in einer gewissen Situation ein weiterer Ausbau nicht mehr möglich ist (Sackgasse), müssen frühere Teilschritte zurückgenommen werden (daher Backtracking!). Zurücknehmen und erneutes Ausbauen wird solange wiederholt, bis eine Lösung gefunden wird oder bis man erkennt, dass im Rahmen der vorgegebenen Beschränkungen keine Lösung existiert. Im negativen Fall muss der (endliche) "Suchraum" vollständig exploriert werden, was i.d.R. exponentielle Komplexität bedeutet.

Beispiel: Suche in einem Irrgarten.

12

Beispiel: MU-Puzzle von Hofstadter

Gegeben ist folgendes Zeichensystem: Alphabet: M, I, U Startwort (z.B.): "MI" Zielwort (z.B.): "MU"Operatoren (x,y sind Variablen für Teilstrings): (R1) xI xIU (R2) Mx Mxx (R3) xIIIy xUy (R4) xUUy xyFrage: Ist "MU" aus "MI" ableitbar?

13

Allgemein: „Interpolationsproblem“

Gegeben: • Zustandsmenge S,• eine Menge von Operatoren O, d.h. jeder op aus

O ist von der Art op: S S,• Startzustand s und Zielzustand z (beide aus S).

Frage: Gibt es eine Operatorsequenz

op1, ... opn mit  

      z = opn(opn-1( .... op1(s) ... )) ?

14

Parameter: s (aktueller Zustand), z (Zielzustand) und t (verbleibende Suchtiefe).

Ausgabe: eine Operatorsequenz (als Liste (java.Vector))

muTrack(s, z: S, t: int) Liste von O { wenn s = z dann rückgabe [ ]; /* leere Liste */ wenn t = 0 rückgabe null; /* "Fehlanzeige" */ res: Liste von O; /* späterer Ausgabeparameter */ n: S; /* nächster Folgezustand */ für alle op in O führe_aus { n := op(s); wenn nicht n = null /* d.h. Operator anwendbar */ dann { res := muTrack(n,z,t-1); wenn nicht res = null dann rückgabe mitErstem(op,res); } } rückgabe null; /* "Fehlanzeige" */ }

15

3    Speicherverfahren3.1    Strukturierte Daten

Definition (strukturiertes Datum): Ein strukturiertes Datum ist ein Tupel aus

            ( Adresse a, Knoteninhalt k, Zeigerfeld z* ) • wobei die Adresse ein Wort, der Knoteninhalt wiederum

ein ordinaler oder strukturierter Typ und das Zeigerfeld eine Folge von Adressen oder Relationen zwischen Adressen ist, z.B:

Adresse Knoteninhalt * * ... * • Speicherung: injektive Abbildung von einer Datenstruktur

bestehend aus Knoten mit Inhalten und Relationen (zu anderen Knoten der Datenstruktur) auf Adressen von Speicherzellen.

16

3.2    Beispiele für strukturierte Daten

Lineare Listen, gekettet oder doppelt gekettet gespeichert.

Speicherstruktur einer doppelt geketteten Liste:         Null

Kopf 1: (k1, Null * , * 2)

2: (k2, 1 * , * 3) ...

Ende n: (kn , n-1 * , * Null) Null

Durch

1: (k1, n * , * 2) …… n: (kn , n-1 * , * 1) entsteht eine zirkuläre Liste.

17

Speicherung eines Arrays:

Wir betrachten ein Array a [ 0..n1, 0..n2, ..., 0..nk] .Annahme: die Speicherung beginnt in e, und für jeden

Knoten sind r Zellen notwendig. Dann wird das Element    a [ x1, x2, ..., xk ] an folgender

Adresse abgelegt: e + r ( x1 (n2+ 1)(n3 + 1) … (nk+ 1) + x2 (n3+ 1) ... (nk + 1) + ... + xk-1 (nk + 1) + xk)

Zahlenbeispiel: k = 3, n1 = 2, n2 = 3, n3 = 4, r = 5, e = 23.Dann wird das Element    a [ x1, x2, ..., xk ] an folgender Adresse abgelegt: e + r ( x1 (n2+ 1)(n3 + 1) + x2(n3 + 1) + x3 ),

z.B. ist die Adresse des Elements    a [1, 3, 2] : 23 + 5 (1 • 4 • 5 + 3 • 5 + 2) = 208 

18

Beispiel: Im Fall n1 = n2 = … = nk =9 wird das Element

a [x1, x2 , …, xk]

an folgender Adresse abgelegt: e + r •( x1 •10k-1 + x2 • 10k-2 + … + xk-1 • 10 + xk )

= e + r • die Zahl mit der Dezimaldarstellung x1 x2 … xk .

Die allgemeine Adressberechnung kann also als Verallgemeinerung der Dezimaldarstellung aufgefasst werden: mit möglicherweise verschiedenen Basen bei den verschiedenen Ziffern xi (Indizes des Elementes).

19

3.3    Verdichtete Speicherung

Eine Menge von Daten kann verdichtet gespeichert werden, wenn fast alle Knoten den gleichen Wert k haben. In diesem Fall verzichtet man auf die Speicherung dieser Knoten und schließt aus ihrem Nichtauftreten, dass sie den Wert k haben. Dann muss jedoch jeder Knoten seine ursprüngliche Position mit sich führen, d.h. ki geht über in (i, ki).

Beispiel: Gegeben sei eine lineare Liste (1, k1, *) (2, k2, * ) (3, k3, *) (4, k4, *)(5, k5, * ) (6, k6, *)(7, k7 , *)  ... für deren Elemente gilt: k = k2 = k4 = k5 = k6 = ...

Verdichtete Speicherung: (1, k1, *)  (3, k3, *) (7, k7 , *)  ... 

20

3.4    Indizierte Speicherung

Eine lineare Liste K = (k1, ..., kn)  kann in m disjunkte Teillisten aufgespalten werden. Man definiert eine Indexliste, die eine „Liste von Listen“ ist. In der Indexliste werden die Indizes mit einem Namen bzw. der Anzahl der Teillistenelemente und einem Zeiger auf den Beginn der Teillisten abgelegt.

Beispiel:   d1     * k2, k2 * k5, k5 * k6, k6 * Null

d2     * k1, k1 * Null, ....

dm    * k3, k3 * k4, k4 * Null.

Anwendung: bei Datenbanken und Betriebssystemen.

21

Beispiel: unter UNIX wird ein mehrfach indiziertes Speicherblocksystem verwendet.

• Plätze 0 bis 9: enthalten Adressen von 10 Blocks à 512 Bytes.

• Platz 10: enthält einen Zeiger auf eine Tabelle von 128 Blockadressen.

• Platz 11: enthält einen Zeiger auf eine Tabelle von 128 Indexblockadressen, die jeweils eine Tabelle von 128 Blockadressen betreffen (1282 Blockadressen).

• Platz 12: enthält einen Masterindexblock mit Adressen von 128 Indexblöcken à 128 Blockadressen, womit eine dreifache Indizierung erreicht ist (1283 Blockadressen).

Insgesamt können so 2.113.674 Blöcke angesprochen werden.

22

23

3.5    Gestreute Speicherung Seien • K = (k1, ..., kn) eine Datenmenge,• S ein Speicherbereich mit Anfangsadresse e,• w(k) ein Schlüssel zum Knoten k (für jeden Knoten k) (der Schlüssel ist üblicherweise eine Komponente oder

eine Kombination von Komponenten des Knoteninhalts; wir nehmen an, dass verschiedene Knoten verschiedene Schlüssel haben)

• s eine Speicherfunktion vom Schlüsselraum der Daten in den Raum der Adressen.

Wir sprechen von gestreuter Speicherung, wenn für jeden Knoten k gilt:

Speicheradresse des Knotens k = e + s(w(k)).Das heißt, die Speicheradresse eines Knotens kann aus

dem Schlüssel eines Knotens und daher aus dem Knoteninhalt berechnet werden!

24

Anforderungen an die Speicherfunktion s• leicht zu berechnen,• sollte nur vom Schlüsselraum, aber nicht von der

Datenmenge abhängen,• injektiv auf der Menge der vergebenen Schlüssel.

Diese Forderungen sind wegen der Größe des Schlüsselraumes nicht gleichzeitig zu erfüllen: der Schlüsselraum, d.h. die Zahl der möglichen Schlüssel ist oft viel größer als die Zahl der zu vergebenden Speicheradressen. Injektivität auf dem ganzen Schlüsselraum ist dann nicht zu erreichen.

Oft sind zwar nur relativ wenige Schlüssel tatsächlich vergeben. Welche, hängt aber von der Anwendung ab.

Daher verzichtet man oft auf die Injektivität.

25

Kollision, Überlauf:

Ist eine Speicherfunktion nicht injektiv, so können derselben Adresse mehrere Schlüssel zugeordnet werden.

Kollision oder Überlauf.

Werden derselben Adresse m (m > 1) Schlüsselknoten zugeordnet, sprechen wir von einem Überlauf der Größe m-1.

26

Zur Vermeidung von Kollisionen wird eine weitere Anforderung an die Speicherfunktion gestellt:

• Möglichst gleichmäßige Verteilung der Schlüssel auf den Adressraum.

Mehr noch: auch Regelmäßigkeiten bei den Schlüsseln sollten nicht auf den Adressraum übertragen werden.

Beispiel: Variablennamen beginnen oft mit x, z.B: x1, xi.

Hängt die Adresse nur vom ersten Zeichen ab, erhält man dann viele Kollisionen!

27

Beispiele

1. Beispiel für eine Speicherfunktion

Divisionsrestverfahren: s(w(k)) := w(k) mod m wobei m eine Primzahl ist.

Warum nicht z.B. m = 2k ?

Dann würde die Adresse s(w(k)) nur von den letzten k Bits der Binärdarstellung von w(k) abhängen. Daraus folgt höhere Kollisionsgefahr (bei ungleichmäßiger Verteilung der letzten k Bits in den Schlüsseln)!

28

2. Beispiel für eine Speicherfunktion

Ist w(k) der Schüssel von k, so kann s(w(k)) z.B. durch eine feste Anzahl und eine bestimmte Auswahl von Stellen in w2 gegeben sein:

Hier: jeweils letzte und drittletzte Ziffer.

w w2 s(w(k))

723 522729 79

23 529 59

417 173889 89

233 54289 29

3 9 09

999 998001 01

top related