• Keine Ergebnisse gefunden

Entwurfsprinzipien für Datentypen

N/A
N/A
Protected

Academic year: 2022

Aktie "Entwurfsprinzipien für Datentypen"

Copied!
54
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

Algorithmen und Datenstrukturen

Werner Struckmann

Wintersemester 2005/06

(2)

4. Listen und abstrakte Datentypen

4.1 Abstrakte Datentypen 4.2 Listen

4.3 Keller

4.4 Schlangen

(3)

Datentypen

Unter einem Datentyp versteht man die Zusammenfassung von Wertebereichen und Operationen zu einer Einheit.

Abstrakter Datentyp: Schwerpunkt liegt auf den

Eigenschaften, die die Wertebereiche und Operationen besitzen.

Konkreter Datentyp: Realisierung der Wertebereiche und Operationen stehen im Vordergrund.

Primitive Datentypen: bool, char, int, real, . . .

(4)

Datentypen

Komplexe Datentypen, sog. Datenstrukturen, werden durch

Kombination primitiver Datentypen gebildet. Sie besitzen selbst spezifische Operationen. Datenstrukturen können vorgegeben oder selbstdefiniert sein.

Dabei wird über das Anwendungsspektrum unterschieden in

Generische Datentypen: Werden für eine Gruppe ähnlicher Problemstellungen entworfen und sind oft im Sprachumfang bzw. der Bibliothek einer Programmiersprache enthalten (Feld, Liste, Keller, Schlange, Verzeichnis, . . . ).

Spezifische Datentypen: Dienen der Lösung einer eng umschriebenen Problemstellung und werden im

Zusamenhang mit einem konkreten Problem definiert.

(5)

Entwurfsprinzipien für Datentypen

Anforderungen an die Definition eines Datentyps:

Die Spezifikation eines Datentyps sollte unabhängig von seiner Implementierung erfolgen. Dadurch kann die

Spezifikation für unterschiedliche Implementierungen verwendet werden.

Reduzierung der von außen sichtbaren (zugänglichen)

Aspekte auf die Schnittstelle des Datentyps. Dadurch kann die Implementierung später verändert werden, ohne dass Programmteile, die den Datentyp benutzen, angepasst werden müssen.

(6)

Entwurfsprinzipien für Datentypen

Aus diesen Anforderungen heraus ergeben sich zwei Prinzipien:

Kapselung (encapsulation): Alle Zugriffe geschehen immer nur über die Schnittstelle des Datentyps.

Geheimnisprinzip (programming by contract): Die interne Realisierung des Datentyps bleibt dem Benutzer verborgen.

(7)

Abstrakte Datentypen

Informatik-Duden: Ein Datentyp, von dem nur die Spezifikation und Eigenschaften (in Form von zum Beispiel Regeln oder

Gesetzmäßigkeiten) bekannt sind, heißt abstrakt. Man abstrahiert hierbei von der konkreten Implementierung.

Dies kann für

eine klarere Darstellung,

für den Nachweis der Korrektheit oder

für Komplexitätsuntersuchungen von Vorteil sein.

Ein abstrakter Datentyp wird kurz als ADT bezeichnet.

Ein ADT wird ohne Kenntnis der internen Realisierung verwendet (Geheimnisprinzip). Dabei wird nur von der Schnittstelle

(Kapselung) Gebrauch gemacht.

(8)

Abstrakte Datentypen

Wir werden ADTen durch algebraische Spezifikationen beschreiben:

Eine Signatur bildet die Schnittstelle eines ADTs.

Mengen und Funktionen, die zur Signatur „passen“, heißen Algebren.

Axiome schränken die möglichen Algebren ein.

Der Themenkomplex „algebraische Spezifikationen“ wird hier nur einführend behandelt.

(9)

Signaturen

Eine Signatur

Σ = (

S,

Ω)

besteht aus

einer Menge von Sorten S und

einer Menge von Operatorsymbolen

.

Jedes Operatorsymbol f

:

