6. Datenstrukturen und Speicherstrukturen Datenstrukturen

Werbung
6. Datenstrukturen und Speicherstrukturen
Inhalt:
 Datenstrukturen – Definition und Arten
 Dynamische Speichertechnik
 Dynamische lineare Datenstrukturen:
Umsetzung mit Techniken der dynamischen
Speicherallokation und Zeigertechnik
 Nichtlineare dynamische Datenstrukturen
Die Umsetzung
einfacher
Datenstrukturen im
Speicher des
Rechners erfolgt mit
bislang bekannten
Mitteln:
Strukturen, Felder
und
mehrdimensionale
Felder (Matrizen)
Peter Sobe
1
Datenstrukturen - Definition
Eine Datenstruktur beschreibt die Art der Organisation der Daten

im Speicher des Rechners während der Verarbeitung

auf Speichermedien (Festplatte, Band, CD, DVD,
FlashRAM u.ä.)
Die Daten werden dabei in Elemente aufgeteilt. Die einzelnen
Elemente werden in regelmäßige Relationen gesetzt, z.B. eine
Vorgänger- und Nachfolger-Relation.
Peter Sobe
2
Datenstrukturen - Definition
Verschiedene Quellen:
www.computerlexikon.com
Als Datenstruktur bezeichnet man das Konstrukt in einem
Programm (bzw. im Speicher), das Daten auf eine gewisse Weise
speichert. Durch den speziellen Aufbau einer Datenstruktur
versucht man gewünschte Funktionen besonders effizient zu
implementieren, wobei man zumeist entweder auf geringen
Speicherbedarf oder hohe Geschwindigkeit hin optimiert.
Durch eine falsche Datenstruktur für ein Problem, kann ein
Programm um ein Vielfaches länger für die Lösung benötigen als
eines, das auf eine für das Problem besser geeignete Datenstruktur zurückgreift.
Peter Sobe
3
Datenstrukturen - Definition
Verschiedene Quellen:
wikipedia
In der Informatik ist eine Datenstruktur ein mathematisches Objekt
zur Speicherung von Daten. Es handelt sich um eine Struktur,
weil die Daten in einer bestimmten Art und Weise angeordnet
und verknüpft werden, um den Zugriff auf sie und ihre Verwaltung
geeignet zu ermöglichen. Datenstrukturen sind daher nicht nur
durch die enthaltenen Daten charakterisiert, sondern vor allem
durch die Operationen auf diesen Daten, die Zugriff und
Verwaltung realisieren.
Die Definition von Datenstrukturen erfolgt durch die Angabe einer
konkreten Spezifikation zur Datenhaltung und der dazu nötigen
Operationen. Diese konkrete Spezifikation legt das allgemeine
Verhalten der Operationen fest und abstrahiert damit von der
konkreten Implementierung der Datenstruktur.
Peter Sobe
4
Datenstrukturen - Definition
Verschiedene Quellen:
Ralf Hartmut Güting und Stefan Dieker: Datenstrukturen und Algorithmen,
3. Auflage, Teubner-Verlag, Stuttgart 2004,
Reihe Leitfäden der Informatik
Unter einer Datenstruktur verstehen wir die Implementierung eines
Datentyps auf algorithmischer Ebene. Das heißt, für die Objekte der
Trägermengen der Algebra wird eine Repräsentation festgelegt und die
Operationen werden durch Algorithmen realisiert. Die Beschreibung einer
Datenstruktur kann andere Datentypen benutzen, für die zugehörige
Datenstrukturen bereits existieren oder noch zu entwerfen sind
(“schrittweise Verfeinerung”). So entsteht eine Hierarchie von Datentypen
bzw. Datenstrukturen. Ein Datentyp ist vollständig implementiert, wenn
alle benutzten Datentypen implementiert sind. Letztlich müssen sich alle
Implementierungen auf die elementaren Typen (Basisdatentypen, Felder,
Strukturen/Records) und Mechanismen (Zeiger, Adressarithmetik,
Referenzen) einer Programmiersprache abstützen.
Peter Sobe
5
Datenstrukturen - Definition
Begriffe und Zusammenhänge …
aus
Ralf Hartmut Güting und Stefan Dieker: Datenstrukturen und Algorithmen,
3. Auflage, Teubner-Verlag, Stuttgart 2004,
Reihe Leitfäden der Informatik
Gegenstand
Ebene
Mathematik
Algorithmierung
Funktion
Spezifikation
Implementierung
Implementierung
Algorithmus
Spezifikation
Implementierung
Programmierung
Peter Sobe
Algebra
Spezifikation
Programm,
Unterprogramm
u.ä.
Datenstruktur
Spezifikation
Implementierung
Typ, Struktur,
Modul mit eigenen
Daten und
Zugriffsfunktionen
6
Datenstrukturen
Für viele Anwendungen ist die Wahl einer geeigneten Datenstruktur
eine wesentliche Entscheidung
Frage: Wie organisiert man die Daten im Speicher, damit sie günstig
verarbeitet werden können? Günstig kann heißen:
 Programmcode zur Verarbeitung einfach und kurz
 wenig Anweisungen (schneller, weniger Prozessorbelastung)
 inhaltlich verwandte Elemente stehen nah beieinander (schneller)
 Geringer Speicherbedarf
