• Keine Ergebnisse gefunden

Algorithmische Mathematik I

N/A
N/A
Protected

Academic year: 2022

Aktie "Algorithmische Mathematik I"

Copied!
43
0
0

Volltext

(1)

Gennadiy Averkov

IMO | FMA| OvGU Magdeburg 22. Januar 2018

A[1]

A[2]

A[4] A[5]

A[3]

A[6] A[7]

(2)

Inhaltsverzeichnis

1 Mathematische Grundlagen 5

1.1 Mathematische Grundbegriffe . . . 5

1.2 Summen und Produkte . . . 5

1.3 Pr¨adikate und Quantoren . . . 6

1.4 Widerspruchsbeweis . . . 6

1.5 Vollst¨andige Induktion . . . 6

1.6 Asymptotische Notation . . . 7

2 Grundlagen zu Algorithmen und Programmierung 8 2.1 Stellenwertsysteme . . . 8

2.2 Rechenprobleme . . . 10

2.3 Einige Grundkonzepte imperativer Programmierung . . . 11

2.4 Prozeduren, Parameter¨ubergabe und Rekursion . . . 12

2.5 Konstruktion neuer Datentypen . . . 14

2.6 Random Access Machine . . . 15

3 Sortierproblem 16 3.1 Sortieren durch Einf¨ugen . . . 16

3.2 Sortieren durch Mischen . . . 19

3.3 Die Datenstruktur Heap und der Heap-Sort . . . 24

3.4 Priorit¨atsschlangen auf der Basis von Heaps . . . 30

3.5 Untere Schranken an die Laufzeit von Sortieralgorithmen . . . 32

3.6 Countingsort . . . 33

3.7 RadixSort . . . 34

3.8 QuickSort . . . 35

4 Verkettete Datenstrukturen 38 4.1 Stack, Schlange und Deque als Arrays . . . 38

4.2 Stack und Schlange als einfach verkettete Listen . . . 39

4.3 Deque als eine doppelt verkette Liste . . . 40

4.4 Zeiger-basierte bin¨are B¨aume . . . 41

5 Berechnungen mit reellen Zahlen 41 5.1 Darstellung reeller Zahlen in einem Stellenwertsystem . . . 41

5.2 Rundungsfehler . . . 42

(3)

5.3 Fehler der Gleitkommazahlenarithmetik . . . 42

5.4 Verfahrensfehler . . . 42

5.5 Datenfehler . . . 43

5.6 Modellierungsfehler . . . 43

(4)

Einleitung

Organisation: Schein f¨ur 50 % der ¨Ubungsaufgaben (2er Gruppen) und Program- mierprojekt (3er Gruppen). In der Vorlesung: Theorie. In den ¨Ubungen: Diskussion der Aufgaben + Einf¨uhrung in C++ (f¨ur absolute Anf¨anger, nur Basics).

Die in der Vorlesung formulierten Aufgaben k¨onnen entweder in den ¨Ubungsbl¨attern dran kommen oder als Aufgaben zum Selbstlernen benutzt werden.

In dem Kurs geht es um die Algorithmen und Mathematik.

M¨ogliches Muster:

Problem/Objekt aus der Praxis→ mathematisches Modell→ mathematische Theorie dazu→ Algorithmische Theorie.

Beispiel dazu: Euklidische Steiner-B¨aume. Im Fall von drei Ecken eines gleichseitigen Dreiecks. Im Fall von vier Ecken eines Quadrats. Eigenschaften der Steiner-Punkte.

Wie konsturiert man die Euklidische Steinerb¨aume.

Ein weiteres Beispiel: Bestimmung der Kusszahl. Eine mathematische Aufgabe. Un- tere Schranken in konkretem Dimensions k¨onnen unter anderem mit Hilfe des Com- puters ausgerechnet werden. Somit beweist man Theoreme mit Hilfe vom Computer.

Hier benutzt man Algorithmen/Computer als Hilfsmittel zur L¨osung mathemati- scher Probleme (wird immer ¨ofter gemacht). Heutzutage hat fast jede mathemati- sche Theorie eine algorithmische ‘Ecke’. In vielen Theorien spielen die Algorithmen eine zentrale Rolle (Numerik, Optimierung, Algebra, Graphentheorie usw. usw.).

(5)

1 Mathematische Grundlagen

1.1 Mathematische Grundbegriffe

Wir werden die folgenden Begriffe benutzen: Aussage, Menge, Abbildung, Tupel, Re- lation und die zugeh¨origen Operationen und Standardbezeichnungen. Das alles wird auch in den anderen Kursen (lineare Algebra und Analysis) ausf¨uhrlich diskutiert.

Falls man im Laufe der Vorlesung Fragen dazu hat, bitte melden!

Inklusion von Mengen wird in diesem Kurs als ⊆ (in manchen Quellen verwendet man auch⊂).

Sei X eine Menge. Dann ist die Potenzmenge von X die Menge aller Teilmengen von X. Bezeichnung: 2X, Formal: 2X :={A:A⊆X}, Anmerkung: hatX genaun Elemente, so hat 2X genau 2n Elemente (wird sp¨ater genauer diskutiert).

Zahlenmengen:

N :={1,2,3, . . .}

N0 :={0,1,2, . . .}

Z :={0,1,−1,2,−2, . . .}

Q :={pq :p∈Z, q∈N} R reelle Zahlen

In der Mathematik verwendet steht := f¨ur ‘wird definiert als’. D.h., links steht ein neues Symbol bzw. eine neue Bezeichnung und rechts die Bedeutung davon.

N⊆N0 ⊆Z⊆Q⊆R⊆C

In manchen Quellen wirdNals N={0,1,2, . . .} definiert.

1.2 Summen und Produkte

Die Anzahl der Elemente in einer MengeX (Kardinalit¨at vonX genannt) wird als

|X|bezeichnet. Man setzt die Kardinalit¨at von ∅ gleich 0.

SeiXeine nichtleere endliche Menge. Dann kannXalsX={x1, . . . , xn}dargestellt werden mitxi6=xj ⇔i6=j f¨ur alle i, j∈ {1, . . . , n}.

F¨ur eine Abbildungf :X→Rdefiniert man

• P

x∈X

f(x) :=f(x1) +. . .+f(xn)

• Q

x∈X

f(x) :=f(x1)·. . .·f(xn)

Im Fall X = ∅ definiert man f¨urf :X → R und P

x∈X

f(x) = 0 und Q

x∈X

f(x) = 1.

P,Q

sind wohldefiniert (ohne Beweis).

(6)

1.3 Pr¨adikate und Quantoren

Sei X Menge. Dann heißt P :X → {falsch,wahr} Pr¨adikat auf X. Etwa P :N→ {falsch,wahr}, P(k) :=

”k(k+ 1) ist durch 3 teilbar f¨ur alle k∈N”.

Durch ein Pr¨adikat P :X → {falsch,wahr} kann man die Menge {x ∈X : P(x)}

definieren.

∀x ∈ X : P(x) f¨ur ein Pr¨adikat P auf eine Menge X steht f¨ur die Aussage

”die BedingungP(x) gilt f¨ur allex∈X“.∀heißt das Allgemeinheitsquantor (Bedeutung:

f¨ur∀lle).

∃x ∈X :P(x) bezeichnet die Aussage

”die BedingungP(x) gilt f¨ur ein x∈X“. ∃ heißt Existenzquantor (Bedeutung: es∃xistiert).

1.4 Widerspruchsbeweis

Wir wollen nun ein paar n¨utzliche Beweistechniken diskutieren. Eine davon ist der Widerspruchsbeweis. Am besten l¨asst sich der Beweis an einem Beispiel diskutieren.

Wir zeigen durch einen Widerspruch, dass unendliche viele Primzahlen gibt. An- genommen, die Anzahl der Primzahlen w¨are endlich. Seien dann p1, . . . , pn mit n∈Nalle Primzahlen (mit p1 = 2, p2 = 3, p4 = 5 usw. ). Wir betrachten die Zahl k= 1 +Qn

i=1pi. Die Zahlk besitzt eine Primfaktorzerlegung und daher auch einen Primfaktorp. Da p1, . . . , pn alle Primzahlen sind, istp =pj f¨ur ein j ∈ {1, . . . , n}.

Die Zahlpjteiltknach der Konstruktion und sie teilt auch das ProduktQn

i=1pi, weil pj in diesem Produkt vorkommt. Somit teilt pj auch die Differenz k−Qn

i=1pi = 1.

Dies ist ein Widerspruch, weil 1 keine Teiler außer sich selbst besitzt.

1.5 Vollst¨andige Induktion

Vollst¨andige Induktion ist eine weitere Beweistechnik, die sehr oft in der diskreten und algorithmischen Mathematik verwendet wird.

SeiP ein Pr¨adikat aufN. Dann sind die folgenden Bedingungen ¨aquivalent:

(i) P(n) gilt f¨ur alle n∈N.

(ii) P(1) gilt, und f¨ur alle n∈N gilt:P(n)⇒P(n+ 1).

Es gibt mehrere offensichtliche Variationen zu dieser ¨Aquivalenz. Verifizierung von P(1) heißt Induktionsanfang, Verifizierung der Implikation P(n) ⇒P(n+ 1) heißt Induktionsschritt.

Beispiel 1.1. Als Beispiel, zeigen wir

n

X

i=1

i= n(n+ 1)

2 (1.1)

f¨ur alle n ∈ N. Die Formel (1.1) ist wahr f¨ur n = 1, denn links so wie rechts hat man in diesem Fall die eins.

(7)

Sei n ∈ N eine Zahl, f¨ur welche (1.1) wahr ist (Diese Annahme nennt man die Induktionsvoraussetzung). Nun zeigen wir, dass (1.1) mitn+ 1 an der Stelle vonn gilt. Man hat

n+1

X

i=1

i=n+ 1 +

n

X

i=1

iInd.V or.= n+ 1 + n(n+ 1)

2 = (n+ 1)(n+ 2) 2

Nach der Induktionsvoraussetzung gilt1 +· · ·+ (n+ 1) = (1 +· · ·+n) + (n+ 1) =

n(n+1)

2 + (n+ 1). Durch das Ausklammern von n+ 1 erhalten wir n(n+1)2 +n+ 1 =

(n+1)(n+2)

2 .

Mit Hilfe der vollst¨andigen Induktion kann man neben den Gleichungen auch Un- gleichungen und auch verschiedene andere Aussagen beweisen.

Aufgabe 1.2. In der Analysis werden Sie zeigen, dass f¨ur alle x1, . . . , xk≥0 (k∈ N) die Ungleichung

k

Y

i=1

xi

!1/k

≤ 1 k

k

X

i=1

xi

