Felder sind ein imperatives Sprachelement. Wir betrachten sie vor allem in Kapitel 4 und 5.
Hier soll gezeigt werden,
- wie imperative und funktionale Sprachkonstrukte zusammenwirken
- wie Listen and Felder zusammenhängen
Eine Speichervariable oder einfach nur Variable ist ein Speicher/Behälter für Werte. Charakteristische Operationen auf einer Variablen v:
- Zuweisen eines Werts w an v;
- Lesen des Wertes, den v enthält/speichert/hat.
Der Zustand einer Variablen v ist undefiniert, wenn ihr noch kein Wert zugewiesen wurde; andernfalls ist der Zustand von v durch den gespeicherten Wert charakterisiert.
Begriffsklärung: (Variable)
Variablen stellen wir graphisch durch Rechtecke dar:
v: true v enthält/speichert den Wert true
3.1.5 Felder/Arrays in ML
Begriffsklärung: (eindimensionale Felder)
Ein Feld (engl. Array) ist ein n-Tupel von Variablen des gleichen Typs T. Die Variablen des Tupels
nennt man die Komponenten des Felds, T den Komponententyp. Die Komponenten sind
in ML und Java von 0 bis n-1 durchnummeriert.
ML stellt standardmäßig eine Datenstruktur für Felder bereit, die bzgl. des Elementtyps parametrisiert sind.
Typen: Ist t ein ML-Typ, dann bezeichnet t array den Typ der Felder mit Elementen aus t.
Funktionen (Auszug): (* Erklärungen: siehe unten *) array : int * t Æ t array (* Konstruktor *) fromList: t list Æ t array (* Konstruktor *) length : t array Æ int (* lesend *)
sub : t array * int Æ t (* lesend *) update : t array * int * t Æ unit
(* schreibend/modifizierend *)
Array: Eine Datenstruktur mit Modifikation
Erklärung:
array (n,x) erzeugt neues Feld mit n Komponenten;
jede Komponente wird mit x initialisiert.
fromList xl erzeugt neues Feld mit n Komponenten, wobei n die Länge der Liste xl ist;
die Komponenten werden mit den Listenelementen initialisiert.
length ar liefert die Anzahl der Feldkomponenten.
sub (ar,i) liefert den Wert, der in der i-ten Kom- ponente von ar gespeichert ist.
update (ar,i,x) speichert den Wert x an die i-te Komponente von ar. Dabei wird ar verändert!! Ergebnis ist der Wert ().
Beispiel: (Modifikation in Feldern)
> val a = fromList [1,2,3];
val a = fromList[1, 2, 3] : int Array.array
> update (a,0,7);
val it = () : unit
> a;
val it = fromList[7, 2, 3] : int Array.array
> val b = a;
val b = fromList[7, 2, 3] : int Array.array
> update (a,1,8);
val it = () : unit
> b;
Beispiel: (Funktionen mit Feldparametern)
(* swap vertauscht die Einträge in den Komponenten mit Index i und j.
Vorbedingung: 0 <= i < length a
0 <= j < length a
*)
fun swap a i j =
let val tmp = sub (a,i) in update (a,i,sub(a,j));
update (a,j,tmp) end;
(* issorted prüft, ob Feld a sortiert ist
*) fun issorted (a:int array) =
issortemb a (length a) and issortemb (a: int array) i =
if i <= 1
then true
else sub(a,i-2) <= sub (a,i-1) andalso issortemb a (i-1);
3.1.6 Signaturen und Strukturen
Begriffsklärung: (Modulsystem)
Ein Programmmodul fasst mehrere Deklarationen zusammen und stellt sie unter einem Namen zur Verfügung.
ML-Module heißen Strukturen und fassen Datentyp- und Funktionsdeklarationen zusammen.
Jede Struktur besitzt eine Signatur, die angibt, - welche Typen und
- welche Funktionen eine Struktur deklariert.
Bemerkung: (Signaturen/Strukturen)
Die Sprachkonstruktre „Signatur“ und „Struktur“
implementieren und verallgemeinern die in 3.1 eingeführten Konzepte von Datenstrukturen mit Signaturen:
- ML-Strukturen können auch Ausnahmen (engl.
Exception) und andere Strukturen enthalten
- ML-Signaturen können unabhängig von Strukturen deklariert werden.
Definition von Strukturen:
structure Environment = struct
val emptyEnv = nil;
fun lookup (n, nil) = NONE
| lookup (n, (n1,i)::env) = if n = n1 then SOME i
else lookup (n,env) fun insert ((n,i), env ) = (n,i)::env fun delete (n, nil) = nil
| delete (n, (n1,i)::env) =
if n = n1 then delete (n,env) else (n1,i)::(delete (n,env)) end;
ML inferiert aus der Strukturdefinition eine Signatur:
structure Environment :
{val 'a emptyEnv : 'a list, val ('a, 'b) insert :
('a * 'b) * ('a * 'b) list
-> ('a * 'b) list, val (''a, 'b) lookup :
''a * (''a * 'b) list -> 'b option val (''a, 'b) delete :
Beispiele: (praktische Strukturen in ML)
type elem = char
type vector = string
type instream = instream type outstream = outstream
val stdErr = <outstream> : outstream
val openAppend = fn : string -> outstream val flushOut = fn : outstream -> unit
val inputAll = fn : instream -> string val inputLine = fn : instream -> string
val output1 = fn : outstream * char ->unit val closeIn = fn : instream -> unit
val closeOut = fn : outstream -> unit
val lookahead = fn: instream->char option val stdIn = <instream> : instream
val input = fn : instream -> string val print = fn : string -> unit
val endOfStream = fn: instream -> bool
val output = fn: outstream * string ->unit val inputN = fn : instream * int -> string val openIn = fn : string -> instream
val openOut = fn : string -> outstream val stdOut = <outstream> : outstream
Auszug aus der Signatur der Struktur TextIO:
Definition und Anwendung von Signaturen:
signature STRINTENV = sig
val emptyEnv : (string,int) list val lookup :
string * (string,int) list -> int option val insert :
(string*int) * (string,int) list
-> (string,int) list end;
Signaturen sind „Typen für Strukturen“. Sie können benutzt werden, um Strukturen einzuschränken:
- auf weniger Funktionen, Werte, … - auf eingeschränktere Typen
structure StrIntEnv : STRINTENV =
Environment;
Beispiel: (Anwendung von Signaturen)
Die Struktur StrIntEnv besitzt keine Funktion delete und kann nur für (string,int)-Bindungen verarbeiten.
Anwendung von Strukturen in ML:
- Ist S eine Struktur in ML, dann kann man ihre Deklarationen d mit S.d verwenden.
- Das Kommando „open“ macht die Deklarationen eines Moduls direkt sichtbar:
open S;
(* d kann nun direkt verwendet werden *)
Beispiel: (Öffnen einer Struktur)
open StrIntEnv;
macht die Name der drei Funtionen von StrIntEnv direkt verwendbar.
3.2 Algorithmen auf Listen und Bäumen
Sortieren und Suchen sind elementare Aufgaben, die in den meisten Programmen anfallen.
Verfahren zum Suchen und Sortieren spielen eine zentrale Rolle in der Algorithmik.
Bemerkung:
Lernziele:
- Intuitiver Algorithmusbegriff
- Kenntnis wichtiger/klassischer Algorithmen und Algorithmenklassen
- Zusammenhang Algorithmus und Datenstruktur - Wege vom Problem zum Algorithmus
- Implementierungstechniken für Datenstrukturen und Algorithmen (vom Algorithmus zum Programm)
Wir führen in den Bereich Algorithmen und Datenstrukturen ausgehend vom Problem ein.
Andere Möglichkeit wäre gemäß der benutzten Datenstrukturen (Listen, Bäume, etc.).
3.2.1 Sortieren
Übersicht über 3.2:
• Sortieren
• Suchen
Sortieren ist eine Standardaufgabe, die Teil vieler speziellerer, umfassenderer Aufgaben ist.
Untersuchungen zeigen, dass „mehr als ein Viertel der kommerziell verbrauchten Rechenzeit auf
Sortiervorgänge entfällt“ [Ottmann, Widmayer:
Algorithmen und Datenstrukturen, Kap. 2].
Begriffsklärung: (Sortierproblem)
Gegeben ist eine Folge s , ..., s von sogenannten Datensätzen. Jeder Satz s hat einen Schlüssel k . Wir gehen davon aus, dass die Schlüssel ganzzahlig sind. Aufgabe des Sortierproblems ist es, eine
Permutation π zu finden, so dass die Umordnung der Sätze gemäß π folgende Reihenfolge auf den
Schlüsseln ergibt:
k ≤ k ≤ ... ≤ k
1 N
i i
π(1) π(2) π(N)
Bemerkung:
Offene Aspekte der Formulierung des Sortierproblem:
- Was heißt, eine Folge ist „gegeben“?
- Ist der Bereich der Schlüssel bekannt?
- Welche Operationen stehen zur Verfügung, um π zu bestimmen?
- Was genau heißt „Umordnung“?
Wir benutzen Datensätze folgenden Typs type dataset = int * string
mit Vergleichsoperator:
infix 4 leq
fun op leq((kx:int,dx),(ky:int,dy))= (kx<=ky)
Entwickle eine Funktion
sort: dataset list Æ dataset list
so dass sort(xl) für alle Eingaben xl aufsteigend sortiert ist. Insbesondere sind mehrfach Einträge und mehrere Einträge mit gleichem Schlüssel nicht
ausgeschlossen.
Aufgabenstellung:
Sortieren durch Auswahl (selection sort)
Algorithmische Idee:
• Entferne einen minimalen Eintrag min aus der Liste.
• Sortiere die Liste, aus der min entfernt wurde.
• Füge min als ersten Element an die sortierte Liste an.
Wir betrachten:
• Sortieren durch Auswahl (engl. selection sort)
• Sortieren durch Einfügen (engl. insertion sort)
• Bubblesort
• Sortieren durch rekursives Teilen (quick sort)
• Sortieren durch Mischen (merge sort)
• Heapsort
(* select: Hilfsfunktion
liefert einen minimalen Eintrag der Liste bzw. x, falls x minimal. *) fun select x nil = x
| select x (y::yl) = if x leq y
then select x yl else select y yl;
(* delete: Hilfsfunktion
löscht ein Vorkommen von x aus der Liste.*) fun delete x nil = nil
| delete x (y::yl) = if (x = y)
then yl
else y :: delete x yl;
(* selectionsort: sortiert Liste
min: ein minimaler Eintrag in Liste xl.
rest: die Liste xl ohne min *) fun selectionsort nil = nil
| selectionsort (x::xl) =
let val min = select x xl;
val rest = delete min (x::xl);
in
min :: (selectionsort rest) end;
Sortieren durch Einfügen (insertion sort)
Algorithmische Idee:
• Sortiere zunächst den Rest der Liste.
• Füge dann den ersten Eintrag in die sortierte Liste ein.
(* insert: Hilfsfunktion
fügt Argument in sortierte Liste ein *) fun insert a nil = [a]
| insert a (y::yl) = if (a leq y)
then a :: (y :: yl)
else y :: (insert a yl)
(* insertionsort: sortiert Liste *) fun insertionsort nil = nil
| insertionsort (x::xl) =
insert x (insertionsort xl)
Bubblesort
(* bubble: Hilfsfunktion
liefert einen maximalen Eintrag der Liste und die Liste ohne den maximalen Eintrag *) fun bubble rl e nil = (rl,e)
| bubble rl e (y::yl) = if (e leq y)
then bubble (rl@[e]) y yl else bubble (rl@[y]) e yl
(* bubblesort: sortiert Liste *) fun bubblesort nil = nil
| bubblesort (x::xl) =
let val (rl,max) = bubble nil x xl in (bubblesort rl)@[max]
end
Algorithmische Idee:
• Schiebe einen Eintrag nach rechts heraus:
- Beginne dazu mit dem ersten Eintrag e.
- Wenn schieben von e auf einen gleichen oder größeren Eintrag y stößt, schiebe y weiter.
- Ergebnis: maximaler Eintrag max und Liste ohne max
• Sortiere die Liste ohne max und hänge max an.
Quicksort: Sortieren durch Teilen
fun split p nil = (nil,nil)
| split p (x::xr) =
let val (below, above) = split p xr in if p leq x then (below,x::above)
else (x::below,above) end
fun qsort nil = nil
| qsort (p::rest) =
let val (below,above) = split p rest in (qsort below) @ [p] @ (qsort above) end
Algorithmische Idee:
• Wähle einen beliebigen Datensatz mit Schlüssel k aus, das sogenannte Pivotelement.
• Teile die Liste in zwei Teile:
- 1. Teil enthält alle Datensätze mit Schlüsseln < k - 2. Teil enthält die Datensätze mit Schlüsseln ≥ k
• Wende quicksort rekursiv auf die Teillisten an.
• Hänge die resultierenden Listen und das Pivotelement zusammen.
Bemerkung:
Quicksort ist ein typischer Algorithmus gemäß der Divide-and-Conquer-Strategie:
- Zerlege das Problem in Teilprobleme.
- Wende den Algorithmus auf die Teilprobleme an.
- Füge die Ergebnisse zusammen.
Sortieren durch Mischen:
Algorithmische Idee:
• Hat die Liste mehr als ein Element, berechne die Länge der Liste div 2 (halfsize).
• Teile die Liste in zwei Teile der Länge halfsize (+1).
• Sortiere die Teile.
• Mische die Teile zusammen.
Bemerkung:
Mergesort ist auch effizient für das Sortieren von Datensätzen, die auf externen Speichermedien liegen und nicht vollständig in den Hauptspeicher geladen werden können.
open List;
fun merge nil nil = nil
| merge nil yl = yl
| merge xl nil = xl
| merge (x::xl) (y::yl) = if (x leq y)
then x :: (merge xl (y::yl)) else y :: (merge (x::xl) yl);
(* mergesort: sortiert gegebene Liste halfsize: Hälfte der Listenlänge.
front: Vordere Hälfte der Liste.
back : Hintere Hälfte der Liste. *) fun mergesort nil = nil
| mergesort (x::nil) = [x]
| mergesort xl = let
val halfsize = (length xl) div 2;
val front = take (xl,halfsize);
val back = drop (xl,halfsize);
in (* Merge 2 sortierte Listen! *)
merge (mergesort front) (mergesort back) end;
Heapsort
Heapsort verfeinert die Idee des Sortierens durch Auswahl:
Æ Minimum bzw. Maximum wird nicht durch lineare Suche gefunden, sondern mit logarithmischem Aufwand durch Verwendung einer besonderen
Datenstruktur, dem sogenannten Heap.
Algorithmische Idee:
• 1. Schritt: Erstelle den Heap zur Eingabeliste.
• 2. Schritt:
- Entferne Maximumelement aus Heap (konstanter Aufwand) und hänge es an die Ausgabeliste.
- Stelle Heap-Bedingung wieder her (logarithmischer Aufwand).
- Fahre mit Schritt 2 fort bis der Heap leer.
Bemerkung:
• Ziele des Vorgehens:
- Beispiel für komplexe, abstrakte Datenstruktur - Zusammenhang der algorithmischen Idee und
der Datenstruktur.
• Der Begriff „Heap“ ist in der Informatik überladen.
Auch der Speicher für zur Laufzeit angelegte Variablen wird im Englischen „heap“ genannt.
Begriffsklärung: (zu Bäumen)
Ein Baum heißt markiert, wenn jeder Knoten
eine Markierung besitzt. Im Zusammenhang dieses Kapitels sind Markierungen Datensätze.
Die Höhe eines Baumes ist der maximale Abstand eines Blattes von der Wurzel; die Höhe eines
Baumes, der nur aus einem Blatt besteht, ist 1;
die Höhe des leeren Baums ist 0.
Die Tiefe eines Knotens ist sein Abstand zur Wurzel.
Die Knoten gleicher Tiefe t nennt man das Niveau t.
Ein Binärbaum ist ein Baum, in dem jeder Knoten keinen oder einen linken oder einen rechten oder einen linken und rechten Unterbaum besitzt.
Die Größe eines Baums ist die Anzahl seiner Knoten.
Ein Baum der Größe n heißt indiziert, wenn man seine Knoten mittels der Indizes 0,...,n-1 ansprechen kann.
Ein Binärbaum heißt strikt, wenn jeder Knoten ein Blatt ist oder zwei Unterbäume besitzt.
Ein Binärbaum der Höhe h heißt vollständig, wenn er strikt ist und alle Blätter die Tiefe h haben.
Ein Binärbaum der Höhe h heißt fast vollständig, wenn
- jedes Blatt die Tiefe h oder h-1 hat, - für die Knoten K des Niveaus h-1 gilt:
1. Hat K 2 Kinder, dann auch alle linken Nachbarn von K.
2. Hat K keine Kinder, dann sind auch alle rechten Nachbarn von K kinderlos.
3. Es gibt maximal ein K mit genau einem Kind, und dies ist links.
Beispiel: (Fast vollständiger indizierter und markierter Binärbaum)
88
57 2
151 42
31
0:
1: 2:
3: 4: 5:
signature FVBINTREE = sig
(* Typ fast vollständiger Binärbäume, ggf. leer *) type fvbintree;
(* Erzeugt fast vollständigen Binärbaum, wobei die Listenelemente zu Markierungen werden *) val create : dataset list -> fvbintree;
(* Anzahl der Knoten des Baums *) val size : fvbintree -> int ;
(* Markierung am Knoten mit Index i *)
val get : fvbintree * int -> dataset ;
(* Vertausche Markierungen der Knoten mit Ind. i, ,j *) val swap : fvbintree * int * int -> fvbintree;
(* Entferne letzten Knoten *)
val removeLast: fvbintree -> fvbintree ; (* Knoten mit Index i hat linkes Kind *)
val hasLeft : fvbintree * int -> bool ; (* Knoten mit Index i hat rechtes Kind *)
val hasRight : fvbintree * int -> bool
(* Index des linken Kinds von Knoten mit Index i *) val left : fvbintree * int -> int ;
(* Index des rechten Kinds von Knoten mit Index i *) val right : fvbintree * int -> int
Signatur für fast vollständige, markierte,
indizierte Binärbäume:
Vorgehen:
• Wir gehen zunächst davon aus, dass wir eine Datenstruktur zur Signatur FVBINTREE hätten und realisieren heapsort damit; d.h.
ohne die Struktur/Implementierung zu kennen.
• Wir liefern dann eine effiziente Implementierung der Signatur.
• Im Zusammenhang mit der objektorientierten
Programmierung werden wir die Implementierung weiter verfeinern.
Bemerkung:
• Wir benutzen die Datenstruktur ohne die Implemen- tierung zu kennen; man sagt die Datenstruktur ist für den Nutzer abstrakt und spricht von
abstrakter Datenstruktur.
• Die Signatur beschreibt die Schnittstelle der abstrakten Datenstruktur. Die Benutzung von Schnittstellen abstrakter Datenstrukturen ist ein zentraler Bestandteil der SW-Entwicklung.
• Eine abstrakte Datenstruktur kann unterschiedliche Implementierungen haben. Implementierungen können ausgetauscht werden, ohne dass der Nutzer seine Programme ändern muss!
Begriffsklärung: (Heap)
Ein markierter, fast vollständiger, indizierter Binärbaum mit n Knoten heißt ein Heap der Größe n, wenn die folgende Heap-Eigenschaft erfüllt ist:
Ist M ein Knoten und N ein Kind von M mit Markierungen k und k , dann gilt:
k ≥ k .
N M M
N
Beispiel: (Heap)
88
57 62
25 51
42 31
10
Heap der Größe 8:
Bei einem Heap sind die Knoten entsprechend einem Breitendurchlauf indiziert (siehe Beispiel unten).
0:
1: 2:
3: 4: 5: 6:
7:
Herstellen der Heap-Eigenschaft:
Bemerkung:
Die Heap-Eigenschaft garantiert, dass der Schlüssel eines Knotens M größer gleich aller Schlüssel in den Unterbäumen von M ist. Insbesondere steht in der Wurzel ein Element mit einem maximalen Schlüssel.
Sei ein markierter, fast vollständiger Binärbaum gegeben, der die Heap-Eigenschaft nur an der Wurzel verletzt.
Die Heap-Eigenschaft kann hergestellt werden, indem man den Wurzelknoten M rekursiv in dem Unterbaum mit dem größeren Schlüssel
versickern lässt:
- Gibt es kein Kind, ist nichts zu tun.
- Gibt es genau ein Kind N, dann ist dies links und kinderlos: Ist k < k , vertausche die
Markierungen.
- Gibt es zwei Kinder und ist N das Kind mit dem größeren Schlüssel: Ist k < k , vertausche die Markierungen und fahre rekursiv mit dem
Unterbaum zu N fort.
N M
N M
Beispiel: (Versickern lassen)
62
57 18
51 42
0:
1: 2:
3: 10 4: 5:
62
57 42
51 18
0:
1: 2:
3: 10 4: 5:
18
57 62
51 42
0:
1: 2:
3: 10 4: 5:
(* Annahme: Die Kinder von ix in b erfüllen die Heap-Eigenschaft *)
fun heapify b ix =
let val ds = get(b,ix) in
if hasLeft(b,ix)
andalso not (hasRight(b,ix)) then
let val lx = left(b,ix) in if get(b,lx) leq ds
then b
else swap(b,ix,lx) end
else
if hasRight(b,ix) then
let val lx = left(b,ix) ; val rx = right(b,ix);
(* zu betrachtendes Kind *) val cx =
if get(b,lx) leq get(b,rx) then rx
else lx
in if get(b,cx) leq ds then b
else heapify (swap(b,ix,cx)) cx end
else b end
Funktion heapify formuliert auf Basis von FVBINREE:
Konkretisierung des Heapsort-Algorithmus:
1. Schritt:
- Erzeuge Binärbaum-Repräsentation aus Eingabefolge.
- Stelle Heap-Eigenschaft her, indem heapify ausgehend von den Blättern für jeden Knoten aufgerufen wird. Es reicht, nur Knoten mit Kindern zu berücksichtigen.
2. Schritt::
- Schreibe den Wurzel-Datensatz in die Ausgabe.
- Schreibe den Datensatz des letzten Elementes in den Wurzelknoten.
- Entferne das letzte Element.
- Stelle die Heap-Eigenschaft wieder her.
- Fahre mit Schritt 2 fort, solange die Größe > 0.
Lemma
:In einem fast vollständigen Binärbaum der Größe n
sind die Knoten mit den Indizes (n div 2) bis n-1 Blätter.
Heapsort: Abstrakte Version
Wir betrachten zunächst Heapsort auf Basis des abstrakten Datentyps mit Signatur FVBINTREE:
Heapsort profitiert davon, dass sich fast vollständige, markierte, indizierte Binärbäume sehr effizient mit Feldern realisieren lassen:
(* Hilfsfunktion für Schritt 1 *)
fun heapifyAll b = hpfyEmb b ((size b) div 2) and hpfyEmb b 0 = if size b = 0 then b
else heapify b 0
| hpfyEmb b ix = hpfyEmb (heapify b ix) (ix-1)
(* heapsort: sortiert gegebene Liste *) fun heapsort xl =
rev (sortheap (heapifyAll (create xl))) and sortheap hp =
if size hp = 0 then []
else let val maxds = get(hp,0) ;
val hp1 = swap(hp,0,(size hp)-1);
val hp2 = removeLast hp1 ; val hp3 = heapify hp2 0 in maxds::(sortheap hp3) end;
88
57 62
25 51
42 31
10
0:
1: 2:
3: 4: 5: 6:
7:
0: 88 1: 62 2: 57 3: 31 4: 42 5: 51 6: 25 7: 10
Beachte:
Ist ix der Index eines Knotens, dann ist:
- 2*(ix+1)-1 der Index des linken - 2*(ix+1) der Index des rechten Kindes.
Beispiel: (Heaps in Feldern)
Realisierung von Heaps mit Feldern:
structure FvBinTree : FVBINTREE = struct open Array;
(* Typ fast vollständiger Binärbäume, ggf. leer.
Implementiert mit Feldern;
erste Komponente: genutzte Größe *) type fvbintree = int * (dataset array);
(* fromList: ‘a list -> ‘a array *)
fun create xl =(List.length xl,fromList xl);
fun size (s,fbt) = s;
fun get ((s,fbt),i) = sub(fbt,i);
(* swap: Modifiziert fbt *) fun swap ((s,fbt),i,j) =
let val tmp = sub(fbt,i) in update(fbt,i,sub(fbt,j));
update(fbt,j,tmp);
(s,fbt) end;
fun removeLast (s,fbt) = (s-1,fbt) fun left ((s,fbt),i) = 2*(i+1)-1 ; fun right ((s,fbt),i) = 2*(i+1) ; fun hasLeft((s,fbt),i) =
(left((s,fbt),i)) < s fun hasRight((s,fbt),i)=
(right((s,fbt),i)) < s end;
open FvBinTree;
Es hätte klar werden müssen:
• Zu einem algorithmischen Problem (hier Sortieren) gibt es im Allg. viele Lösungen.
• Lösungen unterscheiden sich in:
- der Laufzeiteffizienz (messbar) - der Speichereffizienz (messbar)
- der „Komplexität“ der Verfahrensidee (im Allg.
nicht messbar).
In den folgenden Kapiteln werden wir demonstrieren,
• wie einige der obigen Algorithmen in anderen Sprachenparadigmen formuliert werden können;
• wie der Effizienz/Komplexitätsbegriff präzisiert werden kann.
3.2.2 Suchen
Abschließende Bemerkungen zu 3.2.1
Die Verwaltung von Datensätzen basiert auf drei grundlegenden Operationen:
- Einfügen eines Datensatzes in eine Menge von Datensätzen;
- Suchen eines Datensatzes mit Schlüssel k;