Algorithmen und Datenstrukturen

Werbung
Algorithmen und Datenstrukturen
Werner Struckmann
Wintersemester 2005/06
1. Der Algorithmenbegriff
1.1 Der intuitive Algorithmenbegriff
1.2 Ein Beispiel: Sortieren durch Einfügen
1.3 Programmiersprachen: der praktische Algorithmenbegriff
1.4 Berechenbarkeit: der formale Algorithmenbegriff
Der intuitive Algorithmenbegriff
Gegeben sei ein „Problem“. Eine Handlungsvorschrift, deren
mechanisches Befolgen
◮
ohne Verständnis des Problems
◮
mit sinnvollen Eingabedaten
◮
zur Lösung des Problems führt,
wird Algorithmus genannt.
Ein Problem, für dessen Lösung ein Algorithmus existiert, heißt
berechenbar.
1.1 Der intuitive Algorithmenbegriff
1-1
Beispiele für Algorithmen
◮
Zerlegung handwerklicher Arbeiten in einzelne Schritte,
◮
Kochrezepte,
◮
Verfahren zur schriftlichen Multiplikation,
◮
Algorithmus zur Bestimmung des größten gemeinsamen
Teilers zweier natürlicher Zahlen.
1.1 Der intuitive Algorithmenbegriff
1-2
Präzisierung des Begriffs
Ein Algorithmus ist eine wohldefinierte Rechenvorschrift, die eine
(evtl. leere) Menge von Größen als Eingabe verwendet und eine
Menge von Größen als Ausgabe erzeugt.
Ein Algorithmus ist also eine Abfolge von Rechenschritten, die die
Eingabe in die Ausgabe umwandelt.
◮
Der Algorithmus muss durch einen endlichen Text in einer
wohldefinierten Sprache beschrieben sein.
◮
Die Objekte der Berechnung müssen klar sein.
◮
Die Operationen müssen mechanisch ausführbar sein.
◮
Die Reihenfolge der Operationen muss feststehen.
1.1 Der intuitive Algorithmenbegriff
1-3
Eigenschaften von Algorithmen
◮
Terminierend: Für alle korrekten Eingaben hält der
Algorithmus nach endlich vielen Schritten an.
◮
Vollständigkeit: Alle Fälle, die bei korrekten Eingabedaten
auftreten können, werden berücksichtigt.
◮
Determiniert: Der Algorithmus liefert bei jedem Ablauf mit
den gleichen Eingaben das gleiche Ergebnis.
◮
Deterministisch: Der Algorithmus läuft bei jedem Ablauf mit
den gleichen Eingaben durch diesselbe Berechnung.
1.1 Der intuitive Algorithmenbegriff
1-4
Beispiele zu Eigenschaften
Nichtterminierender Algorithmus:
1. Wähle zufällig eine natürliche Zahl.
2. Ist die Zahl gerade, wiederhole ab 1.
3. Ist die Zahl ungerade wiederhole ab 1.
Nicht vollständiger Algorithmus:
1. Wähle zufällig eine Zahl x .
2. Wähle zufällig eine Zahl y .
3. Das Ergebnis ist x /y .
Was ist, wenn y = 0 sein sollte?
1.1 Der intuitive Algorithmenbegriff
1-5
Beispiele zu Eigenschaften
Nicht determinierter Algorithmus:
1. Wähle zufällig eine natürliche Zahl zwischen 260 und 264 .
2. Prüfe, ob die Zahl eine Primzahl ist.
3. Falls nicht, wiederhole ab 1.
Das Ergebnis ist immer eine Primzahl, aber nicht immer die
gleiche, daher ist der Algorithmus nicht determiniert.
Nichtdeterministischer Algorithmus, jedoch determiniert:
1. Mische das Eingabefeld zufällig.
2. Prüfe, ob jedes Element kleiner als das rechts von ihm
stehende ist.
3. Falls nicht, wiederhole ab 1.
1.1 Der intuitive Algorithmenbegriff
1-6
Algorithmen und Datenstrukturen
◮
Algorithmen
◮
◮
◮
◮
◮
◮
elementare Operationen
sequenzielle, bedingte, wiederholte Ausführung
Unterprogramme
die Schritte werden an anderer Stelle beschrieben und sind
mehrfach verwendbar.
Rekursionen
derselbe Algorithmus wird auf ein oder mehrere gleichartige
Teilprobleme angewendet.
„gleichzeitige“ Ausführung
Datenstrukturen
◮
◮
einfache Daten: Zahlen, Zeichen, Wahrheitswerte
komplexe Datenstrukturen: Felder, Listen, Bäume
1.1 Der intuitive Algorithmenbegriff
1-7
Codierung von Daten
◮
Ein Code ist eine (berechenbare) Funktion, die jeder
Zeichenkette einer Urbildmenge eindeutig eine Zeichenkette
aus einer Bildmenge zuordnet.
◮
In technischen Systemen dienen Codes überwiegend der
Darstellung von Nachrichten.
◮
Codierungen ermöglichen beispielsweise die Verwendung von
Nachrichten in Rechnern und die Übertragung von
Nachrichten.
◮
Beispiel „Zahldarstellung in Stellenwertsystemen“:
(22)10 = (112)4 = (10110)2
◮
Beispiel „ASCII-Code zur Darstellung von Zeichen“:
B 7−→ 66 7−→ 1000010
1.1 Der intuitive Algorithmenbegriff
1-8
Sortieren durch Einfügen
◮
Spezifikation: Eine Liste < a1 , a2 , . . . , an > von n ganzen
Zahlen, n ≥ 1, soll aufsteigend sortiert werden.
◮
Objekte: Zahlen, Listen von Zahlen.
◮
Operationen: Vergleiche, Einfügen in Liste, Löschen aus
Liste.
◮
Eingabe: Eine Liste < a1 , a2 , . . . , an > von n Zahlen.
◮
Ausgabe: Eine Permutation < a1′ , a2′ , . . . , an′ > der Eingabe
mit a1′ ≤ a2′ ≤ . . . ≤ an′ .
Die zu sortierenden Zahlen werden auch als Schlüssel bezeichnet.
Beim Sortieren durch Einfügen wird jede Zahl aus der
Ausgangsliste an die richtige Stelle in der Zielliste eingefügt.
1.2 Ein Beispiel: Sortieren durch Einfügen
1-9
Sortieren durch Einfügen
1. Fange beim zweiten Element an.
2. Bewege die Zahl an die richtige Stelle der Liste
3. Schiebe dazu solange Elemente in der Liste nach rechts bis
die richtige Stelle erreicht ist.
4. Wiederhole die Schritte mit dem nächsten Element bis das
Ende der Liste erreicht ist.
5. Die Liste hat jetzt die richtige Sortierung und kann
zurückgegeben werden
1.2 Ein Beispiel: Sortieren durch Einfügen
1-10
Beispiel
5 2 4 6 1 3
2 4 5 6 1 3
2 5 4 6 1 3
1 2 4 5 6 3
2 4 5 6 1 3
1 2 3 4 5 6
1.2 Ein Beispiel: Sortieren durch Einfügen
1-11
Pseudocode
insertionSort(A)
j ← 2;
while j ≤ length(A) do
key ← A[j];
// insert A[j] into the
// sorted sequence A[1 .. j-1]
i ← j - 1;
while i > 0 und A[i] > key do
A[i + 1] ← A[i];
i ← i - 1;
od;
A[i + 1] ← key;
j ← j + 1;
od;
1.2 Ein Beispiel: Sortieren durch Einfügen
1-12
Korrektheit und Komplexität
◮
Korrektheit: Leistet der Algorithmus das Gewünschte? Das
heißt: Sortiert der Algorithmus die Liste und terminiert dann?
◮
Komplexität: Wie viele Rechenschritte und wie viel Speicher
benötigt der Algorithmus?
1.2 Ein Beispiel: Sortieren durch Einfügen
1-13
Schleifeninvariante
In jeder Iteration gilt:
◮
A [1..j − 1] enthält immer die Elemente, die auch vorher dort
lagen.
◮
Die Elemente in diesem Bereich sind sortiert.
Eine Schleifeninvariante
◮
Initialisierung: gilt vor dem ersten Schleifendurchlauf.
◮
Fortsetzung: gilt vor dem n + 1-Schleifendurchlauf, falls sie
vor dem n-Schleifendurchlauf galt.
◮
Terminierung: liefert eine nützliche Bedingung, sobald die
Schleife abbricht.
1.2 Ein Beispiel: Sortieren durch Einfügen
1-14
Schleifeninvariante im Beispiel
Schleifeninvariante: Das Teilfeld A [1..j − 1] besteht aus den
ursprünglich in A [1..j − 1] enthaltenen Elementen in geordneter
Reihenfolge.
Initialisierung: j = 2. A [1] besteht aus dem Element, was vorher
auch schon dort war. Die Liste A [1] ist sortiert.
Fortsetzung: A [j − 1], A [j − 2], . . . werden jeweils nach rechts
verschoben, A [j ] an der richtigen Stelle eingefügt.
Terminierung: j = n + 1 eingesetzt in die Invariante ergibt: A [1..n]
enthält die Elemente, die vorher in A [1..n] enthalten waren, in
geordneter Reihenfolge.
1.2 Ein Beispiel: Sortieren durch Einfügen
1-15
Übersicht
Code
Kosten
insertionSort(A)
j ← 2;
c1
while j ≤ length(A) do
c2
key ← A[j];
c3
i ← j - 1;
c4
while i > 0 und A[i] > key c5
do;
A[i + 1] ← A[i];
c6
c7
i ← i - 1;
od
A[i + 1] ← key;
c8
j ← j + 1;
c9
od;
Anzahl
1
n
n−1
n−1
Pn
j =2 tj
Pn
(tj − 1)
j
=
2
Pn
j =2 (tj − 1)
n−1
n−1
tj : Anzahl, wie oft der Test der inneren Schleife ausgeführt wird
1.2 Ein Beispiel: Sortieren durch Einfügen
1-16
Laufzeit
Die Gesamtlaufzeit ergibt sich zu:
T (n) = c1 + c2 n + c3 (n − 1) + c4 (n − 1) + c5 ·
n
X
tj
j =2
n
n
X
X
+ c6 ·
(tj − 1) + c7 ·
(tj − 1)
j =2
j =2
+ c8 (n − 1) + c9 (n − 1)
1.2 Ein Beispiel: Sortieren durch Einfügen
1-17
Günstigster Fall
Wenn das Feld sortiert ist, gilt immer A[i] ≤ key , also tj = 1.
T (n) = c1 + c2 n + c3 (n − 1) + c4 (n − 1)
+ c5 (n − 1) + c8 (n − 1) + c9 (n − 1)
= an + b = Θ(n)
1.2 Ein Beispiel: Sortieren durch Einfügen
1-18
Ungünstigster Fall
Wenn das Feld umgekehrt sortiert ist, gilt nie A[i] ≤ key , also
tj = j .
T (n) = c1 + c2 n + c3 (n − 1) + c4 (n − 1)
!
1
+ c5 n(n + 1) − 1
2
1
1
+ c6 (n − 1)n + c7 (n − 1)n
2
2
+ c8 (n − 1) + c9 (n − 1)
= an2 + bn + c = Θ(n2 )
j
Mittlerer Fall: tj ≈ 2 .
Ergibt ebenfalls Θ(n2 ).
1.2 Ein Beispiel: Sortieren durch Einfügen
1-19
Programm und Programmiersprache
Ein Programm ist die Formulierung eines Algorithmus und seiner
Datenbereiche in einer Programmiersprache.
Eine Programmiersprache erlaubt es, Algorithmen präzise zu
beschreiben. Insbesondere legt eine Programmiersprache
◮
die elementaren Operationen,
◮
die Möglichkeiten zu ihrer Kombination und
◮
die zulässigen Datenbereiche
eindeutig fest.
Unter „programmieren“ versteht man den Vorgang des Erstellens
eines Programms.
1.3 Programmiersprachen: der praktische Algorithmenbegriff
1-20
Entwicklung der Programmiersprachen
JAVA
1995
93
91
SCHEME−Standard
89
87
Programmiersprachen in der
Informatikausbildung
C++
85
83
81
ADA
79
CSP
ALGOL68
67
59
◮
Modula-2
◮
Scheme
◮
Scheme/Java
◮
Java
LOGO
69
61
Algol 68
PROLOG
PASCAL
71
63
◮
SCHEME
C
73
65
Algol
SMALLTALK80
MODULA2
77
1975
◮
OCCAM
SIMULA
PL/I
BASIC
COBOL
ALGOL
LISP
57
1955
FORTRAN
1.3 Programmiersprachen: der praktische Algorithmenbegriff
1-21
Definition von Programmiersprachen
Unter Semiotik versteht man die Lehre von der Entstehung, dem
Aufbau und der Wirkungsweise von Zeichen und
Zeichenkomplexen. Sie umfasst die folgenden Bereiche.
Die lexikalische Struktur einer Programmiersprache bestimmt die
textuellen Grundbausteine der Programme. Solche Bausteine sind
etwa Schlüsselwörter und Bezeichner. Sie werden zum Beispiel
durch Aufzählung oder reguläre Ausdrücke bestimmt.
Die Syntax einer Programmiersprache beschreibt, wie aus den
Grundbausteinen vollständige Programme gebildet werden
können. In den meisten Fällen wird die Syntax einer
Programmiersprache durch eine kontextfreie Grammatik
festgelegt.
1.3 Programmiersprachen: der praktische Algorithmenbegriff
1-22
Definition von Programmiersprachen
Die Bedeutung der syntaktisch korrekten Programme ist durch die
Semantik der Sprache gegeben. Sie kann beispielsweise mithilfe
von Zustandsfolgen (operationelle Semantik) oder durch
Funktionen, die den syntaktischen Einheiten zugeordnet sind
(denotationale Semantik), definiert werden.
Die Pragmatik einer Programmiersprache untersucht ihre
Anwendbarkeit und Nützlichkeit. Sie gehört nicht zur Definition der
Sprache.
1.3 Programmiersprachen: der praktische Algorithmenbegriff
1-23
Definition von Programmiersprachen
Lexikalische Struktur:
Bezeichner: Buchstabe · (Buchstabe, Ziffer)∗
Schlüsselwörter: while, do, od
Syntax:
<Folge>
<Anweisung>
<Zuweisung>
<While>
::=
::=
::=
::=
<Anweisung> ; <Folge> | <Anweisung>
<Zuweisung> | <While> | ...
<Bezeichner> := <arith. Ausdruck>
while <log. Ausdruck> do <Folge> od
(Operationelle) Semantik:
Eine (partielle) Funktion f, die Zustände auf Zustände abbildet.
1.3 Programmiersprachen: der praktische Algorithmenbegriff
1-24
Klassifikation der Programmiersprachen
Die Programmiersprachen lassen sich grob in drei Klassen
einteilen:
◮
Maschinensprachen
Bits und Bytes, für den menschlichen Leser kaum verständlich
◮
Maschinenorientierte Sprachen (Assembler)
stellen die Befehle in einem Mnemo-Code dar
ADDIC 23, R0
STO R0, #12004
◮
Problemorientierte Sprachen
imperative, funktionale, objektorientierte, deduktive Sprachen
Ein Computer versteht nur Maschinensprachen!
1.3 Programmiersprachen: der praktische Algorithmenbegriff
1-25
Implementierung von Programmiersprachen
Compiler übersetzen Quellprogramme aus problemorientierten
Sprachen in äquivalente Zielprogramme in Maschinensprachen:
cc -o prog prog.c
prog input output
Interpreter lesen das Programm zusammen mit den Eingabedaten
ein und führen es aus:
scm prog.scm input output
Mischverfahren übersetzen das Programm zunächst mit einem
Compiler in eine Zwischensprache. Das übersetzte Programm wird
anschließend interpretiert:
javac prog.java
java prog input output
1.3 Programmiersprachen: der praktische Algorithmenbegriff
1-26
Verarbeitung von Java-Programmen
◮
Zuerst wird ein
Quellprogramm vom
Compiler in Bytecode
übersetzt.
◮
Im zweiten Schritt wird der
Bytecode vom Interpreter
ausgeführt. Der Bytecode
kann als Maschinencode
der sogenannten virtuellen
Java-Maschine angesehen
werden. Bytecode ist
portabel.
Java−Quellprogramm
javac
Java−Bytecode
java
VMfürWindows
java
VMfürLinux
1.3 Programmiersprachen: der praktische Algorithmenbegriff
1-27
Berechenbarkeit/Entscheidbarkeit
◮
Um zu zeigen, dass ein Problem berechenbar ist, kann man
einen Algorithmus angeben.
◮
Gibt es mathematisch beschreibbare Problemstellungen, die
nicht berechenbar sind? Solch ein Nachweis setzt eine
mathematisch exakte Formulierung des Algorithmenbegriffs
voraus.
◮
Es existieren verschiedene Ansätze zur Präzisierung des
Algorithmenbegriffs: Turing-Maschinen, Markov-Algorithmen,
partiell-rekursive Funktionen,. . .
1.4 Berechenbarkeit: der formale Algorithmenbegriff
1-28
Turing-Maschine
◮
Alan M. Turing (1912–1954), britischer Mathematiker:
„Berechenbar“ heißt auf einer Maschine ausführbar.
◮
Turing-Maschine: mathematisches Modell einer
Rechenmaschine.
1.4 Berechenbarkeit: der formale Algorithmenbegriff
1-29
Bestandteile einer Turing-Maschine
◮
beliebig langes Band bestehend aus einzelnen Feldern,
◮
Alphabet B von Zeichen, die in den Feldern gespeichert
werden können,
◮
Lese-/Schreibkopf für genau ein Feld,
Aktionen r , l , s < B ,
◮
◮
◮
◮
◮
Kopf ein Feld nach rechts bewegen: r ,
Kopf ein Feld nach links bewegen: l ,
stoppen: s ,
schreiben von x ∈ B : x ,
◮
Turing-Tafel,
◮
Steuereinheit steuert Bewegungen und Schreibaktionen,
jeweils in einem von endlich vielen Zuständen.
1.4 Berechenbarkeit: der formale Algorithmenbegriff
1-30
Turing-Tafel
Eine Turing-Tafel besteht aus einer Folge von Anweisungen der
Gestalt (z , x , a , z ′ ) mit
◮
z ∈ Z = {0, 1, . . . , m}: Zustand der Maschine vor Ausführung
der Anweisung,
◮
x ∈ B : Zeichen auf dem Band,
◮
a ∈ A = {r , l , s } ∪ B : Aktion,
◮
z ′ ∈ Z ∪ {⊥}: Folgezustand nach Ausführung der Anweisung.
⊥ ist der Endzustand. Für alle z ∈ Z , x ∈ B gibt es genau eine
Anweisung (z , x , a , z ′ ), mit a ∈ A , z ′ ∈ Z ∪ {⊥}. Eine
Turing-Maschine arbeitet also deterministisch.
1.4 Berechenbarkeit: der formale Algorithmenbegriff
1-31
Aktionen
z
a=l
... 1 0 3 ...
a=s
... 1 0 3 ...
a=2
a=r
z′ = ⊥
... 1 0 3 ...
1.4 Berechenbarkeit: der formale Algorithmenbegriff
z′
z′
... 1 0 3 ...
z′
... 1 2 3 ...
1-32
Alternative Darstellung
Zustandsüberführungsdiagramm:
x3 : a3
x0 : a0
z0
x2 : a2
z1
z11
x1 : a1
z2
1.4 Berechenbarkeit: der formale Algorithmenbegriff
...
1-33
Die churchsche These
◮
Eine (partielle) Funktion f : N0 → N0 heißt
turing-berechenbar, wenn es eine Turing-Maschine gibt, die
für jeden Eingabewert n aus dem Definitionsbereich nach
endlich vielen Schritten mit der Bandinschrift f (n) anhält.
◮
Die Turing-Berechenbarkeit ist ein mathematisches Modell zur
Beschreibung von Algorithmen.
◮
These von Church: Der intuitive Begriff „berechenbar“ wird
durch den mathematischen Begriff „turing-berechenbar“
erfasst. Es handelt sich hier um eine prinzipiell nicht
beweisbare These.
◮
Weitere mathematische Modelle (s. oben) erwiesen sich als
äquivalent. Dies ist ein starkes Indiz für die Gültigkeit der
churchschen These.
1.4 Berechenbarkeit: der formale Algorithmenbegriff
1-34
Das Halteproblem
◮
Das Halteproblem für Turing-Maschinen ist nicht entscheidbar
(berechenbar), d. h., es gibt keinen Algorithmus
(Turing-Maschine), der für alle Turing-Maschinen und für alle
möglichen Eingaben entscheidet, ob die Turing-Maschine mit
dieser Eingabe anhält oder nicht.
◮
Dieser Satz schließt nicht aus, dass man für spezielle
Turing-Maschinen entscheiden kann, ob sie halten. Er besagt
lediglich, dass es kein allgemeines Verfahren gibt.
1.4 Berechenbarkeit: der formale Algorithmenbegriff
1-35
Herunterladen