erf¨ullt ist. Die linke Seite heißt das geometrische Mittel und die rechte Seite das arithmetische Mittel. Zeigen Sie diese Ungleichung, im Fall, dassk eine Zweierpo- tenzk= 2n (n∈N), mit Hilfe der vollst¨andigen Induktion ¨uber n.

Aufgabe 1.3. Sei X endliche Menge und sei 2X :={Y : Y ⊆X}.

Zeigen Sie|2X|= 2|X| durch Induktion ¨uber |X|. Hinweis: in diesem Fall kann man die Induktion mit|X|= 0 beginnen.

1.6 Asymptotische Notation

Bei der Analyse von Algorithmen redet man oft von der Gr¨oßenordnung von Funk- tionen. Daher verwendet man sehr gerne die sogenannte asymptotische Notation.

Seienf, g:N→RFunktionen.

Man schreibt f(n) = O(g(n)), f¨ur n → ∞, wenn eine Konstante c > 0 und ein n0 ∈ N existiert derart, dass |f(n)| ≤ c|g(n)| f¨ur alle n ≥ n0 gilt. Die Bezeich- nung f(n) = O(g(n)) steht f¨ur f(n) hat die Gr¨oßenordnung h¨ochstens g(n) bis auf eine mutliplikative Konstante. Die Schreibweise f(n) = O(g(n)) ist streng ge- nommen nicht ganz korrekt, in der Literatur aber sehr verbreitet. Die Korrekte Schrebweise w¨aref(n)∈O(g(n)) (d.h.,f(n) ist liegt in der Menge aller Funktionen der Gr¨oßenordnung h¨ochstens O(g(n))). In der Literatur verwendet man n¨amlich oft O(g(n)) als eine Schreibweise f¨ur eine anonyme Funktion der Gr¨oßenordnung h¨ochstensg(n). In diesem Kurs spielen die Betr¨age in der Definition vonO(g(n)) in der Regel keine Rolle, weil man fast ausschließlich nichnegative Funktionen disku- tiert.

Man schreibt f(n) = Ω(g(n)), f¨ur n → ∞, wenn eine konstante c > 0 und ein n0 ∈ N existieren derart, dass|f(n)| ≥c|g(n)| f¨ur allen≥n0 gilt. In diesem Fall:

Gr¨oßenordnung mindestensg(n), bis auf multiplikative Konstanten.

(8)

Man schreibtf(n) = Θ(g(n)) wenn f(n) =O(g(n)) undf(n) = Ω(g(n)) gilt (also:

Gr¨oßenordnung genaug(n) bis auf multiplikativen Konstanten).

Beispiel 1.4. Seif(n) :=√

2n+ 5−10. Man hatf(n) = Θ(√

n),f¨urn→ ∞, denn einerseits hat man√

2n+ 5−10≤√

2n+ 5≤√

7n=√ 7√

nf¨ur alle n∈N, woraus f(n) =O(√

n) folgt. Andererseits hat man √

2n+ 5−10≥√

n−10≥ 12

nf¨ur alle n≥400, woraus f(n) = Ω(√

n) folgt.

Aufgabe 1.5. Sind die folgenden asymptotischen Absch¨atzungen richtig:n! =O(nn), nn= Ω(n!),n! =O(2n), nn=O(n!)?

2 Grundlagen zu Algorithmen und Programmierung

2.1 Stellenwertsysteme

Im Computer werden alle Daten mit 0 und 1 dargestellt. Es ist klar, dass man alle Daten mit ganzen Zahlen darstellen kann (denn man kann Symbole mit ganzen Zahlen nummerieren). Um als zu verstehen, wie man die Daten mit 0 und 1 darstellt, muss vor allem gekl¨art werden, wie man die ganzen Zahlen mit 0 und 1 darstellt.

Daf¨ur werden wir die Stellenwertsysteme einf¨uhren.

In der Informatik benutzt man meistens die Stellenwertsystem zu den Basen b ∈ {2,8,10,16}

Aufgabe 2.1. Zeigen Sie Folgendes. Seib∈N,b≥2. Dann besitzt jede Zahl z∈Z,

≥0 eine eindeutige Darstellung als z=

k

X

i=0

zibi (2.1)

mitk∈N0 und zi∈ {0, . . . , b−1} und zk6= 0 f¨ur z6= 0.

Die Zahlenz0, . . . , zk heißen dieStellen vonzim Stellenwertsystem zur Basisb, und wir schreiben in diesem Fall

zk· · ·z0 (zur Basisb) =z.

Hier heißt z0 die niedrigste Stelle und zk die h¨ochste Stelle. Die Zahl z heißt k- stellige Zahl im Stellenwertsystem zur Basisb. Bei negativen ganzen Zahlen geht die Darstellung zur Basisbgenau so (mit Vorzeichen).

Beispiel 2.2. Schriftliche Addition, Subtratktion, Multiplikation und Division zu einer beliebigen Basisb geht genau wie zur Basis b= 10. Etwa

Addition zur Basis 2:

1 0 1

+ 1 1

1 0 0 0

Bemerkung 2.3. Konvertierung von Basis b zur Basis 10. Direkt nach (2.1)oder das sogenannte Horner-Schema, das einem erm¨oglicht, die Anzahl der Multiplika- tionen zu sparen: F¨ur k= 2,

z2b2+z1b+z0 =b(bz2+z1) +z0,

(9)

f¨urk= 3

z3b3+z2b2+z1b+z0 =b(b(bz3+z2)

| {z }

1. Runde

+z1)

| {z }

2. Runde

+z0.

| {z }

3. Runde

usw.

Beispiel 2.4. Die Konvertierung von der Basis 10 zu einer anderen Basis geht durch die iterative Division mit Rest. Konvertieren wir die Zahl 46 in das System zur Basis3. Man hat

46 = 15·3 + 1 = (5·3 + 0)·3 + 1 = 5·32+ 0·3 + 1

= (1·3 + 2)·32+ 0·3 + 1

= 1·33+ 2·32+ 0·31+ 1·30 Das heißt:

46 (zur Basis 10)= 1201 (zur Basis 3).

Beispiel 2.5. Die Basis10 benutzen Menschen, die Basis 2die Computer, und die Basis16 die Menschen, die maschinennah mit Computern arbeiten. Die Konvertie- rung zwischen der Basis 2 und der Basis 16 einfach ist, weil 16 eine Potenz von 2 ist. Die Basis 16 erm¨oglicht aber eine kompaktere Darstellung von Zahlen. Die Ziffern des 16er System (auch Hexadezimal-System genannt) sind die 16 Symbo- le0, . . . ,9, A, B, C, D, E, F. Wir k¨onnen diese Ziffern als Zahlen im Dezimalsystem oder Bin¨arsystem darstellen:

Hexadezimal Dezimal Bin¨ar

0 0 0000

1 1 0001

2 2 0010

3 3 0011

4 4 0100

5 5 0101

6 6 0110

7 7 0111

8 8 1000

9 9 1001

A 10 1010

B 11 1011

C 12 1100

D 13 1101

E 14 1110

F 15 1111

Hier ist A zur Basis 16 gleich der10 zur unserer Standardbasis 10, usw. und F zur Basis16ist gleich der15zur Basis10. Die ZahlBEE im Hexadezimal-System kann ins Bin¨arsystem konvertiert werden, indem man jede Ziffer durch ihre Bin¨ardarstellung ersetzt.

BEE(im Hexadezimalsystem)= 1011 1110 1110(im Bin¨arsystem).

(10)

Um sich zu vergewissern, dass das tats¨achlich stimmt, kann man sich ¨uberlegen, was diese Gleichheit im Dezimalsystem bedeutet:

(23+ 21+ 20)

| {z }

B

162+ (23+ 22+ 21)

| {z }

E

161+ (23+ 22+ 21)

| {z }

E

160

=211+ 29+ 28+ 27+ 26+ 25+ 23+ 22+ 21

Um zu sehen, wie man im Computer Daten mit 0 und 1 darstellt kann unter Linux den hexdump-Befehl benutzen:

echo "aaabbbb" | hexdump -C

Ausgabe:

00000000 61 61 61 62 62 62 62 0a |aaabbbb.|

00000008

Hier ist 61 die hexadecimale Unicode-Kodierung des Buchstaben a, 62 die hexa- dezimale Unicode-Kodierung des Buchstaben b und 0a die hexadezimale Unicode- Kodierung von ‘neue Zeile’.

Aufgabe 2.6. Bei rationalen Zahlen, geht die Berechnung der Dezimaldarstellung zur Basisb genau so wie im Dezimalsystem. Berechnen Sie die Dezimaldarstellung von1/2, 1/3, 1/4, 1/5 und 1/6 im Bin¨arsystem.

Bemerkung 2.7. Wenn man im Computer nicht-negative ganze Zahlen in einemn- Bit-Register speichert, so gehen im Fall eines arithmetischen ¨Uberlaufs die h¨oheren Stellen verloren. Zum Beispiel gilt

11111110 + 00000010 = 100000000

zur Basis 2. (Im Dezimalsystem: 254 + 2 = 256). Wenn man diese Berechnung in einem8-Bit-Register ausf¨uhrt ist das Resultat gleich0. D.h. in einem n-bit Register werden die arithmetischen Operationen modulo2n durchgef¨uhrt.

2.2 Rechenprobleme

DieRechenproblemesind Eingabe-R¨uckgabe-Relationen. Etwa beschreit die Relation (a, p)∈N2 : p ist Primfaktor vona .

zwischen zwei nat¨urlichen Zahlen das Rechenproblem ‘bestimme einen Primfaktor der gegebenen nat¨urlichen Zahl’. Das zugrundeliegende Rechenproblem algorith- misch zu l¨osen heißt, einen Algorithmus zu entwickeln, der zu jeder m¨oglichen Eingabe eine passende R¨uckgabe bestimmt oder feststellt, dass es keine passende R¨uckgabe existiert.

In unserem Beispiel ist f¨ur die Eingabe a = 10, die Zahl p = 2 eine passende R¨uckgabe. Die Zahl p = 5 passt auch, denn 5 ist ebenfalls ein Primfaktor von 10.

F¨ura= 1 hat man keine passende R¨uckgabe: so m¨usste in der L¨osungsalgorithmus mit einer Meldung terminieren, dass es keine passende R¨uckgabe gibt.

(11)

Die Berechnung einer Abbildung/Funktionf :X→Y ist ein Spezialfall. In diesem Fall istf(x) die eindeutige R¨uckgabe f¨ur die Eingabex∈X. Zum Beispiel k¨onnen wir das Problem der der Potenz als die Funktionf :N2 →Nmitf(a, p) :=ap. Mit Worten: f¨ur gegebene a, p∈Nsollap algorithmisch berechnet werden.