Beispiele:
•
Lineares Feld mit sortierten Elementen, z.B. für Telefonbucheinträge
•
Baumstruktur mit Verweisen auf Vater- und Mutterelemente zur
Nachbildung von Stammbäumen
•
Netzstruktur, z.B. mit Struktur einer elektrischen Schaltung
Peter Sobe
7
Datenstrukturen - Arten
 Lineare Strukturen
 Felder – eindimensional indizierte Strukturen
 Listen, Warteschlangen, Stapel
 Zirkulare Strukturen
 einfache Ringe
 vermaschte Ringe
 Mehrdimensional indizierte Strukturen
 Bäume
 Allgemeiner Baum
 Binärbaum ( Darstellung als Feld möglich)
 Netze (allgemeine Graphen)
 Hash-Strukturen
Peter Sobe
8
Dynamische Speichertechnik (1)
Unter dynamischer Speichertechnik versteht man die Bildung
neuer Speicherplätze auf der Basis von Standarddatentypen und
deklarierten Strukturen zur Laufzeit. Es wird Speicherplatz zur
Laufzeit bereitgestellt und darauf ein neues Element einer
Datenstruktur initialisiert.
Dieses neue Element wird mit bereits vorhandenen Elementen
in der Datenstruktur in Beziehung gesetzt. Ebenso können auch
zur Laufzeit Elemente aus der Datenstruktur herausgelöst
werden und deren Speicherplatz wieder freigegeben werden.
Im Gegensatz werden die bisher benutzen Variablen und Felder
als statische Datenstrukturen angesehen. Die Sichtbarkeit und
der belegte Speicherplatz wird durch ihre Deklaration im
globalen Kontext des Programms oder innerhalb Funktionen
bestimmt.
Peter Sobe
9
Dynamische Speichertechnik (2)
Zur Realisierung solcher dynamischer Strukturen werden zwei
Technologien benötigt:
1. Die dynamische Speicherallokation und –freigabe
2. Die Verwendung der Zeigertechnik zum dynamischen
Herstellen und Lösen von Verbindungen von Elementen
innerhalb der Datenstruktur
Die vom Programmierer geschaffene Organisation der Daten
(also wo welches Element im Speicher steht und wie es mit den
anderen Elementen in Beziehung gesetzt wird) wird durch
Datenstrukturen beschrieben.
Peter Sobe
10
Dynamische Speichertechnik (3)
Die dynamische Speicherallokation und –freigabe wird in C mit den
Funktionen
void *malloc(size_t size); // gibt die Anfangsadr. des
// allokierten Bereichs zurück
void free(void *addresse);
durchgeführt, die in <alloc.h> zu finden sind.
Es werden size Bytes allokiert und die Adresse dieses Speicherbereiches zurückgegeben. Ist der Rückkehrwert NULL, ist nicht
genügend Speicher vorhanden. Ist die Größe in Bytes bekannt, kann
diese direkt als Argument übergeben werden, sonst empfiehlt sich die
Verwendung von sizeof(x), welche die notwendige Größe in Bytes für
das Element x ermittelt.
Bei der Speicherfreigabe mit free ist nur die Adresse des
Speicherbereichs anzugeben.
Peter Sobe
11
Dynamische lineare Datenstrukturen
Dynamisches Feld:
 eine eindimensionale Datenstruktur
 Elemente werden durch Zahlenindex angesprochen
 Feld kann wachsen und schrumpfen – durch dynamische
