Übung: Algorithmen und Datenstrukturen SS 2007 Prof. Lengauer Sven Apel, Michael Claÿen, Christoph Zengler, Christof König Blatt 9 Votierung in der Woche vom 02.06.0706.06.07 Aufgabe 26 Vergleich von Suchverfahren Im Skript nden sich auf Seite 68 und 69 zwei Übersichtstabellen zu den Laufzeiten einzelner Operationen. Die folgenden Aussagen sind diesen beiden Tabellen entnommen. Begründen Sie diese jeweils. Falls es sich um einen worst case handelt, reicht zur Begründung die Angabe eines Beispiels. (a) Im average case benötigt eine erfolgreiche Suche in einer nicht sortierten Liste (n + 1)/2 Schritte. Sei n die Länge der Liste, im Erfolgsfall wird die Suche spätestens beim letzten Element fündig. Die Wahrscheinlichkeit dafür an der i-ten Stelle (1 ≤ i ≤ n) fündig zu werden ist 1/n. Damit ergibt sich der folgende Erwartungswert: Lösung: E(n) = 1 1 1 · 1 + · 2 + ··· + · n n n n Dies läÿt sich zu E(n) = 1 1 n(n + 1) n+1 · (1 + 2 + · · · + n) = · = n n 2 2 vereinfachen. (b) Im failure case benötigt die Suche in einer nicht sortierten Liste im Durchschnitt n + 1 Schritte. Im Fehlerfall muss die Liste komplett bis zum Ende abgesucht werden. Damit sind n + 1 Schritte nötig. Lösung: 1 (c) Im failure case benötigt die Suche in einer sortierten Liste im Durchschnitt (n + 2)/2 Schritte. Sei x der zu suchende Wert und i die Position desjenigen Elements in der sortierten Liste, das als erstes gröÿer als x ist. Im Extremfall ist x gröÿer als alle Elemente in der Liste, dann sei i = n + 1. Jede der Positionen von 1 ≤ i ≤ n+1 ist wiederum gleichwahrscheinlich. Und somit ergibt sich wieder: Lösung: E(n) = 1 1 (n + 1)(n + 2) n+2 · (1 + 2 + · · · + (n + 1)) = · = n+1 n+1 2 2 (d) In einem binären Suchbaum ist der worst case für das Finden eines Elements n Schritte. Ein binärer Suchbaum kann zu einem Pfad ausarten. Falls das gesuchte Element das Blatt des Pfades ist, benötigt man n Schritte. Lösung: (e) Sei n die Anzahl der Werte in einer Hash-Tabelle mit m Slots. Warum braucht man zum Finden des Minimums/Maximums m Schritte? Handelt es sich dabei um eine worst oder best case Abschätzung? Es müssen jeweils alle Slots der Hash-Tabelle durchsucht werden, um eindeutig das Minimum bestimmen zu können. Also sowohl worst als auch best case Abschätzung. Man hat vielleicht schon eher das Minimum gefunden, kann sich aber erst am Ende sicher sein, dass es tatsächlich das Minimum ist. Wenn man das Löschen von Einträgen vernachlässigt, kann man das Minimum/Maximum auch jeweils in einem Schritt berechnen. Dazu benötigt man nur einen zusätzlichen Verweis in der HashTabelle. Lösung: (f) Was ist mit left-height bzw. right-height fr die Laufzeit zum Finden des Minimums/Maximums in einem Suchbaum gemeint? Wie sieht damit die worst, average und best case Laufzeit genau aus? Bei left-height handelt es sich um die Höhe des linkesten Pfades im Suchbaum. Analog für right-height. Damit ergibt sich für den worst case eine Abschätzung von n Schritten (Ausartung zum Pfad), für den average case eine logarithmische Abschätzung und für den best case eine Operation, die in einem Schritt ausgeführt werden kann. Lösung: Aufgabe 27 Hashing Gegeben ist folgender Code einer Klasse Point: public class Point { 2 public int x; public int y; public Point(int x, int y) { this.x = x; this.y = y; } public String toString() { return "(" + x + "/" + y + ")"; } } public boolean equals(Object o) { if (o instanceof Point) { Point p = (Point) o; return p.x == this.x && p.y == this.y; } return false; } Diese Punkte sollen in ein Array mittels eines Hashing-Verfahrens einsortiert werden. Dazu gibt es die Klasse PointArray. Der Konstruktor dieser Klasse legt ein neues PointArray einer gegebenen Gröÿe an. Die Funktion getHash(Point p) liefert zu einem Punkt p den entsprechenden Hashwert. Die Methode printArray() gibt das PointArray aus. Das Codefragment hierzu sieht wie folgt aus: public class PointArray{ private Point[] points; public PointArray(int length) { points = new Point[length]; } private int getHash(Point p) { return (p.x + p.y) % points.length; } public void printArray() { for (int i=0; i<points.length-1; i++) { System.out.print(points[i] + " - "); } System.out.print(points[points.length-1] + "\n"); } 3 } a) Implementieren Sie eine Methode insertPoint(Point p), die einen gegebenen Punkt p in das Array einfügt. Die Position des Punktes im Array soll dabei über die Hashfunktion ermittelt werden. Bei einer Kollision sollen Sie die Strategie des linear chaining / probing benutzen. Behandeln Sie auch den Fall, dass das Array voll ist. b) Implementieren Sie eine Methode getPoint(Point p), die einen gegebenen Punkt p im Array sucht. Sie soll den Punkt zurückgeben, falls sie ihn gefunden hat. Findet sie den Punkt nicht, soll sie null zurückgeben. Die Funktion soll dabei natürlich nur so weit wie nötig suchen, und nicht jedesmal das ganze Array durchlaufen. c) Testen Sie Ihre Methoden mit der folgenden Testklasse und verizieren Sie die Ergebnisse: public class Test { public static void main(String[] args) { PointArray points = new PointArray(10); Point Point Point Point Point Point Point p1 p2 p3 p4 p5 p6 p7 = = = = = = = new new new new new new new Point(4,4); Point(5,3); Point(7,8); Point(9,1); Point(10,11); Point(14,11); Point(1,1); points.insertPoint(p1); points.insertPoint(p2); points.insertPoint(p3); points.insertPoint(p4); points.insertPoint(p5); points.insertPoint(p6); points.insertPoint(p7); System.out.println("Array: "); points.printArray(); System.out.println("\nSearches: "); System.out.println("4,4: " + points.getPoint(new Point(4,4))); System.out.println("14,11: " + points.getPoint(new Point(14,11))); System.out.println("3,0: " + points.getPoint(new Point(3,0))); System.out.println("1,1: " + points.getPoint(new Point(1,1))); System.out.println("8,0: " + points.getPoint(new Point(8,0))); 4 } } Anmerkung: Den Code Der Klassen Point und Test nden Sie im Stud.IP und auf der Vorlesungs-Hompage. Ebenso das Codefragment der Klasse PointArray. Lösung: a) public void insertPoint(Point p) { assert numOfElements < points.length : "There's no place left in the array"; int place = getHash(p); // Array[place] is empty, so insert Point here if (points[place] == null) { points[place] = p; numOfElements++; } // Search for the next free position } else { for (int i=0; i<points.length; i++) { if (points[(place + 1 + i) % points.length] == null) { points[(place + 1 + i) % points.length] = p; numOfElements++; return; } } } b) public Point getPoint(Point p) { int place = getHash(p); // Place is empty, so return null if (points[place] == null) { return null; } // Point was found at place, so return it if (points[place].equals(p)) { return p; // Search in the array until the point or a null-entry is found } else { for (int i=0; i<points.length; i++) { Point temp = points[(place + 1 + i) % points.length]; 5 if (temp == null) { return null; } else if (temp.equals(p)) { return temp; } } } } return null; c) Array: (9/1) - (10/11) - (1/1) - null - null - (7/8) - (14/11) - null - (4/4) - (5/3) Searches: 4,4: (4/4) 14,11: (14/11) 3,0: null 1,1: (1/1) 8,0: null Aufgabe 28 Charakterisierungen von Bäumen Sei G = (V, E) ein einfacher ungerichteter Graph mit n Knoten und m Kanten. Zeigen Sie, dass folgende Aussagen äquivalent sind: 1. G ist zusammenhängend und m = n − 1. 2. G ist kreisfrei und m = n − 1. 3. Jede weitere Kante erzeugt einen (den ersten) Kreis. 4. Zwischen je zwei Knoten u, v gibt es genau einen einfachen Weg Lösung: Wir zeigen die Äquivalenz der vier Aussagen, indem wir 1 ⇒ 2 ⇒ 3 ⇒ 4 ⇒ 1 zeigen. 1 ⇒ 2 : Sei G = (V, E) ein zusammenhängender einfacher Graph mit |E| = |V | − 1. Es ist zu zeigen, dass G kreisfrei ist. Annahme: G enthalte einen Kreis. Sei Gz = (Vz , Ez ) ein derartiger Kreis. Es gilt |Vz | = |Ez |. Deniere die Graphen Gi = (Vi , Ei ) für |Vz | ≤ i ≤ |V | induktiv folgendermaÿen: Der Basisfall ist G|Vz | = Gz . Für alle |Vz | < i ≤ |V | wähle ein vi ∈ V \ Vi−1 aus, so dass ein vi0 ∈ Vi−1 existiert mit ei = {vi , vi0 } ∈ E . Einen derartigen Knoten vi gibt es immer, da G zusammenhängend ist. Deniere Gi := (Vi−1 ∪ {vi }, Ei−1 ∪ {ei }) für |Vz | < i ≤ |V |. Dann gilt für alle |Vz | ≤ i ≤ |V | : |Vi | = |Ei |. Auÿerdem ist G|V | ein Teilgraph von G, wobei V|V | = V ist und E|V | ⊆ E . Dies widerspricht aber |E| = |V | − 1. 6 2 ⇒ 3 : Sei G = (V, E) ein kreisfreier Graph mit |E| = |V | − 1. Es ist zu zeigen, dass für alle e ∈ V2 \ E : G0 := (V, E ∪ e) enthält einen (ersten) Kreis. Annahme: es existiert ein e ∈ V2 \E : G0 := (V, E ∪e) keinen Kreis enthält. Für G0 gilt |V | = |E| + 1 = |E ∪ e|. Solange G0 Knoten vom Grad 1 enthält, entferne diese. Man erhält den Graph G00 = (V 00 , E 00 ), so dass |V 00 | = |E 00 | und V 00 6= ∅. Damit enthält G00 nur noch Knoten mit Grad mindestens 2. Sei v ∈ V 00 beliebig. Starte eine Tiefensuche bei v bis die erste Rückkante gefunden wurde. Da jeder Knoten mindestens Grad zwei hat, kann man jeden Knoten wieder verlassen. Da es nicht unendlich viele Knoten gibt, wird man irgendwann bei einem schon besuchten Knoten nocheinmal landen. Diese Rückkante schlieÿt einen Kreis. Dies widerspricht der Annahme. 3 ⇒ 4 : Sei G ein Graph, so dass jede weitere Kanten einen ersten Kreis erzeugt. Zu zeigen: zwischen je zwei Knoten u, v ∈ V gibt es genau einen einfachen Weg. Annahme: Sei u, v ∈ V , so dass es zwei einfache Wege W1 = [u, . . . , v] und W2 = [u, . . . , v] zwischen u und v gibt. Sei W das gemeinsame Präx der Wege W1 und W2 und x der letzte Knoten des gemeinsamen Präx. Ersetze in beiden Wegen das Präx durch x. Suche anschlieÿend in beiden Wegen den nächsten gemeinsamen Knoten y . Damit gibt es zwei disjunkte einfache Wege zwischen x und y und somit einen Kreis. Widerspruch zur Voraussetzung. 4 ⇒ 1 : Sei G = (V, E) ein Graph, so dass es zwischen je zwei Knoten u, v ∈ V genau einen einfachen Weg gibt. Damit ist G zusammenhängend. Es bleibt noch zu zeigen, dass |E| = |V | − 1 ist. Induktion über |V |. Die beiden Basisfälle |V | = 1, 2 sind klar. Induktionsschritt: Sei e = {u, v} ∈ E beliebig. Dann handelt es sich hierbei um den einen einfachen Weg zwischen u und v . Entfernt man e zerfällt der Graph G in zwei Teilgraphen G1 = (V1 , E1 ) und G2 = (V2 , E2 ), so dass V = V1 ∪ V2 und E = E1 ∪ E2 ∪ {e} und somit |V | = |V1 | + |V2 | und |E| = |E1 | + |E2 | + 1. Damit erhält man nach Induktionsannahme: |E| = |V1 | − 1 + |V2 | − 1 + 1 = |V | − 1. Damit ist die Behauptung bewiesen. 7