s1 . . . sns besteht aus einem Namen f, einer Folge s1, . . . , snS, n0, von

Argumentsorten und einer Wertesorte s ∈ S.

Operatorsymbole ohne Parameter heißen Konstante.

(10)

Algebren

Es sei eine Signatur

Σ = (

S,

Ω)

gegeben. Eine Algebra AΣ

= (

AS,A

)

zur Signatur

Σ

besteht aus

den Trägermengen As der Sorten s ∈ S, d. h. AS

=

{As | sS}, und

(partiellen) Funktionen auf den Trägermengen Af

:

As1 × . . . × AsnAs,

d. h. A

=

{Af | f

:

s1 . . . sns

}.

(11)

Beispiel

Eine Signatur für den ADT „Bool“ sei (vorläufig) gegeben durch:

S

=

{Bool}

Ω =

{true

:

Bool, false

:

Bool}

Mögliche Algebren für diese Spezifikation sind:

ABool

=

{T, F} AtrueT AfalseF erwartungskonform ABool

=

N Atrue1 Afalse0 große Trägermenge ABool

=

{1} Atrue1 Afalse1 kleine Trägermenge

(12)

Axiome

Die Zahl der möglichen Algebren kann durch Axiome eingeschränkt werden.

Axiome sind (hier) Gleichungen, die die Funktionen in ihrer Wirkung einengen.

Eine Menge von Axiomen bezeichnen wir mit

Φ

.

(13)

Signaturdiagramme

Signaturen lassen sich übersichtlich durch Signaturdiagramme mit Sorten als Knoten und Operatorsymbolen als Kanten darstellen:

sn s0 ...

f

s

Ausblick: Signaturdiagramme sind Beispiele für Graphen, die wir in Kürze betrachten werden.

(14)

Notationen für Operatorsymbole

Mit dem Platzhaltersymbol _ für Argumente von Funktionen führen wir die folgenden Notationen ein:

Präfix: f

(

_

)

,

+ +

_, . . . f

(

x

)

,

+ +

i

Infix: _ ≤ _, _

+

_, _ ∨ _, . . . a ≤ b, m

+

n, p ∨ q Postfix: _

!

, _2, . . . n

!

, x2

Mixfix: |_|, if_then_else_fi, . . . |x|

Bei der Präfixnotation schreiben wir auch kurz f.

(15)

ADT der Wahrheitswerte

S

=

{Bool}

Ω =

{true

:

Bool, false

:

Bool,

¬_

:

Bool → Bool,

_ ∨ _

:

Bool × BoolBool, _ ∧ _

:

Bool × BoolBool}

Φ =

{xfalse

=

false ∧ x

=

false, x ∧ true

=

true ∧ x

=

x,

x ∨ true

=

true ∨ x

=

true, false ∨ false

=

false,

¬false

=

true, ¬true

=

false}

Bool true

false

,

¬

(16)

ADT der natürlichen Zahlen

S

=

{Nat}

Ω =

{0

:

Nat,

succ

:

Nat → Nat}

Φ =

{}

Nat

0 succ

Damit wird z. B. die Zahl 3 als

succ

(

succ

(

succ

(

0

))) =

succ3

(

0

)

dargestellt.

Der Term succn

(

0

)

stellt die natürliche Zahl n dar. Da es keine Axiome gibt, kann dieser Term nicht vereinfacht werden.

(17)

ADT der natürlichen Zahlen

S

=

{Nat}

Ω =

{0

:

Nat,

succ

:

Nat → Nat,

add

:

Nat × NatNat}

Φ =

{add

(

x, 0

) =

x,

add

(

x, succ

(

y

)) =

succ

(

add

(

x, y

))

}

Nat

0 succ

add

Dies ist eine formale Spezifikation der natürlichen Zahlen mit der Konstanten 0, der Nachfolgerfunktion und der Addition.

Implementierungen sind nicht verpflichtet, die Operationen gemäß der Axiome zu realisieren. Sie müssen lediglich das durch die

