Algorithmen und Datenstrukturen Werner Struckmann Wintersemester 2005/06 4. Listen und abstrakte Datentypen 4.1 Abstrakte Datentypen 4.2 Listen 4.3 Keller 4.4 Schlangen 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.1 Abstrakte Datentypen 4-1 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. 4.1 Abstrakte Datentypen 4-2 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. 4.1 Abstrakte Datentypen 4-3 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. 4.1 Abstrakte Datentypen 4-4 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. 4.1 Abstrakte Datentypen 4-5 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. 4.1 Abstrakte Datentypen 4-6 Signaturen Eine Signatur Σ = (S , Ω) besteht aus ◮ einer Menge von Sorten S und ◮ einer Menge von Operatorsymbolen Ω. Jedes Operatorsymbol f : s1 . . . sn → s besteht aus einem Namen f , einer Folge s1 , . . . , sn ∈ S , n ≥ 0, von Argumentsorten und einer Wertesorte s ∈ S . Operatorsymbole ohne Parameter heißen Konstante. 4.1 Abstrakte Datentypen 4-7 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 | s ∈ S }, und ◮ (partiellen) Funktionen auf den Trägermengen Af : As1 × . . . × Asn → As , d. h. AΩ = {Af | f : s1 . . . sn → s ∈ Ω}. 4.1 Abstrakte Datentypen 4-8 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 } Atrue ≔ T Afalse ≔ F erwartungskonform ABool = N Atrue ≔ 1 Afalse ≔ 0 große Trägermenge ABool = {1} Atrue ≔ 1 Afalse ≔ 1 kleine Trägermenge 4.1 Abstrakte Datentypen 4-9 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 Φ. 4.1 Abstrakte Datentypen 4-10 Signaturdiagramme Signaturen lassen sich übersichtlich durch Signaturdiagramme mit Sorten als Knoten und Operatorsymbolen als Kanten darstellen: s0 f .. . s sn Ausblick: Signaturdiagramme sind Beispiele für Graphen, die wir in Kürze betrachten werden. 4.1 Abstrakte Datentypen 4-11 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!, x 2 Mixfix: |_|, if _then_else _fi , . . . |x | Bei der Präfixnotation schreiben wir auch kurz f . 4.1 Abstrakte Datentypen 4-12 ADT der Wahrheitswerte S = {Bool } Ω = {true :→ Bool , false :→ Bool , ¬_ : Bool → Bool , _ ∨ _ : Bool × Bool → Bool , _ ∧ _ : Bool × Bool → Bool } Φ = {x ∧ false = false ∧ x = false , x ∧ true = true ∧ x = x , true Bool ¬ false ∨, ∧ x ∨ true = true ∨ x = true , false ∨ false = false , ¬false = true , ¬true = false } 4.1 Abstrakte Datentypen 4-13 ADT der natürlichen Zahlen S = {Nat } Ω = {0 :→ Nat , succ 0 Nat succ : Nat → Nat } Φ = {} ◮ ◮ Damit wird z. B. die Zahl 3 als succ (succ (succ (0))) = succ 3 (0) dargestellt. Der Term succ n (0) stellt die natürliche Zahl n dar. Da es keine Axiome gibt, kann dieser Term nicht vereinfacht werden. 4.1 Abstrakte Datentypen 4-14 ADT der natürlichen Zahlen S = {Nat } Ω = {0 :→ Nat , succ : Nat → Nat , add : Nat × Nat → Nat } succ 0 Nat Φ = {add (x , 0) = x , add (x , succ (y )) = add succ (add (x , y ))} 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. 4.1 Abstrakte Datentypen 4-15 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 succ n (0). Diese entsteht, wenn man die Gleichungen von links nach rechts anwendet, bis alle add-Operationssymbole verschwunden sind. 4.1 Abstrakte Datentypen 4-16 Implementierung eines abstrakten Datentyps Implementierung eines ADTs heißt: ◮ Realisierung der Sorten s ∈ S durch Datenstrukturen As Beispiel: Nat { B m (m-stellige Vektoren über {0, 1}) ◮ Realisierung der Operatoren f : s1 . . . sn → s durch Funktionen Af : As1 × . . . × Asn → As Beispiel: add : Nat × Nat → Nat { _ + _ : B m × B m → B m ◮ Sicherstellen, dass die Axiome (in den Grenzen der darstellbaren Werte bzw. der Darstellungsgenauigkeit) gelten. 4.1 Abstrakte Datentypen 4-17 Implementierung eines abstrakten Datentyps Beispiel: ANat = B m Darstellung von x ∈ N mit m Binärziffern zm−1 , . . . , z0 ∈ B : x= m −1 X zi · 2i i =0 Darstellbarer Zahlenbereich: 0 ≤ x ≤ 2m − 1 Die Gültigkeit der Rechengesetze muss gewährleistet sein. 4.1 Abstrakte Datentypen 4-18 Alternative Notation Im Folgenden wird alternativ zur mathematischen Schreibweise folgende an Programmiersprachen angelehnte Notation genutzt: S = {Nat } Ω = {0 :→ Nat , succ : Nat → Nat , add : Nat × Nat → Nat } Φ = {add (x , 0) = x , add (x , succ (y )) = succ (add (x , y ))} 4.1 Abstrakte Datentypen type Nat import ∅ operators 0 :→ Nat succ : Nat → Nat add : Nat × Nat → Nat axioms ∀i , j ∈ Nat add (i , 0) = i add (i , succ (j )) = succ (add (i , j )) 4-19 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. 4.1 Abstrakte Datentypen 4-20 Lineare Datentypen 4.2 Listen ◮ 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. 4-21 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. x1 4.2 Listen x2 x3 ... xn 4-22 Typische Operationen für Listen 4.2 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 4-23 Parametrisierte Datentypen 4.2 Listen ◮ 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. 4-24 Listen [] length head T Liste : 4.2 Listen Nat tail type List (T ) import Nat operators [] :→ List _ : _ : T × List → List head : List → T tail : List → List length : List → Nat axioms ∀l ∈ List , ∀x ∈ T head (x : l ) = x tail (x : l ) = l length ([]) = 0 length (x : l ) = succ (length (l )) 4-25 Implementierungen 4.2 Listen ◮ 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. 4-26 Implementierungen Eine Liste kann als Verkettung einzelner Objekte implementiert werden. Man spricht von einer einfach verketteten Liste. Beispiel: Liste von Namen („Oliver“, „Peter“, „Walter“) tail head Oliver Peter Walter 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. 4.2 Listen 4-27 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) 4.2 Listen 4-28 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 X Oliver Peter Walter Aufwand: Θ(1) 4.2 Listen 4-29 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 4.2 Listen 4-30 Einfügen eines Listenelements am Ende 1. Navigieren zum letzen Knoten. 2. Erzeugen eines neuen Knotens. 3. Einhängen des neuen Knotens. head X Oliver Peter Walter Aufwand: Θ(n) 4.2 Listen 4-31 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 4.2 Listen 4-32 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“ Schritt 1 (v =⊥) Betrachteter Knoten: α Schritt 2 (v = α) Betrachteter Knoten: β Schritt 3 (v = β) Betrachteter Knoten: γ Objekt gefunden, Rückgabe von v =β head α Oliver 4.2 Listen γ β Peter Walter 4-33 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“ Schritt 1 (v =⊥) Betrachteter Knoten: α Schritt 2 (v =⊥) Betrachteter Knoten: β Schritt 3 (v = α) Betrachteter Knoten: γ Objekt gefunden, Rückgabe von v =α head α Oliver 4.2 Listen γ β Peter Walter 4-34 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. Schritt 2 Betrachteter Knoten: α Schritt 1 Betrachteter Knoten: β head α Oliver 4.2 Listen γ β Peter Walter 4-35 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 4.2 Listen Peter Walter 4-36 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. x5 x4 x4 x3 x2 x1 4.3 Keller 4-37 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. 4.3 Keller 4-38 Implementierungen ◮ Realisierung durch eine Liste: top ... xn ◮ x1 Realisierung durch ein Feld: top x1 x2 x3 4.3 Keller ... xn 4-39 Keller empty top T empty? Stack push Bool pop Anmerkung: pop (empty ) =⊥ top (empty ) =⊥ Diese Fälle bleiben undefiniert. 4.3 Keller type Stack (T ) import Bool operators empty :→ Stack push : Stack × T → Stack pop : Stack → Stack top : Stack → T empty ? : Stack → Bool axioms ∀s ∈ Stack , ∀x ∈ T pop (push (s , x )) = s top (push (s , x )) = x empty ?(empty ) = true empty ?(push (s , x )) = false 4-40 Implementierungen 4.3 Keller ◮ 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. 4-41 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 Vorkehrungen getroffen werden. 4.3 Keller 4-42 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. 4.3 Keller 4-43 Anwendungsbeispiel: Überprüfung von Klammerstrukturen 4.3 Keller ◮ 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 "( ] ]". 4-44 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: ◮ ◮ ◮ 4.3 Keller 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. 4-45 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. x5 x1 x5 x4 x3 x2 x1 4.4 Schlangen 4-46 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. 4.4 Schlangen 4-47 Implementierungen ◮ Realisierung durch eine Liste: Ende Anfang ... xn ◮ Realisierung durch ein zyklisch verwaltetes Feld: Ende - 4.4 Schlangen x1 - xn ... Anfang x3 x2 x1 - ... - 4-48 Schlangen empty front empty? Queue T enter Bool leave Anmerkung: leave (empty ) =⊥ front (empty ) =⊥ Diese Fälle bleiben undefiniert. 4.4 Schlangen type Queue (T ) import Bool operators empty :→ Queue enter : Queue × T → Queue leave : Queue → Queue front : Queue → T empty ? : Queue → Bool axioms ∀q ∈ Queue , ∀x ∈ T 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 4-49 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 ) 4.4 Schlangen front (x : []) = x front (x : l ) = front (l ) empty ?([]) = true empty ?(x : l ) = false 4-50 Anwendungen Eine häufige Anwendung sind Algorithmen zur Vergabe von Ressourcen an Verbraucher. ◮ Prozessverwaltung: ◮ ◮ ◮ ◮ Druckerverwaltung: ◮ ◮ ◮ 4.4 Schlangen 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. Ressource: Drucker Elemente der Warteschlange: Druckaufträge Grundidee: Druckaufträge werden nach der Reihenfolge ihres Eintreffens abgearbeitet. 4-51 Deques 4.4 Schlangen ◮ 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. 4-52