• Keine Ergebnisse gefunden

5.1.2 Das Halteproblem

N/A
N/A
Protected

Academic year: 2022

Aktie "5.1.2 Das Halteproblem"

Copied!
40
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

5 Theorie der Algorithmen

5.1 Berechenbarkeit

5.1.1 Äquivalenz der Algorithmus-Begriffe 5.1.2 Das Halteproblem

5.1.3 Das Äquivalenzproblem 5.1.4 Rekursionssatz

5.2 Komplexität von Algorithmen

5.2.1 Vorüberlegungen und Beispiele 5.2.2 O-Kalkül

5.2.3 Polynomialer und exponentieller Aufwand

5.2.4 Die Klassen P und NP

5.3 Korrektheit: Test und Verifikation von Programmen

5.3.1 Testen 5.3.2 Verifikation

5.1 Berechenbarkeit

Frage: Gilt für jedes beliebige Problem, dass es ent- weder mit dem Computer lösbar (berechenbar) ist oder als unlösbar erkannt werden kann?

(Hilbertsches Entscheidungsproblem) Antwort: Nein!

Das Hilbertsche Entscheidungsproblem ist nicht bere- chenbar.

Beispiel

Gegeben sei irgendeine beliebige Aussage über die natürlichen Zahlen. Dann gibt es keinen Algorithmus, der feststellt, ob diese Aussage wahr oder falsch ist.

(Unvollständigkeitstheorem von Gödel)

Weitere Beispiele von Church, Kleene, Post, Turing, ca.1930 - 1940.

(2)

Präzisierung des Algorithmus-Begriffs

Gödel: Folge von Regeln zur Bildung mathematischer Funktionen aus einfacheren mathematischen Funktio- nen.

Church: Lambda-Kalkül

Turing: Turing - Maschine (abstrakte Maschine mit sehr einfachen Anweisungen)

Church - Turing - These

Alle Definitionen von Algorithmus sind gleichwertig.

5.1.1 Äquivalenz der Algorithmus-Begriffe

Man könnte auch einen Algorithmus definieren als eine Verarbeitungsvorschrift, die in Java angegeben werden kann.

Dann vermutet man schon, dass ein äquivalenter Algo- rithmus auch in C oder FORTRAN oder Pascal oder LISP oder PROLOG angegeben werden kann. Dies trifft in der Tat zu.

Programmiersprachen unterscheiden sich nicht in der Mächtigkeit, sondern in der Natürlichkeit, Effizienz, Klarheit usw., in der sie ein bestimmtes Problem aus- drücken können.

Wir erkennen:

Die Berechenbarkeit eines Problems hängt nicht von der Programmiersprache ab.

(3)

5.1.2 Das Halteproblem

Gegeben ein beliebiges Programm P und beliebige Da- ten D dazu. Wird das Programm jemals anhalten, oder läuft es endlos lange?

Das Halteproblem ist nicht berechenbar.

D.h., es gibt keinen Algorithmus, der für beliebiges P und beliebiges D das Halteproblem lösen kann.

Beispiel: Fermat'sche Vermutung

Es gibt keine natürlichen Zahlen a, b, c, so dass

an + bn = cn gilt für n > 2.

Algorithmus

Modul Fermat (n)

/* Prüfe die Fermat'sche Vermutung mit n als Eingabe */

Wiederhole für a = 1, 2, 3, ...

Wiederhole für b = 1, 2, 3, ...,a

Wiederhole für c = 2, 3, 4, ..., a + b Falls an + bn = cn

dann gebe a, b, c und n aus, stoppe

Stoppt für n = 1 (a = 1, b = 1, c = 2) Stoppt für n = 2 (a = 4, b = 3, c = 5) Was geschieht für beliebiges n > 2?

(4)

Beweis der Nichtberechenbarkeit des Halteproblems

Beweistechnik: Beweis durch Widerspruch

Angenommen, es gäbe einen Algorithmus, der das Halteproblem löst. Wir nennen ihn Stop-Tester.

Dann konstruieren wir einen Algorithmus “fun” derart, dass Stop-Tester(fun) die Antwort “JA” liefert, wenn

“fun” nicht terminiert und die Antwort “NEIN”, wenn

“fun” terminiert.

Damit haben wir einen immanenten Widerspruch, und es kann Stop-Tester somit nicht geben.

Algorithmus "Stop-Tester"

Lies P /* Programm */

Lies D /* Daten */

Falls P(D) stoppt, dann gib JA aus sonst gib NEIN aus.

Stop - Tester - Spezial

Lies P /* Programm */

Lies P als Daten Falls P(P) stoppt, dann gib JA aus sonst gib NEIN aus.

(5)

Algorithmus fun(P)

/* Wir nehmen an, dass der Algorithmus Stop-Tester und damit auch Stop-Tester-Spezial existiert. */

Lies P

Stop-Tester-Spezial(P) Falls Antwort = JA

dann gehe in eine Endlosschleife sonst stoppe.

S to pp t P (P )?

E n dlos sch leife

a n ha lte n

J a N ein

P

Algorithmus fun(fun)

Lies fun

Stop-Tester-Spezial(fun) Falls Antwort = JA

dann gehe in eine Endlosschleife sonst stoppe.

S top p t F u n (F un )?

E n d lo ssc hleife

an h alten

J a N ein

F u n

(6)

Widerspruch in fun(fun)

Wenn der Algorithmus “fun” stoppt, dann gerät er in eine Endlosschleife.

Wenn “fun” nicht stoppt, dann gerät er auf eine STOP- Anweisung.

Wir schließen