Ein weiterer Spezialfall ist die ¨Uberpr¨ufung einer Eigenschaft. Solche Probleme nennt man Entscheidungsprobleme. Sie k¨onnen als Berechnung einer Funktion f : X → {0,1}interpretiert werden. Etwa:f :N→ {0,1}mitf(n) = 1, wennneine Primzahl ist, und f(n) = 0 sonst. Mit Worten: es soll ¨uberpr¨uft werden, ob eine gegebene nat¨urliche Zahl neine Primzahl ist.

2.3 Einige Grundkonzepte imperativer Programmierung

Um die Programmierung unabh¨angig von einer konkreten Programmiersprache zu diskutieren, benutzten wir den sogenannten Pseudocode. Pseudocode ist eine Co- deskizze, die ein wie ein Computerprogramm aussieht, in der man aber auch bei Bedarf Worte benutzen kann. Mit Pseudocode kann man Algorithmen beschreiben, ohne auf die technischen Details der Programmierung einzugehen.

EineProgramm-Variable ist ein Beh¨alter f¨ur Werte bzw. Daten. Der Wert der Varia- blen kann durch eine Zuweisung festgelegt werden. Man betrachte z.B. die folgenden zwei Zeilen.

1: x:= 5

2: x:= 2·x+ 4

Hier wird in der ersten Zeile einer Variablen x der Wert 5 zugewiesen. Als := Be- zeichnen wir in unserem Pseudocode die Zuweisung. In der zweiten Zeile wird der Variablen x ein neuer Wert zugewiesen, wobei man sich bei der Zuweisung in der rechten Seite auf den aktuellen Wert bezieht.

Um mit die Variablen und Daten zu bearbeiten benutzt man dieKontrollbefehle wie if-then,if-then-else (Verzweigung) undwhile and for (Schleifen).

Hier ein Code, der die Werte der Variablen x und y vertauscht, wenn am Anfang x > y gilt:

1: if x > y :

2: t:=x

3: x:=y

4: y:=t

5: end

D.h. die drei Zuweisungen werden genau dann ausgef¨uhrt wenn beim Erreichen der Zeile 1 des Codes die Bedingungx > y gilt. Die Variable tist eine Zusatzvariable, die beim Vertauschen benutzt wird.

If-then-else ist analog aufgebaut. Im else-Teil stehen die Befehle, die ausgef¨uhrt werden, wenn die gegebene Bedingung nicht erf¨ullt ist.

Ein ArrayAder L¨angenist eine Liste ausnVariablen, wobei die Variablen mit auf- einanderfolgenden ganzen Zahlen indexiert sind. In den allermeisten meisten Spra- chen einschließlich C und C++ werden die Arrays ab 0 indexiert. Bei Indexierung

(12)

ab 1 (die wir im Pseudocode dieser Vorlesung benutzen) ist ein ArrayA der L¨ange naus den Variablen A[1], . . . , A[n] zusammengesetzt, auf welche man durch die An- gabe des Indexizugreifen kann. Die Variable A[i] heißt die i-te Komponente, oder dasi-te Element des ArraysA. Die Anzahl der Komponente eines ArraysAwird als L¨ange[A] bezeichnet.

Wir illustrieren einen andere Kontrollstruktur, die for-Schleife, indem wir zeigen, wie man damit die Summe aller Elemente eines n-elementigen Arrays bestimmen kann.

1: s:= 0

2: for i:= 1, . . . ,L¨ange[A]:

3: S:=S+A[i]

4: end

Einewhile-Schleife ist eine Kontrollestruktur die aus dem Rumpf und der Bedingung der Schleife besteht, wobei die Befehle des Rumpfs der Schleife iterativ ausgef¨uhrt werden, solange die Bedingung der Schleife erf¨ullt ist. In der while-Schliefe steht die Bedingung vor dem Rumpf. In manchen Programmiersprachen hat man auch schleifen, bei denen die Bedingung nach dem Rumpf steht (repeat-until-Schleifen).

Hier ein Beispiel, wie man die Komponenten ein Array mit Hilfe einer while-Schleife umkehren kann:

1: i:= 1

2: j :=L¨ange[A]

3: while i < j :

4: A[i] und A[j] vertauschen

5: i:=i+ 1 B Zum n¨achsten i

6: j:=j−1 BZum n¨achsten j

7: end

2.4 Prozeduren, Parameter¨ubergabe und Rekursion

Prozedur (Funktion, Unterprogramm) ist ein Code innerhalb eines Programms mit eigener Eingabe.

Stellen wir uns vor, wir m¨ussen zur L¨osung einer Rechenaufgabe immer wieder testen, ob x ∈ [p, q] f¨ur gegebene p, q, x ∈ Z gilt. In diesem Fall lohnt es sich, eine sogenannteProzedur anzulegen, welche genau diesen Test durchf¨uhrt. (Andere Namen:Programm-Funktion,Unterprogramm):

b=Ist-zwischen(x, p, q) if p≤x≤q oder q≤x≤q :

b=Wahr end

b=Falsch

Die Variablenx, p, q heißen Eingabeparameter der Prozedur und die Variable b ist die R¨uckgabe-Variable. In vielen modernen Programmiersprachen benutzt man f¨ur

(13)

die R¨uckgabe keinen Variablennamen sondern den Befehlreturn. Das sieht dann so aus:

Ist-zwischen(x, p, q)

if p≤x≤q oder q≤x≤q : return Wahr

end

return Falsch

Durch ein return wird die Prozedur mit dem vorgegeben Wert beendet.

Man kann auch Prozeduren ohne R¨uckgabe betrachten. In C++ sind es die Funk- tionen mit dem R¨uckgabetyp void.

In manchen Sprachen (wie z.B. in C++) stehen mehrere Arten der Parameter¨ubergabe zur Verf¨ugung, wie z.B. Ubergabe durch Kopie¨ und die Ubergabe durch Referenz.¨ Wenn z.B. im vorigen Pseudocodex,pundqdurch Kopie ¨ubergeben werden, so ent- stehen bei jedem Aufruf der Prozedur die drei Variablenx, pundq, welche dann ent- sprechend initialisiert werden. Etwa, bei der Ausf¨uhrung von Ist-zwischen(a, b, c) mitx=a, y=b, z=c.

Bei der ¨Ubergabe durch Referenz, ist der Eingabeparameter lediglich ein weiterer Name f¨ur eine Variable, die bereits existiert. Ich illustriere das am Bespiel vom Vertauschen:

v o i d v e r t a u s c h e n ( i n t& x , i n t& y ) { i n t t=x ;

x=y ; y=t ; }

i n t main ( ) {

i n t a =2 ,b=3;

v e r t a u s c h e n ( a , b ) ; r e t u r n 0 ;

}

Damit die Werteaundb in der main-Funktion vertauscht werden, m¨ussen die Ein- gabeparameterx und y Referenzvariablen sein. In diesem Fall sind x und y zweite Namen f¨ur a bzw. b. Die Variable t ist eine lokale Variable der Funktion vertau- schen. Sie entsteht bei jeder Ausf¨uhrung von vertauschen und verschwindet nach der Terminierung dieser Funktion.

(14)

p:=Potenz(a, n) if n= 0 :

p:= 1

else if n gerade: q :=Potenz(a, n/2) p:=q2

else:

q :=Potenz(a,(n−1)/2) p:=pq2.

end

Diese rekursive Umsetzung ist in vielen Situationen besser als die nicht-rekursive iterative Umsetzung mitO(n) Iterationen.

Es kann ¨uberpr¨uft werden, dass alles was man rekursiv umsetzt auch ohne Rekursion, etwa mit Schleifen und Arrays, umsetzen kann. Dies gilt auch f ¨Ur das Potenzieren oben. Die rekursiven Umsetzungen sind aber manchmal leichter zu verstehen bzw.

eleganter.

Prozeduren, die sich selbst aufrufen, heißen rekursive. Hier ein Beispiel einer Proze- dur, welcheanf¨ura∈Zund n∈N0 ausrechnet.

2.5 Konstruktion neuer Datentypen

Zur Konstruktion neuer/eigener Datentypen stehen in h¨oheren Programmierspra- chen verschiedene Mittel und Bibliotheken zur Verf¨ugung. Wir diskutieren hier keine Abstraktionsmechanismen (wie OOP oder Templates), sondern mehr die Maschine- nebene.

Was man verwenden kann ist: Arrays/Listen, Strukturen und Zeiger.

Strings sind z.B. im wesentlichen Listen von Zeichen. Genau so k¨onnen auch Files als Listen von Zeichen interpretiert werden.

Strukturen erm¨oglichen Daten verschiedener Datentypen in einem ‘P¨ackchen’ zu vereinigen. Zum Beispiel. Ein Datentype auto kann durch Modell, Kennzeichen, Ki- lometerstand, und andere sogenannte Attribute definiert werden. Ein farbiger Punkt auf der Ebene durch drei Attribute,x-Komponente,y-Komponente und Farbe. Usw.

Zeiger sind Adresse-Variablen. D.h., eine Zeiger-Variable speichert die Adresse eines Orts (einer anderen Variablen) in Speicher. Zeiger k¨onnen in verschiedenster Situa- tionen benutzt werden (insb. um verkettete Datenstrukturen zu implementieren).

F¨ur Zeiger hat man zwei Grundoperationen: Adresse von einem Objekt, und Ob- jekt unter gegebener Adresse. In vielen Programmiersprachen haben die komplexen Objekte das Zeigerverhalten (etwa, Arrays in Python).

Im folgenden werden wir Algorithmen mit Pseudocode beschreiben (‘unverbindli- cher Code’, selbsterkl¨arend). Unsere Vereinbarung: bei Parameter¨ubergabe werden Arrays durch Referenz ¨ubergeben, einfache Datentypen durch Kopie).

(15)

2.6 Random Access Machine

Random Access Machine (kurzRAM), oder auf deutsch,Maschine mit wahlfreiem Zugriff, wird unsere Idealisierung des realen Rechners sein. Die Analyse und Ent- wicklung von Algorithmen in diesem Kurs wird im Rahmen der RAM durchgef¨uhrt.

Wir nehmen an, die Zellen unserer Maschine k¨onnen ganze Zahlen beliebiger Gr¨oße speichern (d.h. die Bit-Gr¨oße der Speicherzellen ist unendlich). Die Speichergr¨oße (d.h,. die Anzahl der Speicherzellen) ist ebenfalls unbeschr¨ankt (d.h., unendlich).

Wir k¨onnen alle anderen Datentypen auf der Basis der ganzen Zahlen umsetzen.

genannt. Wir brauchen eine mathematische Abstraktion f¨ur einen realen Rechner.

Dies ist die Random Access Machine. Sie kann rein formal eingef¨uhrt werden. Ich finde es besser, wenn wir zun¨achst eine etwas informelle Beschreibung benutzen, durch welche wir festlegen welche Datentypen, Operationen und Kontrollstrukturen f¨ur uns elementar sind.