Speichertechnik
Lineare Liste:
 eine eindimensionale Datenstruktur
 Liste kann wachsen und schrumpfen
 Effizientes Einfügen neuer Elemente und Löschen
 (kein Zahlenindex zum Zugriff)
Weitere lineare Datenstrukturen: Warteschlange, Stapel (hier nicht
ausgeführt)
Peter Sobe
12
Dynamisches Feld (1)
Bei manchen Anwendungen ist die Anzahl der zu verarbeitenden
Elemente nicht bekannt und kann je nach Eingabewerten stark variieren.
Felder mit einer maximalen Anzahl von Elementen sind dann ungünstig.
Lösung:
Ein dynamisches Feld, das sich mit Mitteln der dynamischen
Speicherverwaltung (malloc, realloc, free) der benötigten Anzahl von
Elementen anpasst.
Zustand, nach Einfügen
von 3 weiteren Elementen
Startzustand, mit 3
Elementen als Reserve
Elemente
Zustand, nach Einfügen
von 2 Elementen
Elemente
Elemente
Zustand, nach Austragen
von 4 Elementen
Elemente
Peter Sobe
13
Dynamisches Feld (2)
Konstanten und Variablen zur Verwaltung:
#define N_START 3
#define N_DELTA 5
unsigned int n_allocated=0, n_elements=0;
Anfang:
 Allokieren von N_START Elementen
 Setzen von n_allocated = N_START
feld = (elem_typ*) malloc(sizeof(elem_typ)* N_START);
if (feld==NULL) { /* Fehlerbehandlung */}
n_allocated = N_START;
Peter Sobe
14
Dynamisches Feld (3)
Einfügen eines neuen Elements am Ende:
 Prüfen, ob ausreichend Speicher allokiert ist
 ggf. dynamisches Vergrößern des Feldes und Anpassen der Variable
n_allocated
 Einfügen des Elements in feld[n_elements]
 Erhöhen von n_elements um 1
if (n_elements+1 > n_allocated)
{
feld = realloc(feld, sizeof(element_typ) * (n_allocated + N_DELTA));
if (feld==NULL) { /* Fehlerbehandlung */ }
n_allocated = n_allocated + N_DELTA;
}
feld[n_elements] = neues_element;
n_elements = n_elements+1;
Peter Sobe
15
Dynamisches Feld (4)
Ausgliedern eines Elements an Position p (0≤p<n_elements):
 Verschieben der Feldelemente ab Position p+1 um eine Stelle nach links
(kleinerer Index)
 Verringern von n_elements um 1
 Prüfen, ob n_elements < n_acclocated – N_DELTA, wenn ja, dann
