Präsentation herunterladen
1
Kapitel 6. Suchverfahren
Wir betrachten nun Listen, in denen die einzelnen Elemente (Knoten) sowohl einen Inhalt als auch einen Schlüssel besitzen. In C kann jetzt der Strukturtyp Knoten z. B. in folgender Weise definiert sein: typedef struct Knoten { int key; char *Inhlt; /*Inhlt ist Zeiger auf eine Zeichenkette. */ struct Knoten *next; }ListElmt; Definieren wir nun eine Variable des Typs ListElmt ListElmt *elmnt; Dann beinhaltet elmnt->key den Schlüsselwert (hier eine integer Größe). Für ein Wörterbuch würde man char key; statt int key; vereinbaren, so daß elmnt->key (Schlüsselwert) dann ein beliebige Zeichenkette sein kann.
2
Sequentielle Suche Kosten: Cavg (n) = i = n - 1 2 1 n
Suche nach Element mit Schlüsselwert K Falls nicht bekannt, ob die Elemente der Liste nach ihren Schlüsselwerten sortiert sind, besteht nur die Möglichkeit, die Liste sequentiell zu durchlaufen und elementeweise zu überprüfen (sequentielle Suche) Kosten: Erfolglose Suche erfordert n Schleifendurchläufe erfolgreiche Suche verlangt im ungünstigsten Fall n -1 Schleifendurchläufe ( und n Schlüsselvergleiche) mittlere Anzahl von Schleifendurchläufen bei erfolgreicher Suche: Cavg (n) = i = n-1 i=0 n - 1 2 1 n
3
Binäre Suche Auf sortierten Listen können Suchvorgänge effizienter durchgeführt werden Sequentielle Suche auf sortierten Listen bringt nur geringe Verbesserungen (für erfolglose Suche durchschnittlich N/2 Vergleiche). Binärsuche wesentlich effizienter durch den Einsatz der Divide-and-conquer-Strategie. Suche nach Schlüssel K in Liste mit aufsteigend sortierten Schlüsseln:
4
1. Falls Liste leer ist, endet die Suche erfolglos
1. Falls Liste leer ist, endet die Suche erfolglos. Sonst: Betrachte Element der Liste an mittlerer Position m. 2. Falls K = Schlüsselwert dieses Elementes, dann ist das gesuchte Element gefunden . 3. Falls K < Schlüsselwert, dann durchsuche die linke Teilliste von Position 1 bis m-1 nach demselben Verfahren. 4. Sonst (K > Schlüsselwert) durchsuche die rechte Teilliste von Position m + 1 bis Listenende nach demselben Verfahren.
5
Binäre Suche (2) Iterative Lösung int binsearch(int v) {
int l=1; int r= N; int x; while (r>=1) x = (l+r)/2; if (v < a[x].key) r = x-1; else l = x+1; if (v == a[x].key) return a[x].data; } return -1
6
Kosten Cmin ( n ) = 1 Cmax ( n ) = [ log2 (n+1)] Cavg ( n ) log2 (n+1) -1, für große n
7
Definition der Fibonacci-Zahlen F0 = 0 F1 = 1
Fibonacci-Suche Ähnlich der Binärsuche, jedoch wird Suchbereich entsprechend der Folge der Fibonacci-Zahlen geteilt. Definition der Fibonacci-Zahlen F0 = 0 F1 = 1 Fk = F k-1 + F k-2 für k >= 2. Teilung einer Liste mit n = Fk-1 sortierten Elementen: 1 i n F k-2 -1 F k-1 -1 F k - 1
8
- Element an der Position i = F k-2 wird mit dem Schlüssel K
- Element an der Position i = F k-2 wird mit dem Schlüssel K verglichen - Wird Gleichheit festgestellt, endet die Suche erfolgreich. - Ist K größer, wird der rechte Bereich mit F k-1 -1 Elementen, ansonsten der linke Bereich mit F k-2 -1 Elementen auf dieselbe Weise durchsucht. Kosten - für n = F k -1 sind im schlechtesten Fall k-2 Suchschritte notwendig, d. h. O ( k ) Schlüsselvergleiche - Da gilt F k c k , folgt C max ( n ) = O ( log ( n+1 ) ) = O ( log2 n) .
9
Sprungsuche Prinzip - Zunächst wird der sortierte Datenbestand in Sprüngen überquert, um den Abschnitt zu lokalisieren, der ggf. den gesuchten Schlüssel enthält, - danach wird der Schlüssel im gefundenen Abschnitt nach irgendeinem Verfahren gesucht. . . . . . . . . . . . . . . . L 1 m 2m 3m n
10
Einfache Sprungsuche - konstante Sprünge zu Positionen m, 2 m, 3 m, ... - Sobald K <= Schlüsselwert[i] mit i = j * m (j = 1, 2, ...), wobei a[i].key der Wert des inspizierten Schlüssels ist wird im Abschnitt von (j-1)m+1 bis j*m sequentiell nach dem Suchschlüssel K gesucht. Mittlere Suchkosten ein Sprung koste a ; ein sequentieller Vergleich b Einheiten Cavg (n) = (a*n/m + b*(m-1))/2
11
Optimale Sprungweite m = V ( a / b ) n bzw. m = V n falls a = b C avg ( n ) = a V n - a / 2 Komplexität O (V n )
12
Exponentielle Suche Anwendung wenn Länge des sortierten Suchbereichs zunächst unbekannt bzw. sehr groß ist. Vorgehensweise - für Suchschlüssel K wird zunächst obere Grenze für den zu durchsuchenden Abschnitt bestimmt i = 1; while (K > Schlüsselwert[i]) i *= 2; - Für i > 1 gilt für den auf diese Weise bestimmten Suchabschnitt Schlüsselwert[i DIV 2] < K <= Schlüsselwert[i] - Suche innerhalb des Abschnitts mit irgendeinem Verfahren
13
Sind in der sortierten Liste nur positive, ganzzahlige
Sind in der sortierten Liste nur positive, ganzzahlige Schlüssel ohne Duplikate gespeichert, wachsen Schlüsselwerte mindestens so stark wie die Indizes der Elemente. - i wird höchstens log2 K mal verdoppelt - Bestimmung des gesuchten Intervalls erfordert maximal log2 K Schlüsselvergleiche - Suche innerhalb des Abschnitts (z. B. mit Binärsuche ) erfordert auch höchstens log2 K Schlüsselvergleiche Gesamtaufwand O ( log2 K )
14
Interpolationssuche Schnellere Lokalisierung des Suchbereichs indem Schlüsselwerte selbst betrachtet werden, um „Abstand“ zum Schlüssel K abzuschätzen nächste Suchposition pos wird aus den Werten ug und og der Unter- und Obergrenze des aktuellen Suchbereichs wie folgt berechnet: K - Schlüsselwert[ug] pos = ug * (og - ug) Schlüsselwert[og]- Schlüsselwert[ug]
15
Sinnvoll, wenn der Schlüsselwert im betreffenden Bereich einigermaßen gleichverteilt ist
erfordert dann im Mittel lediglich log2 log2n + 1 Schlüsselvergleiche Im schlechtesten Fall (stark ungleichmäßige Werteverteilung) entsteht jedoch linearer Suchaufwand ( O ( n ) )
16
Anmerkungen zur Implementierung in C
Strukturierte Typen in C Struktur ist eine Ansammlung von mehreren Variablen unter einem gemeinsamen Namen. Syntax für einen Strukturtyp: struct Bezeichner {Komponenten}. Komponenten (im einfachsten Falle): Typ Komponentenname; Danach können gleich noch Variable des eigeführten Typ vereinbart werden. Beispiel: Implementierung einer Liste durch ein Array: struct List { double A[10]; int length; } l1,l2; l1 und l2 sind jetzt Variable des vereinbarten Typs. Ansprache über Variablenname.Komponentenname. Beispiele: l1.A[3]=3.2; l1.length = 10;
17
Rekursive Definition eines Strukturtyps
Rekursive Definition nur über Zeiger möglich. struct List { double A[10]; int length; struct List *liste; } l1,l2; liste ist damit einZeiger auf eine Struktur vom Typ List.
18
Verwendung von typedef
Mit typedef kann ein neuer Name nach Bedarf für einen Datentyp eingeführt werden. Syntax: typedef typ declarator Im einfachsten Falle ist der declarator einfach ein Bezeichner: typedef typ bezeichner Beispiel für letzteren Fall: typedef struct List { double A[10]; int length; } Meine_Liste; Dabei ist struct List {double A[10]; int length;} der typ und Meine_Liste ist jetzt der bezeichner für den durch die struct Deklaration eingeführten Datentyp List. Man kann dann Variablen dieses Typs als Meine_Liste L1; deklarieren. Speicher anfordern (Objekt erzeugen) mit L1 = (Meine_Liste *)malloc (sizeof L1); Nun geht z. B. L1->length = 10;
Ähnliche Präsentationen
© 2024 SlidePlayer.org Inc.
All rights reserved.