Informatik · Klasse 11–12 · aktualisiert
Abstrakter Datentyp: ADT einfach erklärt
Du lernst, was ein abstrakter Datentyp festlegt, wie sich Signatur und Semantik unterscheiden und wie du einen Stack spezifizierst.
0 von 9 Aufgaben gelöst
Kurz gesagt
Ein abstrakter Datentyp (ADT) legt fest, welche Operationen es gibt und was sie bewirken; wie sie im Speicher umgesetzt werden, lässt er offen. Die Signatur nennt Namen und Typen, die Semantik die Bedeutung. Ein Stack lässt sich so mit push, pop und top beschreiben.
Stell dir den „Zurück“-Knopf einer App vor: Das Zuletzt-Geöffnete käme zuerst wieder dran. Genau so arbeitet ein Stack. Du musst dafür nicht wissen, wie der Speicher dahinter aufgebaut ist. Diese Trennung zwischen „Was kann ich damit tun?“ und „Wie ist es gebaut?“ ist die Idee des abstrakten Datentyps.
Deine Lernziele
Hake ab, was du schon kannst. Löst du alle Aufgaben eines Abschnitts, hakt Mela das Ziel für dich ab.
4 Abschnitte
Ein abstrakter Datentyp, kurz ADT, fasst Daten mit der Menge aller zulässigen Operationen zusammen, über die man auf diese Daten zugreift. Der Zugriff läuft also nur über diese Operationen. Deshalb sind die Daten nach außen gekapselt.
Ein ADT beschreibt, was die Operationen tun. Wie sie es tun, lässt er offen. Die Umsetzung im Speicher heißt Implementierung. Wer den ADT benutzt, muss die innere Umsetzung nicht kennen, denn der ADT verwaltet seine Daten im Speicher selbst.
Dazu gehören angestrebte Eigenschaften. Bei der Kapselung kann der Anwender in die interne Struktur der Daten nicht eingreifen. Dadurch ist die Gefahr, Daten ungewollt zu löschen oder zu verändern, deutlich herabgesetzt. Bei der Modularität können viele Verbesserungen nachträglich über den ADT übernommen werden, ohne dass sich die Programme ändern, die ihn benutzen.
Definition
Abstrakter Datentyp (ADT)
Ein ADT besteht aus Daten und der Festlegung aller zulässigen Operationen auf diese Daten. Er legt fest, was die Operationen tun, nicht aber, wie sie umgesetzt werden.
Merke: Ein ADT legt das Was fest. Das Wie (die Implementierung) bleibt offen.
Teste dich
Die Signatur ist die formale Schnittstelle einer Operation. Sie besteht aus dem Namen und den Parametern (Anzahl, Art und Reihenfolge). Bei stark typisierten Programmiersprachen, in denen jeder Wert einen festen Typ hat, gehört auch der Typ des Ergebnisses dazu. Auch die Menge aller Operationen eines ADT nennt man Signatur.
Die Semantik legt die Bedeutung und das Zusammenspiel der Operationen fest. Eine Spezifikation eines ADT besteht aus beidem: aus einer Signatur und einer Semantik.
Eine Signatur allein ist rein syntaktisch. Das heißt: Sie beschreibt nur die äußere Form, nicht die Bedeutung. Sie nennt Namen, die Stelligkeit (wie viele Eingaben eine Operation hat) und das Ergebnis. Ob ein Zeichen wie + wirklich addiert, steht erst in der Semantik.
Das zeigt der Vergleich von Stapelspeicher (Stack) und Warteschlange (Queue). Ihre Signaturen sind gleich aufgebaut: push und enqueue nehmen beide ein Element und die Struktur und liefern die Struktur zurück. Unterschiede ergeben sich erst mit der Semantik: Der Stack arbeitet nach dem Prinzip Last in, first out, die Queue nach First in, first out.
Definition
Signatur
Die Signatur nennt den Namen einer Operation, ihre Parameter und, bei stark typisierten Programmiersprachen, auch den Typ des Ergebnisses. Sie sagt nichts darüber, was die Operation bedeutet.
Beispiel: pop: Signatur und Semantik
- 1Signatur: pop: STACK → STACK
- 2Gelesen: pop bekommt einen Stack und liefert einen Stack.
- 3Semantik: pop entfernt das oberste Element und gibt den neuen Stack zurück.
Teste dich
Ein Stack (Stapelspeicher) arbeitet nach dem Prinzip Last in, first out, kurz LIFO. Stell dir einen Stapel Umzugskisten vor: Du legst eine neue Kiste oben drauf und nimmst auch nur oben eine weg. Der Zugriff ist im Regelfall nur auf das oberste Element möglich.
Die Signatur des Stacks hat fünf Operationen. emptyStack erzeugt einen leeren Stack. isStackEmpty fragt, ob der Stack leer ist. push packt ein Element oben auf den Stack. pop entfernt das oberste Element und gibt den neuen Stack zurück. top gibt das oberste Element zurück, ohne es zu entfernen. Eine andere Beschreibung des Stapelspeichers lässt pop das oberste Objekt liefern und entfernen; die Operation zum bloßen Ansehen heißt dort peek (zuweilen auch top).
Die Semantik besteht aus Regeln. Dabei steht x für ein Element und s für einen Stack. Der Sonderfall ist der leere Stack: Auf ihm ergeben pop und top den Wert ERROR, also einen Fehler. Auch eine Java-Beschreibung des Stacks (ein Interface, das nur Signaturen festlegt und die Semantik in Kommentaren nennt) verlangt für pop und top als Voraussetzung: Der Stack ist nicht leer.
| Regel | Bedeutung |
|---|---|
| isStackEmpty(emptyStack()) = TRUE | Der neu erzeugte Stack ist leer. |
| isStackEmpty(push(x,s)) = FALSE | Nach einem push ist der Stack nicht leer. |
| pop(push(x,s)) = s | pop nach push liefert wieder den alten Stack. |
| top(push(x,s)) = x | top liefert das zuletzt gelegte Element. |
| pop(emptyStack()) = ERROR | Aus dem leeren Stack lässt sich nichts entfernen. |
| top(emptyStack()) = ERROR | Im leeren Stack gibt es kein oberstes Element. |
Merke: Sonderfall leerer Stack: pop und top ergeben dort ERROR.
Teste dich
Ein Stack kann auf verschiedene Weisen implementiert werden. Zwei Möglichkeiten bieten dieselbe Schnittstelle an, zum Beispiel stack_empty, stack_push und stack_pop. Von außen sehen sie gleich aus. Innen sind sie verschieden gebaut.
Liste: Jedes Element liegt in einem eigenen Knoten. Der Knoten speichert einen Zeiger (einen Verweis) auf die Daten und einen Zeiger next auf den nächsten Knoten. Der Stack merkt sich nur den obersten Knoten (top). Bei push entsteht ein neuer Knoten, sein next zeigt auf das bisherige top, und er wird das neue top. Bei pop wird top auf das next des obersten Knotens gesetzt. Die Größe stimmt stets mit der Anzahl der Elemente überein. Dafür ist der Speicherverbrauch hoch, weil für jedes Element zusätzlich ein Zeiger auf das nächste gespeichert wird. Auch ist das Einfügen und Auslesen nicht in konstanter Zeit möglich, da dabei stets Speicheroperationen ausgeführt werden.
Feld: Der Stack besitzt ein Feld (bottom) und die Zahlen len und size. Aus dem Code folgt: len ist die Anzahl der abgelegten Elemente, denn stack_empty prüft, ob len gleich 0 ist. size ist die Größe des Feldes. Bei push kommt das Element an die Stelle len, danach wird len um 1 größer. Ist len gleich size, ist das Feld voll und wird vor dem Ablegen vergrößert. Gibt es dafür keinen Speicher mehr, meldet push einen Fehler (ERR_NOMEM). Der Speicherverbrauch pro Element ist geringer, weil kein Zeiger auf das nachfolgende Element nötig ist. Der Zugriff auf die Elemente ist in konstanter Zeit möglich, solange keine Speicheroperationen durchgeführt werden müssen.
| Merkmal | Liste | Feld |
|---|---|---|
| Operationen | stack_empty, stack_push, stack_pop | stack_empty, stack_push, stack_pop |
| Aufbau | Knoten mit Zeiger auf die Daten und next, Stack merkt top | Feld bottom mit den Zahlen len und size |
| Speicher pro Element | höher (Zeiger auf das nächste Element) | geringer (kein Zeiger nötig) |
Merke: Liste und Feld bieten dieselben Operationen an, setzen sie aber verschieden um. Das ist die Implementierung.
Teste dich
Alles auf einen Blick
Abstrakter Datentyp
ADT
Daten plus alle zulässigen Operationen, Zugriff nur über diese Operationen
Offen
Wie die Operationen umgesetzt werden, legt der ADT nicht fest
Signatur und Semantik
Namen und Typen auf der einen, Bedeutung auf der anderen Seite
Stack
Last in, first out mit emptyStack, isStackEmpty, push, pop und top
Leerer Stack
pop und top ergeben ERROR
Implementierung
Liste und Feld bieten dieselben Operationen, setzen sie aber verschieden um
Musteraufgabe · Schritt für Schritt
Einen Stack Schritt für Schritt durchspielen (ausgedachtes Beispiel)
- 1Annahme: Die Elemente sind Zahlen. Wir starten mit s0 = emptyStack(). Es gilt isStackEmpty(s0) = TRUE.
- 2s1 = push(4, s0): Oben liegt 4. Wegen isStackEmpty(push(x,s)) = FALSE ist s1 nicht leer.
- 3s2 = push(9, s1): Oben liegt 9, darunter 4.
- 4top(s2) = 9, denn top(push(x,s)) = x.
- 5pop(s2) = s1, denn pop(push(x,s)) = s. Oben liegt wieder 4.
- 6pop(s1) = s0, denn s1 ist push(4, s0). Der Stack ist wieder leer.
- 7pop(s0) = ERROR, denn pop(emptyStack()) = ERROR.
Fehler finden
Prüfe die Aussagen zu Signatur und Semantik In einer Zeile steckt ein Fehler. Tippe sie an.
Fehler in Zeile 3Gleich aufgebaute Signaturen sagen nichts über die Arbeitsweise. Richtig: Unterschiede zwischen Stack und Queue ergeben sich erst durch die Semantik.
Lückentext
Wähl in jeder Lücke das passende Wort und prüf dann deine Antworten.
Ein ADT lässt die der Operationen offen. Name, Parameter und bei stark typisierten Sprachen auch der Ergebnistyp einer Operation bilden ihre . Nach der Spezifikation ergibt pop(emptyStack()) den Wert .
Karteikasten
Erst selbst überlegen, dann umdrehen.
Übung mit Feedback