Gäbe es einen Algorithmus Stop-Tester, so würde sich unter ausschließlicher Verwendung dieses Algorithmus ein immanenter Widerspruch zeigen lassen. Daraus folgt, dass es einen Algorithmus Stop-Tester nicht ge- ben kann.

5.1.3 Das Äquivalenzproblem

Gegeben seien zwei beliebige Programme P1 und P2. Man gebe einen Algorithmus an, der feststellt, ob P1 und P2 für beliebige Daten D dieselbe Ausgabe erzeugen (also äquivalent sind).

Behauptung

Das Äquivalenzproblem ist nicht berechenbar.

(7)

Partielle Berechenbarkeit

Bisher haben wir verschiedene Probleme betrachtet, die nicht berechenbar sind (d.h. keine algorithmische Lö- sung haben).

Ein Problem heißt partiell berechenbar, wenn es einen Algorithmus gibt, der für jeden Eingabewert, für den ein Ergebnis definiert ist, nach endlich vielen Schritten an- hält und das korrekte Ergebnis liefert. In allen anderen Fällen, in denen kein Ergebnis definiert ist, bricht der Algorithmus nicht ab.

Beispiel

Das Halteproblem ist partiell berechenbar.

Beweis:

Schrittweise Ausführung des Programms P(D). Falls die schrittweise Ausführung hält, wissen wir, dass das Pro- gramm hält. Falls die schrittweise Ausführung nicht hält, können wir keine Aussage machen (vielleicht wird sie irgendwann in sehr ferner Zukunft halten?)

Partielle Berechenbarkeit

Bisher:

P rob lem

b erech en ba r nich t b erech en b a r

Jetzt:

P ro blem

b e re ch en ba r p artie ll n ich t b ere ch en ba r b ere ch en ba r

(8)

5.1.4 Rekursionssatz

Gegeben sei ein Programm P(D), das eine bestimmte Funktion ausführt. Dann gibt es ein Programm P'(P'), das dieselbe Funktion ausführt, wobei es eine Kopie von sich selbst als Eingabedaten benutzt.

Beispiel 1

Modul Drucke(D) Gib D aus.

Dann gibt es ein Modul Drucke', das seinen eigenen Programmtext ausdruckt.

Beispiel 2

Man kann einen Compiler in einer Programmiersprache so schreiben, dass er sich selbst compilieren kann!

5.2 Komplexität von Algorithmen

5.2.1 Vorüberlegungen und Beispiele

Berechenbarkeit, wie zuvor eingeführt, bedeutet nur, dass ein Problem prinzipiell algorithmisch lösbar ist. Es heißt aber nicht, dass es mit vernünftigem Aufwand praktisch durchführbar ist.

A lle A u fga b en ste llu ng en

B e rech e nb are A ufga b e n - s te llu ng en B e rech en - u nd d ur ch fü h rb a re A ufga b en - s te llu ng en

(9)

Ressourcen

Bei der Ermittlung der Komplexität eines Algorithmus betrachtet man in der Regel die folgenden Ressourcen Zeit und Speicherplatz.

Häufig geht eine Optimierung des Bedarfs an einer Ressource auf Kosten der anderen Ressource (z. B.

weniger Zeit, dafür mehr Speicher).

In der Komplexitätstheorie wird meist die Anzahl der Operationen in Abhängigkeit von der Anzahl der Eingabedaten als Komplexitätsmaß betrachtet.

Beispiel 1: Komplexität der Multiplikation

Es seien zwei 4-stellige Zahlen zu multiplizieren. Es wird der übliche Schul-Algorithmus angewendet. Jede Zeile kann in 4 Operationen errechnet werden. Es gibt 4 Zei- len, die anschließend zu addieren sind.

Insgesamt ist die Anzahl der Operationen proportional zu n2, wenn n die Anzahl der Ziffern ist.

1984 x 6713

11904

1 3888

1984

5952

13318592

(10)

Verbesserter Multiplikationsalgorithmus

Zerlegung der vierstelligen Multiplikation in zwei zwei- stellige M;ultiplikationen:

A B C D

19 8 4

x

6 7 1 3

(100*A + B) * (100*C + D)

= 10000*AC + 100*AD + 100*BC + BD

= 10000*AC + 100*(AD + BC) + BD

= 10000*AC + 100*[(A + B)(C + D) - AC - BD] +BD AC = 19 x 67 = 1273

(A+B)(C+D)-AC-BD = (103x80)-1273-1092 = 5875 BD = 84 x 13 = 1092

1273 5875 1092

13318592

Alle Multiplikationen sind nur noch 2-stellig. Es sind nur noch 3 Zeilen mit je 4 Ziffern zu addieren.

Man kann generell zeigen, dass die Anzahl der Opera- tionen proportional zu n1,59 < n2 ist.

Beispiel 2: Sortieren durch Auswahl

Gegeben seien n Zahlen, die zu sortieren sind.

Algorithmus Auswahlsortieren Wiederhole n-mal

Markiere die erste Zahl der Eingabemenge.

Vergleiche sie mit der zweiten, dritten usw.

Wenn eine kleinere als die erste gefunden wird, markiere diese.

Wenn das Ende der Eingabemenge erreicht ist, Übertrage die markierte kleinste Zahl in den Ausgabe-Datenstrom und sperre sie für die weitere Bearbeitung.

Komplexität: Proportional zu n2

(n Durchläufe mit jeweils größenordnungsmäßig n Ver- gleichen)

Anmerkung: Es gibt Sortieralgorithmen mit Komplexität n log n.

(11)

Asymptotisches Verhalten

In der Praxis interessiert oft nicht so sehr die genaue Anzahl der Operationen, sondern das asymptotische Wachstum für große n.