Als Grundoperationen erlauben wir

• Zuweisung (f¨ur ganzzahligen Datentyp),

• Addition von ganzzahligen Variablen,

• Multiplikation einer ganzzahligen Variablen mit einer Konstante,

• Ganzzahlige Division einer ganzzahligen Variablen durch eine Konstante,

• Zugriff zu Speicherzellen durch Index,

• Vergleichsoperationen <,≤,=,≥, >0,

• Kontrollstrukturen if-then-else, while, for.

Unsere Algorithmen werden als Pseudocode formuliert und ihre Analyse wird im Rahmen dieses Modells durchgef¨uhrt. F¨ur Pseudocode legen wir fest, dass stan- dardm¨aßig in Prozeduren alle einfachen Datentypen durch Kopie und alle komplexen Datentypen (Arrays usw.) durch Referenz ¨ubergeben werden.

Wir lassen Multiplikation von zwei ganzzahligen Variablen ist in unserem Modell keine Grundoperationen. Denn, wenn das eine Grundoperationen w¨are, so h¨atte der folgende Psuedocode die LaufzeitO(n):

x:= 2

fori= 1, . . . , n : x:=x2

end

Der Pseudocode w¨urde als 22n in der Zeit O(n) berechnen. Die Zahl 22n hat 2n+ 1 Bin¨arstellen. Wir w¨urden also eine Zahl exponentieller Bitgr¨oße in linearer Zeit be- rechnen. Weil das zu unrealistisch ist, schließen wir Multiplikation beliebiger ganzer Zahlen als Grundoperation aus.

(16)

3 Sortierproblem

Das Sortierproblem ist das Problem, in einem gegebenen Array A einer beliebigen L¨ange n ∈ N0, die Komponenten so zu vertauschen, dass nach dem Vertauschen die Bedingung A[1] ≤ . . . ≤ A[n] erf¨ullt ist. Wir argumentieren auf Arrays mit ganzzahligen Komponenten. Die pr¨asentierten Algorithmen k¨onnen aber auch f¨ur Arrays mit allgemeineren Inhalten verwendet werden.

3.1 Sortieren durch Einf¨ugen

Wir beginnen mit dem Sortieren durch Einf¨ugen (engl. Insertion Sort). Ist A ein Array der L¨ange n, so k¨onnen wir Auch Teilarrays A[i . . j] von f¨ur 1≤ i≤j ≤n betrachten. Das TeilarrayA[i . . j] ist das Teil vonAmit den KomponentenA[k] f¨ur i≤k≤j.

Die Idee von Sortieren durch Einf¨ugen ist ein sortiertes Teilarray iterativ wachsen zu lassen:

· · · ·

| {z }

schon sortiert

· · · ·

| {z }

nicht bearbeitet

einf¨ugen

−−−−−→ · · ·· · ·

| {z }

sortiert

· · · ·

| {z }

nicht bearbeitet

Das (eine der Komponenten des nichtsortierten Teilarrays) wird in das bereits sortierte Teil solange aufgenommen bis das nicht bearbeitete Teil leer wird.

Wir wissen also, wie die ¨außere Schleife aussieht:

Sortieren-durch-Einf¨ugen(A) fori= 2, . . . ,L¨ange[A]:

B A[1. . i−1]schon sortiert B TODO: A[1. . i]sortieren end

Hier eine schematische Darstellung:

· · · ·

| {z }

schon sortiert

i · · · ·

| {z }

nicht bearbeitet

Nun k¨onnen wir das eigentliche Einf¨ugen in der inneren Schleife umsetzen:

(17)

Sortieren-durch-Einf¨ugen(A)

1: for i:= 2, . . . ,L¨ange[A]:

2: BA[1. . i−1] ist sortiert

3: x:=A[i] B wird eingef¨ugt

4: j:=i B A[j] ‘frei’

5: BSchleife: Steht links vor freien Position was gr¨oßeres als x?

6: whilej >1 undA[j−1]> x:

7: B Wir ¨andern die freie Position (wie bei 15-Puzzle)

8: A[j] :=A[j−1]

9: j:=j−1

10: end

11: A[j] :=x B Position f¨ur x gefunden

12: end

Eine schematische Darstellung:

· · ·

| {z }

≤x

· · · ·

| {z }

>x noch nicht verschoben

j

· · ·i

| {z }

>x schon verschoben

· · · ·

| {z }

noch nicht bearbeitet

Bemerkung 3.1. Die und-Operation in der While-Schleife ist in den Program- miersprachen in der regele eine sogenannte tr¨age Operation. In diesem Fall heißt es: istj >1 nicht erf¨ullt, so kennt man, dass der Ausdruck als Falsch ausgewertet wird. In diesem Fall wird A[j−1]> xnicht ausgewertet. Wenn man bei j≤1, die OperationA[j−1]> xauswerten w¨urde, so h¨atte (je nach der Programmiersprache) einen Laufzeitfehler.

Bemerkung 3.2. Sortieren durch Einf¨ugen terminiert. In der inneren Schleife wird j in jeder Iteration verkleinert, sodass jede Ausf¨uhrung der Inneren schleife end- lich viele Iterationen macht.Die ¨außere Schleife macht offensichtlich nicht mehr las L¨ange[A] Iterationen.

Theorem 3.3(Korrektheit des Sortierens durch Einf¨ugen). Sortieren durch Einf¨ugen l¨ost das Sortierproblem.

Beweis. Bei den Arrays A der L¨ange h¨ochstens 1, macht die ¨außere Schleife keine einzige Iteration, sodassA gar nicht ver¨andert wird. Der Algorithmus arbeitet also korrekt auf Arrays der L¨ange h¨ochstens 1.

Hat A eine gr¨oßere L¨ange, so gilt in der ersten Iteration (mit i= 2) offensichtlich die Bedingung, dassA[1. . i−1] sortiert ist. Das heißt, wenn am Ende der ¨außeren Schleife das TeilarrayA[1. . i] sortiert wird, so funktioniert das gesamte Verfahren korrekt. Denn so beh¨alt man w¨ahrend der gesamten Ausf¨uhrung die Bedingung, dass A[1. . i−1] zur Beginn der ¨außeren Schleife sortiert ist, sodass am Ende der Ausf¨uhrung das gesamte Array sortiert wird.

Es bleibt zu zeigen, dass jede Iteration der ¨außeren Schleife das ArrayA[1. .i] sortiert.

Das Element A[i] wird in der Variablen x aufgehoben, sodass A[i] ¨uberschrieben werden kann. Die innere Schleife ist so aufgebaut, dass man das Teilarray vonA[1. .i−

(18)

1] aus den Elementen, die echt gr¨oßer alsxsind iterativ um eine Position nach rechts verschiebt (mit dem rechten Element beginnend). Die Bedingung, dass die Position A[j] ‘frei’ ist, Beim Beginn jeder Iteration der inneren Schleife ist die Komponente A[j] ‘frei’, das heißt, wenn man das aufgehobene ElementxinA[j] kopieren w¨urde, enthielte das ArrayA alle seinen urspr¨unglichen Elemente.

Eine Aussage, die an einer Stelle des Algorithmus immer erf¨ullt ist, heißtInvariante.

Insbesondere Spricht man vonSchleifeninvarianten.

Die Laufzeit ist die Anzahl der Elementaroperationen/Grundoperationen (vgl. unse- re Definition der Random Access Machine), die ein Algorithmus bei der Ausf¨uhrung auf einer gegebenen Eingabe durchf¨uhrt.

Die Worst-Case-Laufzeit ist die maximale Laufzeit auf einer gegebenen Menge der Eingaben.

Der Speicheraufwand ist die Anzahl der Speicherzellen, die ein Algorithmus neben der Speicherung der Eingabe zus¨atzlich ben¨otigt (beachte, unsere Random Access Machine hatte den ganzzahligen Datentype als Grundlage)

Theorem 3.4 (Zur Effizienz des Sortierens durch Einf¨ugen). Die Worst-Case- Laufzeit des Sortierens durch Einf¨ugen auf Arrays der L¨angenist Θ(n2). Der Spei- cheraufwand ist Θ(1).

Beweis. Der Speicheraufwand ist offensichtlich Θ(1), da man lediglich drei zus¨atzliche Variableni, j und x benutzt.

Wir zeigen, dass die Laufzeit auf jedem Array der L¨angen≥1 h¨ochstens quadratisch ist. Zur Ausf¨uhrung einer Iteration der ¨außeren Schleife braucht man h¨ochstens cn Elementaroperationen f¨ur eine Konstante c >0 (da die innere Schleife nicht mehr als n Iterationen macht). Die ¨außere Schleife macht nicht mehr als n Iterationen, sodass die Gesamtlaufzeit aller Iteration nicht h¨oher als cn2 ist. Es folgt, dass die Laufzeit des Sortierens durch Einf¨ugen auf eine Array der L¨angengleichO(n2) ist.

Es bleibt zu zeigen, dass die Laufzeit auf manchen Arrays der L¨angengleich Ω(n2) ist. Angenommen,n≥1 undAist ein absteigend sortiertes Array mitnunterschied- lichen Elementen, etwaA= [n, . . . ,1]. Dann macht f¨ur jedesi∈ {2, . . . , n}die innere Schleife genaui−1 Iterationen. Da man in jeder inneren Iteration mindestens eine Elementaroperation durchf¨uhrt, ist die Gesamtlaufzeit mindestens

n

X

i=2

(i−1) =

n−1

X

k=1

k= (n−1)n

2 = Ω(n).

Bemerkung 3.5. Man macht asymptotische und keine exakten Absch¨atzungen der Laufzeit macht man aus dem folgenden Grund, weil man analysieren will, weil man sich haupts¨achlich f¨ur die Gr¨oßenordnung der Laufzeit f¨ur gen¨ugend große Einga- ben interessiert. Bei einer exakteren Absch¨atzung m¨usste man den Rechner genauer

‘festlegen’ (das will man allerdings nicht).

(19)

3.2 Sortieren durch Mischen

Die Idee ist, zwei rekursiv sortierte m¨oglichst gleichlange Teilarrays zu einem sor- tierten Array zu mischen:

· · · ·

| {z }

rekursiv sortiert

· · · ·

| {z }

rekursiv sortiert

mischen

−−−−−→ · · · ·

| {z }

sortiert

Wir definierenx abgerundet und x aufgerundet f¨urx∈R:

bxc:= max{z∈Z : z≤x}, dxe:= min{z∈Z : x≤z}. Das Sortieren durch Mischen ist sehr einfach aufgebaut:

Sortieren-durch-Mischen(A)

1: if L¨ange[A]>1:

2: m:=bL¨ange[A]/2c B Mitte des Arrays

3: A[1. . m] in einem neuen Array Laufheben.

4: A[m+ 1. .L¨ange[A]] in einem neuen ArrayR aufheben.