Axiome beschriebene Verhalten gewährleisten.

(18)

Beispiel

Es soll 2

+

3 berechnet werden.

2

+

3

=

add

(

succ

(

succ

(

0

))

,succ

(

succ

(

succ

(

0

))))

=

succ

(

add

(

succ

(

succ

(

0

))

, succ

(

succ

(

0

))))

=

succ

(

succ

(

add

(

succ

(

succ

(

0

))

,succ

(

0

))))

=

succ

(

succ

(

succ

(

add

(

succ

(

succ

(

0

))

,0

))))

=

succ

(

succ

(

succ

(

succ

(

succ

(

0

)))))

Der ADT Nat erfüllt eine besondere Eigenschaft: Jeder Term

besitzt eine eindeutige Normalform succn

(

0

)

. Diese entsteht, wenn man die Gleichungen von links nach rechts anwendet, bis alle

add-Operationssymbole verschwunden sind.

(19)

Implementierung eines abstrakten Datentyps

Implementierung eines ADTs heißt:

Realisierung der Sorten s ∈ S durch Datenstrukturen As Beispiel: Nat { Bm (m-stellige Vektoren über {0, 1}

)

Realisierung der Operatoren f

:

s1 . . . sns durch Funktionen Af

:

As1 × . . . × AsnAs

Beispiel: add

:

Nat × NatNat { _

+

_

:

Bm × BmBm

Sicherstellen, dass die Axiome (in den Grenzen der

darstellbaren Werte bzw. der Darstellungsgenauigkeit) gelten.

(20)

Implementierung eines abstrakten Datentyps

Beispiel: ANat

=

Bm

Darstellung von x ∈ N mit m Binärziffern zm1, . . . ,z0B:

x

=

mX1 i=0

zi · 2i

Darstellbarer Zahlenbereich: 0 ≤ x ≤ 2m − 1

Die Gültigkeit der Rechengesetze muss gewährleistet sein.

(21)

Alternative Notation

Im Folgenden wird alternativ zur mathematischen Schreibweise folgende an Programmiersprachen angelehnte Notation genutzt:

S

=

{Nat}

Ω =

{0

:

Nat,

succ

:

Nat → Nat,

add

:

Nat × NatNat}

Φ =

{add

(

x, 0

) =

x,

add

(

x, succ

(

y

)) =

succ

(

add

(

x, y

))

}

type

Nat

import

operators

0

:

Nat

succ

:

Nat → Nat

add

:

Nat × NatNat

axioms

i,jNat

add

(

i, 0

) =

i

add

(

i, succ

(

j

)) =

succ

(

add

(

i, j

))

(22)

Algebraische Spezifikationen

Eine Import-Anweisung erlaubt die Angabe der Sorten, die zusätzlich zur zu definierenden Sorte benötigt werden.

Eine algebraische Spezifikation eines ADTs besteht aus

einer Signatur und

aus Axiomen und ggf. zusätzlich

aus Import-Anweisungen.

Eine Algebra, die die Axiome erfüllt, heißt Modell der Spezifikation.

Auf die Frage nach der Existenz und Eindeutigkeit von Modellen können wir hier nicht eingehen.

(23)

Lineare Datentypen

In diesem Kapitel besprechen wir die linearen Datentypen Liste, Keller und Schlange.

Nichtlineare Datentypen sind zum Beispiel Bäume und Graphen, die später behandelt werden.

In vielen Programmiersprachen sind lineare Datentypen und ihre grundlegenden Operationen Bestandteil der Sprache oder in Bibliotheken verfügbar.

Listen spielen in der funktionalen Programmierung eine große Rolle.

(24)

Listen

Eine (lineare) Liste ist eine Folge von Elementen eines gegebenen Datentyps.

Es können jederzeit Elemente in eine Liste eingefügt oder Elemente aus einer Liste gelöscht werden.

Der Speicherbedarf einer Liste ist daher dynamisch, d. h., er steht nicht zur Übersetzungszeit fest, sondern kann sich noch während der Laufzeit ändern.

Listen und ihre Varianten sind die wichtigsten dynamischen Datenstrukturen überhaupt.

xn

x1 x2 x3 . . .

(25)

Typische Operationen für Listen

Erzeugen einer leeren Liste

Testen, ob eine Liste leer ist

Einfügen eines Elements am Anfang/Ende einer Liste

Löschen eines Elements am Anfang/Ende einer Liste

Rückgabe des ersten/letzten Elements einer Liste

Bestimmen von Teillisten, zum Beispiel: Liste ohne Anfangselement

Testen, ob ein gegebener Wert in einer Liste enthalten ist

Berechnen der Länge einer Liste

Bestimmen des Vorgängers/Nachfolgers eines Listenelements

(26)

Parametrisierte Datentypen

Die im Folgenden eingeführten abstrakten Datentypen sind parametrisiert.

Die Spezifikation eines abstrakten Datentyps kann ein oder mehrere Sortenparameter enthalten, die unterschiedlich instanziiert werden können.

Beispiel: In der Spezifikation der Listen tritt der Parameter T auf. List

(

T

)

kann dann beispielsweise durch List

(

Bool

)

,

List

(

Nat

)

oder List

(

List

(

Nat

))

instanziiert werden.

(27)

Listen

T Liste Nat

head length

: tail

[ ]

type

List

(

T

) import

Nat

operators

[] :

List

_

:

_

:

T × ListList head

:

List → T

tail

:

List → List length

:

List → Nat

axioms

lList,∀xT head

(

x

:

l

) =

x

tail

(

x

:

l

) =

l length

([]) =

0

length

(

x

:

l

) =

succ

(

length

(

l

))

(28)

Implementierungen

Listen können mithilfe verketteter Strukturen implementiert werden. Hier gibt es viele Varianten: einfache und doppelte Verkettung, Zeiger auf das erste und/oder letzte Element der Liste, zirkuläre Listen, . . .

Alternativ können Listen durch Felder implementiert werden.

Die Methoden sehen dann natürlich anders aus.

Beispielsweise müssen beim Einfügen eines Elements am Anfang der Liste alle anderen Elemente um eine Position nach hinten verschoben werden.

Dynamische Datenstrukturen nutzen den zur Verfügung stehenden Speicherplatz weniger effizient aus. Wenn der benötigte Speicherplatz vor dem Programmlauf genau abgeschätzt werden kann, können statische Strukturen sinnvoll sein.

(29)

Implementierungen

Eine Liste kann als Verkettung einzelner Objekte implementiert werden. Man spricht von einer einfach verketteten Liste.

Beispiel: Liste von Namen („Oliver“, „Peter“, „Walter“)

head

Oliver Peter Walter

tail

Die Kästen stellen sogenannte Knoten (engl. node) dar. Jeder Knoten enthält einen Wert vom Typ T und eine Referenz auf den nächsten Knoten. Der letzte Knoten enthält den leeren Verweis.

(30)

Bestimmen des ersten Listenelements

func

head(l: Liste): T

begin

var

k: <Referenz auf Knoten>;

k

<Kopf der Liste l>;

if

k <ungültige Referenz>

then return

<keinen Wert>;

fi;

return

k.wert;

end

Aufwand:

Θ(

1

)

(31)

Einfügen eines Listenelements am Anfang

1. Erzeugen eines neuen Knotens.

2. Referenz des neuen Knotens auf den ehemaligen Kopfknoten setzen.

3. Kopfreferenz der Liste auf den neuen Knoten setzen.

head

Oliver Peter Walter

X

Aufwand:

Θ(

1

)

(32)

Einfügen eines Listenelements am Anfang

func

addFirst(v: T; l: Liste): Liste

begin var

k: <Referenz auf Knoten>;

k

<neuer Knoten>;

k.wert

v;

k.referenz

<Kopf der Liste l>;

<Kopf der Liste l>

k;

return

l;

end

(33)

Einfügen eines Listenelements am Ende

1. Navigieren zum letzen Knoten.

2. Erzeugen eines neuen Knotens.

3. Einhängen des neuen Knotens.

head

Oliver Peter Walter

X

Aufwand:

Θ(

n

)

(34)

Einfügen eines Listenelements am Ende

func

addLast(v: T; l: Liste): Liste

begin var

k: <Referenz auf Knoten>;

var

nk: <Referenz auf Knoten>;

k

<Kopf der Liste l>;

nk

<neuer Knoten>;

nk.wert

v;

nk.referenz

<ungültige Referenz>;

if

k <ungültige Referenz>

then

<Kopf der Liste l>

nk;

return

l;

fi;

while

k.referenz <gültige Referenz>

do

k

k.referenz;

od;

k.referenz

nk;

return

l;

end

(35)

Vorgänger in einfach verketteten Listen

1. Navigieren bis zum Knoten k, dabei Referenz v auf

Vorgängerknoten des aktuell betrachteten Knotens mitführen.

2. Rückgabe des Knoten v. Aufwand:

Θ(

n

)

Beispiel: Bestimmung des Vorgängers von „Walter“

head

Oliver Peter Walter

α β γ

Schritt 1 (v=⊥) Betrachteter Knoten: α

Schritt 2 (v=α) Betrachteter Knoten: β

Schritt 3 (v=β) Betrachteter Knoten:γ

Objekt gefunden, uckgabe von v=β

(36)

Vorgänger in einfach verketteten Listen

Finden des m-ten Vorgängers eines Knotens k in einer Liste:

1. Navigieren bis zum Knoten k, die Referenz v wird wie vorher mitgeführt, beginnt aber erst am Kopf der Liste, sobald der

(

m

+

1

)

-te Knoten betrachtet wird.

2. Rückgabe von v.

Beispiel: Bestimmung des zweiten Vorgängers von „Walter“

head

Oliver Peter Walter

α β γ

Schritt 1 (v=⊥) Betrachteter Knoten: α

Schritt 2 (v=⊥) Betrachteter Knoten: β

Schritt 3 (v=α) Betrachteter Knoten:γ

Objekt gefunden, uckgabe von v=α

(37)

Vorgänger in doppelt verketteten Listen

Alternativ kann man auch die Datenstruktur ändern: Jeder Knoten wird um eine Referenz auf den Vorgängerknoten ergänzt. Die

Suche nach dem m-ten Vorgänger von k erfolgt dann von k aus nach vorne.

head

Oliver Peter Walter

α β γ

Schritt 2 Betrachteter Knoten: α

Schritt 1 Betrachteter Knoten: β

(38)

Doppelt verkettete Listen

Der Zugriff auf den Vorgängerknoten wird vereinfacht.

Es wird zusätzlicher Speicherplatz pro Element verbraucht.

Verwaltung des zweiten Zeigers bedeutet zusätzlichen Aufwand.

Beispiel: Löschen eines Knotens head

Oliver Peter Walter

(39)

Keller

Ein Keller (stack) ist eine Liste, auf die nur an einem Ende zugegriffen werden kann.

Keller arbeiten nach dem Last-In-First-Out-Prinzip und werden deshalb auch LIFO-Speicher genannt.

x1 x2

x4

x3 x5

x4

(40)

Operationen für Keller

Erzeugen eines leeren Kellers (empty)

Testen, ob ein Keller leer ist (empty?)

Rückgabe des ersten Elements eines Kellers (top)

Einfügen eines Elements am Anfang eines Kellers (push)

Löschen eines Elements am Anfang eines Kellers (pop)

Keller dürfen nur mithilfe dieser Operationen bearbeitet werden.

(41)

Implementierungen

Realisierung durch eine Liste:

xn . . . x1

top

Realisierung durch ein Feld:

xn x1 x2 x3 . . .

top

(42)

Keller

T Stack Bool

top empty?

push pop

empty

Anmerkung:

pop(empty) =⊥ top(empty) =⊥ Diese Fälle bleiben undefiniert.

type

Stack

(

T

) import

Bool

operators

empty

:

Stack

push

:

Stack × TStack pop

:

Stack → Stack

top

:

Stack → T

empty

? :

Stack → Bool

axioms

sStack,∀xT

pop

(

push

(

s, x

)) =

s top

(

push

(

s,x

)) =

x empty

?(

empty

) =

true

empty

?(

push

(

s, x

)) =

false

(43)

Implementierungen

Es ist sinnvoll, bei der Implementierung von Datenstrukturen auf bereits vorhandene Strukturen zurückzugreifen.

Der abstrakte Datentyp „Keller“ wird durch Rückgriff auf den Datentyp „Liste“ realisiert.

(44)

Implementierungen

Die Sorte Stack(T) wird implementiert durch die Menge AList(T) der Listen über T.

Die Operatoren werden durch die folgenden Funktionen implementiert:

empty

= []

push

(

l, x

) =

x

:

l pop

(

x

:

l

) =

l

top

(

x

:

l

) =

x empty

?([]) =

true empty

?(

x

:

l

) =

false

Die Fehlerfälle pop

(

empty

)

und top

(

empty

)

bleiben unbehandelt.

In einer konkreten Realisierung müssen hierfür natürlich

(45)

Anwendungen

Keller gehören zu den wichtigsten Datenstrukturen überhaupt. Sie werden zum Beispiel

zur Bearbeitung von Klammerstrukturen,

zur Auswertung von Ausdrücken und

zur Verwaltung von Rekursionen benötigt.

(46)

Anwendungsbeispiel: Überprüfung von Klammerstrukturen

Wir werden jetzt an einem konkreten Beispiel erläutern, wie Keller in der Praxis benutzt werden.

Ziel ist es, einen Algorithmus zu entwickeln, der eine Datei daraufhin überprüft, ob die in dieser Datei enthaltenen

Klammern (, ), [, ], { und } korrekt verwendet wurden.

Beispielsweise ist die Folge "( [a] b {c} e )" zulässig, nicht aber

"( ] ]".

(47)

Anwendungsbeispiel: Überprüfung von Klammerstrukturen

Es wird ein anfangs leerer Keller erzeugt.

Es werden alle Symbole bis zum Ende der Eingabe gelesen:

Eine öffnende Klammer wird mit push auf den Keller geschrieben.

Bei einer schließenden Klammer passiert folgendes:

Fehler, falls der Keller leer ist.

Sonst wird die Operation pop durchgeführt. Fehler, falls das Symbol, das vom Keller entfernt wurde, nicht mit der

schließenden Klammer übereinstimmt.

Alle anderen Symbole werden überlesen.

Fehler, falls der Keller am Ende der Eingabe nicht leer ist.

Die Eingabe ist zulässig.

(48)

Schlangen

Ein Schlange (queue) ist eine Liste, bei der an einem Ende Elemente hinzugefügt und am anderen entfernt werden können.

Schlangen arbeiten nach dem First-In-First-Out-Prinzip und werden deshalb auch FIFO-Speicher genannt.

x2 x3 x4 x5

x5 x1

x1

(49)

Operationen für Schlangen

Erzeugen einer leeren Schlange (empty)

Testen, ob eine Schlange leer ist (empty?)

Einfügen eines Elements am Ende einer Schlange (enter)

Löschen eines Elements am Anfang einer Schlange (leave)

Rückgabe des ersten Elements einer Schlange (front)

Schlangen dürfen nur mithilfe dieser Operationen bearbeitet werden.

(50)

Implementierungen

Realisierung durch eine Liste:

. . .

Ende Anfang

xn x1

Realisierung durch ein zyklisch verwaltetes Feld:

Ende Anfang

x1 x2 xn . . . x3

- . . .

- - -

(51)

Schlangen

T Queue Bool

front empty?

enter leave empty

Anmerkung:

leave(empty) =⊥ front(empty) =⊥ Diese Fälle bleiben undefiniert.

type

Queue

(

T

) import

Bool

operators

empty

:

Queue

enter

:

Queue × TQueue leave

:

Queue → Queue

front

:

Queue → T

empty

? :

Queue → Bool

axioms

qQueue, ∀xT

leave

(

enter

(

empty,x

)) =

empty leave

(

enter

(

enter

(

q,x

)

,y

)) =

enter

(

leave

(

enter

(

q,x

))

, y

)

front

(

enter

(

empty,x

)) =

x front

(

enter

(

enter

(

q, x

)

,y

)) =

front

(

enter

(

q,x

))

empty

?(

empty

) =

true

empty

?(

enter

(

q x

)) =

false

(52)

Implementierungen

Der abstrakte Datentyp „Schlange“ wird ebenfalls durch den Rückgriff auf den abstrakten Datentyp „Liste“ implementiert.

Die Sorte Queue(T) wird implementiert durch die Menge AList(T) der Listen über T.

Die Operatoren werden durch die folgenden Funktionen implementiert:

empty

= []

enter

(

l,x

) =

x

:

l leave

(

x

: []) = []

leave

(

x

:

l

) =

x

:

leave

(

l

)

front

(

x

: []) =

x

front

(

x

:

l

) =

front

(

l

)

empty

?([]) =

true empty

?(

x

:

l

) =

false

(53)

Anwendungen

Eine häufige Anwendung sind Algorithmen zur Vergabe von Ressourcen an Verbraucher.

Prozessverwaltung:

Ressource: Rechenzeit

Elemente der Warteschlange: rechenwillige Prozesse

Grundidee: Jeder Prozess darf eine feste Zeit lang rechnen, wird dann unterbrochen und hinten in die Warteschlange wieder eingereiht, falls weiterer Bedarf an Rechenzeit vorhanden ist.

Druckerverwaltung:

Ressource: Drucker

Elemente der Warteschlange: Druckaufträge

Grundidee: Druckaufträge werden nach der Reihenfolge ihres Eintreffens abgearbeitet.

(54)

Deques

Eine deque (double-ended queue) ist eine Liste, bei der an beiden Enden Elemente hinzugefügt und entfernt werden können.

Nach den vorangegangenen Beispielen sollte klar sein, welche Operationen eine Deque besitzt und wie diese implementiert werden können.

Referenzen

ÄHNLICHE DOKUMENTE

„Select m3u file“ schon drin stehen. WICHTIG: Wählt als nächstes bei „Select type“ Gstreamer aus. Und zum Abschluss auf grün drücken. Es erfolgt jetzt noch eine Rückmeldung

ƒ Zeigen mehrere Zeiger auf denselben reservierten Speicher- bereich, darf free nur mit einem der Zeiger aufgerufen werden. ƒ Auch die anderen Zeiger dürfen dann nicht mehr

Gesucht ist eine Methode breakCycles() , der den Graphen nach Zyklen durchsucht und diese durch Entfernen der Kante, durch die der Zyklus gefunden wurde, auflöst. public

Obj top(); (Oberstes Element auslesen und nicht entfernen ) boolean is empty(); (Test ob Stack leer).. Steffen Reith Lineare dynamische

Die Schnittstelle eines Stapels besteht aus vier Operationen: void push(Obj data); (Daten auf den Stack legen) Obj pop(); (Oberstes Element entfernen).. Obj top(); (Oberstes

Die zweite Strophe ist nicht Wort für Wort übersetzt und die Wortwahl verweist mehr auf die dynamische Äquivalenz, aber die Übersetzung ist trotzdem auch formal äquivalent

Dank Aids haben wir in der Forschung maßgebliche Fortschritte gemacht, auf die wir auch in der Be- handlung anderer Krankheiten zu- rückgreifen können; dank Aids haben wir

Erstelle ein cell array von 5 Funktionshandles (Hinweis: erstelle die Funktionshandles ent- weder mit anonymen Funktionen oder als Handle auf eine vorhandene Funktion), sowie ein