Beispiel

Sei 3n2 + 5n die Anzahl der Operationen. Dann ist für großes n der quadratische Anteil dominierend. Man sagt, die Komplexität wächst mit n2.

Größe n der Daten

log2n

• s

n

• s

n2

• s

2n

• s 10 0.000003 s 0.00001 s 0.0001 s 0.001 s 100 0.000007 s 0.0001 s 0.01 s 1014 Jahr- hunderte 1,000 0.00001 s 0.001 s 1 s astrono- misch 10,000 0.000013 s 0.01 s 1.7 min astrono- misch 100,000 0.000017 s 0.1 s 2.8 h astrono- misch

5.2.2 O-Kalkül

Berechnung der Komplexität unter Abstraktion

von Konstanten (die maschinenabhängig sind)

von weniger dominanten Termen (die für große Werte des Komplexitätsparameters n nicht mehr signifikant sind)

(12)

Asymptotisches Wachstum (1)

Definition 1

Seien f und g zwei positive, reellwertige Funktionen, die auf einem gemeinsamen Bereich D definiert sind. Dann bedeutet

f = O(g),

dass es eine Konstante c > 0 und einen Anfangswert x0

gibt derart, dass

f(x) ≤ cg(x) für alle x > x0, x, x0∈ D gilt.

Man sagt, f wächst asymptotisch höchstens wie g.

Asymptotisches Wachstum (2)

Definition 2 Es bedeutet f = Ω (g),

dass es eine Konstante c > 0 und einen Anfangswert x0

gibt derart, dass

f (x) ≥ cg(x) für alle x > x0 , x, x0∈ D gilt.

Man sagt, f wächst asymptotisch mindestens wie g.

(13)

Beispiel für den O-Kalkül

Sortieren durch Auswahl

Eingabeparameter: n Anzahl der Elemente Der Vergleich zweier Zahlen ist in konstanter Zeit c1

möglich.

Innere Schleife: t1 = nc1

Äußere Schleife: t2 = nt1 = n2 c1

Gesamter Rechenaufwand:

t = t2 = n2 c1= O(n2)

Einfache Rechenregeln des O-Kalküls

(1) f = O(f)

(2) O(O(f)) = O(f) (3) cO(f) = O(f) (4) O(f+c) = O(f)

(5) O(f) + O(f) = O(f) (6) O(f) * O(g) = O(fg) (7) O(f) + O(g) = O(f+g) (8) O(fg) = O(f) * O(g)

Konvention: Diese Gleichungen werden von links nach rechts gelesen.

O(f) ist, mathematisch gesehen, eine Menge von Funk- tionen.

Mengenoperationen:

M1 + M2 := { x + y | x ∈ M1 und y ∈ M2 } M1 * M2 := { x * y | x ∈ M1 und y ∈ M2 }

(14)

Beweis von Regel (7) als Beispiel

(7) O(f) + O(g) = O(f+g)

h1(n) = O(f), also: Es gibt c1, n1 mit h1(n) c1f(n) für alle n > n1

h2(n) = O(g), also: Es gibt c2, n2 mit h2(n) c2g(n) für alle n > n2

Addition liefert

h1(n) + h2(n) c1f + c2g für alle n > max(n1,n2).

Setzen wir h3(n) = h1(n) + h2(n), c3 = max(c1,c2) und n3 = max(n1,n2), so gilt

h3(n) c3 (f + g), für alle n > n3, was nach Definition h3(n) = O(f + g) bedeutet.

Beispiel: Multiplikation mit dem Standard-Algorithmus im O-Kalkül

1984 x 6713 11904 1 3888 1984 5952 13318592

Eingabeparameter: n1, n2 Längen der Faktoren

Multiplikation zweier Ziffern in konstanter Zeit c1 mög- lich.

Addition zweier Ziffern sei in konstanter Zeit c2 möglich.

Berechnung einer Zeile: t1 = n1 * c1

Berechnung von n2 Zeilen: t2 = n2 * t1 = n2 * n1 * c1

Addition von n2 Zeilen der Länge ≤ n1 + 2:

t3 = n2 * (n1 + 2) * c2

t = t2 + t3 = n1n2c1 + (n1 + 2) n2c2

= n1n2c1 + n1n2c2 + 2n2c2

= n1n2(c1 + c2) + 2n2c2

= O(n1n2 + n2)

= O(n1n2)

mit n := max {n1, n2} ist t = O(n*n) = O(n2)

(15)

Zwei Arten von Schranken

Für ein gegebenes Problem lässt sich oft eine untere Schranke für die Komplexität einer Lösung aus der Theorie angeben, unabhängig vom Algorithmus.

Beispiel: Minimumsuche

Suche das Minimum aus n unsortierten Zahlen.

Jede Zahl muss mindestens einmal angesehen (vergli- chen) werden. Daher gilt: Das Problem der Minimumsu- che hat Komplexität Ω (n).

Darüber hinaus lässt sich für einen gegebenen Algo- rithmus die Abhängigkeit der Anzahl der Operationen von n ermitteln und dadurch eine für diesen Algorith- mus spezifische Schranke ermitteln.

Beispiel: Minimumsuche

Ein dummer Algorithmus A1 sortiert die n Zahlen und erhält dann das Minimum als erste Zahl des Ergebnis- ses.

Sortieren durch Auswahl: O(n2) Bestimmung der ersten Zahl: O(1)

Der Algorithmus A1 hat also die Komplexität O (n2).

(16)

Obere und untere Schranken

t Z eit

m a x

n A u fw an d s p ara m ete r