Verkleinern des Feldes mittels realloc()
for (i=p+1;i<n_elements; i++)
feld[i-1] = feld[i];
n_elements = n_elements – 1;
if (n_elements < n_allocated – N_DELTA)
{
feld = realloc(feld, sizeof(element_typ) * (n_allocated - N_DELTA));
n_allocated = n_allocated – N_DELTA;
}
Peter Sobe
16
Zeigertechnik bei linearen Listen
Die Verwendung der Zeigertechnik zum dynamischen
Herstellen und Lösen von Verbindungen von Objekten
und der Datenstruktur erfordert in C den Einsatz des
Verbundtyps struct{...}, da die Zeiger in das zu verbindende
Objekt einbezogen werden müssen.
Da das Objekt selbst eine Struktur ist, muss demzufolge der
Zeiger auf die nächste Struktur auch vom gleichen Strukturtyp
sein, z.B.
struct list_element
{ float f;
// weitere Elemente
struct list_element *next;
};
// Zeiger auf Nachfolger-Element
Peter Sobe
17
Lineare Listen
Beispiel:
Objekt 1
Objekt 3
Objekt 2
Listenelement
Durch einen Verbund (struct) müssen eigentliches Objekt
und
als Zeiger vereint werden.
struct listelem
{ float t; char position[STRLEN];
Objekt
struct listelem *next;
};
typedef struct listelem list_elem_t;
…
list_elem_t *anker = NULL;
Peter Sobe
18
Lineare Liste - Definition
Eine Liste ist eine verkettete Folge von Elementen (Objekten),
die aus Standarddatentypen zusammengesetzt sind und für die gilt:
1. Es gibt genau ein Listenelement, das keinen Vorgänger hat
und Listenanfang heißt. Auf dieses Element zeigt der
Listenanker.
2. Es gibt genau ein Listenelement, das keinen Nachfolger hat
und Listenende heißt.
3. Die übrigen Listenelemente haben genau einen Vorgänger
und genau einen Nachfolger.
4. Alle Listenelemente sind vom Listenanker aus durch
Nachfolgerbildung in endlich vielen Schritten erreichbar.
Peter Sobe
19
Listen
Objekt 1
Objekt 3
Objekt 2
NULLZeiger
Objekt 1 ist der Listenanfang und Objekt 3 das Listenende.
Die Objekte enthalten die Nutzdaten, die wiederum aus mehreren
Elementen bestehen können. Als zusätzliches charakteristisches
Element tritt ein Zeiger hinzu, der die Nachfolgerelation darstellt.
Das Listenende enthält den NULL-Zeiger. Die Anzahl der Objekte
wird als Länge der Liste bezeichnet. Da die Nachfolgerelation
eine Ordnungsrelation induziert, spricht man von linearen
dynamischen Datenstrukturen.
Hier: einfach verkettete Liste – es gibt auch doppelt verkettete
Listen (vorwärts und rückwärts verkettet)
Peter Sobe
20
Operationen mit Listen
Als typische Operationen mit Listen gelten:
1. Das Erzeugen eines Listenelementes (create)
2. Das Löschen eines Listenelementes (obfree)
3. Das Einketten eines Listenelementes an eine
festzulegende Position (insert)
4. Das Anhängen eines Listenelementes als neues letztes
Element (append)
5. Das Ausketten eines bestimmten Listenelementes
(dequeue)
6. Das Suchen eines Listenelementes (find)
7. Das Traversieren einer Liste (traverse)
Peter Sobe
21
Erzeugen eines Listenelementes (create)
list_elem_t *create(list_elem_t x)
{ list_elem_t *neu;
neu= (list_elem_t*) malloc(sizeof x); //Reservieren von Speicherplatz
*neu=x;
//Belegung des Speichers
return neu;
}
Der Funktion wird ein Objekt x übergeben, das die Werte für das neu
zu erzeugende Heap-Objekt enthält.
Der Rückkehrwert von create() ist die Heapadresse neu.
Peter Sobe
22
Einketten eines Listenelementes (insert)
void insert(list_elem_t *pos, list_elem_t *neu)
{ // pos zeigt auf das Listenelement, hinter dem das
// Listenelement neu eingekettet werden soll
neu->next=pos->next;
pos->next = neu;
}
pos->n
pos
o
o
neu
o
neu->n = pos->n
Voraussetzung: Die Liste hat bereits Elemente.
23
Peter Sobe
Ausketten eines Listenelementes (dequeue)
void dequeue(list_elem_t *pos)
{ // pos zeigt auf Element vor dem auszukettenden Element
list_elem_t *h;
h=pos->next;
pos->next=(pos->next)->next;
free(h);
}
pos->n = (pos->n)->n
pos
o
o
o
pos->n
free(h)
Gesonderte Behandlung erforderlich, wenn erstes Element auszuketten
ist.
Peter Sobe
24
Suchen eines Listenelementes (find)
list_elem_t *find(int krit, list_elem_t *anker)
{ list_elem_t *a;
a=anker;
while (a!=NULL)
{
if ((a->i)==krit) return a;
//Objekt a gefunden
a=a->next;
}
return NULL;
//nichts gefunden
}
o
a
krit ?
o
a=a->n
krit ?
Peter Sobe
25
Nichtlineare dynamische Datenstrukturen
Eine nichtlinear verkettete Menge von Elementen (Objekten),
die aus Standarddatentypen zusammengesetzt sind, nennt man
nichtlineare dynamische Datenstruktur.
Solche Strukturen sind vor allem:

Bäume (Binärbäume, allg. Bäume)
typisch für diese Strukturen ist die Anordnung als
Hierarchie, d.h. zwischen den Elementen besteht noch
eine Halbordnungsrelation

