Listen

Werbung
Kapitel 3: Listen
Lineare Liste: endliche Folge von Elementen eines Grundtyps
<a1, a2, ..., an> (n>=0), leere Liste <> falls n=0
Listenelemente besitzen Schlüssel, eigentliche Information und
ggf. weitere Komponenten (z.B. Zeiger).
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
G.Heyer
1
Algorithmen und Datenstrukturen
Implementierung (Zeiger):
Zeiger (*ptr).next zeigt auf an (erleichtert Hintereinanderhängen
von Listen). Notation in C: ptr->next
praktische Realisierung: Verwendung 2er Dummy-Elemente,
die Kopf und Ende repräsentieren.
head -> |__|_| -> | a1 | | -> ... -> | an | | -> | | | <- tail
Eine Struktur für einfach verkettete Listenelemente
typedef struct ListElmt_ {
void
*data;
struct ListElmt_
*next;
} ListElmt;
G.Heyer
2
Algorithmen und Datenstrukturen
Einfach verkettete Listen:
Eine Struktur für verkettete Listen
typedef struct List_ {
int
size;
int
void
(*match)(const void *key1, const void *key2);
(*destroy)(void *data);
ListElmt
ListElmt
*head;
*tail;
} List;
G.Heyer
3
Algorithmen und Datenstrukturen
Einfach verkettete Listen:
Public Interfaces
void list_init(List *list, void (*destroy)(void *data));
void list_destroy(List *list);
int list_ins_next(List *list, ListElmt *element, const void *data);
int list_rem_next(List *list, ListElmt *element, void **data);
#define list_size(list) ((list)->size)
#define list_head(list) ((list)->head)
#define list_tail(list) ((list)->tail)
#define list_is_head(list, element) ((element) == (list)->head ? 1 : 0)
#define list_is_tail(element) ((element)->next == NULL ? 1 : 0)
#define list_data(element) ((element)->data)
#define list_next(element) ((element)->next)
G.Heyer
4
Algorithmen und Datenstrukturen
Einfach verkettete Listen:
Initialisieren einer Liste
void list_init(List *list, void (*destroy)(void *data))
{
list->size = 0;
list->destroy = destroy;
list->head = NULL;
list->tail = NULL;
return;
}
G.Heyer
5
Algorithmen und Datenstrukturen
Einfach verkettete Listen:
list_ins_next
int list_ins_next(List *list, ListElmt *element, const void *data)
{ ListElmt
*new_element;
if ((new_element = (ListElmt *)malloc(sizeof(ListElmt))) == NULL)
return -1;
new_element->data = (void *)data;
if (element == NULL) {
if (list_size(list) == 0)
list->tail = new_element;
new_element->next = list->head;
list->head = new_element; }
else {
if (element->next == NULL)
list->tail = new_element;
new_element->next = element->next;
element->next = new_element;}
list->size++;
return 0;
}
G.Heyer
6
Algorithmen und Datenstrukturen
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
G.Heyer
7
Algorithmen und Datenstrukturen
Implementierung (doppelt verkettet):
Zeiger (*ptr).next zeigt auf das nachfolgende und (*ptr).previous
gleichzeitig auf das vorhergehende Listenelement.
head -> |__|_| <-> | a1 | | <-> ... <-> | an | | <- tail
struct node
{
int key;
struct node *next;
struct node *previous;
};
G.Heyer
8
Algorithmen und Datenstrukturen
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)
G.Heyer
9
Algorithmen und Datenstrukturen
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
G.Heyer
10
Algorithmen und Datenstrukturen
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
G.Heyer
11
Algorithmen und Datenstrukturen
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.
G.Heyer
12
Algorithmen und Datenstrukturen
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 _| .
G.Heyer
13
Algorithmen und Datenstrukturen
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.
G.Heyer
14
Algorithmen und Datenstrukturen
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.
G.Heyer
15
Algorithmen und Datenstrukturen
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).
G.Heyer
16
Algorithmen und Datenstrukturen
Herunterladen