O b e re S ch ra n k e g e g e b e n d u rch A lg o rith m u s u n te re S ch ra n k e a us T h eo rie

Die Erforschung solcher Schranken wird in der Kom- plexitätstheorie behandelt, einem wichtigen Zweig der Theoretischen Informatik.

Vergleich zweier Algorithmen

A1

A2

n n0

tm a x

Wir sehen:

Der asymptotisch optimale Algorithmus ist nicht unbe- dingt der bessere für alle n; es kann sich lohnen, für kleine n enen anderen Algorithmus zu wählen als für große n.

(17)

Rekurrenzrelation: "Teile und herrsche"

Das Verfahren "Teile und Herrsche" erweist sich oft als gutes Entwurfsprinzip für effiziente Algorithmen!

Idee

Aufteilen des Gesamtproblems in kleinere Teilprobleme derart, dass die Summe der Aufwände für die Teilpro- bleme und des Aufwands für das Zusammenfügen ge- ringer ist als der Aufwand für das direkte Lösen des Ge- samtproblems.

Dies ist der Trick bei allen effizienten Sortieralgorith- men!

Beispiel 1 für "Teile-und-herrsche": Sortieren

Modul TH-Sortiere(Liste)

/*Sortiert eine gegebene Liste aus n Namen alphabetisch.*/

Falls n > 1

dann TH-Sortiere(erste Listenhälfte) TH-Sortiere(zweite Listenhälfte) Mische die zwei Hälften zusammen

(18)

Aufwand für TH-Sortier-Algorithmus

T(n) sei der Aufwand für das Sortieren von n Zahlen.

Sortieren von n/2 Zahlen erfordert T(n/2).

Mischen der beiden Hälften ist proportional zu n.

Der Aufwand für das TH-Sortieren durch Halbieren und Mischen ist also

T(n) = 2 T(n/2) + cn T(1) = k

Diese Rekurrenzrelation (rekursives Gleichungssy- stem) hat die geschlossene Lösung:

T(n) = cn log n + kn Wir erkennen:

T(n) = O(n log n)

Das ist besser als O(n2)!

Das Verfahren "Teile und herrsche" hat also zu einem Algorithmus mit geringerer Komplexität geführt.

Entwicklung von T(n) (1)

( )

T n k falls n = 1 2T(n / 2) + cn falls n > 1

= 





T(n) = 2T(n/2) + cn

= 2(2T(n/4) + cn/2) + cn = 4T(n/4) + 2cn

= 4(2T(n/8) + cn/4) + 2cn = 8T(n/8) + 3cn ...

Behauptung

T(n) = 2iT(n/2i) + icn (i ist die aktuelle Rekursionsstufe) Beweis durch vollständige Induktion über i:

i = 1:

21T(n/21) + 1cn = 2T(n/2) + cn Schluss von i auf i + 1:

T(n) = 2iT(n/2i) + icn

= 2i(2T(n/2i+1) + cn/2i) + icn

= 2i+1T(n/2i+1) + (i+1)cn ‰

( )

T n k falls n = 1

2 T(n / 2 ) + icn falls n > 1 mit i 1

i i

= ≥



(19)

Für Zweierpotenzen

Betrachte nur solche n, die Zweierpotenzen sind:

20 21

2 i-1 n 2i

Denn da der Zeitaufwand des Algorithmus mindestens linear wächst (Ω(n)), gilt für alle n n2:

T(n) T(n2)

mit n2 := 2i, so dass 2i-1 < n 2i = n2. Dann gilt:

n = 2i bzw. i = log2 n = ld n Sei n > 1. Dann ist

T(n) = 2iT(n/2i) + icn

= nT(1) + cn ld n

= nk + cn ld n

= O(n ld n)

Beispiel 2 für "Teile-und-herrsche":

Multiplikation

Multiplikation von 4-stelligen Zahlen durch Zurückfüh- rung auf die Multiplikation von 2-stelligen Zahlen.

Es sei X * Y zu berechnen.

Dann trenne man X und Y auf in Zahlen mit halber Län- ge. Beispiel:

X = 1984; A = 19 B = 84

Y = 6713; C = 67 D = 13

Nun gilt:

X * Y = A * C * 104 + ((A + B) * (C + D) - AC - BD) * 102

+ BD

Die Zehnerpotenzen bedeuten nur ein Schieben der Zahlen nach links beim Addieren. Es sind nun nur noch drei Multiplikationen mit Zahlen halber Länge nötig:

A * C, B * D und (A + B) * (C + D)

Der Additionsaufwand ist proportional zu n, n = Anzahl der Ziffern

(20)

Aufwand für die TH-Multiplikation

T(n) = 3T(n/2) + cn

T(1) = k

Lösung der Rekurrenzrelation ergibt:

T(n) = (2c + k)nld 3 - 2cn

= O(nld 3)

= O(n1,59)

Also ist auch hier der Aufwand geringer als O(n2) beim Schul-Algorithmus.

5.2.3 Polynomialer und exponentieller Auf- wand

Eine gute Näherung für die Durchführbarkeit von Algo- rithmen ist die Annahme, dass

Algorithmen mit polynomialem Aufwand (O(nc)) durchführbar sind

Algorithmen mit exponentiellem Aufwand (O(cn)) nicht durchführbar sind

Daher ist es eine zentrale Fragestellung der Theore- tischen Informatik, ob es für ein gegebenes Problem einen Algorithmus mit polynomialem Aufwand ge- ben kann.

(21)

Klassifikation der Komplexität von Problemen

P ro blem s tellu n g

p o lyn om ina l ? e x p on en tie ll

e in A lg orith m u s m it p o lyno m in a len A ufw a n d ist b ek a n n t