5: Sortieren-durch-Mischen(L)

6: Sortieren-durch-Mischen(R)

7: Mischen(A, L, R) B sortierte L und R wirden in das A gemischt

8: BBemerkung: hier werden L und R aufgel¨ost

9: end

Beim Mischen wirdAsukzessiv mit Inhalten vonLundRgef¨ullt. Die ArraysLsowie R werden gleichzeitig von links nach rechts gescannt, die jeweils kleinere Kompo- nenten wird inA aufgenommen:

(20)

Mischen(A,L,R)

1: a:= 1,l:= 1,r := 1 BLaufindizes f¨ur jeden der drei Arrays

2: B Los!

3: while l≤L¨ange[L] und r ≤L¨ange[R]:

4: if L[l]≤R[r]:

5: A[a] :=L[l]

6: a:=a+ 1, l:=l+ 1

7: else:

8: A[a] :=R[r]

9: a:=a+ 1, r:=r+ 1

10: end

11: end

12: B Nun einen der beiden Reste aus L bzw. R in A kopieren

13: while l≤L¨ange[L]:

14: A[a] :=L[l]

15: a:=a+ 1, l:=l+ 1

16: end

17: while r≤L¨ange[R]:

18: A[a] :=R[r]

19: a:=a+ 1, r:=r+ 1

20: end

Hier die schematische Darstellung der Hauptphase von Mischen (Initialisierung und die While-Schleife in den Zeilen 1–11):

L · · · ·

| {z }

schon kopiert

l

· · · ·

| {z }

noch nicht kopiert

mischen

−−−−−→ A · · · ·

| {z }

schon gef¨ullt

a

· · · ·

| {z }

noch nicht gef¨ullt

R · · · ·

| {z }

schon kopiert

r

· · · ·

| {z }

noch nicht kopiert

Invarianten der Hauptphase sind:

• Ist xschon kopiert und y noch nicht kopiert so giltx≤y.

• Alle kopierten Komponenten befinden sich im gef¨ullten Teil von A in einer aufsteigend sortierten Reihenfolge.

Zur Endlichkeit:

• Sortieren durch Mischen terminiert auf Arrays der L¨ange h¨ochstens 1.

• Ist die L¨ange des Arraysn≥2, so macht das Verfahren zwei rekursive Aufrufe auf Arrays der L¨ange h¨ochstensn−1 und einen Aufruf von Mischen.

(21)

• Das heißt, unter der Voraussetzung, dass das Mischen terminiert, kann die Endlichkeit des Sortierens durch Mischen per Induktion nachngezeigt werden.

• Mischen terminiert, da sich in jeder Iteration jeder Schleife der Index l oder der Index r erh¨oht wird und die Abbruchbedingungen von der Gr¨oße von l und r abh¨angen.

Lemma 3.6(Die Laufzeit, die Korrektheit und der Speicheraufwand von Mischen).

Mischen erf¨ullt die folgenden Bedingungen:

(a) Das Verfahren ist korrekt: gilt L¨ange[A] = L¨ange[L] +L¨ange[R], so werden die Komponenten aufsteigend sortierter ArraysLundR in das ArrayAin einer aufsteigend sortierten Reihenfolge gemischt.

(b) Die Laufzeit f¨ur jedes Array A der L¨ange nist Θ(n).

(c) Der Speicheraufwand ist Θ(1).

Beweis. Die Aussagen ¨uber den Speicheraufwand sind klar.

Zur Korrektheit: das Element aus L oder R, welches in das R aktuell kopiert wird ist nicht gr¨oßer als die Elemente die zu einem sp¨ateren Zeitpunkt kopiert werden, denn unter den beiden aktuellen Elementen von L und R w¨ahlt man das Kleinere.

Jedes Element vonLund R wird in einem Zeitpunkt der Ausf¨uhrung inA kopiert.

Zur Laufzeit: In jeder Iteration wird entweder ein Element vonLoder ein Element vonRabgearbeitet. Das heißt, die Gesamtanzahl der Iteration von allen drei Schlei- fen ist genauL¨ange[L] +L¨ange[R] =n. Pro Iteration macht man mindestens eine Elementoperation und h¨ochstens konstant viele Elementaroperationen. Die Laufzeit ist daher Θ(n).

Bevor wir die Effizienz vom Sortieren durch Mischen sauber analysieren, ¨uberlegen wir informell, was die Laufzeit von Sortieren durch Mischen sein k¨onnte. Am besten betrachten wir zuerst Arrays deren L¨anger eine Zweirpotenz ist. Wir vereinfachend an, die Laufzeit T(n) w¨are eine Funktion in n (das ist nicht ganz richtig, aber eigentlich fast richtig). Nun hat man T(2k) = 2T(2k−1) +a2k, wenn die Laufzeit von Mischen eine Funktion in der L¨ange vonAw¨are (wie gesagt, nicht ganz richtig, aber fast richtig). Hierbei ist a > 0 eine Konstante. Diese rekursive Relation kann man nun immer wieder verwenden

T(2k) = 2T(2k−1) +a2k

= 22T(2k−2) +a2k+a2k

= 23T(2k−3) +a2k+a2k+a2k

=· · ·

= 2kT(1) +a2k+· · ·+a2k

| {z }

k

= 2kT(1) +ak2k

= Θ(k2k).

(22)

Wenn wir nun keine Zweierpotenzen betrachten undk= lognbenutzen, so erhalten wir Θ(nlogn). Dieser informeller Beweis kann in einen formal korrekten Beweis konvertiert werden.

Theorem 3.7 ( ¨Uber Sortieren durch Mischen). F¨ur das Sortieren durch Mischen gilt:

(a) Das Verfahren l¨ost das Sortierproblem.

(b) Die Laufzeit auf allen Arrays der L¨ange nist Θ(nlogn).

(c) Der Speicheraufwand auf allen Arrays der L¨ange n ist Θ(n).

Beweis. Korrektheit: Wir zeigen die Korrektheit durch Induktion ¨uber n∈N0. F¨ur Arrays der Gr¨oße h¨ochstens eins ¨andert das Verfahren das Array A nicht und ar- beitet somit korrekt. Sein≥2 und sei das Verfahren korrekt auf Arrays der L¨ange h¨ochstensn−1. Auf Arrays der L¨angenmacht Sortieren durch Mischen zwei rekur- sive Aufrufe des Sortierens durch Mischen mit Arrays der L¨ange h¨ochstens dn/2e, wobei f¨urn≥2 der Wert dn/2e nicht h¨oher als n−1 ist. Nach der Induktionsvor- aussetzung sortieren die rekursiven Aufrufe die beiden Teilarrays. Die Korrektheit f¨ur Arrays der L¨angen folgt nun aus der Korrektheit des Mischens.

Laufzeit: Wir zeigen, dass die LaufzeitO(nlogn) ist. Wir w¨ahlen im Folgenden eine Konstante c > 0, f¨ur welche die folgende Aussage durch Induktion gezeigt werden kann: Die Laufzeit vom Sortieren durch Mischen auf Arrays der L¨ange n ≥ 2 ist h¨ochstens cnlogn. Damit diese Aussage f¨ur 2 ≤n ≤ 3 wahr wird, reicht es c eine obere Schranke an die Laufzeit f¨ur Arrays der L¨ange 2 und 3 zu w¨ahlen. Sein≥4 und sei die Laufzeit des Sortierens durch Mischen auf Arrays der L¨angek∈ {2, . . . , n−1}

h¨ochstenscklogk. Wir zeigen nun, dass die Laufzeit vom Sortieren durch Mischen auf Arrays der L¨ange n h¨ochstens cnlogn ist, wenn die Konstante c > 0 passend fixiert ist.

Das Sortieren durch Mischen besteht aus zwei rekursiven Aufrufe auf Arrays der L¨angebn/2c unddn/2e, einem Aufruf von Mischen (mit der linearen Laufzeit in n) und dem Kopieren der Teilen vonAin die ArraysL(mit der linearen Laufzeit inn).

Mit der Verwendung der Induktionsvoraussetzung ergibt sich die obere Schranke T :=cbn/2clogbn/2c

| {z }

Sortieren vonL

+cdn/2elogdn/2e

| {z }

Sortieren vonR

+ an

|{z}

Kopieren und Mischen

an die Gesamtlaufzeit, wobei a > 0 eine Konstante ist. (Hier und im Folgenden bezeichnet log ohne Angabe der Basis den Logarithmus zur Basis 2.) Es sei be- merkt, dass die Induktionsvoraussetzung tats¨achlich anwendbar ist, weil f¨ur n≥ 4 die Ungleichungen

2≤ n

2 ≤ bn/2c ≤ dn/2e ≤n+ 1

2 ≤n−1

erf¨ullt sind und somit die beiden Werte bn/2c und dn/2eim Bereich {2, . . . , n−1}

liegen.

Die Terme unter den Logarithmen k¨onnen wir mit Hilfe von Ungleichungen dn/2e ≤ dn/2e ≤ n+ 1

2 ≤ 3n 4

(23)

absch¨atzen. So ergibt sich die Schranke:

T ≤c(bn/2c+dn/2e) log(3n/4) +an

=cnlog(3n/4) +an

=cn(logn−log(4/3)) +an

=cnlogn+ (a−clog(4/3))n.

Nun gilt die Absch¨atzung T ≤ cnlogn wenn wir c groß genug fixieren, sodass die Bedingungclog(4/3)≥aerf¨ullt ist.

Auf eine ¨ahnliche Weise kann auch die untere Schranke Ω(nlogn) auf Arrays der L¨angengezeigt werden.

Speicheraufwand: Wir zeigen, dass der Speicheraufwand O(n) ist. Wir m¨ussen also zeigen, dass f¨ur eine Konstante c > 0 der Speicheraufwand auf Arrays der L¨ange n≥2 h¨ochstens cnist. Das wird durch Induktion ¨uber n gezeigt. F¨urn= 2 reicht es, c gen¨ugend groß zu w¨ahlen. Sei n ≥ 3 und sei der Speicheraufwand f¨ur Arrays der L¨ange k ∈ {2, . . . , n−1} h¨ochstens ck. F¨ur ein beliebiges Array der L¨ange n ergibt sich die obere Schranke

S:= cdn/2e

| {z }

rekursive Aufrufe aufLundR

+ an

|{z}

Kopieren inLundR

an den Speicheraufwand, wobei hiera >0 eine Konstante ist. In dieser Absch¨atzung scheint der Speicheraufwand von Mischen ignoriert zu sein. Mischen hat einen Kon- stanten Speicheraufwand. Wenn wir alsoagen¨ugend groß w¨ahlen, k¨onnen wir durch den Terman den Aufwand von Mischen mitber¨ucksichtigen. Die Erkl¨arung, warum man im Gegenteil zur Analyse der Laufzeit nur einen Term f¨ur die beiden rekursiven Aufrufe hat ist die Folgende. Wenn der erste rekursive Aufruf terminiert hat, gibt er den von ihm benutzten Speicher wieder frei; dieser Speicher kann anschließend beim zweiten rekursiven Aufruf benutzt werden.

