PDF-Datei

Werbung
1661
Definitionen
Algorithmus: Ein Algorithmus ist ein Verfahren zur Lösung eines Problems. Ein
Problem besteht jeweils in der Zuordnung eines Ergebnisses zu jedem Element
aus einer Klasse von Probleminstanzen; insofern realisiert ein Algorithmus eine
Funktion. Die Beschreibung eines Algorithmus besteht aus einzelnen Schritten
und Vorschriften, die die Ausführung dieser Schritte kontrollieren. Jeder Schritt
muß klar und eindeutig beschrieben sein und mit endlichem Aufwand in
endlicher Zeit ausführbar sein.
man definiert man einen Algorithmus so abstrakt wie möglich und sollte so
wenig implizite Annahmen über die verwendeten Objekte machen, wie möglich.
Datentyp: Datentypen entsprechen den Algorithmen. Allerdings beschreiben sie die
Objekte mit denen ein Algorthmus arbeitet. Datentypen entsprechen den
Objekten in der objektorientierten Programmierung. Zu den Objekten gehören
immer Operationen, die auf ihnen arbeiten. Die in einer Algebra verwendeten
Mengen und Operationen sind so zu verwenden, wie sie in der Mathematik
definiert werden. Ein Datentyp macht keine Angaben über seine
Implementierung.
Datenstruktur: Implementierung eines Datentyps auf algorithmischer Ebene
Graph: Ein Graph stellt eine Menge von Objekten zusammen mit einer Beziehung
(Relation) auf diesen Objekten dar. Objekte werden als Knoten, Beziehungen als
Kanten dargestellt. Eine Beziehung wird im allgemeinen durch eine gerichtete
Kante (einen Pfeil) dargestellt. Der Spezialfall einer symmetrischen Beziehung
wird durch eine ungerichtete Kante dargestellt.
1
grundlegendes
Schritte zur Abstrahierung der Laufzeit:
1. keine Unterscheidung der Art der Elementaroperationen
2. Aufteilung der Mengen aller Eingaben in Komplexitätsklassen
3. Betrachtung der Spezialfälle, vor allem average case und worst case
4. durch Weglassen von multiplikativen und additiven Konstanten wird nur
noch das Wachstum einer Laufzeitfunktion T (n) betrachtet (dies geschieht
mit Hilfe der O-Notation)
Definition O-Notation: seien f : N → R+ , g : N → R+
f = O(g) :⇔ ∃n0 ∈ N, c ∈ R, c > 0 : ∀n ≥ n0 f (n) ≤ c · g(n)
O(g) ist eine so definierte Funktionsmenge:
O(g) = {f : N → R+ |∃n0 ∈ N, c ∈ R, c > 0 : ∀n ≥ n0 f (n) ≤ c · g(n)}
sprich: f ist ein Element von O(g), genau dann wenn es ein n0 und ein c gibt, für
die gilt, daß ab einer Stelle n ≥ n0 gilt, bei der f (n) immer kleiner als c · g(n) ist.
O-Notation setzt obere Grenze, Ω-Notation untere Grenze, Θ grenzt einen Bereich
genau ein
Datentyp (Algebra): ein System, welches aus einer oder mehreren Objektklassen mit
dazugehörigen Operationen besteht, man muß festlegen, wie die Objektmengen
(sorts) und Operationen (ops) heißen, wieviele und was für Objekte die
Operationen als Argumente benötigen und welche Art von Objekt sie als Ergebnis
liefern (Signatur, ist syntaktisch, nicht semantisch). Zur Festlegung der Semantik
ist jeder Sorte eine Trägermenge (sets) zuzuordnen und jedem Operationssymbol
eine Funktion (functions) mit entsprechenden Argument- und Wertebereichen.
Bei der Spezifikation als Algebra gibt man Trägermengen und Funktionen direkt
an, unter Verwendung der in der Mathematik üblichen Notationen.
Spezifikation als abstrakter Datentyp adt: verwendet anstelle von sets und functions
Axiome (axs), welche die interessierenden Aspekte der Wirkungsweise der
Operationen charakterisieren (Standardbeispiel Stack); ist aber problematisch, da
die Anzahl der Gesetze sehr groß werden kann, die Bedeutung des Datentyps
intuitiv schwer erkennbar ist und schwierig zu überprüfen ist, ob die Menge der
Gesetze vollständig und widerspruchsfrei ist.
2
grundlegende Datentypen
Listen: auf Listen ist im Gegensatz zu Mengen eine Ordnung definiert (Reihenfolge
mit Vorgänger und Nachfolger), es dürfen Duplikate auftreten
Modelle von Listen: list1 : einfache Liste für rekursive Verarbeitung mit erstem Element
und Restliste bietet Operation empty, isempty, first, rest, append und concat
list2 Liste mit expliziten Positionen erlaubt wahlfreien Zugriff auf Listenelemente;
Operationen: empty, isempty, concat, front, last, next previous, bol, eol, insert, delete,
find (liefert Position des ersten Elementes, das Bedingung erfüllt), retrieve
Implementierungen von Listen: (a) doppelt verkettete Listen unterstützen list2
vollständig:
type list
= ^listelem;
pos
= ^listelem;
listelem = record value
: elem;
pred, succ : ^listelem
end;
(b) einfach verkettete Listen unterstützen kein previous
type list
= record head, last: ^listelem
listelem = record value: elem;
succ ^listelem
end;
end;
(c) sequentielle Darstellung im Array hat Nachteile (Einfügen und Entfernen
verlangt Verschieben der Folgeelemente, Maximalgröße muß vorher
festgelegt werden, Positionszeiger nicht stabil)
(d) einfach oder doppelt verkettete Listen im Array: Elemente bestehen aus Wert
und Zeiger auf Folgeelement (die Zeiger werden durch Array-Indizes
ersetzt), es können mehrere Liste in einem Array dargestellt werden,
zusätzlich wird noch eine Liste der freien Felder verwendet; Nachteile wie
oben, aber Vorteile: Aufrufe des Laufzeit- und Betriebssystems zum Kreieren
neuer Elemente werden vermieden, komplette Struktur kann extern
gespeichert werden
Stacks: LIFO-Struktur, Spezialfall von Listen mit den Operationen top (=first), pop
(=rest), push (=append), wird üblicherweise als sequentielle Darstellung im Array
implementiert, eignen sich zur Bearbeitung von Klammerstrukturen (einfach
Implementation mit getrennten Operator- und Operanden-Stack)
Queues: FIFO-Struktur, entspricht list1 ohne concat und append: front, enqueue, dequeue,
queue; wird üblicherweise als sequentielle Darstellung im Array implementiert; da
die Queue das Array „durchwandert“, wird das Array als zyklisch aufgefaßt;
Anwendung besonders zur Realisierung von Warteschlangen
Abbildungen: (mappings) ordnet jedem Element des Argumentbereiches ein Element
der Zielwertbereichs zu; bei kleiner Anzahl direkte Implementierung im Array
3
binäre Bäume: besteht aus eine Menge von Objekten (Knoten) mit einer Relation auf
der Knotenmenge (graphisch durch Kanten dargestellt), die die Knotenmenge
hierarchisch organisiert. Minimale Höhe ist dlog2 (n + 1)e − 1,
können mit Zeigern:
type tree = ^node;
node = record key
: elem;
left, right : tree
end;
oder zeigerlos als Array der Länge 2h+1 − 1 implementiert werden:
Index (p) = i
-> Index (p.left) = 2·i
Index (p.right) = 2·i + 1
allgemeine Bäume: Implementierung über Arrays (Voraussetzung, daß alle Knoten
höchstens einen festen Grad d haben), oder über Binärbäume, bei denen der
Zeiger auf den linken Sohn zeigt und von dort aus jeweils auf dessen rechten
Bruder
4
Datentypen zur Darstellung von Mengen
Mengen mit Durchschnitt, Vereinigung, Differenz: Bitvektor (Aufwand ist
proportional zur Größe des Universums); ungeordnete Liste (sehr ineffizient, da
obige Operationen für zwei Listen der Größen n und m die Komplexität O(n ∗ m)
haben; geordnete Liste (Aufwand proportional zur Größe der Mengen) paralleler
Durchlauf O(n + m)
Dictionaries: Mengen mit INSERT, DELETE, MEMBER:
Hashing: Abbildung: h : D → {0, . . ., m − 1}
Grundidee des Hashverfahrens ist es, aus dem Wert eines Elementes seine
Adresse zu berechnen; bei komplexen Records wird ein Schlüssel gewählt;
gewünschte Eigenschaften: surjektiv, gleichmäßige Verteilung der Schlüssel in
den Behältern, effizient berechenbar; haben (allgemein) sehr schlechtes
worst-case-Verhalten (O(n), aber sehr gutes average-case-verhalten (O(1)), sind
relativ einfach zu implementieren
bei offenen Hashverfahren kann ein Behälter beliebig viele Schlüssel aufnehmen,
bei geschlossenen nicht (Kollision, PKollision = 1 − m(m−1)(m−2)...(m−n+1)
(m mn
Behälterzahl, n - Schlüsselzahl); bei offenem Hashing ist jeder Behälter eine Liste
von Schlüsseln, die Behälter werden durch ein Array von Zeigern verwaltet,
worst case ist sehr schlecht: O(n), average case ist O(1 + n/m) = O(1)
bei geschlossenen Hashing werden Elemente in Array gespeichert (begrenzte
Aufnahmekapazität, sollte Auslastung von 80% nicht übersteigen, eignet sich nur,
wenn Gesamtanzahl beschränkt und kaum dynamisch ist), zur
Kollisionsbehandlung werden weitere Hashfunktionen, die für jeden Wert jede
Tabellenzelle inspizieren sollten, benutzt – bei der Suche wird die gleiche Folge
von Zellen betrachtet wie beim Eintragen und bei der ersten Leerzelle
abgebrochen, d. h. Felder mit gelöschten Elementen müssen markiert werden
(und werden nicht wieder freigegeben)
Kollisionsstrategien: lineares Sondieren hi (x) = (h(x) + c ∗ i) mod m (c und m
teilerfremd, damit alle Zellen getroffen werden), Nachteil: Kettenbildung;
quadratisches Sondieren vermeidet Clusterbildung bei Sekundärkolissionen
(Suchen nach freien Feldern in immer größeren Abständen abwechselnd vor und
nach dem ursprünglichen Feld); Doppel-Hashing (Verwendung von zwei
unabhängigen Hashfunktionen (d. h. Wahrscheinlichkeit für Doppelkollision m12 )
Hash-Funktionen: Divisionsmethode h(k) = k mod m, aufeinanderfolgende
Werte werden dabei auf aufeinanderfolgende Zellen abgebildet;
Mittel-Quadrat-Methode: Es wird eine Ziffernfolge aus der Mitte der quadrierten
Zahl genommen, dadurch werden aufeinanderfolgende Werte besser gestreut.
Man muß für jede Anwendung überprüfen, ob eine gewählte Hashfunktion die
Schlüssel gleichmäßig verteilt. Ein Nachteil von Hash-Verfahren ist, daß die
Menge der gespeicherten Schlüssel nicht effizient in sortierter Reihenfolge
aufgelistet werden kann.
5
binäre Suchbäume: Sei T ein Baum. Die Knotenmenge von T wird ebenso mit T
bezeichnet. Eine Knotenmarkierung ist eine Abbildung µ : T → D für irgendeinen
geordneten Wertebereich D. In einem binären Suchbaum ist der Wert Schlüssels
des linken Sohnes kleiner und der des rechten Sohnes größer als die jeweilige
Wurzel.
Algorithmen delete, insert und member folgen jeweils einem einzigen Pfad im
Baum von der Wurzel zu einem Blatt oder inneren Knoten, der Suchaufwand ist
proportional zur Länge dieses Pfades, d. h. O(log n) im balancierten bzw. O(n) im
degenerierten (entsteht durch Einfügen sortierter Schlüssel) Baum. Mittlerer
2
wahrscheinlicher Suchaufwand
√ ist 2 ∗ ln 2 ∗ log n = O(log n), nach n
Update-Operationen aber Θ( n) (Baum wird allmählich linkslastig, d. h.
Löschprozedur sollte zufällig Vorgänger oder Nachfolger des zu löschenden
Schlüssels an freie Position setzen
Algorithmen: Überprüfen, ob ein Element im Baum vorkommt (member, rekursiv,
t ist der Baum, x das gesuchte Element): Wenn t = nil dann false zurückgeben;
oder wenn aktueller Eintrag = dem gesuchten Element, dann true zurückgeben;
oder wenn aktueller Eintrag größer als das gesuchte Element, dann Rückgabe von
member(linkerT eilbaum, W ert); oder wenn aktueller Eintrag kleiner als das
gesuchte Element, dann Rückgabe von member(rechterT eilbaum, W ert)
Einfügen eines Elementes (insert): zuerst nach x im Baum suchen, wenn es nicht
vorhanden ist, dann an der Stelle, an der die Suche erfolglos endete, den neuen
Knoten einfügen
Löschen (delete, p ist der Zeiger auf das aktuelle Element): nach x im Baum
suchen, wenn es gefunden wird und es sich dabei um einen Blatt handelt, dann
Blatt löschen; oder wenn es ein Knoten mit einem Sohn ist, Sohn an die Stelle des
Elementes setzen (p entfernen und den Zeiger von p auf den Sohn p setzen); oder
wenn es sich um einen Knoten mit zwei Söhnen handelt, im Teilbaum vom p den
Knoten q bestimmen, der das kleinste Element y > x enthält, im Knoten p
Schlüssel x durch y ersetzen; den Schlüssel y aus dem Teilbaum mit Wurzel q
entfernen
Nachteil: besitzen schlechtes Worst-Case-Verhalten, deswegen:
AVL-Bäume: ist ein balancierter Suchbaum, garantiert O(log n) für alle drei
Dictionary-Operationen. Im AVL-Baum unterscheidet sich für jeden Knoten die
Höhe seiner zwei Teilbäume um höchstens 1. Nach Update wird der Knoten zur
Wurzel zurückverfolgt und nötigenfalls rebalanciert.
Rebalancieren: von der aktuellen Position aus wird der Pfad zur Wurzel
gelaufen, in jedem Knoten wird dessen Balance geprüft (d. h. auch, daß alle
Teilbäume unterhalb des gerade betrachteten (aus der Balance geratenen) Knotens
selbst balanciert sind, Ordnungseigenschaften bleiben dabei erhalten
einfache Rotation (wenn bei einem aus der Balance geratenen Knoten ein äußerer
Teilbaum am tiefsten hängt); doppelte Rotation (wenn der mittlere Teilbaum am
tiefsten ist)
6
Algorithmen: folgen jeweils einem Pfad von der Wurzel zu einem Blatt und dann
evtl. den Pfad vom Blatt zurück und nehmen dabei evtl. (Doppel-)Rotation vor.
Jede Operation hat O(1), insgesamt also O(h) (h=Höhe des Baumes) maximale
Höhe ist etwa 1, 44 ∗ log n = O(log n), d. h. höchstens 44% höher als ein vollständig
ausgeglichener binärer Suchbaum, benötigt O(n) Speicherplatz.
Externes Suchen: B-Bäume: extern bedeutet die vollständige Darstellung auf den
Hintergrundspeicher, benötigt O(1) internen Speicherplatz (aber natürlich Ω(n)
externen); Hauptproblem also: Datensätze mit Schlüsseln aus geordnetem
Wertebereich so organisieren, daß ein Datensatz mit gegebenem Schlüssel mit
möglichst wenig Seitenzugriffen bearbeitet werden kann; Lösung: Speicherseiten
werden als Knoten eines Suchbaums aufgefaßt, da die Suchkosten proportional
zur Pfadlänge sind, wählt man Bäume mit hohem Verzweigungsgrad
(Vielweg-Suchbaum). Ein B-Baum der Ordnung m ist ein Vielweg-Suchbaum mit
speziellen Eigenschaften (Anzahl der Schlüssel pro Knoten liegt zwischen m und
2m, alle Pfadlängen von der Wurzel zu einem Blatt sind gleich, jeder innere
Knoten mit s Schlüsseln hat s + 1 Söhne); Höhe = O(logm+1 n); die Struktur ließe
sich so festlegen:
type BNode = record used: 1..2m;
keys: array[1..2m] of keytype;
sons: array[0..2m] of BNodeAddress
end
(used gibt an, wieviele Schlüssel im Knoten gespeichert sind)
Es kann innerhalb eines Knotens binär gesucht werden. Die Kosten für eine Suche
sind beschränkt durch die Höhe des Baumes. Die Struktur kann bei
Änderungsoperationen recht einfach aufrecht erhalten werden. Die Algorithmen
für das Einfügen und Entfernen verletzen temporär die Struktur des B-Baumes,
indem Knoten mit 2m + 1 Schlüsseln entstehen (diese Verletzung heißt Overflow,
der Knoten ist überfüllt) oder Knoten mit m − 1 Schlüsseln (Underflow, der
Knoten ist unterfüllt). Es wird dann eine Overflow- oder Underflow-Behandlung
eingeleitet, die jeweils die Struktur in Ordnung bringt.
Algorithmen: insert (root, x): suche nach x im Baum mit Wurzel root; wenn x nicht
gefunden, dann sei p das Blatt, an dem die Suche endete, füge x an der richtigen
Position in p ein; wenn p jetzt 2m + 1 Schlüssel hat, dann overflow
delete (root, x): suche nach x im Baum mit Wurzel root; wenn x in einem inneren
Knoten liegt, dann suche x0 , den Nachfolger von x (den nächstgrößeren
gespeicherten Schlüssel) im Baum (x0 liegt in einem Blatt); vertausche x mit x0 ;
wenn x in stattdessen in einem Blatt liegt, dann lösche x aus p; wenn nun p nicht
die Wurzel ist, dann wenn p hat m − 1 Schlüssel underflow
overflow: Ein Overflow eines Knotens p wird mit einer Operation split(p)
behandelt, die den Knoten p mit 2m + 1 Schlüsseln am mittleren Schlüssel km+1
teilt, so daß Knoten mit Schlüsselfolgen k1 . . .km und km+2 . . .k2m+1 entstehen, die
7
jeweils m Schlüssel enthalten. km+1 wandert „nach oben“, entweder in den
Vaterknoten oder in einen neuen Wurzelknoten. Dadurch kann der Vaterknoten
überlaufen. Die Behandlung eines Overflow kann sich also von einem Blatt bis
zur Wurzel des Baumes fortpflanzen.
underflow: Um einen Underflow in p zu behandeln, werden der oder die Nachbarn
von p betrachtet. Wenn einer der Nachbarn genügend Schlüssel hat, wird seine
Schlüsselfolge mit der von p ausgeglichen (Operation balance), so daß beide etwa
gleichviele Schlüssel haben. Andernfalls wird p mit dem Nachbarn zu einem
einzigen Knoten verschmolzen (Operation merge).
balance: der Vaterknoten und die benachbarten Söhne werden so umsortiert, daß
der mittlere Schlüssel der gesamten Schlüsselfolge mit dem Schlüsselwert, den
beide Söhne im Vaterknoten einrahmen, ausgetauscht wird. Die beiden Söhne
werden dementsprechend neu aufgeteilt.
merge: wenn Vaterknoten nicht die Wurzel ist und m − 1 Schlüssel hat, dann
underflow (q); wenn er aber die Wurzel ist, dann werden die verschmolzenen
Nachbarn zum neuen Wurzelknoten
Priority Queues: Mengen mit INSERT, DELETEMIN: Warteschlangen, deren
Elemente gemäß einer Priorität eingeordnet werden, es wird das Element mit der
höchsten Priorität entnommen. Wird über Multimengen (Mengen, die Duplikate
zulassen) realisiert, Darstellung als Auflistung oder als Paare (Wert, Anzahl).
Lassen sich effizient mit modifizierten AVL-Bäumen oder partiell geordneten
Bäumen (wird bei Heapsort eingesetzt) realisieren. Partiell geordneter Baum
(Heap) bedeutet, daß in der Wurzel jeweils das Minimum (Maximum) eines
Teilbaums steht. In einem Heap ist die Folge der Knotenmarkierungen auf einem
Pfad monoton steigend.
Algorithmen: Folgendes gilt für links-vollständige partiell geordnete Bäume. Das
heißt, alle Ebenen bis auf die letzte sind voll besetzt (vollständiger Baum), und auf
der letzten Ebene sitzen die Knoten so weit links wie möglich. insert: erzeuge
neuen Knoten und füge ihn auf der ersten freien Position der untersten Ebene ein;
falls diese besetzt ist, beginne neue Ebene; vergleiche den erzeugten Knoten mit
dem Vater und vertausche gegebenfalls, solange, bis der jeweilige Vater nicht
mehr größer als der neue knoten ist
deletemin: gib den Eintrag der Wurzel als Minimum aus und ersetze ihn durch den
Eintrag der letzten besetzten Position im Baum; vergleiche die neue Wurzel mit
ihren Söhnen und vertausche sie solange möglich mit dem kleineren Eintrag der
beiden
8
Sortieralgorithmen
allgemeines: Berechnung einer Folge von geordneten Werten (Permutation der
Ausgangsfolge), Unterscheidung nach intern (alle Records werden im
Hauptspeicher gehalten) und extern, Unterscheidung nach Methodik (Sortieren
durch Einfügen, Auswählen; Divide-and-Conquer; Fachverteilen),
Unterscheidung nach Effizienz (einfache Verfahren O(n2 ), gute Verfahren
O(n log n)), Unterscheidung nach „im Array oder nicht“ (Ziel durch Vertauschen
erreichen, oder z. B. zwei Arrays benutzen), Unterscheidung nach allgemeinem
oder eingeschränkten (Records müssen spezielle Eigenschaften haben) Verfahren;
in der Analyse werden die wesentlichen Operationen Vergleich zweier Schlüssel
und vertauschen zweier Datensätze gezählt; ein Verfahren ist stabil, wenn die
Reihenfolge von Schlüsseln gleichen Wertes nicht geändert wird.
Jeder Schlüsselvergleichssortieralgorithmus benötigt Ω(n log n) Vergleiche im
worst case
Sortieren durch direktes Auswählen: entnimmt jeweils der unsortierten Folge das
Minimum und hängt es an die sortierte Folge an, d. h.
n + (n − 1) + (n − 2) + . . . + 1 = O(n2 ) Schleifendurchläufe, dazu O(n2 ) Vergleiche
und O(n) Vertauschungen
Sortieren durch Einfügen: entnimmt der unsortierten Folge das erste Element und fügt
es
Position in der sortierten Folge ein, d. h. im worst case
Pnan der richtigen
2
2
2
i=1 i = O(n ) Schleifendurchläufe, dazu O(n ) Vergleiche und O(n )
Vertauschungen
Bubblesort: zwei benachbarte Elemente eines Arrays werden vertauscht, wenn sie in
der falschen Reihenfolge sind; äußere Schleife läuft über alle Elemente, innere
vertauscht evtl. aufeinanderfolgende Elemente, Laufzeit O(n2 )
Divide-and-Conquer-Methoden: DAC-Paradigma:
if die Objektmenge ist klein genug
then löse das Problem direkt
else
Divide:
Zerlege die Menge in mehrere Teilmengen (wenn möglich, gleicher Größe).
Conquer: Löse das Problem rekursiv für jede Teilmenge.
Merge:
Berechne aus den für die Teilmengen erhaltenen Lösungen eine
Lösung des Gesamtproblems.
ein balancierter DAC-Algorithmus (Teilmengen sind etwa gleich groß) hat eine
Laufzeit O(n) falls Divide- und Merge-Schritt jeweils nur O(1) Zeit brauchen,
O(n log n) falls Divide- und Merge-Schritt in O(n) durchführbar sind. fi
Mergesort: O(n log n) im worst case, sortiert nicht in situ, wird vor allem extern
benutzt; benutzt einfachen Divide- und arbeitet im Merge-Schritt: Array oder
verkettete Liste wird rekursiv bis auf Einzelelemente geteilt und hernach rekursiv
9
sortierend (Verschmelzung der einzelner Listen, bis alles wieder zusammengefügt
ist) zusammengesetzt (Laufzeit O(m + n) bei parallelem Durchlauf)
Quicksort: O(n log n) im average case und O(n2 ) im Wurstkäse (da nicht vorausgesetzt
werden kann, daß im Divide-Schritt eine balancierte Aufteilung erreicht wird; bei
Clever-Quicksort werden zufällig drei Elemente ausgewählt und dabei das
mittlere als Pivot benutzt, hat sich als praxistauglich erwiesen.
Algorithmus: wähle irgendeine Schlüsselwert der Eingabefolge aus und berechne
zwei Teilfolgen, von denen eine kleiner ist als der Schlüsselwert und die andere
nicht, mache dies solange rekursiv, bis keine Unterfolgen mehr entstehen, hänge
dann alles aneinander.
Platzbedarf für Rekursionsstack kann O(n) werden.
Heapsort: benutzt partiell geordneten Baum (Heap, siehe priority Queue), garantiert
O(n log n) im worst case
Baumsortieren: Einfügen in eine geordnete Folge durch einen AVL-Baum – ein
inorder-Durchlauf liefert die sortierte Ergebnisfolge
Sortieren durch Fachverteilen: wenn die Schlüsselwerte bestimmten Einschränkungen
unterliegen und andere Operationen als Vergleiche angewandt werden können,
kann man schneller als Ω(n log n) sortieren; Bucketsort verwendet eine Liste von
Records (ähnliches offenem Hashing), bei denen sich ein Key in O(1) einem
Behälter zuordnen läßt, es benötigt also O(n). Radixsort: Man kann das Sortieren
auch in mehreren Phasen ablaufen lassen, z. B. erst nach dem letzte Zeichen eines
Keys konstanter Länge, dann nach dem vorletzten, oder auch durch
Datumsdarstellung. Es ist hierbei wesentlich für alle Phasen außer der ersten, daß
Records ans Ende einer Bucketliste angefügt werden. Man benutze eine
Listenimplementierung, die Anhängen ans Ende in O(1) Zeit erlaubt.
Radixsortieren eignet sich z. B. recht gut, wenn die Schlüssel Zeichenketten fester,
geringer Länge sind; wir fassen dann jeden Buchstaben als eine Ziffer zur Basis 26
auf.
10
Graphen
grundsätzliches: Ein Graph stellt eine Menge von Objekten zusammen mit einer
Beziehung (Relation) auf diesen Objekten dar. Objekte werden als Knoten,
Beziehungen als Kanten dargestellt. Eine Beziehung wird im allgemeinen durch
eine gerichtete Kante (einen Pfeil) dargestellt. Der Spezialfall einer symmetrischen
Beziehung wird durch eine ungerichtete Kante dargestellt.
Ein gerichteter Graph ist ein Paar G = (V, E), wobei gilt: V ist eine endliche,
nichtleere Menge (die Elemente werden Knoten genannt), E ⊆ V × V (die
Elemente heißen Kanten).
Bäume sind Spezialfälle von Graphen.
Darstellung durch Adjazenzmatrix: eine Adjazenzmatrix ist eine boolesche
n × n-Matrix (darin werden nur vorhandene Relationen markiert); bei
kantenmarkierten Graphen kann man direkt die Kantenbeschriftung statt der
booleschen Werte in die Matrix (markierte Adjazenzmatrix) eintragen; Vorteil:
man kann in O(1) feststellen, ob eine Kante existiert, Nachteil: hoher Platzbedarf
(O(n2 )), Auffinden aller Nachfolger eines Knotens benötigt O(n)
Darstellung durch Adjazenzlisten: Man verwaltet für jeden Knoten eine Liste seiner
(Nachfolger-) Nachbarknoten. Über einen Array der Länge n = |V | ist jede Liste
direkt zugänglich (bei markierten Graphen können die Listenelemente weitere
Einträge enthalten), Vorteil: geringerer Platzbedarf von O(n + e). Man kann alle k
Nachfolger eines Knotens in Zeit O(k) erreichen. Ein wahlfreier Test, ob v und w
benachbart sind, kann aber nicht mehr in konstanter Zeit durchgeführt werden,
da die Adjazenzliste von v durchlaufen werden muß, um das Vorhandensein von
w zu überprüfen.
Graphdurchlauf: für den systematischen Durchlauf eines Graphen wird angenommen,
daß alle Knoten von einer Wurzel aus erreichbar sind (Wurzelgraph); Expansion
in einen Baum, dann Tiefendurchlauf (entspricht einem Preorder-Durchlauf der
Expansion, der jeweils in einem bereits besuchten Knoten abgebrochen wird) und
den Breitendurchlauf (besucht die Knoten der Expansion ebenenweise, das
bedeutet, daß im Graphen zunächst alle über einen Pfad der Länge 1, dann der
Länge 2 usw. erreichbaren Knoten besucht werden, bei schon besuchten Knoten
wird wie beim Tiefendurchlauf ebenfalls abgebrochen); In einem allgemeinen
Graphen (ohne die Bedingung, daß alle Knoten von einem Wurzelknoten aus
erreichbar sein müssen) können bei einem Durchlauf unbesuchte Knoten
verbleiben;
11
1801
Betriebssysteme
Stichworte: Prozessor, Hauptspeciher, Ein-/Ausgabegeräte,
von-Neumann-Architektur, Befehlsausführung, Mindestbreite des Adreßbusses,
Caches, Cache-Konsistenz, Cache-Verdrängungsstrategien,
Sekundär-/Tertiärspeicher, Controller, Gerätetreiber, Controllerregister (Ein-,
Aus-, Status-, Kontroll-), memory mapped I/O, virtuelles Gerät (Abstraktion,
Kapselung, Schichtenmodell)
Festplattenstrategien: FCFS (first come first serve); SSTF (shortest seek time first)
bearbeitet den Auftrag zuerst, dessen Spuren am nächsten liegen; SCAN bewegt
des Kopf abwechselnd von innen nach außen und zurück und führt dabei die
Aufträge aus, deren Spuren gerade überquert werden
DMA: direkter Speicherzugriff ohne CPU-Arbeit, Gerätekontroller handelt Zugriff mit
DMA-Kontroller aus
Prozesse: Prozeßkontext besteht aus Registerinhalten (z. B. Befehlszähler), Grenzen
des Adreßraums, Prozeßnummer usw.; Prozeßzustände sind rechnend, bereit und
blockiert; Schedulingstrategien FCFS (Nachteil bei langen Jobs), SJF, Prioritäten,
round robin,
Kommunikation zwischen Prozessen: geschieht entweder über Versand von
Nachrichten oder gemeinsamen Speicherbereich; verbindungslos in einem
Rechner über Signale, im Client-Server-betrieb über Systemrufe send und receive,
verbindungsorientiert über Pipes;
Speicherverwaltung Buddy-Strategie: Der Hauptspeicher besteht aus
zusammenhängenden Stücken, die jeweils eine Zweierpotenz viele Seiten
enthalten.EinStück ist entweder frei oder belegt. Wenn der Allokierer einen
zusammenhängenden Bereich einer bestimmten Länge benötigt, nimmt er das
kleinste freie Stück, das mindestens die erforderliche Länge aufweist. Wenn es
mehr als doppelt so lang ist wie der benötigte Bereich, so wird es halbiert, das
eine halbe Stück wird benutzt, das andere bleibt frei. Wenn Zwei benachbarte
Stücke frei werden, so werden sie wieder verschmolzen.
Prozeßsynchronisation: ist z. B. notwendig, wenn mehrere Prozesse ein betriebsmittel
belegen wollen, wird mit Semaphoren gehandhabt. Ein Semaphor ist ein
abstrakter Datentyp mit Zählvariable (enthält Anzahl freier Betriebsmittel) und
Prozeßmenge (merkt sich anfragende Prozesse), benutzt zwei Operationen up()
und down(); down() belegt das Betriebsmittel oder fügt den Prozeß in die
wartendende Prozeßmenge ein, up() gibt Betriebsmittel frei und holt sich ggf.
nächsten Prozeß aus der Menge. Während der Semaphorenarbeit sperrt das
Betriebssystem die Unterbrechung derselben. Falls count nur 0 oder 1 werden
kann, spricht man von wechselseitigem Ausschluß (mutual exclusion)
Threads: verwenden gleichen Adreßraum wie ihr Erzeuger und unterscheiden sich
nur durchunterschiedliche Stacks und Register; Vorteile: schnelle Kontextwechsel,
12
Datensharing, Nachteile: fehlender Speicherschutz, I/O blockiert u. U. (wenn
Threads nicht im Kernel implementiert sind) ganzen Task; Anwendung bei
Servern und als Gerätetreiber für langsame Geräte
FAT: ist ein Array, in dem für jeden Block ein Eintrag steht, im Dateiverzeichnis steht
die Adresse i für den ersten Block einer Datei, in F AT [i] die Adresse j des
zweiten Blockes, in F AT [j] die Adresse des nächsten Blockes, für den letzten
Block eof , für freie Blöcke f ree; wird in aufeinanderfolgenden Blöcken
bespeichert, sollte so groß bemessen sein, daß sie komplett in den Hauptspeicher
oder Cache paßt; bietet gute Übersicht, welche Blöcke noch frei sind; Größe
berechnet sich: b × logB2 b (b =Anzahl der Blöcke, log2 b = Länge der Blockadresse in
Bit, B =Länge eines Blocks in Bit)
inodes: Variante des Index-Prinzips (Tabelle, die zu jeder logischen die physische
Blockadresse enthält); sind an fester Position auf Platte gespeichert; bieten keine
Übersicht, welche Blöcke noch frei sind, dafür kann man zusätzlich Bitvektor
verweden; ist aufgebaut aus einem Attribut-Block (Zugriffsrechte, Größe, UID,
GID,. . . ), dann die physischen Adressen der ersten zwölf Blöcke einer Datei,
dann die Adresse eines Blocks, der die Adressen der nächsten logischen
Dateiblöcke enthält (einfach-indirekter Index), dann die Adressen des
zweifach-indirekten und dreifach-indirekten Indexblocks; berechnet sich:
12 + logB b + ( logB b )2 + ( logB b )3 (ein Block kann logB b Blockadressen enthalten)
2
2
2
2
13
Speicherverwaltung
grundsätzliches: virtuelle Speicherverwaltungssystem nutzen Lokaliltätseigenschaft
von Programmen aus (Prozesse benutzen i. A. nicht sämtliche Daten und
Programme, sondern in jedem Zeitintervall nur einen Teil Davon); sind dem
Benutzer transparent;
Verfahren: Segmentierung (Segmente enthalten logisch zusammenhängende
Informationen und können relativ groß sein, entsprechen (Unter-)Programmen
und Datenmodulen, werden von Benutzer oder Compiler festgelegt, variable
Länge); Seitenwechselverfahren (logischer und physikalischer Adreßraum
werden in Seiten fester Länge unterteilt, Kapazität kann klein sein; ist dem
Benutzer transparent
Probleme: Einlagerungszeitpunkt (Betriebssystem muß festlegen, wann Daten
eingelagert werden), Zuweisungsproblem (an welcher Stelle im Speicher sollen
Daten eingelagert werden; bei Segmenten Problem, da diese keine einheitliche
Größe besitzen ; Zuweisungsstrategien: first-fit, best-fit, worst fit, Speicher
fragmentiert extern (Lücken für Segmente zu klein, muß in regelmäßigen
Abständen verdichtet werden); bei Seiten kein Problem, da Seitengröße konstant,
dabei aber interne Fragmentierung (Datenmengen entsprechen i. A. nicht dem
Vielfachen einer Seitengröße), Ersetzungsproblem (welche Daten sollen
ausgelagert werden – bei Seiten größeres Problem (häufigere Auslagerung),
Strategien: FIFO, LIFO, LRU, LFU (least frequently used – Seite, auf die am
seltensten zugegriffen wurde, wird ausgelagert)
Zusammenspiel Cache-MMU: virtueller Cache (liegt zwischen CPU und MMU,
höherwertige Bits der virtuellen Adresse werden als Tags abgelegt; keine
Verzögerung durch MMU, aber viel größerer Adreßraum), physikalischer Cache
(zwischen MMU und Speicher, Bits der physikalischen Adresse), Mischformen (z.
B. bei DEC Alpha, z. B. Index virtuell und Tag physikalisch)
Segmentorientierte Speicherverwaltung: virtuelle Adresse besteht aus zwei Teilen,
Selektor zeigt auf das Segment, Offset auf genaue Position innerhalb des
Segmentes; wird durch Segmentdeskriptor beschrieben (enthält Basisadresse,
Segmentgröße und Zugriffsrechte), mit dessen Hilfe eine 32 bit lange lineare
Adresse berechnet; Globale Deskriptor-Tabelle enthält Deskriptoren der
Segmente, die von allen Prozessen genutzt werden dürfen, Lokale
Deskriptortabelle enthält Segmentdeskriptoren für eigenen Code und Daten von
Prozessen
Seitenorientierte Speicherverwaltung: physikalische Adressen werden mit Hilfe eines
zweistufigen hierarchischen Tabellensystems berechnet; lineare Adresse ist
logisch in verschiedene Teile zerlegt (Directory -Offset bei 4-MByte-Seiten,
Directory-PageTable-Offset bei 4-KByte-Seiten),
Directory=Seitentabellenverzeichnis, Page Table=Seitentabelle; jeder Prozeß hat
eigenes Seitentabellenverzeichnis; Seitentabellen(verzeichnis)-Einträge enthalten
weiterhin Steuerbits
14
Ausnahmebehandlung
grundsätzliches: werden meist softwaremäßig behandelt, außer RESET oder BERR;
entspricht Unterprogramm, welches durch externes Signal oder bei
Fehlerbedingung im µP aufgerufen wird
Abarbeitung einer Ausnahme:
1. feststellen, ob interne oder externe Anforderung, bei externer über IE
schauen, ob Unterbrechung zugelassen ist
2. wenn sinnvoll, augenblickliche Operation beenden
3. PC, SR, evtl. weitere Register, auf Stack retten
4. Feststellen der Quelle der Unterbrechungsanforderung
5. Informieren der Systemkomponenten entweder über Signal INTA oder über
Statussignale
6. Deaktivieren bestimmter andere Unterbrechungsanforderungen
7. Ermitteln der Startadresse der Ausnahmeroutine, Laden dieser Adresse in PC
8. Ausführen der Ausnahmeroutine
9. Restaurieren der gesicherten Registerinhalte, Reaktivierung der zeitweilig
gesperrten Unterbrechungsanforderungen, Fortsetzen des Programmes
Interrupts: NMI werden nach Abschluß des gerade ausgeführten Befehls unbedingt
durchgeführt; während Behandlung eines NMI ist kein weiterer Interrupt (egal ob
NMI oder IRQ) zugelassen; IRQ (bzw. INT) werden nur ausgeführt, wenn IE
gesetzt ist; Verschachteln von IRQ ist möglich, wenn Programmierer IE nicht
zurücksetzt
Traps: führen zu Abbruch des Befehls (z. B. nach Division durch 0, Überlauf,
Bereichsüberschreitung in Arrays, Access Violation, invalid opCode, Stack
Overflow
Faults: führen idR zu Wiederholung des Befehls nach Beseitigung der Ursache, z. B.
Pagefault
mehrere Interruptquellen: früher Polling, heute: Interrupt-Vektortabelle liegt meist an
niederwertigsten Adresse, enthält 2n Einträge der Länge m und relative Adresse
jedes Eintrags, bezogen auf der Wert des Basisregisters. Komponente sendet INT,
µP liefert INTA, Komponente setzte IVN in Register, µP kann Interrupt nun
ausführen; Softwareinterrupts SWI n (oder INT n): Startadresse ist an festgelegter
Speicherstelle abgelegt
Prioritäten bei mehreren Anforderungen: NMI und Traps haben hohe Priorität
interrupt controller
15
Rechnernetze
grundsätzliches: Einsatz zum Daten-, Programm-, Geräteteilen, Rechenlastverteilen,
Client-Server-Betrieb; Rechnernetze unterschiedlichster Konfigurationen und
Betriebsweisen lassen sich mit Hilfe von Brücken und Gateways miteinander
vernetzen,
ISO-Referenzmodell: logische Aufteilung eines Systems impliziert in keiner Weise die
physikalische Struktur, es müssen nicht sämtliche Schichten in allen Systemen
realisiert sein; beschreibt zu jeder Schicht die allgemeinen Aufgaben, die der
nächsthöheren Schicht bereitzustellenden Dienste und die zu realisierenden
Funktionen;
Schicht 7: Anwendungsschicht (application layer): verwendet die Funktionen
aller unteren Schichten, um die gestellten Aufgaben zum Datenaustausch zu lösen
Schicht 6: Darstellungsschicht (presentation layer): dient zur identische
Interpretation der in Form von Bitfolgen ausgetauschten Datenobjekte
Schicht 5: Kommunikationssteuerungsschicht (session layer): stellt einerseits
zusätzliche Dienste zur Verfügung, die es den kommunizierenden Prozessen
erlauben, ihren Dialog zu kon-trollieren und zu synchronisieren, zum anderen
reicht sie Dienste der Transport-schicht, die die Güte einer Verbindung und den
eigentlichen Datenaustausch betreffen, unverändert an die Darstellungsschicht
durch.
Schicht 4: Transportschicht (transport layer): erweitert die Dienste der
Vermittlungsschicht um Wiederanordnung, Güteanforderungen
(Verzögerungszeiten, Fehlerrate, Durchsatz, Sicherheit), Prioritäten; Im Gegensatz
zur Vermittlungsschicht, die Verbindungen zwischen Endsystemen bereitstellt,
liefert die Transportschicht Verbindungen zwischen in verschiedenen
Endsystemen aktiven, miteinander kommunizierenden Anwendungsprozessen.
Schicht 3: Vermittlungsschicht (network layer): stellt Instanzen übergeordneter
Schichten, die in beliebi-gen Endsystemen residieren, logische
Übertragungskanäle zur Verfügung, ohne daß die betreffenden Endsysteme
direkt physikalisch miteinander verbunden sein müssen. Die Dienste der
Vermittlungsschicht bieten den benutzenden Instanzen der Transportschicht
Funktionen zur Flußkontrolle an, die es den Instanzen gestatten, die Rate der im
Endsystem eintreffenden Daten zu kontrollieren.
Schicht 2: Sicherungsschicht (data link layer): stellt unter Verwendung der
Dienste der Bitübertragungsschicht weitgehend sichere Übertragungskanäle zur
Verfügung (Fehlererkennung, -korrektur)
Schicht 1: Bitübertragungsschicht (physical layer): stellt physikalische
Übertragungskanäle zur Verfügung, die es gestatten, beliebige Bitfolgen zu
übertragen. Hierbei können seitens der anfordernden Instanzen Anforderungen
an den bereitzustellenden Übertragungskanal wie Fehlerrate, Verfügbarkeit,
Übertragungsgeschwindigkeit und Über-tragungsverzögerung vorgegeben
werden.
16
untere drei Schichten ermöglichen den Transport von Daten zwischen den
Endsystemen der eigentlichen kommunizierenden Instanzen der
Anwendungsschicht, obere vier Schichten sind für einen reibungslosen Verlauf
des Dialogs zwischen Anwendungsprozessen sind;
Signalübertragung: analog, digital, binär, (an-)isochron, Schrittdauer bei isochron:
vS = T1 , Schrittgesachwindigkeit in Baud ( 1s ); Übertragungsverfahren
Amplitudenmodulation, Frequenzmodulation, Phasendifferenzmodulation,
PCM-Verfahren
Datenübertragung: synchrone Übertragung in festem Zeitraster, asynchrone mit
Stopp- und Startzeichen; Datenübertragungseinrichtungen können besitzen:
Signalumsetzer (paßt die Signale der Datenendeinrichtung den Forderungen des
Übertragungskanals an), Verwürfler/Entwürfler (vermeiden lange Ketten selber
Werte), Anschalteinheit (baut Verbindung auf/ab), Prüfeinheit,
Synchronisierungseinheit, Asynchron-/Synchronumsetzer, Fehlerschutzeinheit
(nutzt redundante Information), Datenkompression/-dekompression;
Datensicherung: Übertragungsfehler sollen erkannt werden, als Verfahren werden
zeichenweise Informationssicherung (jedes codewort erhält Parität, man kann
damit kann jede ungerade Anzahl verfälschter Binärzeichen erkennen),
blockweise Informationssicherung (das jeweils i. Bit mehrere Codewörter wird
mit einem Paritätszeichen gesichert), kreuzweise Informationssicherung (beide
vorgherige Verfahren kombiniert) und blockweise zyklische
Informationssicherung (benutzen binäre Polynome) verwendet
einfache Sicherungsprotokolle: ARQ (Automatic Repeat ReQuest) setzen voraus, daß
sich Rahmen während ihrer Übertragung nicht überholen können;
Sende-und-Warte-Protokolle senden ACK oder NAK; Fensterprotokolle erlauben
weitere Rahmen zu senden (bestimmte Anzahl), verwenden Befehle go-back-n
(alles ab n nochmal senden) und selective-reject (einen Rahmen wiederholen)
17
Herunterladen