k ein A lg o rith m u s m it p o lyn om ina le n A ufw a n d be ka n nt

m a n w eiß : es ka n n ke in en po lyno m in a len A lg o rithm us geb en

Besonders die Probleme aus der mittleren Klasse sind für Informatiker interessant!

Beispiel 1: bin-packing problem

Gegeben n Kästen mit unterschiedlichem Gewicht, T Lastwagen mit maximaler Zuladung G. Ist es möglich, alle Kästen zu verladen?

3

T o n n e n 3

T o n n e n 5 T o n n e n 4

T o n n e n 8

T o n n e n 5

T o n n e n

n = 6 K äs te n T = 2 L a stz üg e

m a xim a le La d u ng G = 1 4 T o n ne n

Ein einfacher Fall des Kastenproblems

(22)

Algorithmus "Schwerster Kasten zuerst"

3

T o n n e n 3

T o n n e n 5 T o n n e n 4

T o n n e n

8 T o n n e n

5 T o n n e n

N ac hd em ein L a stzu g m it d em A lg o rithm u s "S ch w erste K ä sten z ue rs t"

be la de n w ur de , m üs sen d ie ver bleib en d en K äs ten n ich t u nb ed ing t auf de n nä ch sten W a gen pa ssen .

m a x im ale L a du n g G = 1 4 T o n ne n

Führt nicht in allen Fällen zu einer Lösung!

Beachte:

Viele Probleme der Informatik sind analog zum bin- packing problem, z.B. das Laden von n Programmen unterschiedlicher Größe in den Speicher von T pa- rallelen Prozessoren.

Lösung

3

T o n n e n 3

T o n n e n

5 T o n n e n 4

T o n n e n

8 T o n n e n

5 T o nn e n

E in e L ö su n g de s K as te n p ro b lem s

(23)

Beispiel 2: Travelling Salesman

Gegeben ein Straßennetz zwischen n Städten mit Ent- fernungen. Kann ein Handelsreisender mit Kilometerbe- schränkung eine Rundreise machen, bei der er jede Stadt genau einmal besucht?

Beispiel 3: Hamilton-Zyklus

Gegeben ist ein Straßennetz als Graph. Gibt es eine Rundreise, bei der jede Stadt genau einmal besucht wird?

D er ein fach e F a ll e in es H a m ilto n -Z yk lu s

K no te n b ed eu ten S tä d te, L in ien b ed eu ten S tra ß en .

Die Lösung heißt Hamilton-Zyklus.

Es ist kein Algorithmus mit polynomialem Aufwand be- kannt!

(24)

Lösung zum Beispiel-Graphen

E in e L ö su n g z u d en H a m ilto n -Z yk len

Beispiel 4: Stundenplanproblem

Gegeben eine Liste von Fächern, zu unterrichtende Schüler und ein Lehrplan (Anzahl der Stunden in jedem Fach in jedem Schuljahr). Gibt es einen Stundenplan, bei dem kein Schüler eine Freistunde hat?

Es ist kein Algorithmus mit polynomialem Aufwand be- kannt!

Ansätze in der Praxis

Suche Näherungslösung statt perfekter Lösung ( z. B.

Stundenplan mit kleinen Lücken)

Suche Algorithmus, der für durchschnittliche Einga- befälle schnell arbeitet, selbst wenn der ungünstigste Fall exponentiell bleibt

(25)

5.2.4 Die Klassen P und NP

Alle Probleme, die mit polynomialem Aufwand berech- net werden können, für die es also einen Algorithmus gibt, der durch ein Polynom nach oben beschränkt ist, bilden die Klasse P.

(Wie schon erwähnt, bedeutet das nicht immer, dass die Berechnung praktikabel ist, denn das Polynom kann sehr groß sein!)

Alle Probleme, zu denen eine (wie auch immer ermit- telte) gegebene Lösung in polynomialer Zeit verifiziert (als korrekt bewiesen) werden kann, bilden die Klasse NP.

Die Klasse P ist in der Klasse NP enthalten.

Die vorangegangenen vier Beispiele sind alle in NP (ei- ne wie auch immer ermittelte Lösung ist mit polynomia- lem Aufwand verifizierbar).

Man weiß bis heute nicht, ob es Probleme gibt, die in NP, aber nicht in P sind!

NP-Vollständigkeit

Ein Problem heißt NP-vollständig (NP-complete), wenn gilt:

Wenn es für dieses Problem einen polynomialen Al- gorithmus gibt, dann gibt es für alle Probleme in NP einen polynomialen Algorithmus.

NP-vollständige Probleme sind die "schwersten" Pro- bleme in NP.

Die Beispiele 1 bis 4 (bin-packing, travelling salesman, Hamilton-Zyklus, Stundenplan) sind NP-vollständig.

Viele Informatiker glauben, dass P ≠ NP ist, dass also P eine echte Teilmenge von NP ist.

(26)

Zusammenfassung Komplexität (1)

P N P

N P -

v o lls tä n d ig

e x p o n e n tie ll?

e x p o n en tie ll!

P = N P ?

Zusammenfassung Komplexität (2)

P ro ble m

nic ht in N P in N P

(vor ge sch la g ene L ö su ng p olyno m ia l verifiz ierb ar)

p olyno m ia l (z.B . S ortieren) e x p o n en tiell

(w en n p olyn o m ia le L ö s un g fü r d ies es P ro b le m e x istie rt, d an n h ab en a lle P r o b le m e in N P ein e p olyn o m ia le L ö su n g )

N P -vo lls tä nd ig nich t N P -vo lls tä nd ig