Wir sch¨atzen dn/2e durch

dn/2e ≤(n+ 1)/2≤3n/4 ab und erhalten

S ≤(3c/4 +a)n.

Somit hat manS ≤cn, wenn c≥4agilt. Wir w¨ahlen also in diesem Beweisc groß genug, sodassc≥4agilt.

Die untere Schranke Ω(n) an den Speicheraufwand auf allen Arrays der L¨ange n kann analog gezeigt werden.

Aufgabe 3.8. Zeigen Sie, dass das Sortieren durch Mischen auf allen Arrays der L¨angen die Laufzeit Ω(nlogn) hat.

Aufgabe 3.9. Zeigen Sie, dass das Sortieren durch Mischen auf allen Arrays der L¨ange der L¨ange nden Speicheraufwand Ω(n) hat.

Der Unterschied der Worst-Case-Laufzeiten Θ(n2) und Θ(nlogn) macht sich bei der Vergr¨oßerung der Array-L¨ange n sehr schnell bemerkbar. Das asymptotische Verhalten vonnlognist viel ‘¨ahnlicher’ zum Verhalten vonnals zum Verhalten von n2. Salopp kann man das Verhalten nlognals ‘beinah linear’ beschreiben.

(24)

Bemerkung 3.10(Anwendungen der Sortierverfahren). Sortierverfahren haben di- verse Anwendungen.

1. Wenn die Datens¨atze sortiert sind, so kann man unter den Datens¨atzen viel schneller suchen. Die Suche nach einem Element mit einem gegebenen Wert x kann in einem Sortierten Array in der Worst-Case-LaufzeitΘ(logn) durch- gef¨uhrt werden. Im Gegenteil w¨urde eine solche Suche in einem unsortierten Array die Worst-Case-Laufzeit Θ(n) haben.

2. Verschiedene Algorithmen benutzen Sortierverfahren als Hilfsmittel (etwa ei- nige Algorithmen zur Berechnung der konvexen H¨ulle in der Dimension zwei).

3. Durch das Sortieren kann man die Vielfachheiten der Elemente der Arrays z¨ahlen. Die Vielfachheit ist die Angabe, wie oft das Element im Array vor- kommt.

4. Durch das Sortieren kann man ein Array mit potenziellen Wiederholungen von Werten zu einem Array ohne Wiederholungen der Werte konvertieren.

3.3 Die Datenstruktur Heap und der Heap-Sort

Man spricht von einer Datenstruktur, wenn man die vorhandenen Daten auf eine besondere Weise organisiert, und eine Reihe aus aus Prozeduren f¨ur diese Daten zur Verf¨ugung stellt, die ein ‘Team’ bilden, d.h., die Prozeduren ‘helfen sich’ gegenseitig bzw. erg¨anzen sich auf eine nat¨urliche Weise. Das Team aus Prozeduren, die zur Verf¨ugung stehen, nennt man ein Interface. In der Regel hat man Prozeduren zum Ablesen, zur ¨Anderung und zum L¨oschen verschiedener Datens¨atze.

In diesem Abschnitt wird die Datenstruktur Heap eingef¨uhrt. Diese Datenstruktur ist recht n¨utzlich, und wir werde sie im Teil 2 dieses Kurs als Hilfsmittel bei einem Algorithmus zur Berechnung der k¨urzesten Wege in einem Graphen verwenden.

Der Heap-Sort ist im Wesentlichen ein Nebenprodukt bei der Umsetzung der Da- tenstruktur Heap. Der Heap-Sort besteht aus zwei Phasen:

Array Phase 1 (Erzeugung des Heaps)

−−−−−−−−−−−−−−−−−−−→Heap Phase 2 (Aufl¨osung des Heaps)

−−−−−−−−−−−−−−−−−−−→sortiertes Array.

Der Heap-Sort hat asymptotisch die gleiche Worst-Case-Laufzeit wie das Sortieren durch Einf¨ugen und einen besseren Speicheraufwand.

In diesem Abschnitt betrachten wir ArraysA, welche neben dem AttributL¨ange[A]

noch ein weiteres zus¨atzliches AttributGr¨oße[A] mit 0≤Gr¨oße[A]≤L¨ange[A]

haben. Somit wird das Array in zwei Teile aufgeteilt: A[1. .Gr¨oße[A]] und der PufferA[Gr¨oße[A] + 1. .L¨ange[A]].

Die KomponentenA[i] mit 1≤i≤Gr¨oße[A] werden als Knoten eines sogenannten geordneten Baums interpretiert, indem man f¨ur die Komponenten A[i] die Vater- Kind Relation einf¨uhrt. BeiGr¨oße[A] = 0 ist der Baum leer. Ansonsten heißtA[1]

dieWurzel des Baums. F¨ur 2≤i≤Gr¨oße[A] heißtA[bi/2c] der Vater des Konten A[i].

Im folgenden Bild sind die Vater-Kind-Relationen im Fall vonGr¨oße[A] = 8 dar- gestellt:

(25)

A[1]

A[2]

A[4]

A[8]

A[5]

A[3]

A[6] A[7]

Wenn also 1≤i≤Gr¨oße[A] gilt, so ist die Komponente A[2i] das linke Kind von A[i], falls 2i≤Gr¨oße[A] gilt, und die KomponenteA[2i+ 1] dasrechte Kind von A[i], falls 2i+ 1≤Gr¨oße[A]. Man kann also Knoten ohne Kinder, Knoten nur mit dem linken Kind und Knoten mit zwei Kindern haben.

Wir werden die folgenden einfachen Prozeduren benutzen:

v=Vater(i)

1: v:=bi/2c

k=Linkes-Kind(i)

1: k:= 2i

k=Rechtes-Kind(i)

1: k:= 2i+ 1

DerNachfahreeines Knotens ist rekursiv als ein Kind oder ein Nachfahre eines Kinds definiert. Der Vorfahre eines Knotens ist rekursiv als der Vater oder ein Vorfahre des Vaters definiert.

Gilt f¨ur einen Konten A[i], 2t ≤ i <2t+1 und i≤Gr¨oße[A], so heißt t die Tiefe von A[i]. Die Wurzel befindet sich somit in der Tiefe 0, die Knoten A[2] und A[3]

(falls vorhanden) in der Tiefe 1 usw. EinPfad der L¨anget ist eine Folge v0, . . . , vt, bei der vi Vater von vi+1 f¨ur alle 0 ≤i < t gilt. Hier ein Beispiel, in dem man ein Pfad und die Angabe der Tiefen sieht:

Tiefe 0 Tiefe 1 Tiefe 2 Tiefe 3

A[1]

A[2]

A[4]

A[8]

A[5]

A[3]

A[6] A[7]

EinMax-Heap, ist ein Array AmitGr¨oße[A]≤L¨ange[A], f¨ur das die sogenannte Max-Heap-Eigenschaft

A[Vater(i)]≥A[i]

f¨ur alle i = 2, . . . ,Gr¨oße[A] erf¨ullt ist. Analog werden auch Min-Heap durch die Min-Heap-Eigenschaft eingef¨uhrt. Wenn man von einem Heap redet, so meint man einen Max- oder einen Min-Heap. Ob man Max- oder Min-Heaps diskutiert ist ziem- lich egal. In diesem Abschnitt werden wir die Max-Heaps diskutieren. Hier ein Bei- spiel eines Max-Heaps der Gr¨oße 8:

(26)

A[1] = 21 A[2] = 14

A[4] = 8 A[8] = 2

A[5] = 7

A[3] = 10 A[6] = 9 A[7] = 3

Heaps kann man als rekursive Datenstrukturen auffassen, weil jeder Knoten des Heaps als Wurzel einesTeilheaps interpretiert werden kann. In diesem Beilspiel ist der Teilheap mit der WurzelA[2] farblich hinterlegt:

A[1] = 21 A[2] = 14

A[4] = 8 A[8] = 2

A[5] = 7

A[3] = 10 A[6] = 9 A[7] = 3

Die H¨ohe eines Heaps ist die maximale Tiefe der Knoten des Heaps plus eins. Der Heap aus dem vorigen Bild hat zum Beispiel die H¨ohe 3. Des weiteren definieren wir dieH¨ohe eines Heapknotens als die H¨ohe seines Teilheaps. Der KnotenA[2] aus dem vorigen Bild hat die H¨ohe 2.

In der folgenden Prozedur wird f¨ur einen gegebenen Index i∈ {1, . . . ,Gr¨oße[A]}

unter den Indizes aus

{i,Linkes-Kind(i),Rechtes-Kind(i)} ∩ {1, . . . ,Gr¨oße[A]}

ein Indexg mit dem Gr¨oßten Wert vonA[g] gew¨ahlt.

g:=Gr¨oßter-von-drei-Knoten(A, i)

1: l:=Linkes-Kind(i)

2: r :=Rechtes-Kind(i)

3: g:=i

4: if l≤Gr¨oße[A] undA[l]> A[i]:

5: g:=l

6: end

7: if r≤Gr¨oße[A] und A[r]> A[g]:

8: g:=r

9: end

Wir setzen nun die wichtigste Prozedur der Datenstruktur Heap um, die man in der Regel Heapify nennt. Als Eingabe erh¨alt man einen Index i∈ {1, . . . ,Gr¨oße[A]}.

Unter der Voraussetzung, dass f¨ur der linke und rechte Teilb¨aume von A[i] bereits

(27)

Heaps sind, wird der Teilbaum mit der WurzelA[i] durch das Vertauschen der Werte seiner Knoten zu einem Heap konvertiert.

Max-Heapify(A, i)

1: g:=Gr¨oßter-von-drei-Knoten(A, i)

2: while g6=i :

3: VertauscheA[i] und A[g]

4: i:=g

5: g:=Gr¨oßter-von-drei-Knoten(A, i)

6: end

Beispiel 3.11. Wir betrachten das folgende A mit Gr¨oße[A] = 10:

A[1] = 16

A[2] = 4 A[3] = 10

A[4] = 14 A[5] = 7 A[6] = 9 A[7] = 3

A[8] = 2 A[9] = 8 A[10] = 1

Bei der Ausf¨uhrung von Max-Heapify(A,2) werden in der ersten Iteration A[2]

und A[4] vertauscht:

A[1] = 16

A[2] = 14 A[3] = 10

A[4] = 4 A[5] = 7 A[6] = 9 A[7] = 3A[8] = 2 A[9] = 8 A[10] = 1

und in der zweiten IterationA[4] und A[9]:

A[1] = 16

