sequentielle Suche

Werbung
Grundzüge der Informatik 1
Teil 6: Algorithmen und Datenstrukturen
6.3 Suchalgorithmen
Prof. Dr. Max Mühlhäuser
FG Telekooperation
TU-Darmstadt
Agenda
Grundlegendes zu Suchalgorithmen
•
Kennenlernen der wichtigsten Suchalgorithmen
– Grundidee
– Implementierung für int-Felder
– Komplexität (meist ohne Analyse)
•
Experimentieren mit den Algorithmen
– In der Vorlesung: Beispiele und Animationen
– In der Übung: praktische Auseinandersetzung
– Zum Selbststudium: eigene Erstellung von Animationen
•
Anpassung für die Suche nach Objekten
2003 © MM ...
GdI 1 6.3 – Suchalgorithmen
2
Ausgangslage der Suchalgorithmen
• Eine wichtige Kasse von Algorithmen beschäftigt
sich mit der Suche nach Elementen
• Bei den Elementen kann es sich um Zahlen,
Zeichen, Zeichenfolgen oder Objekte handeln
• Die Elemente sind in einer Datenstruktur abgelegt
• Es kann auch nach Elementen gesucht werden, die
eine bestimmte Bedingung erfüllen
– z.B. soll eine boole'sche Funktion true liefert
2003 © MM ...
GdI 1 6.3 – Suchalgorithmen
3
Mögliche Fälle bei der Suche
• Bei der Suche können die folgenden Fälle eintreten:
1. Das Element ist nicht in der Datenstruktur enthalten
• Es ist darauf zu achten, dass keine Zugriffe außerhalb der
Datenstruktur erfolgen, die zu Fehlern führen können
2. Das Element ist genau einmal enthalten
• In diesem Fall soll das Ergebnis - je nach Anwendung - true,
der Wert des Elements oder seine Position / Adresse sein
3. Das Element ist mehr als einmal enthalten
• In einigen Datenstrukturen ist dies nicht zulässig
• In den anderen muss definiert werden, welches Element das
Ergebnis sein soll
2003 © MM ...
GdI 1 6.3 – Suchalgorithmen
4
Sequentielle Suche
• Die einfachste und allgemeinste Methode der Suche von
Elementen ist die sequentielle Suche
• Die Elemente werden dabei der Reihe nach überprüft
• Aufgrund des überaus einfachen Vorgehens wird sie auch als
brute force search ("Suche mit brutaler Gewalt") bezeichnet
• Die sequentielle Suche ist einfach zu implementieren:
– Durch eine Schleife auf Feldern oder Listen
– Durch Tiefensuche auf Bäumen oder Graphen
• Die Komplexität ist mit O(n) sehr ungünstig
• Dennoch ist das Verfahren bei unsortierten Daten notwendig
– hier rechnet man mit einem durchschnittlichen Aufwand von
2003 © MM ...
GdI 1 6.3 – Suchalgorithmen
n
2
5
Algorithmus der Sequentiellen Suche
/**
* Sequentielle Suche nach Element x im Feld a, Komplexitaet O(n).
*
* @return Index des Elements oder -1 (Feld null/leer/x nicht enthalten)
*/
public int bruteForceSearch(int[] a, int x)
{
int i = 0;
if (a == null || a.length == 0)
return -1; // Feld null oder leer
while (i<a.length && a[i] != x)
i++;
// Laufe durch Feld bis Ende des Feldes oder a[i] == x
if (i<a.length)
return i;
else
return -1;
// gefunden an Position i
// nicht enthalten...
}
2003 © MM ...
GdI 1 6.3 – Suchalgorithmen
6
Erweiterung der Sequentiellen Suche
• Eine Alternative zum Schleifenabbruch wie oben ist
die Sentinel-Methode
• Hierbei wird das gesuchte Element an die letzte
Position eingefügt und anschließend gesucht
• Spätestens an der letzten Position wird dann das
Element gefunden
• Wird bei der Suche die letzte Position erreicht, so
war die Suche erfolglos, andernfalls hatte sie Erfolg
• Man spart dabei eine Abfrage pro Schleifendurchlauf
• Ggf. muss allerdings vorher das ganze Feld in ein
größeres Feld kopiert werden (mit O(n))
2003 © MM ...
GdI 1 6.3 – Suchalgorithmen
7
Sequentielle Suche auf sortierten Daten
• Was passiert bei (bekanntermaßen) sortierten Daten?
– Die sequentielle Suche kann abgebrochen werden, sobald das
aktuelle Element größer als das gesuchte Element ist
• Diese Möglichkeit setzt voraus, dass die Elemente sich nach
einem gegebenen Kriterium (bzw. einer gegebenen Funktion)
in eine Ordnung bringen lassen
• Bei Objekten ist dies im allgemeinen nicht möglich
• Die Komplexitätsabschätzung der sequentiellen Suche bleibt
aber auch bei sortierten Daten bei O(n)
• Für die nachfolgenden Suchverfahren wird immer unterstellt,
dass die Daten sortiert vorliegen
2003 © MM ...
GdI 1 6.3 – Suchalgorithmen
8
Binäre Suche
• Die binäre Suche ist ein sehr einfaches, gleichzeitig aber auch sehr
effizientes Verfahren, das in der Praxis oft eingesetzt wird
• Die binäre Suche vergleicht in jedem Schritt das gesuchte Element
mit dem mittleren Element des Suchraums
• Bei Gleichheit bricht die Suche mit Erfolg ab
• Da die Daten sortiert sind, wird sonst nur noch eine Hälfte betrachtet
• Das Verfahren teilt in jedem Schritt den Suchraum in zwei Hälften
• Es wird nur eine der beiden Hälften betrachtet
–
–
bis das Element gefunden wurde
oder der Suchraum sich nicht mehr halbieren lässt
• In diesem Fall wurde das Element nicht gefunden
• Die Komplexität der binären Suche ist O(log2 n)
–
Sowohl für erfolglose wie auch für erfolgreiche Suche
2003 © MM ...
GdI 1 6.3 – Suchalgorithmen
9
Binäre Suche: Schematischer Algorithmus
1. Betrachte zunächst das gesamte Feld
2. Bestimme die mittlere Position des Teilfeldes
3. Ist das Element in der Mitte gleich dem gesuchten?
• Falls ja, weiter bei Schritt 6
4. Ist das mittlere Element kleiner als das gesuchte?
–
–
Falls ja, betrachte die rechte Hälfte als neues Teilfeld
Andernfalls betrachte die linken Hälfte als neues Teilfeld
5. Gehe zu Schritt 2 mit dem neuen Teilfeld
6. Gib den entsprechenden Wert zurück
• -1 falls nicht gefunden
• Ansonsten die (erste gefundene) Position des Elements
2003 © MM ...
GdI 1 6.3 – Suchalgorithmen
10
Beispiel zur Binären Suche
2003 © MM ...
GdI 1 6.3 – Suchalgorithmen
11
Implementierung der Binären Suche
/**
* Binaere Suche nach Element x im Feld a, Komplexitaet: O(log n).
* @return Index des Elements oder -1 (Feld null/leer/x nicht enth.)
*/
public int binSearchIterativ(int[] a, int x)
{
if (a == null || a.length == 0)
return -1;
// Feld null oder leer
int l = 0, r = a.length, midElem = (l+r)/2;
while (r > l && a[midElem] != x) {
if (x < a[midElem])
r = midElem - 1;
else
l = midElem + 1;
midElem = (l + r) / 2;
}
if (a[midElem] == x)
return midElem;
else
return -1;
// Grenzen und mittleres Element
// Suchen solange Elemente da und nicht gefunden
// zu klein --> links weitersuchen
// zu gross --> rechts weitersuchen
// Berechnen der Mitte
// gefunden bei midElem
// nicht gefunden
}
2003 © MM ...
GdI 1 6.3 – Suchalgorithmen
12
Rekursive Implementierung Binärer Suche
/**
* Binaere Suche nach Element x im Feld a, Komplexitaet: O(log n).
* Aufruf: int position = binSearch(array, searchFor, 0, array.length);
*
* @return Index des Elements oder -1 (Feld null/leer/x nicht enth.)
*/
public int binSearch(int[] a, int x, int l, int r)
{
int midElem = (l+r)/2;
// Mittlere Position bestimmen
if (a == null || a.length == 0 || r < l)
return -1;
// Feld null oder Bereich leer?
if (a[midElem] == x)
return midElem;
// Element gefunden – Ende!
if (a[midElem] > x)
return binSearch(a, x, l, midElem - 1);
// Mitte zu gross - suche links weiter
else
return binSearch(a, x, midElem + 1, r);
// Mitte zu klein - suche rechts
}
2003 © MM ...
GdI 1 6.3 – Suchalgorithmen
13
Interpolationssuche
• Wie kann man die binäre Suche so verbessern, dass statt der Mitte eine
„bessere“ Position betrachtet wird?
–
Der Teilungsindex sollte abhängen…
• vom gesuchten Wert
• und den Werten an den Intervallgrenzen
• Die Interpolationssuche entspricht konzeptuell der Suche im Telefonbuch
–
–
Aufschlagen einer „möglichen“ Stelle auf und schauen, wo man sich befindet
Je nachdem, wie weit der aktuelle Eintrag von dem gewünschten ist, wird man
nun entsprechend weit vor- oder zurückblättern.
• Die Interpolationssuche arbeitet am besten bei etwa gleichverteilten Werten
• Bei der binären Suche wurde der Suchindex wie folgt berechnet:
– midElem = l + (r - l) / 2
• Die Interpolationssuche bestimmt den neuen Suchindex wie folgt:
– midElem = l + ((x - a[l] * (r - l)) / (a[r] - a[l])
• Der Rest des Algorithmus kann unverändert übernommen werden
–
Daher verzichten wir auf eine erneute Angabe
2003 © MM ...
GdI 1 6.3 – Suchalgorithmen
14
Beispiel zur Interpolationssuche
2003 © MM ...
GdI 1 6.3 – Suchalgorithmen
15
Anmerkungen
• Die Interpolationssuche benötigt bei zufälliger Verteilung
log2(log2(n+1)) Vergleiche
– der Beweis ist schwierig zu führen und nachzuvollziehen und wird
daher hier weggelassen
• Die Funktion wächst extrem langsam mit größer werdendem n
• Beispiel: log2(log2(109)) ˜ log2(29.897353) ˜ 4.902
• Für (sehr) große, sortierte Felder mit etwa gleichverteilten
Werten ist die Interpolationssuche daher sehr gut geeignet
• Gegenüber der binären Suche ist lediglich die Berechnung des
neuen mittleren Elements aufwändiger
• Aufgrund der Integer-Arithmetik ist dabei darauf zu achten,
dass die Multiplikation vor der Division durchgeführt wird
2003 © MM ...
GdI 1 6.3 – Suchalgorithmen
16
Selbstanordnende Listen
• Annahme: Einige Elemente werden viel öfter gesucht als andere
• Es kann sich lohnen, die am häufigsten gesuchten Elemente weit
vorne in einer Liste anzuordnen
• Als Suchstrategie wird dann sequentielle Suche verwendet
• Hierzu gibt es drei Vorgehensweisen:
–
MF-Regel (Move to Front)
• Das zuletzt gesuchte Element wird zum Kopf der Liste. Der Rest der Liste bleibt
unverändert
–
T-Regel (Transpose)
• Vertausche das gesuchte Element mit dem unmittelbar vorhergehenden
–
FC-Regel (Frequency Count)
• Führe für jedes Element einen Zähler der Zugriffe. Jeder Zugriff erhöht den
Elementzähler um 1
• Ordne nach dem Zugriff auf ein Element dieses so ein, dass die Zählerwerte
absteigend sortiert in der Liste auftreten
• Falls die Zugriffe etwa gleichverteilt sind, ist dieses Vorgehen durch
die Reorganisation noch ineffizienter als einfache sequentielle Suche!
2003 © MM ...
GdI 1 6.3 – Suchalgorithmen
17
Suchen im binären Suchbaum
•
Die Elemente eines binären Suchbaums sind immer geordnet:
–
–
•
alle Elemente des linken Teilbaums eines Knotens sind kleiner als der
Wert des Knotens
alle Elemente des rechten Teilbaums hingegen sind größer als der Wert
des Knotens
Daher kann man im binären Suchbaum wie folgt suchen:
1. Betrachte die Wurzel des gesamten Baums als aktuellen Knoten
2. Vergleiche den aktuellen Knoten mit dem gesuchten Element:
1. Falls die Elemente gleich sind, ist die Suche beendet.
2. Falls das gesuchte Element kleiner als der aktuelle Knoten ist, mache den
Wurzelknoten des linken Teilbaums zum aktuellen Knoten, gehe zu Schritt 2.4
3. Falls das gesuchte Element größer als der aktuelle Knoten ist, mache den
Wurzelknoten des rechten Teilbaums zum aktuellen Knoten, weiter bei 2.4
4. Falls der aktuelle Knoten nicht null ist, gehe zu Schritt 2; sonst Ende
•
Falls der aktuelle Knoten nach Schritt 2.2 oder 2.3 null ist, also kein
entsprechender Teilbaum existiert, ist die Suche erfolglos beendet
2003 © MM ...
GdI 1 6.3 – Suchalgorithmen
18
Algorithmus: Suche im binären Suchbaum
/**
* Suche Wert in einem Binaeren Suchbaum.
* @return Der Knoten, der das Element enthaelt oder null, falls
* das Element nicht enthalten ist.
*/
public BinaryTree treeSearch(int x) {
if (value == x)
return this;
// falls das der richtige Knoten ist, zurueckgeben!
if (x < value && left != null)
return left.treeSearch(x);
// links weitersuchen, falls Wert kleiner und linker Teilbaum da
if (value < x && right != null)
return right.treeSearch(x);
// rechts weitersuchen, falls Wert kleiner und rechter Teilbaum da
return null;
// wenn wir hier anlangen, ist der Wert nicht im Baum enthalten!
}
2003 © MM ...
GdI 1 6.3 – Suchalgorithmen
19
Einfügen im binären Suchbaum
• Der Algorithmus zum Suchen im binären Suchbaum kann so
modifiziert werden, dass er auch zum (sortierten) Einfügen von
neuen Elementen benutzt werden kann.
• Dazu wird zunächst nach dem Element gesucht.
– Ist der zu verfolgende Teilbaum null, wird das Element als neuer
Knoten eingefügt
– Andernfalls wird im Teilbaum weitergesucht
• Hierzu sind nur die Abfragen auf < und > anzupassen
• Zusätzlich wird Rückgabetyp BinaryTree durch void ersetzt.
• Wichtig: wurde der neu einzufügende Knoten gefunden, wird
er nicht erneut eingefügt, da in einem binären Suchbaum jeder
Schlüssel nur einmal vorkommen darf.
2003 © MM ...
GdI 1 6.3 – Suchalgorithmen
20
Beispiel zum binären Suchbaum
2003 © MM ...
GdI 1 6.3 – Suchalgorithmen
21
Anmerkungen zum Anpassen an Objekte
• Die vorgestellten Algorithmen dienen zur Suche nach intWerten
• Für Objekte sind die Algorithmen wie folgt anzupassen:
– Anpassen des Typs der Variablen und des Feldes
– Ersetzen von == durch equals der Klasse Object oder andere
Methoden
• Bei Verwendung eines String-Attributs „name“ :
– (person.getName()).equals(otherPerson.getName())
• Implementieren der Methoden lessThan, lessOrEqual und
equals
– Manchmal kann auch anhand des Suchattributs verglichen werden
– Etwa die Methode compareTo der Klasse String liefert int-Werte
2003 © MM ...
GdI 1 6.3 – Suchalgorithmen
22
Zusammenfassung
• Sequentielle Suche ist für unsortierte Daten geeignet
• Für sortierte Daten ist die Binäre Suche gut geeignet
– da einfach zu implementieren und effizient
• Interpolationssuche ist noch besser als Binäre Suche…
– … falls die Daten sortiert und ungefähr gleichverteilt sind
• Für Listen o.ä. ist oft nur Sequentielle Suche nutzbar
• Alle Algorithmen sind leicht an andere Typen – double
statt int oder Objekte – anpassbar
2003 © MM ...
GdI 1 6.3 – Suchalgorithmen
23
Herunterladen