z.B . b in p ac kin g tim e ta ble

(27)

5.3 Korrektheit: Test und Verifikation von Programmen

Korrektheit bedeutet, dass ein Algorithmus oder ein Programm das in der Spezifikation beschriebene Pro- blem für beliebige Eingabedaten korrekt löst.

Die Korrektheit kann immer nur in Bezug auf eine Spezifikation gezeigt werden!

Definitionen

1. Ein Programm terminiert, wenn es für alle Eingaben letztendlich anhält.

2. Ein Programm ist partiell korrekt, wenn das Ergeb- nis im Falle des Terminierens immer korrekt ist.

3. Ein Programm ist total korrekt, wenn es terminiert und partiell korrekt ist.

4.

Zwei Techniken zum Prüfen der Korrektheit

Testen

Verifikation (formaler Beweis der Korrektheit)

5.3.1 Testen

Als Testen (engl.: debugging) bezeichnet man die Aus- führung eines Programms für eine Menge von Testda- ten.

Die Menge der Testdaten ist endlich und in fast allen praktischen Fällen sehr viel kleiner als die Menge der möglichen Eingabedaten. Deshalb kann Testen nur das Vertrauen in die Korrektheit eines Programms erhöhen, nicht aber die Korrektheit beweisen!

In der Praxis erweist sich die Auswahl einer angemes- senen Testdatenmenge als sehr schwierig. Ebenso schwierig ist die Analyse der Überdeckung eines Pro- gramms durch gegebene Testdaten. Hierzu hat man im Software Engineering Hilfsmittel entwickelt.

(28)

Ablauf des Testens

Ü b e rd e c k ung 1 0 0 %

T e s ta ufw a nd 7 0 %

E nd e d e s T e s te ns

Beispiel für Testen

Es ist ein Programm zu schreiben, das zwei ganze Zahlen multipliziert. Als Operationen sollen nur Addition, Subtraktion, Multiplikation mit 2 und Division durch 2 zulässig sein.

1 public int mult(int x1, int y1) { 2 int x, y, z;

3 z = 0;

4 if ((x1 > 0) && (y1 > 0)) { 5 x = x1;

6 y = y1;

7 while (x != 0) { 8 if (x % 2 == 1) 9 z = z + y;

10 y = 2 * y;

11 x = x/2;

12 } // while 13 } // if 14 return z;

15 }

(29)

Erster Ansatz: Testen als Schwarzer Kasten

(eng: black box testing)

Testen für alle x1, y1 < 1012 ohne Kenntnis der Pro- grammstruktur.

Ergibt 1012*1012 = 1024 Testfälle!

Bei 1ms pro Ausführung ca. 3*1013 Jahre Rechenzeit!

Fazit

Es kann selbst beim Testen mit Zufallszahlen nur ein sehr kleiner Prozentsatz der möglichen Eingabedaten getestet werden.

Zweiter Ansatz: Testen als Weißer Kasten

(eng: white box testing)

Testen unter Kenntnis der Programmstruktur. Auswahl der Testdaten so, dass eine möglichst gute Überdek- kung des Programmcodes erreicht wird.

Anweisungstest

Auswahl der Testdaten so, dass jede Anweisung im Programm mindestens einmal ausgeführt wird.

Vorteil: unnötige Anweisungen werden entdeckt (sol- che, die durch keinen Testdatenfall erreicht werden können). Es sind nur relativ wenige Testfälle nötig.

Nachteil: Viele wichtige Fälle werden überhaupt nicht getestet.

Beispiel if (b) a;

Wenn die Bedingung b erfüllt ist, wird die Anweisung a ausgeführt. Damit ist sowohl die if-Anweisung als auch a einmal durchlaufen worden. Der Fall "b == false" wird nicht getestet!

(30)

Verzweigungstest

Gegeben sei das Flussdiagramm des Programms. Dann werden die Testdaten so ausgewählt, dass jede Ver- zweigung (Verbindungslinie zwischen Kästchen) minde- stens einmal durchlaufen wird.

Vorteil: bessere Überdeckung als mit dem Anwei- sungstest.

Nachteil: erfasst immer noch nicht alle relevanten Fälle.

Beispiel für den Verzweigungstest (1)

if (b1) a1; else a2;

if (b2) a3; else a4;

b 1 ?

a 1 a 2

b 2 ?

a 3 a 4

tr u e fa lse

tr u e fa lse

(31)

Beispiel für den Verzweigungstest (2)

Es gibt vier mögliche Anweisungsfolgen für a1 bis a4:

a1;a3 a1;a4 a2;a3 a2;a4

Gilt nun für alle Testdatenfälle, dass sie entweder die Kombination

b1 true und b2 false oder b1 false und b2 true

enthalten, so sind die Anforderungen an den Verzwei- gungstest erfüllt (alle Pfade im Flußdiagramm werden mindestens einmal durchlaufen), aber die Ablauffolgen

a1;a3 a2;a4

werden nicht getestet.

Wegetest

Im Wegetest werden alle möglichen Ablauffolgen von Anweisungen mindestens einmal durchlaufen. Für das Beispiel mult sehen wir:

x1 y1 durchlaufene

Anweisungen

0 beliebig 3, 4, 14 beliebig 0 3, 4, 14

1 1 3, 4, 5, 6, 7, 8, 9, 10, 11, 14

2 1 3, 4, 5, 6, 7, 8, 10, 11, 7, 8, 9, 10, 11, 7,14

3 1 ...

Es gibt zu viele mögliche Ablauffolgen wegen der Schleife!

Reduktion in der Praxis a) Schleife 0-mal durchlaufen b) Schleife 1-mal durchlaufen

