Algorithmen und Datenstrukturen SS09 Foliensatz 6 Michael Brinkmeier Technische Universität Ilmenau Institut für Theoretische Informatik Sommersemester 2009 Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 1 / 35 Datenstrukturen für dynamische Mengen Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 2 / 35 Datenstrukturen für dynamische endliche Mengen Ziel Entwurf von Datenstrukturen für die Repräsentation und Verwaltung von dynamischen endlichen Teilmengen einer gegebenen Grundmenge D. Speichere eine veränderliche endliche Menge S ⊆ D mit den folgenden Operationen: empty: Setze S = ∅. member: Teste, ob x ∈ S für ein gegebenes x ∈ D. insert: Füge ein gegebenes x ∈ D zu S hinzu. delete: Entferne ein gegebenes x ∈ D aus S. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 3 / 35 Die Signatur für Sets Die Signatur für Sets Sorten: Elements Sets Boolean Operationen: empty: () → Sets insert: Sets × Elements → Sets delete: Sets × Elements → Sets member: Sets × Elements → Boolean Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 4 / 35 Die Modellalgebra für Sets Die Modellalgebra drängt sich förmlich auf. Die Modellalgebra für Sets Elements = ˆ Sets = ˆ Boolean = ˆ einer Menge D P <∞ (D) := {S ⊆ D | S ist endlich} {true, false} empty() := ∅ insert(S, x) := S ∪ {x} delete(S, x) := S \ {x} ( true member(S, x) := false falls x ∈ S falls x ∈ 6 S Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 5 / 35 Implementierung mit Wiederholung Die wohl einfachste Implementierung von Sets verwendet eine einfach verkettete Liste oder ein Array und erlaubt Wiederholungen, d.h. ein Element kann mehrere Male in der Liste bzw. dem Array auftreten. 2 5 2 1 h 2 5 2 1 7 11 7 ∗ 1 7 ··· Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 6 / 35 ∗ 11 pegel = h 7 Implementierung mit Wiederholung Operation Laufzeit empty Erzeuge leere Liste/Array O(1) member(S, x) Suche das erste Auftreten von x O(h) (h = Länge der Liste, bzw. pegel = h) insert(S, x) Füge x vorne in die Liste ein Füge x in A[pegel + 1] in das Array ein O(1) delete(S, x) Suche und entferne alle Vorkommen von x in der Liste/dem Array O(h) Wie vermeidet man bei delete Lücken im Array? Wenn A[j] gelöscht werden soll, dann überschreibe es mit A[pegel] und senke pegel um 1. Achtung: Nach dem Kopieren, muss der neue Wert überprüft werden! Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 7 / 35 Löschen im Array Löschen aller Vorkommen von x im Array i = 1; while i ≤ pegel do if A[i] == x then // Lösche durch Überschreiben A[i] = A[pegel]; pegel = pegel − 1; end else // Gehe zur nächsten Position i = i + 1; end end Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 8 / 35 Implementierung mit Wiederholung Die Implementierung mit Wiederholung hat einen gravierenden Nachteil: Wenn oft schon vorhandene Elemente eingefügt werden, dann ist die Liste deutlich größer als |S|. Dadurch ist der Zeit- und Platzaufwand unnötig hoch. Konsequenz Wir sollten versuchen die Wiederholungen zu vermeiden. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 9 / 35 Einfache Implementierung ohne Wiederholung Wir können die Wiederholungen vermeiden, indem wir insert abändern: insert(S, x): if member(S, x) == false then füge x ein delete muß nicht mehr bis zum Ende der Liste/des Arrays laufen, sondern nur bis zum ersten Vorkommen. Nachteil: Der Zeitaufwand von member, insert und delete ist jeweils O(|S|), denn in allen drei Fällen ist eine lineare Suche notwendig. Vorteil: Der Platz zur Speicherung von S ist Θ(|S|). Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 10 / 35 Verbesserte Implementierung ohne Wiederholung Idee Besitzt D eine totale Ordnung ist eine effizientere Speicherung möglich, indem man die Elemente in der Liste/dem Array aufsteigend sortiert. Die Menge {1, 2, 3, 5, 7, 8, 11} kann dan folgendermaßen repräsentiert werden: 1 2 3 5 1 h 1 2 3 5 7 8 11 ∗ 7 ··· ∗ 8 11 pegel = h Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 11 / 35 Aufsteigend sortierte Listen Vorteil Die lineare Suche kann beim ersten Eintrag y ≥ x abgebrochen werden. Seien a1 < a2 < · · · < an die Elemente von S und x ∈ S das zu suchende Element in S. Falls P(x = ai ) = n1 , ergibt sich die erwartete Anzahl der Vergleiche als: (1 + 2 + · · · + n) · 1 n+1 . = n 2 Ist x nicht in S, aber ist x mit gleicher Wahrscheinlichkeit in jeder Lücke, d.h. 1 P(x < a1 ) = P(x > an ) = P(ai −1 < x < ai ) = n+1 für jedes 2 ≤ i ≤ n, dann ist die erwartete Anzahl der Vergleiche (1 + 2 + · · · + n + n) · 1 n(n + 3) n = < + 1. n+1 2(n + 1) 2 Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 12 / 35 Aufsteigend sortierte Listen Nachteil Bei Einfügen muss das neue Element an der richtigen Stelle eingefügt werden. Das erfordert wieder eine lineare Suche. Konsequenz Die Worst-Case Laufzeiten ändern sich nicht, aber man erhält eine geringere erwartete Laufzeit. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 13 / 35 Aufsteigend sortierte Arrays Nachteil insert und delete benötigen das Verschieben vieler Elemente, um die Ordnung zu erhalten. Damit ist der Zeitaufwand Θ(|S|), denn die Elemente, mit denen nicht verglichen wird, müssen verschoben werden. Vorteil member kann binäre Suche verwenden (AuP). Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 14 / 35 Binäre Suche Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 15 / 35 Die Aufgabe Problem: Lookup Gegeben: Eine total geordnete Menge (D, ≤) Ein schwach geordnetes Array A[0 . . . n] über D, d.h. A[0] ≤ A[1] ≤ · · · ≤ A[n] zwei Indices 0 ≤ a, b ≤ n ein x ∈ D Gesucht: ( true falls x ∈ A[a . . . b] false falls x 6∈ A[a . . . b] und ia,b (x) = min (b + 1, {j | a ≤ j ≤ b und A[j] ≥ x}). ιa,b (x) = Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 16 / 35 Die Aufgabe ia,b (x) = min (b + 1, {j | a ≤ j ≤ b und A[j] ≥ x}) ist der kleinste Index eines Elementes y ≥ x in A[a . . . b], oder b + 1, falls alle Einträge von A[a . . . b] echt kleiner als x sind. 1 2 3 4 5 6 7 8 9 10 11 A: 4 4 7 7 7 9 9 10 12 15 15 i1,11 (7) = 3 erstes Vorkommen i1,11 (8) = 6 erstes Vorkommen eines größeren Elementes i1,11 (20) = 12 Argument größer als alle Einträge Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 17 / 35 Die Idee Falls b < a, ist A[a . . . b] leer. Damit ist x nicht in dem Teilarray und (ιa,b (x), ia,b (x)) = (false, b + 1). Falls a = b, besteht A[a . . . b] aus genau einem Element, d.h. x ist genau dann in A[a . . . b], falls A[a] = x. Somit gilt falls A[a] = x (true, a) (ιa,b (x), ia,b (x)) = (false, a + 1) falls A[a] < x (false, a) falls A[a] > x Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 18 / 35 Die Idee Falls a < b ist, sei m = ⌊(a + b)/2⌋ (die Mitte des Intervalls). Falls x ≤ A[m], liegt i(x) zwischen a und m, sofern x in A enthalten ist. a A: ··· ≤x m z b ≥x ··· Ferner ist x genau dann nicht in A[a . . . b], wenn es nicht in A[a . . . m] ist. Damit gilt ia,b (x) = b + 1 ⇔ ia,m (x) = m + 1 Zusammengefasst ergibt sich ( (true, ia,m (x)) (ιa,b (x), ia,b (x)) = (false, b + 1) falls ia,m (x) ≤ m falls ia,m (x) = m + 1 Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 19 / 35 Die Idee Falls a < b ist, sei m = ⌊(a + b)/2⌋ (die Mitte des Intervalls). Falls x > A[m] liegt i(x) zwischen m + 1 und b + 1. a m b z ··· ··· <x A: <x Ist x in A[a . . . b], so auch in A[m + 1 . . . b] und somit ia,b (m) = im+1,b (x). Außerdem ist x genau dann nicht in A[a . . . b], wenn es nicht in A[m + 1 . . . b] ist, d.h. auch in diesem Fall - und somit immer - gilt (ιa,b (m), ia,b (m)) = (ιm+1,b (x), im+1,b (x)) Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 20 / 35 Die rekursive binäre Suche RecBinSearch(A, a, b, x) – Rekursive Binäre Suche Eingabe: A[1 . . . n], a, b und x Ausgabe: (ιa,b (x), ia,b (x)) if b < a then return (false, b + 1); if b == a then if A[a] == x then return (true, a); if A[a] < x then return (false, a + 1); if A[a] > x then return (false, a); end m = ⌊ a+b 2 ⌋; if x ≤ A[m] then (ι, i ) = RecBinSearch(A, a, m, x); if i == m + 1 then return (false, b + 1); else return (true, i ); else return RecBinSearch(A, m + 1, b, x); end Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 21 / 35 Korrektheit von RecBinSearch Satz Das Teilarray A[a . . . b] von A[1 . . . n] sei aufsteigend sortiert und es gelte 1 ≤ a ≤ n + 1 und 0 ≤ b ≤ n. Dann gilt: Der Aufruf RecBinSearch(A, a, b, x) liefert (ιa,b (x), ia,b (x)) als Resultat. Beweis Die Aussage ist korrekt, falls a > b (erste Zeile). Für b ≥ a beweisen wir die Aussage per Induktion über i = b − a + 1. Induktionsanfang: Falls i = 1 ist b = a. Damit liefern die Zeilen 2-5 die korrekte Ausgabe. ... Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 22 / 35 Korrektheit von RecBinSearch Beweis (Fortsetzung) Induktionsvoraussetzung: Die Behauptung gilt für alle Aufrufe RecBinSearch(A, c, d, x) mit 1 ≤ d − c + 1 ≤ i. Induktionsschritt: Sei i = b − a + 1 > 1. Dann gilt a < b. Somit führt der Algorithmus die Zeilen 6-13 aus. Es gilt a+b m= 2 und a+b m= 2 2a + 1 ≥ = ⌊a⌋ = a 2 1 2b − 1 ≤ = b− =b−1<b 2 2 Damit sind beide Teilarrays – A[a . . . m] und A[m + 1 . . . b] – kürzer als das Originalarray A[a . . . b] und der Algorithmus liefert nach IV das korrekte Ergebnis. ... Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 23 / 35 Korrektheit von RecBinSearch Beweis (Fortsetzung) Fall 1: Falls x ≤ A[m] gilt, wie oben beobachtet ( (true, ia,m (x)) falls ia,m (x) ≤ m (ιa,b (x), ia,b (x)) = (false, b + 1) falls ia,m (x) = m + 1 Das ist genau, was in den Zeilen 8-10 berechnet wird. Fall 2: Falls x > A[m] gilt, wie oben beobachtet (ιa,b (m), ia,b (m)) = (ιm+1,b (x), im+1,b (x)) was in Zeile 12 berechnet wird. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 24 / 35 Laufzeit von RecBinSearch TRBS (i) seien die Worst-Case-Kosten für einen Aufruf von RecBinSearch(A, a, b, x) mit b − a + 1 = i. D.h. TRBS (i) ist die maximale Laufzeit über alle möglichen Arrays A und Indexpaare (a, b). Jeder Aufruf von RecBinSearch verursacht Kosten ≤ C , wenn man die von ihm ausgelösten rekursiven Aufrufe nicht betrachtet. Damit genügt es für jedes i die maximale Anzahl r (i) von Aufrufen inklusive des ersten Aufrufes für b − a + 1 = i zu ermitteln. Die Zeilen 1-5 liefern r (0) = r (1) = 1 Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 25 / 35 Laufzeit von RecBinSearch Falls i ≥ 2, hat das Teilarray, auf dem RecBinSearch aufgerufen wird im ersten Fall die Länge a+b b−a+2 ∗ b−a+1 ∗ m−a+1= −a+1 = = 2 2 2 und im zweiten Fall b−a ∗ b−a+1 a+b = −1+1 = b − (m + 1) + 1 = b − 2 2 2 ∗ ∗ (= sind Übungsaufgaben) Damit gilt i i ,r r (i) ≤ 1 + max r 2 2 Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 26 / 35 Laufzeit von RecBinSearch Lemma Für i ≥ 0 gilt r (i) ≤ 1 + ⌈log i⌉. Beweis Wir beweisen die Behauptung per Induktion über i. Induktionsanfang: Für i = 1 gilt r (1) = 1 = 1 + ⌈log 1⌉. Für i = 2 gilt r (2) = 2 = 1 + ⌈log 2⌉. Induktionsvoraussetzung: Für alle 1 ≤ j < i gilt r (j) ≤ 1 + ⌈log j⌉. ... Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 27 / 35 Laufzeit von RecBinSearch Beweis (Fortsetzung) Induktionsschritt: Sei i ≥ 3. Nach Induktionsvoraussetzung gilt: i i r (i) ≤ 1 + max r ,r 2 2 IV i i ≤ 1 + max 1 + log , 1 + log 2 2 i ≤ 2 + log . 2 Um weiterzumachen, benötigen wir eine Abschätzung für log 2i . Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 28 / 35 ... Laufzeit von RecBinSearch Beweis (Fortsetzung) Behauptung: Für i ≥ 3 gilt i ≤ ⌈log i⌉ − 1. log 2 Beweis der Behauptung: Da i ≥ 3 existiert ein j mit j ≥ 2, so dass 2j−1 < i = 2j−1 + l ≤ 2j mit 1 ≤ l ≤ 2j−1 . Damit gilt j − 1 = log(2j−1 ) < ⌈log(2j−1 + 1)⌉ ≤ ⌈log i⌉ ≤ ⌈log 2j ⌉ = j. ... Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 29 / 35 Laufzeit von RecBinSearch Beweis (Fortsetzung) Weiter gilt 2j−1 l i j−2 j−2 =2 + ≤2 + = 2 · 2j−2 = 2j−1 , 2 2 2 und damit i log ≤ j − 1, 2 was die Behauptung beweist. Nun können wir r (i) weiter abschätzen: i ≤ 2 + ⌈log(i)⌉ − 1 = 1 + ⌈log(i)⌉ . r (i) ≤ 2 + log 2 Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 30 / 35 Laufzeit von RecBinSearch Satz (Laufzeit von RecBinSearch) Der Algorithmus RecBinSearch benötigt auf einen schwach geordneten Array A[a . . . b] höchstens 1 + ⌈log(b − a + 1)⌉ rekursive Aufrufe und hat Laufzeit O(log(b − a + 1)). Korollar Die Binäre Suche auf einem schwach geordneten Array A[1 . . . n] benötigt höchstens Zeit O(log n). Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 31 / 35 Iterative Binäre Suche Rekursionen sind im Allgemeinen technisch sehr aufwändig und damit sehr teuer. Iterative Algorithmen sind häufig deutlich schneller. Daher versuchen wir die rekursive Formulierung umzuschreiben. Da die Rekursion nur die Ergebnisse nach außen“ weitergibt (tail recursion) ” ist das nicht besonders schwer. Wir müssen nur in jeder Runde die Parameter anpassen und die Abbruchbedingung korrekt implementieren. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 32 / 35 Iterative Binäre Suche ItBinSearch(A, a, b, x) – Iterative Binäre Suche Eingabe: A[1 . . . n], a, b und x; Ausgabe: (ιa,b (x), ia,b (x)); if b < a then return (false, a + 1); while a < b do m = ⌊ a+b ⌋; 2 if x ≤ A[m] then b = m else a = m + 1; end if x > A[a] then return (false, a + 1); if x < A[a] then return (false, aq); if x == A[a] then return (true, a); return (false, a); Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 33 / 35 Iterative Binäre Suche Satz (Iterative Binäre Suche) Der Algorithmus ItBinSearch liefert auf einem schwach geordneten Array A[a . . . b] das korrekte Ergebnis. Die Anzahl der Durchläufe durch den Rumpf der Schleife ist höchstens ⌈log(b − a + 1)⌉. Die Laufzeit ist Θ(log n) im besten und im schlechtesten Fall. Beweis. Übungsaugabe! Vorsicht! Die Argumentation verläuft etwas anders als bei der Rekursion. Algorithmen und Datenstrukturen SS09 M. Brinkmeier TU Ilmenau Seite 34 / 35