A[2] = 14 A[3] = 10

A[4] = 8 A[5] = 7A[6] = 9 A[7] = 3 A[8] = 2 A[9] = 4 A[10] = 1

Somit wird die Heapeigenschaft f¨ur den Teilbaum mit der WurzelA[2] erstellt.

Theorem 3.12 (Korrektheit von Heapify). Max-Heapify arbeitet korrekt (d.h., laut der Beschreibung oben).

Beweis. Die Korrektheit von Max-Heapify kann per Induktion nach der Anzahl N der Elemente in dem Baum mit Wurzelibewiesen werden. IstN = 1, so arbeteit

(28)

Max-Heapifyoffensichtlich korrekt. Agenommen,Max-Heapifyarbeitet korrekt f¨ur Teilb¨aume der Gr¨oße h¨ochstensN mitN ∈N. SeiAundiso, dass der Teilbaum mit WurzeliGr¨oßeN+1 hat. Die FunktionGr¨oßter-von-drei-Knotenarbeitet offensichtlich Korrekt, d.h. A[g] das Maximum von A[i], A[Linkes-Kind(i)] und A[Rechtes-Kind(i)]. Der Umtausch in Zeile 3 vonMax-Heapifyerstellt die Heap- Eigenschaft bez¨uglich i und den Kindern von i. Die Heap-Eigenschaft f¨ur B¨aume, deren Wurzel die Kinder voni sind, folgt aus der Induktionsvoraussetzung.

Theorem 3.13(Laufzeit von Heapify). Sei1≤i≤Gr¨oße[A]. Seien der linke und rechte Teilb¨aume des KnotensA[i]. Die Worst-Case-Laufzeit vonMax-Heapify(A, i) f¨ur KnotenA[i]der H¨ohe h ist Θ(h).

Beweis. Die Laufzeiten vom Vertauschen von zwei Knoten und dem Aufruf von Gr¨oßter-von-drei-Knoten(A, i) sind Θ(1). Die Anzahl der Iterationen ist of- fensichtlich nicht H¨oher als h, weil zu jedem Zeitpunkt der Ausf¨uhrung A[i] ein Nachfahre des Knotens, f¨ur welchen der Max-Heapify aufgerufen wurde. Ist der Wert von A[i] echt kleiner als der Wert aller Nachfahren von A[i], so macht das Verfahren genauh Iterationen.

Nun k¨onnen wir mit Hilfe vonMax-Heapifydie erste Phase vom Heapsort umset- zen. In der folgenden Prozedur Max-Heap-erzeugen wird aus einem Array mit n Komponenten durch das Vertauschen der Elemente ein Max-Heap mit n Kno- ten erstellt. Zur Umsetzung: Es wird ¨uber alle Knoten, die Kinder haben, in der R¨uckw¨artsrichtung iteriert und f¨ur jeden Knoten solchen Knoten Max-Heapify aufgerufen.

Max-Heap-erzeugen(A)

1: Gr¨oße[A] :=L¨ange[A]

2: for i:=Vater(Gr¨oße[A]), . . . ,1 mit Schritt −1 :

3: BHier: Teilb¨aume aller Knoten mit Indizes gr¨oßer als ibereits Max-Heaps

4: Max-Heapify(A, i) BNun ist Teilbaum mit Wurzel A[i]dran!

5: end

Beispiel 3.14. Wir verfolgen die Ausf¨uhrung von Max-Heapify am folgenden Beispiel. SeiA:= [4,1,3,2,16,9,10,14,8,7] und Gr¨oße[A] := 10. Wir illustrieren die Arbeit vonMax-Heap-erzeugen(A) an dem gegebenenA.

• A[5] und A[10] werden nicht vertauscht.

• A[4] und A[8] werden vertauscht.

• A[3] und A[7] werden vertauscht.

• A[2] und A[5],A[5] und A[10] werden vertauscht.

• A[1] und A[2],A[2] und A[4],A[4] und A[9] werden vertauscht.

Theorem 3.15. Max-Heap-erzeugen arbeitet korrekt.

(29)

Beweis. Wir setzen n := Gr¨oße[A] = L¨ange[A]. Ein Knoten mit Index i ∈ [n]

hat keine Kinder wenn 2i und 2i+ 1 echt gr¨oßer als n ist. Die Bedingung 2i ≤ n charakterisiert somit die Indizes der Knoten mit Kindern. Somit ist bn/2c = Vater(n) der gr¨oßte Index eines Knotens mit Kindern.

Wir sehen also, dass in der ersten Iteration Teilb¨aume aller Knoten mit Indizes gr¨oßer alsiHeaps sind, weil diese Teilb¨aume nur aus ihrem Wurzelknoten bestehen.

Da wir in jeder Iteration Max-Heapify f¨ur den aktuellen Knoten A[i] ausf¨uhren, bleibt diese Bedingung in jeder Iteration erhalten, sodass man in letzter Iteration nach dem Aufruf von Max-Heapify f¨ur den KnotenA[1] die Heap-Eigenschaft f¨ur alle Knoten erstellen. Es sei noch bemerkt, dass die Rahmenbedingungen f¨ur den Aufruf von Max-Heapify f¨ur den KnotenA[i] erf¨ullt sind, da zu jeder Zeit der Ausf¨uhrung der linke und der rechte Teilbaum des KnotensA[i] bereits Heaps sind.

Die H¨ohe eines geordneten Baums mitn Knoten istO(logn). Somit ergibt sich die obere Schranke O(nlogn) an die Laufzeit von Max-Heap-erzeugen auf Arrays der Langen. Es stellt sich heraus, dass diese obere Schrankenicht optimal ist. Eine exakte Asymptotik wird in Theorem 3.17 angegeben.

Die Laufzeit vonMax-Heap-erzeugenist O(nlogn) mitn:=L¨ange[A].

Lemma 3.16. SeiA Heap der Gr¨oße n∈N. Dann ist blognc+ 1die H¨ohe von A und, f¨ur1≤h≤ blognc+ 1, besitzt A h¨ochstens n

2h−1

der H¨ohe h.

Beweis. Die H¨ohe ist als die maximale Tiefe von Heapknoten plus 1 definiert. Sei t∈ N0. Ein Knoten i mit 2t ≤ i < 2t+1 befindet sich in der Tiefet. Wir sind also auf der Suche nacht mit 2t ≤n <2t+1. Dieses t ist genaublognc. Wir haben also gezeigt, dassblognc+ 1 die H¨ohe von Aist.

Wenn wir einen Knoteni∈[n] fixieren, so befindet sichA[i] in der Tiefe 0 des Teil- baums mit WurzelA[i]. Der KnotenA[2i] befindet sich in der Tiefe 1 des Teilbaums mit der WurzelA[i] usw. Wir sehen also, dass die maximale Tiefe der Knoten des Teilbaums mit Wurzel A[i] als nichtnegativer ganzzahliger Wert t mit der Eigen- schaft 2ti≤n <2t+1ibeschrieben werden kann. Somit ist die H¨ohe des Teilbaums mit Wurzel A[i] als h ∈ N mit 2h−1i ≤ n < i2h definiert. Die Knoten A[i] der Teilb¨aume der H¨oheh sind also durch die Bedingung 2nh < i≤ 2h−1n charakterisiert.

Wir sehen also, dass man h¨ochstens n

2h−1

postive ganzzahlige Werteihat, welche die beiden Schranken erf¨ullen.

Anhand des vorigen Lemmas bestimmen wir die exakte Asymptotik vonMax-Heap-erzeugen: Theorem 3.17.Die Laufzeit vonMax-Heap-erzeugenauf allen Arrays der L¨ange

nist Θ(n).

Beweis. Die Laufzeit betr¨agt Ω(n), weil Max-Heap-erzeugen ¨uber alle Knoten, die Kinder besitzen, iteriert, und die Anzahl dieser Knotenbn/2c= Ω(n) ist.

Wir zeigen die obere Schranke O(n) an die Laufzeit von Max-Heap-erzeugen. Wir fixieren eine Konstante c >0 derart, dass die Laufzeit von Max-heapify auf einem Knoten der H¨ohehh¨ochstenschist. Nun l¨asst sich mit Hilfe von Lemma 3.16 eine obere Schranke an die Gesamtlaufzeit aller Ausf¨uhrungen von Max-Heapify

(30)

innerhalb von Max-Heap-erzeugen berechnen. In Abh¨angigkeit von der H¨ohen der Knoten setzt sich die obere Schranke folgedermaßen zusammen:

blognc+1

X

h=1

j n 2h−1

k

| {z }

obere Schranke Anz. Knoten

oheh

(ch)

|{z}

Laufzeit pro Knoten oheh

≤cn

blognc+1

X

h=1

h

2h−1 ≤cn

X

h=1

h2−h+1

| {z }

<∞

.

Heapsort(A)

1: Max-Heap-erzeugen(A)

2: for i=L¨ange[A], . . . ,2 mit Schritt −1 :

3: B Hier: Elemente des Puffers sind Aufsteigend sortiert und nicht kleiner als die Elemente des aktiven Bereichs.

4: vertauschen(A[1], A[i])

5: Gr¨oße[A] :=Gr¨oße[A]−1

6: Max-Heapify(A,1)

7: end

Theorem 3.18. Der Heapsort arbeitet korrekt. Seine Laufzeit auf Arrays der L¨angen ist O(nlogn).

Beweis. Die Korrektheit folgt direkt. Die Laufzeit ergibt sich aus den Absch¨atzungen der Laufzeit vonMax-Heap-erzeugen undMax-Heapify.

Beispiel 3.19. Arbeitsweise von Heapsort auf dem HeapA:= [16,14,10,8,7,9,3,2,4,1]

mit der Gr¨oße 10 illustrieren. . .

3.4 Priorit¨atsschlangen auf der Basis von Heaps

Die Priortit¨atsschlange ist ein Beh¨alter f¨ur endlich viele Werte, f¨ur welche die fol- genden Operationen zu Verf¨ugung stehen.

• einf¨ugen(S, x) - ein neues Elementx einf¨ugen.

• Maximum(S) - R¨uckgabe von Maximum.

• Maximum-entfernen(S) - entfernt ein maximales Element von S und gibt dieses zur¨uck.

Anwendungen von Priorit¨atsschlangen:

• Zeitplanung (engl.: scheduling) von Jobs auf einem Rechner.

• Ereignisgetriebene Simulatoren.

(31)

Priorit¨atsschlangen auf der Basis von Heaps:

m=Heap-Maximum(A)

1: m:=A[1]

m=Heap-Maximum-entfernen(A)

1: m:=A[1]

2: A[1] :=A[Gr¨oße[A]]

3: Gr¨oße[A] :=Gr¨oße[A]−1

4: Max-Heapify(A)

Heap-Schl¨ußel-Vergr¨oßern(A, i, x)