c) Schleife k-mal durchlaufen, k ≥ 2, fest.

(32)

Suche nach typischen Fehlern

Mit etwas Erfahrung stellt man fest, dass Programmierer immer wieder Fehler machen. Man entwirft daher die Testdaten so, dass sie nach diesen typischen Fehlern suchen.

Beispiele

Feldindex außerhalb der Grenzen des Feldes (arrays)

Anzahl der Schleifendurchgänge +/- 1

Namenskollision zwischen lokalen und globalen Va- riablen und Parametern

Rundungsfehler bei Gleitkommaoperationen

Durch Testen können auch Compilerfehler oder Fehler in den Laufzeitroutinen gefunden werden (im Gegensatz zur Verifikation!).

Code-Inspektion (Code Review)

Programmcode kann nicht nur durch den Rechner, son- dern auch durch den Programmierer getestet werden.

Ein in der Praxis häufig eingesetztes und erstaunlich wirksames Verfahren ist die Code-Inspektion (code re- view, "Schreibtischtest"). Dabei wird der Programmcode eines Programmierers von einem anderen Programmie- rer auf mögliche Fehler untersucht.

Diese Methode erzieht zugleich zur strukturierten Pro- grammierung und zur Dokumentation im Code (durch Kommentare).

(33)

5.3.2 Verifikation

Unter Verifikation versteht man den formalen Beweis der Korrektheit eines Programms durch mathematische Methoden.

Dazu muss bewiesen werden, dass das Programm für beliebige Eingabedaten terminiert und jeweils korrekte Ergebnisse liefert.

Beweis für das Terminieren

Programme ohne Schleifen terminieren immer.

Für Schleifen gilt: Durch mindestens eine Anweisung im Rumpf der Schleife muss eine Variable derart ver- ändert werden, dass nach einer endlichen Anzahl von Durchläufen die Endebedingung erfüllt ist.

Beweistechnik

5. Man suche einen Ausdruck A mit Variablen aus der Schleifenbedingung, dessen Werte in einer endli- chen geordneten Menge M liegen ( z.B. in einem endlichen Intervall aus den ganzen Zahlen).

6. Man zeige, dass A bei jedem Schleifendurchgang streng monoton wächst (oder abnimmt).

(34)

Beispiel für einen Beweis des Terminierens

In Zählschleifen ist die Zählvariable der Ausdruck A.

Anderes Beispiel

Für die while-Schleife im Algorithmus mult gilt:

1. 0 ≤ x ≤ x1 2. Die Anweisung

x = x / 2

läßt x in jedem Durchlauf monoton abnehmen.

Die Variable x wird also mit Sicherheit den Wert 0 errei- chen und damit die Schleife terminieren lassen.

Ähnlich kann man das Terminieren von rekursiven Pro- zeduren oder Funktionen beweisen.

Terminierung rekursiver Algorithmen

Ähnlich wie Terminierung der Schleifen Beispiel

public int fakultaet (int k) { if ( k == 0)

return 1;

else

return (k * fakultaet (k - 1));

}

A ist hier k, der Wert nimmt monoton ab, untere Grenze ist 0.

Schwieriger zu zeigen bei indirekter Rekursion:

A ruft B, B ruft A

(35)

Beweis der Korrektheit von Programmstücken

Der formale Beweis der Korrektheit eines vollständigen Programms für alle möglichen Eingabedaten ist in der Praxis meist sehr schwierig oder unmöglich. Man kann aber manchmal die Korrektheit von wichtigen Pro- grammstücken, insbesondere Schleifen, beweisen.

Beweis durch Induktion

Analog zur Mathematik beweist man:

1. Die Berechnungen in der Schleife sind beim ersten Durchlauf korrekt.

2. Angenommen sie sind beim n-ten Durchlauf korrekt, dann sind sie auch beim n+1-ten Durchlauf korrekt.

Beispiel für Korrektheitsbeweis einer Schleife durch Induktion (1)

public long potenziere(int x) {

// gibt 2x zurück, wobei x als nicht-negative // Ganzzahl angenommen wird

int i; long summe;

summe = 1;

for (i = 0; i < x; i++) { summe += summe;

// ***

}

return summe;

}

(36)

Beispiel für Korrektheitsbeweis einer Schleife durch Induktion (2)

1. Das erste Mal, wenn *** erreicht wird, ist

summe = 2n (also 21)

2. Nehmen wir an, dass *** zum n-ten Male

erreicht wird, dann ist summe = 2n. Beim (n + 1)-ten Male, ist dann summe = 2n + 2n = 2n+1.

3. Deshalb gilt für jedes n, dass summe = 2n ist, sobald

*** zum n-ten Male erreicht wird.

4. Wird die return- Anweisung je erreicht, dann gibt es zwei Möglichkeiten. Entweder wurde die Schleife nicht durchlaufen, so dass in diesem Fall x = 0 ist und deshalb der Anfangswert von summe nicht ge- ändert wird. Dann ist summe = 1, was gleichbedeu- tend mit 20 ist. Oder die Scheife wurde durchlaufen, so dass in diesem Falle die mit *** markierte Stelle x- mal erreicht wurde. Somit ist summe = 2x.

Es wird also wird also auf jeden Fall 2x ausgegeben, sofern nur die Ausgabeanweisung erreicht wird.

Zusicherungen (assertions)

Beim Beweisen (Verifizieren) von Programmstücken muss man sich klarmachen, welche Voraussetzungen an einer bestimmten Programmstelle gelten. Zusiche- rungen (assertions) sind logische Ausdrücke über Programmvariablen. Sie beschreiben, welche logischen Annahmen an einer bestimmten Stelle im Programm gemacht werden können.

