Kapitel 4: Listen Lineare Liste: endliche Folge von Elementen eines Grundtyps. Wir schreiben die Liste als <a1, a2, ..., an> (n>=0) und die leere Liste (falls n=0) als < >. Beispiele: Die Liste der natürlichen Zahlen in geordneter Form: <1,2, ..., n> Oder eine beliebige Zeichenfolge (einschließlich Leerzeichen) <d,a,s, ,i,s,t, ,e,i,n,e, ,L,i,s,t,e> Auch der Text dieser Folie kann als eine Liste betrachtet werden. Listenelemente besitzen Schlüssel, eigentliche Information und ggf. weitere Komponenten (z.B. Zeiger). R. Der 1 Algorithmen und Datenstrukturen (Magister) Operationen auf Listen Operationen: Einfügen, Entfernen, Suchen Implementierungen: 1. Sequentiell (Arrays), 2. Dynamisch (Zeiger) Vorteile sequentieller Speicherung: • schnelle Suchverfahren falls Sortierung vorliegt, da jedes Element über Indexposition direkt ansprechbar (O(1)) Nachteile sequentieller Speicherung: • hoher Änderungsaufwand durch Verschiebekosten (O(n)) • schlechte Speicherplatzausnutzung • inflexibel bei starkem dynamischem Wachstum R. Der 2 Algorithmen und Datenstrukturen (Magister) Implementierung (Zeiger): Zeiger (*ptr).next zeigt auf an (erleichtert Hintereinanderhängen von Listen). Notation in C: ptr->next Praktische Realisierung: • Jedes Element enthält Zeiger auf das nächste Element der Liste. • Zwei Dummy-Elemente, repräsentieren Kopf (head) und Ende (tail) der Liste. Tail zeigt auf sich selbst. |__|_| -> | a1 | | -> ... -> | an | | -> | | | head R. Der tail 3 Algorithmen und Datenstrukturen (Magister) Struktur für einfach verkettete Listen Strukturdatentyp für einfach verkettete Listenelemente struct ListElmt { int void struct ListElmt key; *data; *next; /*Zum Generieren der Daten*/ }; Bemerkung: Die Definition des Strukturdatentyps ListElmt ist rekursiv. Das ist in C nur über Zeiger (*next) möglich. R. Der 4 Algorithmen und Datenstrukturen (Magister) Einfach verkettete Listen Eine Struktur für verkettete Listen struct List{ int size; struct ListElmt *head; struct ListElmt *tail; }; R. Der 5 Algorithmen und Datenstrukturen (Magister) Einfach verkettete Listen: • Nur sequentielle Suche möglich (sowohl im geordneten als auch ungeordneten Fall) • Einfügen und Löschen eines Elementes mit Schlüssel K erfordert vorherige Suche Zyklisch verkettete Liste • Zeiger des letzten (dummy) Elementes zeigt auf head so dass automatisch ein Rücksprung zum Kopf der Liste erfogt. R. Der 6 Algorithmen und Datenstrukturen (Magister) Implementierung (doppelt verkettet): Zeiger (*ptr).next zeigt auf das nachfolgende und (*ptr).previous gleichzeitig auf das vorhergehende Listenelement. |__|_| <-> | a1 | | <-> ... <-> | an | | <-> |__|_| head tail Struktur eines Elementes (ohne explizite Referenz auf Daten) struct ListElmt_ { int key; struct ListElmt_ *next; struct ListElmt_ *previous; }ListElmt; R. Der 7 Algorithmen und Datenstrukturen (Magister) Implementierung (doppelt verkettet): Bewertung • höherer Speicherplatzbedarf als bei einfacher Verkettung • Aktualisierungsoperationen etwas aufwendiger (Anpassung der Verkettung) • Suchaufwand in etwa gleich hoch, jedoch ggf. geringerer Suchaufwand zur Bestimmung des Vorgängers (PREVIOUS(p,L)) • geringerer Aufwand für Operation DELETE(p,L) • Flexibilität der Doppelverkettung besonders vorteilhaft, wenn Element gleichzeitig Mitglied mehrerer Listen sein kann (Multilist-Strukturen) R. Der 8 Algorithmen und Datenstrukturen (Magister) Implementierung (doppelt verkettet): Suchaufwand bei ungeordneter Liste • erfolgreiche Suche cavg=(n+1)/2 (Standardannahme: zufällige Schlüsselauswahl, stochastische Unabhängigkeit der g. Schlüsselmenge) Einfügen oder Löschen eines Elementes • konstante Kosten für Einfügen am Listenanfang • Löschen verlangt meist vorherige Suche • konstante Löschkosten bei positionsbezogenem Löschen und Doppelverkettung Sortierung bringt kaum Vorteile • erfolglose Suche cavg=(n+1)/2 • lineare Kosten für Einfügen in Sortierreihenfolge R. Der 9 Algorithmen und Datenstrukturen (Magister) Häufigkeitsgeordnete lineare Listen Zugriffshäufigkeiten für die einzelnen Elemente bekannt • mittlere Suchkosten cavg(n)=1*p1+2*p2+3*p3+...+n*pn • minimierte Suchkosten wenn p1>p2> ...>pn Selbstorganisierende Listen (wenn Zugriffshäufigkeiten für die einzelnen Elemente nicht bekannt) • Frequency count • Transpose • Move-to-Front R. Der 10 Algorithmen und Datenstrukturen (Magister) Skip-Listen Wörterbücher (Dictionaries): Mengen von Elementen eines Grundtyps mit Operationen Suchen, Einfügen, Entfernen. Wörterbuchproblem: Finden geeigneter Datenstruktur und effizienter Algorithmen für diese Operationen (Listen eine von mehreren Realisierungsmöglichkeiten). Skip-Listen: Effiziente Realisierungsmöglichkeit. a) Perfekte Skip-Listen Def.: Eine perfekte Skip-Liste ist eine sortierte, verkettete lineare Liste, mit Kopf und Endelement, wobei gilt: 1) jedes 2i-te Element hat Zeiger auf das 2i Positionen weiter rechts stehendes Element. 2) Endelement hat Wert unendlich. R. Der 11 Algorithmen und Datenstrukturen (Magister) Jedes Listenelement hat Zeiger auf nächstes Element. Jedes 2. Listenelement hat Zeiger auf übernächstes Element. Jedes 4. Listenelement hat Zeiger auf 4 Positionen weiter rechts stehendes Element, etc. Gesamtzahl der nötigen Zeiger für Länge n: n + n/2 + n/4 + ... + 1 2n Höhe eines Elements: Anzahl der Zeiger des Elements -1. Höhe der Liste (L.höhe): maximale Zeigeranzahl eines Elements - 1: |_ log n _| . R. Der 12 Algorithmen und Datenstrukturen (Magister) Suchen informell: 1. Sei i die Höhe der Skip-Liste, der Kopf der Liste das aktuell inspizierte Objekt. 2. Falls Schlüssel des Objekts, auf das der Niveau-i-Zeiger des aktuellen Objekts zeigt, kleiner als der Suchschlüssel ist, mache dieses zum aktuellen Objekt. 3. dekrementiere i. Falls i >= 0 gehe nach 2. 4. Falls der Schlüssel des Nachfolgers des aktuellen Objekts identisch mit dem Suchschlüssel ist, gib Nachfolger aus, sonst ist Objekt mit Suchschlüssel nicht in Liste. R. Der 13 Algorithmen und Datenstrukturen (Magister) Suchaufwand Suchen in perfekten Skip-Listen ist O(log n). Problem: Einfügen und Löschen führt zu erheblichem Mehraufwand, wenn Skip-Listen perfekt bleiben sollen. Deshalb: randomisierte SLs: Man sorgt dafür, daß die Anzahl der vorkommenden Höhen stimmt (d.h. Hälfte Niveau 1 Zeiger, Viertel Niveau 2 Zeiger etc.). Neues Element erhält Höhe i mit Wahrscheinlichkeit 1 / 2i+1, 0 i maxhöhe Beim Einfügen (und Entfernen) müssen Zeiger entsprechend der zufälligen Höhe des Elements umgesetzt werden. R. Der 14 Algorithmen und Datenstrukturen (Magister) Komplexitätsanalyse (etwas aufwendiger Beweis) zeigt: Erwartungswert (aber nicht worst case) für Kosten von Suchen, Einfügen und Entfernen in randomisierten Skip-Listen ist immer noch O(log n). R. Der 15 Algorithmen und Datenstrukturen (Magister)