Assumption: A ist Heap; 1≤i≤Gr¨oße[A];x≥A[i].

Result: Der Wert vonA[i] wird gleichxgesetzt; Heap-Eigeschaft wird neu erstellt.

1: A[i] :=x

2: while i >1 und A[Vater(i)]< A[i]:

3: vertauschen(A[i], A[Vater(i)])

4: i:=Vater(i)

5: end

Neues Element einf¨ugen:

Max-Heap-einf¨ugen(A, x) Assumption: A ist Heap.

Result: das Element xwird in A eingef¨ugt, sodassA ein Heap bleibt.

1: Gr¨oße[A] :=Gr¨oße[A] + 1

2: A[Gr¨oße[A]] :=−∞

3: Heap-Schl¨ußel-Vergr¨oßern(A,Gr¨oße[A], x) Element entfernen:

Heap-entfernen(A, i)

Assumption: A ist Heap, der das ElementA[i] besitzt.

Result: Das Element A[i] wird aus A enfernet, sodassA ein Heap bleibt.

1: x:=A[Gr¨oße[A]]

2: Gr¨oße[A] :=Gr¨oße[A]−1

3: if x > A[i]:

4: Heap-Schl¨ussel-vergr¨ossern(A, x, i)

5: else:

6: A[i] :=x

7: Heapify(A, i)

8: end

Direkte Implementierung von Priorit¨atsschlangen auf der Basis von Arrays mit se- quentieller Suche ist viel inneffizienter:

(32)

Worst-Case-Laufzeiten von Priorit¨atschlangen:

Arrays mit sequentieller Suche vs. Heap-basierte Umsetzung Sequentielle Suche Heap-basiert

Maximum Θ(n) Θ(1)

Maximum entfernen Θ(n) Θ(logn)

einf¨ugen Θ(1) Θ(logn)

entfernen Θ(n) Θ(logn)

Schl¨ussel ver¨andern Θ(1) Θ(logn)

Worst-Case-Operation Θ(n) Θ(logn)

3.5 Untere Schranken an die Laufzeit von Sortieralgorithmen Theorem 3.20. Man betrachte einen Algorithmus zur Bestimmung der aufstei- genden Reihenfolge der Elemente eines gegebenen Arrays, welcher die aufsteigende Reihenfolge ausschließlich durch das Vergleichen der Komponenten mit Hilfe von≤ bestimmt. Das heißt, der Algorithmus:

• greift auf das gegebene Array A der L¨angen∈N ausschließlich mit Hilfe der Vergleichsoperationen A[i]≤A[j]f¨uri, j ∈ {1, . . . , n} zu.

• berechnet eine Permutation i1, . . . , in der Indizes 1, . . . , n mit der Eigenschaft A[i1]≤ · · · ≤A[in].

Dann macht ein solcher Algorithmus Ω(nlogn) Vergleiche A[i] ≤ A[j] mit i, j ∈ {1, . . . , n} der Komponenten des Eingabearrays A.

Beweis. Bei der Ausf¨uhrung des Algorithmus erstellen wir eine ‘Log-Datei’ auf die folgende Weise. Bei jedem VergleichA[i]≤A[j] wird das Resultat des Vergleichs zur LOG-Datei hinzugef¨ugt (etwa: 1, wenn das Resultat des Vergleichs wahr war und 0 sonst). Sei nun k die maximale Anzahl der Vergleiche vom Typ A[i] ≤ A[j] bei der Ausf¨uhrung des Algorithmus auf Arrays der L¨ange n. Somit ist der Inhalt der Log-Datei am Ende der Ausf¨uhrung ein {0,1}-String der L¨ange h¨ochstens k. Auf Arrays der L¨ange ngib es also h¨ochstens

20+· · ·+ 2k

m¨ogliche Inhalte der Log-Datei. Diese Zahl ist h¨ochstens 2k+1. Die Berechnete Per- mutation ist durch den Inhalt der Log-Datei eindeutig bestimmt, d.h., die R¨uckgabe ist eindeutig durch die Resultate der Vergleiche bestimmt, weil der Algorithmus zum EingabearrayAkeine anderen Informationen hat außer den Informationen, die durch VergleicheA[i]≤A[j] gewonnen wurden.

F¨urnpaarweise verschiedene ganzzahlige Wertex1< . . . < xnhat mann! M¨oglichkeiten diese Werte in einem Array der L¨ange naufzuheben. Die R¨uckgaben (Permutatio- nen) f¨ur all diesen! Arrays sind paarweise verschieden. Es folgt also, dass die Anzahl der Log-Dateien bei der Ausf¨uhrung auf Arrays der L¨angen n mindestens n! sein muss. Es ergibt sich die Ungleichung

2k+1 ≥n!

welche man alsk≥log(n!)−1 umformulieren kann. Das ergibt die Behauptung.

(33)

Aufgabe 3.21. Zeigen Sie log(n!)−1 = Ω(nlogn).

Beweis.

log 1 + log 2 +· · ·+ logn−1 =

n

X

i=3

logi

F¨ur diedn/2eElemente mitn− dn/2e+ 1≤i≤ngilt log(i)≥log(n− dn/2e+ 1)≥ log(n−(n+ 1)/2 + 1)≥log(n/2). Somit ist die Summe mindestensdn/2elog(n/2)≥

1

2nlog(n/2). Man hat log(n/2) = log(n)−1≥ 12log(n) bein≥4. Somit ergibt sich die Absch¨atzung 14nlogn.

3.6 Countingsort

Wenn man ¨uber Zusatzinformation ¨uber die Natur der Komponenten eines Arrays verf¨ugt, so kann man unter Umst¨anden das Array schneller als in der ZeitO(nlogn) sortieren.

Die Eingabe der Prozeduren dieses Abschnitts besteht aus einer nat¨urlichen Zahl k und einem ArrayA, dessen alle Komponenten zu{1, . . . , k}geh¨oren. Istknicht allzu groß im Vergleich zur L¨ange vonA, so kann man den sogenanntenCountingSort verwenden.

Wir beginnen mit der folgenden Prozedur, die f¨ur jedes Elementx∈ {1, . . . , k}z¨ahlt, wie oftxim Array A vorkommt.

Z=Werte-z¨ahlen(A, k)

1: Ein neues, mit Nullen gef¨ulltes Array Z der L¨ange keinf¨uhren

2: for j= 1, . . . ,L¨ange[A]:

3: Z[A[j]] :=Z[A[j]] + 1

4: end

Die folgende Prozedur ist analog zur Verteilungsfunktion aus der Wahrscheinlich- keitstheorie (Elemente der Wahrscheinlichkeitstheorie hatten Sie zum Teil in der Schule, noch viel mehr lernen Sie im dritten Semester). F¨ur jedes i ∈ {1, . . . , k}

z¨ahlt die Prozedur die Anzahl der Elemente in A im Bereich {1, . . . , i} (d.h., die Elemente die nicht gr¨oßer alsisind).

Z=Verteilungsfunktion(A, k)

1: Z :=Werte-z¨ahlen(A, k)

2: for i:= 2, . . . , k :

3: BHier: Z[i−1] ist die Anzahl der Elemente in{1, . . . , i−1}

4: BZ[i]die Anzahl der Elemente mit dem Wert i

5: Z[i] :=Z[i] +Z[i−1]

6: end

Beim Countingsort bleibt der Eingabearray A unver¨andert. Man generiert statt- dessen ein Ausgabearray B, in welchem die Elemente von A in einer aufsteigend sortierten Reihenfolge enthalten sind.

Referenzen

ÄHNLICHE DOKUMENTE

KARLSRUHER INSTITUT F ¨ UR TECHNOLOGIE (KIT) Institut f¨ ur

¨ Uberpr¨ ufen Sie, ob sich die L¨ osungsmenge ¨ andert, wenn Sie zur Stammfunktion F (x) eine beliebige Konstante addieren.. Zeigen Sie, dass die L¨ osungsmenge durch eine

Arrays (oder auch dt. Feld) sind eine Datenstruktur, mit der man viele gleichartige Daten verwalten und verarbeiten kann.. • Wir stellen uns Arrays im Grunde wie Vektoren vor,

Eine Linearkombination l¨ aßt sich gegebenenfalls durch ein lineares Gleichungssy- stem ermitteln. Die Unm¨ oglichkeit der linearen Kombinierbarkeit eines der Vek- toren aus den

Der herunterh¨angende Massenpunkt m 2 kann sich unter dem Einfluss der Schwerkraft nur in vertikaler Richtung be- wegen, der andere Massenpunkt kann sich auf der

[r]

• Pumping Lemma für reguläre Sprachen: Sei L eine

Martin Fuchssteiner Katrin Krohne. TECHNISCHE UNIVERSIT¨

• Die Idee des Sortierens durch Mischen könnte auch mithilfe von Feldern realisiert werden (wie ?-). • Sowohl das Mischen wie das Sortieren könnte man statt rekursiv auch

Hinweis: Entwickeln Sie f um 1/2 und untersuchen Sie den Konvergenzradius dieser Reihe unter der Annahme, dass 1 ein regul¨ arer Punkt von f sei.. Die L¨ osungen sind bis Dienstag,

[r]

Beachte, daß die Operationen, von denen wir teilweise gleich mehrere in einem Schritt ausf¨ uhren, den L¨ osungsraum nicht ¨ andern.. Ziehe das 3-fache (bzw. 2-ten Zeile) ab

Falko Lorenz, Karin Halupczok SoSe 2013. Abgabetermin:

Abgabe bis Do, 06.11., 12 Uhr Aufgabe 1 zur Bearbeitung in der ¨ Ubung Aufgaben 2-4 zur selbst¨ andigen Bearbeitung.

Beispiel 10.36: Die n freien Konstanten in der allgemeinen L¨ osung einer DGL n-ter Ordnung lassen sich z.B.. LINEARE DGLEN MIT KONSTANTEN KOEFFIZIENTEN 189 gemacht.. In der Tat

Alles ¨ uber Lineare Algebra und Matrizen, was man als Physiker wissen sollte. J¨ anich : Analysis f¨ ur Physiker und Ingenieure, Springer, 2001. J¨ anich : Funktionentheorie –

Welches Randwertproblem w¨ urde u dann

(Ω, A, µ) sei ein beliebiger Maßraum und ν ein beliebiges

0 gebe Gott dass immerdar Ihm auf dem Thron der Macht. Des Sieges lichte Ruhmeskron verleihe Glanz

waagerechte Pfeile auftreten. Wie verhalten sich die L¨ osungen, die diese Bereiche schneiden?.. c) Wie lauten die Gleichgewichtspunkte

(Es darf mehrere, oder gar keine passenden Antworten geben. Die Antworten m¨ ussen nicht begr¨ undet werden.).. Welche der untenstehenden Algorithmen haben polynomielle