Gegeben sei folgende Situation:

...

assertion1;

statement1;

statement2;

statement3;

assertion2;

...

Wenn es nun gelingt, unter der Annahme von asserti- on1 und unter Verwendung der statements den Aus- druck assertion2 herzuleiten, hat man die Korrektheit des Programmstücks formal bewiesen.

assertion1 heißt auch pre-condition, assertion2 heißt post-condition.

(37)

Algorithmische Ableitungsregeln

Gilt die Aussage P vor Ausführung von Algorithmus A und beschreibt die Aussage Q die funktionale Abhän- gigkeit der Ausgabegrößen von den Eingabegrößen, so kann aus der Gültigkeit von P und der Ausführung von A die Gültigkeit von Q nach der Ausführung abgeleitet werden.

In Zeichen: [P] A [Q].

[P] A [Q] bedeutet nichts anderes als

[ ]

P A

[ ]

Q (logische Implikation)

Zerlegung in Unteralgorithmen

Ist eine Zerlegung von A in Unteralgorithmen A1,...,An

gegeben, für die jeweils

[ ] [ ]

Pi Ai Qi ,1≤in gilt, so muss n i P Q

Q Q P

P1, n → , i1i,1% ≤ gelten.

Wenn darüberhinaus alle Ai terminieren, ist A (total) kor- rekt.

(38)

Beweis des Terminierens der Zerlegung

Zunächst macht man die Induktionsannahme, dass alle Unteralgorithmen terminieren. Dann zeige man, dass die Zerlegung terminiert, d.h. keine unendliche Schleife eintritt, und schließlich, dass bei der Zusammensetzung der Unteralgorithmen nur endlich viele Wiederholungen möglich sind. Dann wendet man die Regel auf die nicht- elementaren Unteralgorithmen an.

Ableitungsregel für die if-Anweisung

[P]

if (B) A1

else A2

[Q]

Ableitungsregel

Aus [P ∧ B] A1 [Q]

und [P ∧¬ B] A2 [Q]

folgt --- [P] if (B) A1 else A2 [Q]

(39)

Ableitungsregeln für Schleifen

Schleifeninvariante

Eine Schleifeninvariante ist ein logischer Ausdruck, der sich von Durchlauf zu Durchlauf der Schleife nicht än- dert. Die Schleifeninvariante ist somit auch nach dem letzten Durchlauf, beim Schleifenende, gültig und kann deshalb zum Beweis der Korrektheit der Schleife heran- gezogen werden.

while (B) A;

B ?

ne in

ja

P B

A P

P

P N O T B

Ableitungsregel für die while - Schleife

Aus [P ∧ B] A [P]

folgt[P] while (B) A [P ∧ ¬ B]

P ist die Schleifeninvariante. P muss sich auf Variable aus dem Schleifenrumpf A beziehen.

Die Gültigkeit von ¬ B beim Schleifenende ist wichtig für den Beweis der Korrektheit!

(40)

Beispiel für eine Schleifeninvariante

Berechnung der Summe der ersten n Zahlen public int summe(int n) {

int i,s;

s = 0;

i = 0;

while (i < n) { i++;

s += i;

// ***

}

return s;

}

An der Stelle *** gilt die Schleifeninvariante P summe = 0+1+2+3+...+i

bei jedem i-ten Durchlauf der Schleife.

Mit der Endebedingung der Schleife ¬ (i < n) ist wegen des monotonen Wachstums von i dann i = n.

Somit gilt: summe = 0+1+2+3+...+n Die Schleife ist also korrekt.

Verfication is a social process

Das Überzeugen durch einen Beweis in der Mathema- tik oder genauso auch eine Programmverifikation ist ein sozialer Prozess.

Verifikationen sind oft lang und schwer lesbar.

Die Spezifikation ist selbst informal; der Schritt vom Informalen zum Formalen ist niemals verifizierbar!

Die Spezifikation ist außerdem oft sehr umfangreich (z.B. für “Einkommensteuerberechnung”, “Betriebssy- stem”, “Compiler”) und in der Regel nicht vollständig.

Programme interagieren mit der realen (informalen) Umwelt, zum Beispiel mit I/O-Treibern, Benutzer- schnittstellen, A/D-Wandlern usw., die nicht verifizier- bar sind.

Referenzen

ÄHNLICHE DOKUMENTE

 Insbesondere  ist  ein  Computer  ein  solches  Rechenmodell,  somit   kann  auf  ihm  theoretisch  jeder  Algorithmus  ausgeführt  werden,  vorausgesetzt

1. Definiere eine Zählschleife, so dass das Haus vom Nikolaus beliebig oft nebeneinander gezeichnet wird. Verändere den abschließende Drehwinkel oder füge ein Drehen in deine

zum Zeitpunkt t setzt sich aus zwei Beitr¨ agen zusammen: Die Beschleunigung durch die Feder ist proportional zur

 Idee: zwei orthogonale Schlitten, die hin- und herfahren und gemeinsam einen Stift führen.  Schlitten werden durch periodische Funktionen

INIT Schleifenvariable initialisieren BEDINGUNG Bedingung f¨ ur Schleifendurchlauf REINIT Schleifenvariable ver¨

Ist a eine ganze Zahl des Körpers, so nennt man das Ideal, das entsteht, wenn man § alle ganzen Zahlen des Körpers in j; • a durch¬.. laufen lässt, das Hauptideal « und bezeichnet

[r]

Die Kombination ist eine vierstellige natürliche Zahl mit Quersumme 23. Die Summe der ersten beiden Ziffern