Graphen (kreisfreie Graphen, allgemeine Graphen)
bei kreisfreien Graphen liegt noch eine Heterarchie als
Struktur vor, während bei allgemeinen Graphen nur netzartige
Strukturen als allgemeinster Fall auftreten.
Peter Sobe
26
Nichtlineare dynamische Datenstrukturen
Viele Anwendungsalgorithmen basieren auf solchen nichtlinearen
Strukturen.
Bei netzartigen Graphstrukturen müssen diese Algorithmen mit
erschöpfendem Durchsuchen aller Möglichkeiten arbeiten. Das
erfordert programmseitig spezielle Hilfen (globale stacks).
Bei Hierarchien (Bäume) reicht die einfache Rekursivität aus.
Bei vielen Sonderfällen in Hierarchien nutzt man die s.g. Teile-undHerrsche-Algorithmen. Diese Algorithmen teilen den aktuellen
Bearbeitungsraum in Teilbearbeitungsräume und wenden dann den
gleichen Algorithmus rekursiv auf die Teile an (herrschen), solange
bis eine weitere Teilung nicht mehr sinnvoll ist.
Beispiel: Binärbäume
27
Peter Sobe
Binärer Baum und Rekursion (1)
Wurzel B0
Linker Teilbaum
B0L
Rechter Teilbaum
B0R
Binärbaum über Knotenmenge V
B0= (W0, B0L ,B0R)  (W0, , )
Bx= (Wx, BxL ,BxR)  (Wx, , )
mit W0,B0 , Wx,Bx , ε V und  als leeres Element
Indizes werden durch Aneinandereihung gebildet, z.B.
x=0L → xL = 0LL, x=0R → xL = 0RL usw.
Ein Baum ist entweder ein einzelner Knoten oder ein als Wurzel
dienender Knoten, der mit einer Menge von Bäumen verbunden ist.
(beim Binärbaum mit zwei Teilbäumen verbunden)
Peter Sobe
28
Binärer Baum und Rekursion (2)
Verschiedene Strategien zum Traversieren des Baums
Preorder:
1. Besuche die Wurzel des Baumes
2. Besuche den linken Teilbaum
3. Besuche den rechten Teilbaum
1
2
5
3
4
6
7
29
Peter Sobe
Binärer Baum und Rekursion (3)
Verschiedene Strategien zum Traversieren des Baums
Inorder (Symmetrische Strategie):
1. Besuche den linken Teilbaum
2. Besuche die Wurzel
3. Besuche den rechten Teilbaum
4
2
1
Peter Sobe
6
3
5
7
30
Binärer Baum und Rekursion (4)
Verschiedene Strategien zum Traversieren des Baums
Postorder:
1. Besuche den linken Teilbaum
2. Besuche den rechten Teilbaum
3. Besuche die Wurzel
7
6
3
1
2
4
5
31
Peter Sobe
Binärer Baum und Rekursion (5)
Strategien zum Traversieren des Baums (Fortsetzung)
Alle bisherigen Verfahren besuchen entweder tiefe Knoten oder
links stehende Knoten zuerst.
Bei Suchbäumen werden Lösungen u.U. erst spät gefunden.
Level-Order-Traversierung:

Besuche die Knoten “von links nach rechts“ innerhalb einer
Ebene, danach die jeweils tiefere Ebene.

Diese Reihenfolge wird nicht durch Zeiger in innerhalb der
Baumstruktur unterstützt

Diese Reihenfolge wird auch nicht durch Rekursion unterstützt
Peter Sobe
32
Binärer Baum mit sortierten Daten (1)
Baum-Elemente:
ID
Daten
Zeigerlinks
Zeigerrechts
Zeiger auf
Baum-Wurzel:
15
8
4
…
…
…
12
20
…
17
…
…
23
…
NULL-Zeiger, wenn
Nachfolge-Elemente
nicht vorhanden:
Peter Sobe
33
Binärer Baum mit sortierten Daten (2)
Suchen eines Elements mit ID=x im sortierten Binärbaum:
Knoten = Wurzel
Aufsuchen Knoten:
 falls ID==x dann gefunden, Ende
 falls x<ID: Verfolge Zeiger-links
 falls x>ID: Verfolge Zeiger-rechts
Nach „Verfolge“ wird der jeweilige Knoten nach o.g. Regel
besucht, solange bis
 Knoten mit ID gefunden
 oder ein Verfolgen auf den NULL-Zeiger trifft. Dann ist
das gesuchte Element im Baum nicht vorhanden.
Peter Sobe
34
Bewertung Binärbaum
Wurzel
Beispiel:
15
8
20
4
2
17
12
6
10
16
14
23
19
21
26
im Bild
allgemein
Berechnung
Anzahl Ebenen
4
e
Konstruktionsparameter
Anzahl Elemente
15
n = 2e-1
Schritte zum Finden 4 (inkl. Zugriff
eines Elements
auf Wurzel)
Peter Sobe
s=e
s =┌ log2 n ┐
35
Herunterladen