Sie sind auf Seite 1von 81

Zusammenfassung AUD

Algorithmus:
• Ein Algorithmus ist eine eindeutige Handlungsvorschrift zur Lösung eines Problems oder
einer Klasse von Problemen
• Das Verfahren muss in einem endlichen Text eindeutig beschreibbar sein (Finitheit)
o Algorithmen bestehen aus endlich, vielen wohldefinierten Einzelschritten
o Somit können sie zur Ausführung in einem Computerprogramm implementiert, aber
auch in menschlicher Sprache formuliert werden.

• Jeder Schritt des Verfahrens muss tatsächlich ausführbar sein (Ausführbarkeit)


• Der Algorithmus darf zu jedem Zeitpunkt nur endlich viel Speicherplatz benötigen
(Dynamische Finitheit, siehe Platzkomplexität)
• Der Algorithmus darf nur endlich viele Schritte benötigen (Terminierung)
• Bei der Problemlösung wird eine bestimmte Eingabe in eine bestimmte Ausgabe überführt
• Darüber hinaus wird der Begriff Algorithmus in praktischen Bereichen oft auf die folgenden
Eigenschaften eingeschränkt:
o Der Algorithmus muss bei denselben Voraussetzungen das gleiche Ergebnis liefern
(Determiniertheit)
o Die nächste anzuwendende Regel im Verfahren ist zu jedem Zeitpunkt eindeutig
definiert (Determinismus)

Programm:
• Ein Computerprogramm ist eine der Regeln einer bestimmten Programmiersprache
genügende Folge von Anweisungen (bestehend aus Deklaration und Instruktionen), um
bestimmte Funktionen bzw. Aufgaben oder Probleme mithilfe eines Computers zu
bearbeiten oder zu lösen.

Berechenbarkeit:
• Eine mathematische Funktion ist berechenbar (effektiv berechenbar oder rekursiv), wenn für
sie eine Berechnungsanweisung (Algorithmus) formuliert werden kann
(Berechenbarkeitstheorie)
• Die Funktion, die einen Algorithmus berechnet, ist gegeben durch die Ausgabe, mit der der
Algorithmus auf eine Eingabe reagiert
• Der Definitionsbereich der Funktion ist die Menge der Eingaben, für die der Algorithmus eine
Ausgabe produziert
• Wenn der Algorithmus nicht terminiert, dann ist die Eingabe kein Element er
Definitionsmenge

Warum Java?
• Plattformabhängigkeit:
o Java läuft auf den meisten Hardware-Plattformen und Betriebssystemen
• Leicht zu erlernen:
o Saubere Programmiersprache
o Junge Programmiersprache (Erscheinungsjahr 1995)
o Vollständige Fehlermeldung beim Übersetzen
o Programmierumgebung Eclipse: Code-Highlight, automatische Formatierung, Syntax-
Verknüpfung
Ziele/ Eigenschaften beim Entwurf der Programmiersprache Java:
• Einfache, objektorientierte, verteilte und vertraute Programmiersprache
• Robust und sicher
• Architekturneutral und portable
• Leistungsfähig
• Interpretierbar, parallelisierbar und dynamisch

Einfachheit:
• Reduzierter Sprachumfang
• Keine Überladung von Operatoren
• Keine Mehrfachvererbungen
• Memory-Management

Objektorientiert:
• Obkektorierntierte Programmiersprache
• Keine Erweiterung einer prozeduralen Prgrammiersprache

Verteilte Systeme: einfache Kommunikation über Netzwerke, z.B.


• Ausführen von Applets in einem Browser, die von einem Webserver geladen werden
• Unterstützung des TCP/ IP- Protokolls
• Remote Method Invocation (RMI)
• Webservices

Vertrautheit:
• Syntaktische Nähe zu C++
• Verwendung von Entwurfsmustern in der Klassenbibliothek

Robustheit:
• Starke Typisierung
• Garbage Colelction
• Ausnahmebehandlung
• Keine Zeigearithmetik

Sicherheit:
• Class Loader: sichere Zuführung von Klasseninformationen zur Java Virtual Machine
• Security Manager: erlaubt nur den Zugriff auf Programmobjekte, für die entsprechende
rechte vorhanden sind

Architekturneutral:
• Ein Java-Programm läuft auf einer beliebigen Computerhardware, unabhängig von ihrem
Prozessor oder andere Hardwarebestandteilen

Portabilität:
• Primitive Datentypen sind standardisiert bzgl. Ihrer Größe, ihrer internen Darstellung und
ihrem arithmetischen Verhalten
• Graphical User Interfaces unabhängig vom Betriebssystem

Leistungsfähigkeit:
• Dynamische Optimierung der Java Virtual Machine
Interpretierbarkeit: Übersetzung in maschinenunabhängigen Bytecode
• Interpretation auf der Zielplattform

Parallelilierbarkeit:
• Übersetzung des parallelen Ablaufs eigenständiger Programmabschnitte (Multithreading)

Dynamisch:
• Anpassung an sich dynamisch ändernde Rahmenbedingungen, z.B. Austausch von
Bibliotheken zur Laufzeit ohne Anpassung der restlichen Programmteile

Typen von Java-Programmen:


• Applications: sind Java Programme, die …
o Vergleichbar mit „normalen“ Programm anderer Programmiersprachen
o „alleinstehende“ Java-Programme
o auf dem lokalen Rechner direkt von der Java Laufzeitumgebung ausgeführt werden
o Bestehen aus einer oder mehrerer Klassen
o Jede Klassen steht in einer eigenen Datei, die den Namen der Klasse und die Endung
.java hat
o Eine dieser Klassen eine einer main-Methode
• Applets

In der main-Methode kann man z.B.


• Text und Variable ausführen
• Variable, Konstanten, Felder einlegen
• Ausdrücke berechnen und den Wert einer Variable zuwiesen
• For/ while- Schleifen oder if-Anweisungen programmieren
• Vorhandene oder eigene Unterprogramme aufrufen

Java-Programme besitzen für die Ausgabe zwei Datenströme (Streams)


• out: Standart-Ausgabe-Stream
• err: Ausgabe-Stream für Fehlermeldungen
können in demselben Fenster gezeigt oder in getrennte Dateien umgelenkt werden
Grunddatentyp wird automatisch in Strings konvertiert

Kommentare:
• Programm dokumentieren
• Teile auszukommentieren

Primitive Datentypen: bestimmen:


• Den Wertebereich und damit die Speichergröße:
• Die erlaubten
Operationen
Ganzzahlen: byte, short, int, long
Gleitpunktwerte: float, double
(Darstellung 24 oder 24F)
Zeichen: char
Variablen: Wahrheitswerte: boolean
• Bezeichnen einen Speicherplatz, in dem Werte eines bestimmten
Typs gespeichert werden können. Über den Variablennamen kann man auf den
Speicherinhalt zugreifen, ohne die Speicheradresse zu kennen
• haben folgende Eigenschaften:
o Typ
o Wert
o Adresse des Speicherbereichs
o Sichtbarkeitsbereich und Lebensdauer
• Kann mit einer Definition initialisiert werden
• Wenn nicht initialisier, kann sie später mit dem Zuweisungsoperator ein Wert zugewiesen
werden
• Variable müssen vor ihrer Verwendung deklariert werden
• Name einer Variablen unterliegt folgenden Einschränkungen
o : darf nur aus Buchstaben (’a’..’z’,’A’..’Z’), Ziffern (’0’..’9’) und den Zeichen ’$’ und ’_’
bestehen.
o Er darf nicht mit einer Ziffer beginnen.
o Der Bezeichner darf kein Schlüsselwort sein.
o Wert einer Variable kann bei der Definition initialisiert werden

Syntaxdiagramm:

Konstanten:
• Variablen, die ihren Wert nicht ändern könne, sind konstanten
• Einfache Änderbarkeit und Wartbarkeit des Programms
• Mit final
• In Großbuchstaben
• Wörter durch_ getrennt
Ausdrücke dienen zur Berechnung von Werten:
Kann sein:
• Literal: 24
• Variable: x
• Konstante: MATH_PI
• Funktionsabruf mit einem Ausdruck als Parameter:
Math.cos(x)
• Operation bestehend aus einer Operator und Ausdrücken als
Operanden: x+10

Operatoren:
Der Typ einer Variable legt fest, mit welchen Operatoren der Wert einer Variable manipuliert werden
kann:
• Zuweisungsoperator für alle Datentypen (Addition, …)
• Arithmetische Operatoren für numerische Datentypen (ganze Zahlen, Gleitkommazahlen)
• Vergleichsoperatoren für numerische Datentypen
• Vergleichsoperatoren für Character-Datentypen
• Logische Operatoren für den boolensche Grundtyp
Man unterscheidet:
• Einstellige (monadische, unäre) Operatoren (eine Variable)
• Zweistellige (dynamische, binäre) Operatoren (zwei Variablen, Bsp. Addieren)
• Dreistellige (triadische, tertiäre) Operatoren (3-stellige mit Teilvariablen

Mit dem Zuweisungsoperator kann in einer Variable der Wert eines Ausdrucks gespeichert werden.

Unäre arithmetische Operationen:


+ Identität (keine Wirkung)
- Negation
++ Inkrement (Erhöhung um 1)
--Dekrement (Erniedrigung um 1)

Bei ++ und – unterscheidet man Präfix und Postfix Notation:


- Präfix: erst inkrementiert/ dekrementiert, dann zurückgegeben
- Postfix: zuerst eingegeben, danach inkrementiert/ dekrementiert

Binäre arithmetische Operationen:


+, -, *. /, % (Rest bei der ganzzahligen Division, Modulo)

Kombination aus Zuweisung und arithmetische Operationen:


+= -= *= /= %=

Binäre Vergleichsoperatoren:
< > == <= >= !=
Rückgabewert ist vom Typ boolean
Unäre und binäre logische Operatoren:
& und | oder && und || oder ! nicht
Bei && und || wird der zweite Ausdruck nicht mehr evaluiert, wenn das Ergebnis des gesamten
Ausdrucks bereits nach der Auswertung des ersten Ausdrucks feststeht

Bit-Operatoren:
~ Einerkomplement
| bitweise Oder
& Bitweises und
^ bitweises Exklusives- Oder
>> Rechtsschieben (Nachschieben des Vorzeichen-Bits von links)
>>> Rechtsschieben (Nachschieben von Nullen nach links)
<< linksschieben

Der Fragezeichen Operator:


Bsp. a? b : c
Ist der boolische Wert true, so ist der Wert des Ausdruck b, anderenfalls Ausdruck c

Typumwandlung:

Explizite Typumwandlung:
Bsp. float f= 10/3

Explizite Casts:
Bsp: int i = (int) 1e3;

Gültigkeitsbereiche:
• Variablen sind an der Stelle im Programm, an der sie definiert wurden, bis zum Ende des
aktuellen Programm Blocks gültig.
Programmblöcke:
• Werden am Anfang und Ende durch { } getrennt
• Können geschachtelt sein
• Variablennamen in inneren Blöcken durch zur Vermeidung von Fehlerquellen nicht mit
Variablennamen aus übergeordneten Blöcken sein

Kontrollstrukturen:
• Verzweigung
o If-Anweisung (nur ausgeführt, wenn Bedingung den Wert true liefert, wenn nicht
wird der Block übersprungen)

o If/ else- Anweisung (wenn true, wird Bedingung ausgeführt, ansonsten else
Bedingung) (Dangling-else Problem: Zu welchen if Block gehört das else?)
o Switch-Anweisung (damit kann man mehrere if/ else Anweisungen ersetzt werden,
ist vom Typ int, case-Ziele müssen Integer-Literale oder Konstanten sein, kann durch
break verlassen werden

• Schleifen:
o For (Wiederholung eines Programmblocks, Kopf besteht aus drei Ausdrücken:
▪ init, für Initialisierung und Definition benutzt, darf aus mehrere Ausdrücken
bestehen, getrennt durch Komma
▪ Bedingung: wird vor jedem Schleifendurchlauf getestet, solange wiederholt
bis false,
▪ zählen. Nach jedem Schleifendurchlauf ausgeführt um Ausdruck zu
aktualisieren, darf aus mehreren Ausdrücken bestehen, durch Komma
getrennt, kann durch break verlassen werden, continue beendet aktuellen
Schleifendurchlauf und setz nächsten Durchlauf fort
o While (vor jedem Schleifendurchlauf wird Bedingung getestet, ausgeführt wenn true,
Initialisierung und Definition muss vor while-Schleife erfolgen, Aktualisierung
innerhalb des Schleifenblocks
o Do (Prüfung erfolgt nach dem Schleifendurchlaufs, muss mindestens einmal
ausgeführt werden

Feder (arrays) fassen Werte gleichen Typs zu einer Einheit zusammen


Eigenschaften:
• über einen Index kann man auf die einzelnen Werte zugreifen
• Jedes Feld besitzt die Variable length, die die Anzahl der Elemente gibt. Der Index läuft von 0
bis length-1
• Felder sind statisch, kann nach Erzeugung nicht mehr verändert werden

Anlegen von Arrays:


• Int[] zahlen= new int[10]
• Mit Initialisierung: int Zahlen[]= {12,59}

Zugriff auf Elemente:


• Wir mit [] Operator zugegriffen
Mehrdimensionale Felder:
• Der Typ eines Felds kann wieder ein Feld sein, durch die Verschachtelung lässt sich die
Funktion mehrdimensionale Felder realisieren
• Die Felder können unterschiedliche Felder haben

Referenzdatentypen:
• Neben primitiven Datentypen gibt es die Referenzdatentypen:
• Variablen von Referenzdatentypen speichern nicht die eigentlichen Daten, sondern lediglich
eine Referenz auf ein Objekt, d.h. die Anfangsadresse eines Objektes im Speicher
• Die Datentypen beginnen mit einem Großbuchstaben

Felder sind Referenzdatentypen, mehrere Referenzen können auf dasselbe Objekt verweisen

Felder kopieren:
Neues Feld anlegen
int [] feld = {2 , 3, 5, 7, 11 , 13 , 17 , 19 , 23 , 29};
int [] kopie = feld . clone () ;

In bestehendes Feld kopieren


System . arraycopy ( Object src , int srcPos , Object dest , int destPos , int length )

int [] feld1 = {2 , 3, 5, 7, 11 , 13 , 17 , 19 , 23 , 29};


int [] feld2 = new int [10];

System . arraycopy ( feld1 , 0, feld2 , 0 , feld1 . length );

Referenzdatentypen bringen ihre eigenen Operatoren mit


Referenzdatentypen können eigene Variablen haben

Beide Möglichkeiten, ein Feld zu kopieren, erzeugen flache Kopien:


• Sind die Elemente von einem Grunddatentyp, werden sie tatsächlich kopiert
• Sind die Elemente von einem Referenzdatentyp, werden nur die Referenzen kopiert, nicht
das referenzierte Objekt selbst
Klasse String besitzt eine Methode, um Strings zu konkatenieren:
.concat()

Datentypen für Aufzählungen (enumerations)


• Können eine begrenzte Anzahl an int-Werten annehmen
• Werte werden über einen Namen angesprochen
• Beispiele: Wochentage, Monatsnamen, …
• In Java handelt es sich hierbei um Klassen, die von java.lang.Enum abgeleitet sind.
• Die Definition dieses Datentyps muss daher innerhalb einer Klasse, aber außerhalb von
Methoden (z. B. der main-Methode) erfolgen.

Wertgleichheit vs. Identität:


Wie kann man feststellen, ob man eine Referenz auf ein Objekt oder eine tatsächliche Kopie hat?
• Identität: == Operator
o Zwei Objekte sind identisch (gleicher Typ, gleiche Speicheradresse und gleicher Wert)
• Wertgleichheit: equals-Methode
o Zwei (unterschiedliche) Objekte des gleichen Typs haben den gleichen Wert

(Bei primitiven Datentypen prüft der == Operator auf Wertgleichheit)


Unterprogramme:
• Stellen eine Möglichkeit dar, den Programmcode zu strukturieren:
• Mehrere Anweisungen zu einem Block zusammenfassen
• Unterprogramm hat einen Namen und kann über diesen aufgerufen werden
• Können Parameter übergeben werden
• Sind Prozeduren oder Funktionen bekannt
• In objektorientieren Sprachen wie Java sind Funktionen immer an Klassen gebunden, man
spricht von Methoden

Methoden in Java bestehen aus:


• Rückgabetyp
• Methodennamen
• Liste von Parametern
• Methodenrumpf

• Eine Methode, die nichts zurückliefern soll, hat den Rückgabetyp void
• In der Parameterliste muss für jeden Parameter der Typ und der Variablenname angegeben
werden. Unter diesen Namen kann innerhalb der Methode auf den Parameter zugegriffen
werden
• Mit return <ausdruck> kann der Wert des Ausdrucks zurückgegeben werden, der Typ muss
mit den Rückgabewert kompatibel sein
• Methode wird mit return verlassen

Formale vs. Aktuelle Parameter:


• Formale Parameter:
o Variable a und b sind die formalen Parameter der Methode vertausche
• Aktuelle Parameter:
o Sind die Werte, die beim Aufruf der Methode „vertausche“ eingesetzt werden,
können auch Variablen einsetzten, die einen anderen Namen als der formale
Parameter hat

Call-by-value:
• Die Werte der aktuellen Parameter werden evaluiert und als Kopien übergeben. Eventuelle
Veränderungen der formalen Parameter betreffen nur diese Kopien und gehen nach
Beendigung der Methode verloren. Die als aktuellen Parameter eingesetzte Variablen
werden nicht verändert. Felder, die mit call-by-value übergeben werden, sind ineffizient

Call.by.reference:
• Es werden lediglich Verweise (Referenzen) auf die aktuellen Parameter übergeben- Dadurch
ist es möglich, die aktuellen Parameter zu verändern. Die aktuellen Parameter werden nicht
kopiert

Parameterübergabe in Java:
• Parameter werden mittels Call-by-value übergeben
Was ist der Wert?
• Vom primitiven Datentyp (int, double, …): in der Methode eine Kopie dieser Variable
angelegt
• Von einem Referenzdatentyp (String, Array, Integer): Kopie der Referenz auf dieses Objekt
angelegt

Man kann Referenzen nicht ändern, aber das Objekt, auf die die Referenz verweist

Verarbeitung und Speicherung von Daten:


• Jede Eingabe wird im Rechner durch eine Folge von 0 bis 1 kodiert und verarbeitet
• Jede Ausgabe wird für den Nutzer in geeigneter Form dekodiert
• Grund: technisch einfache und zuverlässige Realisierung durch elektronische Schaltelemente
• Ein Schaltelement hat zwei Zustände: „eins“ (1) oder „aus“ (0)
• Das Bit ist die kleinste Informationseinheit, es unterscheidet zwei zustände: 1 oder 0, ja oder
nein, richtig oder falsch
• Byte ist eine Folge von 8 Bit, ein Byte hat 2^8 Bit= 256 mögliche Zustände oder Werte
• DasWort ist eine Folge von Byte, es ist maschinenabhängig definiert
• Länge des Datentyps ist unabhängig von der maschinenabhängigen Wortlänge
Ganze Zahlen:
Anforderungen an die Zahlendarstellung im Rechner:
• Leichte Durchführbarkeit von arithmetischen Operationen
• Einfache elektrische Realisierung
• Genügend großer Zahlenbereich
• Gewohnte Darstellung
• Fehler i der Darstellung einer Zahl sollen erkennbar und korrigierbar sein

Dezimalsystem:

Vereinbarung: Um Verwechslungen zu vermeiden, wird die Basis als Index rechts neben der Zahl
geschrieben

Polyadische Zahlensysteme:

Damit können ganze Zahlen mit höchsten N stellen dargestellt werden


Einschränkungen:
• Technische Realisierung
• Preis
• Erforderliche Rechengenauigkeit

Hexadezimalsystem:
Konversion zwischen Zahlensysteme ist wichtig, da …
• Rechner dual arbeiten
• Die duale Darstellung technisch vorteilhaft ist
• Benutzer dezimal denken gewohnt sind
• Eine Maschine benutzerfreundlich sein soll

Horner-Schema:

Nachteile:
• gesonderte Vorzeichenbehandlung
• Rechenwerk muss Addition und Subtraktion realisieren
• Zwei Darstellungen der Null

Zweierkomplement:
Invertieren aller Stellen von z und addieren 1:
Alle Zahlen mit führender 0 sind positiv
Alle Zahlen mit führender 1 sind negativ
Unsymmetrische Zahlenbereich

Gebrochene Zahlen:
Mit ^- dargestellt

Gleitkommazahlen:
Eine normierte Gleitkommazahl zur Basis B=2 hat dir Form
X=f*2^n
F ist die Mantisse
N ist der Exponent

Darstellung im IEEE Format:


Die erste Ziffer muss eine 1 sein
Wird lassen Vorkommastellen weg
Die 0 benötigt eine Sonderbehandlung
Wir addieren eine Konstante zum Exponenten, um positive und negative Exponenten als
vorzeichenlose Integer darstellen zu können

Darstellung

Konversion von Gleitkommazahl erfordert eine Konversion,


es gilt:
10 ist ca. 2^3,32
2 ist ca 10^0,30

Zahlenformate durch drei Parameter gegeben:


P: Zahl der bits der Martisse
Emax: maximaler Exponent
Emin: minimale Exponent

Basisformate:
1. Vorzeichen-Bit s
2. Verschobener Exponent e=E + bias
3. Bruch f= 1.b0b1 …b-1
msb : most significant bit
lsb : least significant bit

Beispiele:

Gleitkomma-Arithmetik:

Reelle und Gleitkommazahlen:


• Gehen von minus unendlich bis + unendlich
• Es gibt (nicht abzählbar) unendlich viele reelle Zahlen
• Liegen dicht

Logische Variablen:
• True und false
• Benötigen 1 Bit Speicherplatz
• Mit n Variablen lassen sich 2^2^n Verknüpfungen realisieren
• Mit Hilde der Schaltalgebra lässt sich die Funktion digitaler Schaltungen beschreiben

4 Axiome der Schaltalgebra:


• Kommutativgesetz
• Existenz neutraler Elemente
• Distributivgesetz
• Existenz der Komplemente: (Gegenteile)
• Dualität: Existenz einer dualen Aussage, wenn man & und | und 0 und 1 vertauscht

➔ Assoziativgesetz
➔ Absorptionsgesetz (assoziativ mit gewechselten Zeichen)
➔ Idempotenz (a oder a=a ist gleich a und a=a)
➔ De Morgan

Kodierung: für Buchstaben und Zeichen


• ASCII: kompakt, reicht für Länder mit lateinischem Alphabet
• UTF-32: alle Zeichen, die die Menschheit benutzt, hat und wird
• UTF-16 Kompromiss zwischen ASCII und UTF-32

Objektorientierte Programmierung:
Ist ein Verfahren zur Strukturierung von Computerprogrammen, bei dem:
• Zusammengehörige Daten
• Und die darauf arbeitende Programmlogik
• Zu Einheiten zusammengefasst werden, den sogenannten Objekten

Ziel: möglichst einfache Abbildung der realen Welt in einer Programmiersprache

Grundannahmen:
1. Die Welt besteht aus Objekten
2. Objekte sind klassifizierbar

Klassen:
• Eine Klasse ist eine allgemeine Beschreibung einer bestimmten Menge an Objekten
• Klassen legt die Eigenschaften und Verhaltensweisen der ihr zugehörigen Objekte fest
• In der OOP sind Klassen die Konstruktionspläne für Objekte

Objekte:
• Ein Objekt ist ein „Ding mit Eigenschaften und Verhaltensweisen“
• Jedes Objekt wird einer Klasse zugeordnet
• Ein Objekt wird auch als Instanz einer Klasse bezeichnet

Attribute
• Sind individuelle Eigenschaften eines Objektes, die die Merkmale und den aktuellen Zustand
des Objekts festlegen
• Alle Objekte einer Klasse besitzen die gleichen Attribute
• Attribute werden durch Variablen innerhalb einer Klasse definiert

Verhalten:
• Das Verhalten einer Klasse beschreibt, wie ein Objekt der Klasse auf Zustandswechsel oder
Nachrichten von einem anderen Objekt reagiert
• Das Verhalten wird durch Methoden innerhalb einer Klasse definiert, die heweils aud einem
Objekt dieser Klasse operieren
Vergleich:
• Bei der prozeduralen Programmierung werden einzelne Funktionsbereiche eines Algorithmus
sequenziell durchlaufen
• Bei der OOP entfaltet sich die Programmlogik in der Kommunikation und den internen
Zustandsveränderung der Objekte, aus denen das Programm aufgebaut ist

Die vier Grundprinzipien der OOP sind:


• Abstraktion:
• Kapselung:
• Vererbung:
• Polymorphie

Abstraktion:
• Beschreibt die Vorgehensweise, unwichtige Einzelheiten auszublenden und
Gemeinsamkeiten zusammenzufassen, um sich so auf das Wesentliche zu konzentrieren
Abstraktion bei der OOP:
• Beschreibung von gleichartigen Objekten bzw. Objekten mit gemeinsamen Merkmalen
mittels Klasse
• Beschreibung von gleichartigen Klassen bzw. Klasse mit gemeinsamen Merkmalen mittels
Oberklassen
• Verwenden von Objekten ohne Beachten der genauen Implementierung

Kapselung:
• Variablen, die zu einer Klasse gehören verstecken, außerhalb der Klasse nicht sichtbarer sind,
erhöht Struktur und Logik innerhalb des Programms
• Als Kapselung bezeichnet man den kontrollierten Zugriff auf Objekte. Hierbei wird das
Innenleben eines Objekts nach dem Geheimprinzip vor Zugriffen von außen geschützt. Die
nach außen sichtbaren Methoden eines Objekts nennt man auch seine Schnittstelle. Nur
über die Schnittstelle darf auf ein Objekt zugegriffen werden
• Vorteile der Kapselung:
• Die Implementierung einer Klasse kann geändert werden, ohne die Zusammenarbeit mit
anderen Klassen zu beeinträchtigen
• Zur erfolgreichen Benutzung einer Klasse muss nur die öffentliche Schnittstelle betrachtet
werden
• Verbesserte Änderbarkeit, Testbarkeit, Wartbarkeit der Software
Vorteile:
o Verbesserte Änderbarkeit: Implementierung einer Klasse kann problemlos geändert
werden, solange die öffentliche Schnittstelle gleichbleibt
o Verbesserte Testbarkeit: Beschränkung der Zugriffsmöglichkeiten auf eine Klasse
verkleinert automatisch die Anzahl der notwendigen Testfälle
o Verbesserte Wartbarkeit: erleichtert die Einarbeitung in fremde Programmcode und
vereinfacht die Fehlersuche

Vererbung:
• Unterklassen aus Klasse bilden, oder Klasse erhält Eigenschaften und Methoden von einer
Oberklasse → Hierarchie von Klasse
• Besteht zwischen verwandten Klassen eine „ist-ei“-Beziehung so kann die Definition der
einen Klasse auf der Definition der andere Klasse aufbauen
• Dabei kann die abgeleitete Klasse:
• Vorhandene Merkmale übernehmen
• Vorhandene Merkmale überschreiben
• Neue Merkmale ergänzen
• Die Übernahme der Merkmale der
vorhandenen Klasse durch die neue
Klasse bezeichnet man als Vererbung
• Ziele:
• Einheitliche Modellierung
gemeinsamer Merkmale verwandter
Klassen
• Wiederverwendung von
Programmcode
Bsp.: Generalisierung (Beziehung zwischen Ober- und Unterklasse darstellen
o Unterklasse ist Spezialisierung der Oberklasse
o Oberklasse ist Generalisierung der Unterklasse

Polymorphie:
• innerhalb einer Hierarchie können bestimmte Methoden vererbt werden, aber auch normal
überschrieben werden (abhängig von Level der Hierarchie und der konkreten und der
konkreten Klasse können eine Polymorphie ergeben)
• Ist ein Konzept, das besagt, dass verschiede Objekte auf die gleiche Anweisung
unterschiedlich reagieren können
• Kann erreicht werden, indem man Methoden innerhalb einer Klassenhierarchie überschreibt
• Dynamische Bindung: Zuordnung eines Methodennamens zur aufzurufenden Methode erst
zur Laufzeit
Ziele der Verwendung:
• Berücksichtigung von Besonderheiten bei verschiedenen Unterklassen
• Erzeugung einheitlicher Schnittstellen

Vorteile der OOP:


• Starke Modularisierung des Programmcodes
• Verbesserte Wartungsfreundlichkeit
• Vereinfachtes testen des Programmcodes
• Möglichkeit zur parallelen Entwicklung
• Hohe Wiederverwendbarkeit von Programmcode

Klassendefinition:

• (Elemente in eckigen Klammern [] sind optional.


• extends dient zur Angabe einer Basisklasse bei der Vererbung.
• implements dient zur Angabe einer oder mehrerer Schnittstellen.)

Modifikatoren:
Legen Sichtbarkeit und andere Eigenschaften fest:
• Für Klassen:
keinen: Klasse innerhalb Paktes sichtbar
• Public: Klasse innerhalb Pakets sichtbar
• Final: keine Unterklassen von Klasse bildbar, keine Vererbung
• Abstract: als abstrakte Klasse deklariert, keine Objekte instantisiert werden

Für Variablen:

• Keine: in eigener Klasse und eigenem Packet sichtbar


• Public: in allen Klassen sichtbar
• Private: nur innerhalb eigener Klasse sichtbar, Kapselung
• Protected: in eigener Klasse, abgeleiteter Unterklasse und allen Klassen des eigenen Pakets
sichtbar
• Static: unabhängig von der Existenz eines Objekts genutzt werden
• Final: nicht veränderbar
• Transient: verhindert Serialisierung der Variable
• Volatile: verhindert Optimierung durch den Compiler

Für Methoden:

• Keine: in eigener Klasse und allen Klassen des eigenen Pakets sichtbar
• Public: in allen Klassen sichtbar
• Private: nur innerhalb eigener Klasse sichtbar
• Protected: in eigener Klasse, allen abgeleiteten Unterklassen und allen Klassen des eigenen
Pakets sichtbar
• Static: unabhängig von Existenz eines Objekts nutzbar
• Final: verhindert Überschreibung der Methode in Unterklassen
• Abstract: wird in Unterklasse erst definiert
• Synchronized: sperrt die Methode während Ausführung für andere Threads
Abstrakte Klassen werden durch das Wort
abstract unter dem Klassennamen oder
durch Kursivdruck des Klassennamens
dargestellt; ebenso abstrakte Operationen.
Klassenattribute und Klassenoperationen
(static) werden unterstrichen
Gestrichelter Pfeil: implements
{read Only} ist Finale
Static ist unterstrichen (Integer Variable, Klassenvariable)
VERSION unterstrichen ist eine Klassenvariable
Array: String [Anzahl Einträge] {order}
[*]{unique} unendlich viele

Aufbau von Attributen:

Operationen:

Mit folgender Parameterdefinition:

Eigenschaften main:
• Soll Klasse als Applikation ausgeführt werden, muss die Methode in main enthalten sein+
• Main ist public, damit das Laufzeitsystem darauf zugreifen kann
• Main ist static, damit es ohne die Erzeugung eines Objektes der Klasse aufgerufen werden
kann
• Main liefert keine Rückgabewert, sein Rückgabewert ist void
• Aufrufparameter des Programms werden als Array von Strings an main übergeben

Eigenschaften von Instanzelementen:


• Jedes Objekt erhält einen eigenständigen Satz aller nicht statisch Variablen (Instanzvariablen)
seiner Klasse
• Nicht-statische Methoden (Instanzmethoden) können auf die Instanzvaribalen ihres Objekts
zugreifen
• Der Zugriff auf Variablen und Methoden eines Objekts erfolgt mit der Punknotation

Referenzieren des aktuellen Objekts mit this:


• Innerhalb von Instanzmethoden kann auf Instanzelemente mit Hilfe der Referenz this
zugegriffen erden
• Auf diese Weise kann auf Instanzvariablen zugegriffen werden, die durch eine lokale Variable
gleichen Namens verdeckt werden

Aggregationen:

• Ist eine ist-Teil-von-beziehung


• Die Kardinalitäten c1 und cc2 geben an, wie viele Objekte der beteiligten Klassen
miteinander in Beziehung stehen

Notation von Kardinalitäten:

Komposition:

• Ist ein Spezialfall der Aggregation, bei der ein Bestandteil zu genau einem Ganzen gehört und
nicht ohne das Ganze existieren kann

Organisation von Klassen in Dateien:


Einfache Regeln:
• In jeder Datei ist nur eine Klasse definiert
• Der Dateiname entspricht dem Klassennamen + .java
Zusätzlich:
• In jeder Datei darf nur eine öffentliche Klasse definiert sein, zusätzlich nicht-öffentlich
definierte Klassen sind möglich
• Der Dateiname entspricht dem Namen der öffentlichen Klasse

Begriffsdefinition:
Enthält eine Klasse mehrere Methoden mit gleichem Namen, aber unterschiedlichen Signaturen, so
spricht man von überladenen Methoden

Die Signatur einer Methode umfasst:


• Die Anzahl und den Typ ihrer Parameter
• Nicht den Typ des Rückgabewerts

Verwendung von überladenen Methoden:


• Vorbelegen optionaler Parameter
• Verarbeiten unterschiedlichen Parametertypen

Erzeugen von Objekten:

• Mit new Operator


• Wird oft dann von einer Referenztypvariablen referenziert
• Nach Erzeugung wird das Objekt mithilfe eines speziellen Methodentyps initialisiert
• Der Methodentyp heißt Konstruktor
• Konstruktoren tragen den Namen ihrer Klasse als Bezeichner

Konstruktoren
• Haben keinen Rückgabewert, in ihrer Definition darf kein Rückgabetyp stehen (auch nicht
void)
• Dürfen keine return Anweisung enthalten
• Ist in einer Klasse kein Konstruktor definiert, so definiert Java implizit eine parameterlosen
Standardkonstruktor
• Der Standardkonstruktor initialisier Instanzvariablen:
o Integervariablen mit 0
o Gleitpunktvariablen mit 0.0
o Boolsche Variablen mit false
o Referenztypvariablen mit null
• Konstruktoren können überladen werden, müssen sich in ihrer Signatur unterscheiden
• Wird in einer Klasse mindestens ein Konstruktor explizit definiert, ist der implizit definierte
Standardkonstruktor nicht mehr verfügbar
• Wird er benötigt muss er mit explizit definiert werden
• Ein Konstruktor kann einen anderen Konstruktor mit this([parameter]) aufrufen
• Der this() Aufruf kann nur andere Konstruktoren der gleichen Klasse erreichen
• This() muss die erste Anweisung in einem Konstruktor sein
• Nur ein this( ) ist innerhalb eines Konstruktes zulässig

Instanzvariablen und-methoden beschreiben die Eigenschaften und Verhaltensweisen von Objekten


einer Klasse

Klassenvariablen und. -methoden beschreiben Eigenschaften und Verhaltensweisen, die der


gesamten Klasse zuzuordnen sind
• Klassenvariablen sind Bestandteile der Klasse, werden als nicht mit jedem Objekt neu erzeugt
• Klassemethoden können ohne Instanziierung eines Objekts aufgerufen werden
• Die Definition von Klassenvariablen und Klassenmethoden erfolgt mit dem Modifikator static
• Zugriff auf Klassenvariable und Klassenmethode:

Auf Klassenvariablen und -


Methoden kann auch direkt über ein Objekt zugegriffen werden

Statische Methode können nicht


• Auf Instanzvariablen oder -methoden zugreifen
• Das Schlüsselwort this benutzen

• Konstante Klassenvariable
Definition erfolgt mit final Modifikator
• Name ist in Großbuchstaben
• Bei Referenztypkonstanten wird die Referenz als konstant definiert
• Das referenzierte Objekt weiterhin veränderbar

Statische Initialisierungsblock:
• Dient zur Initialisierung von Klassenvariablen bei der ersten Verwendung der Klasse
• Hat Zugriff auf alle statischen Variablen und Methoden der Klasse

Vererbung:
Ziele:
• Einheitliche Modellierung gemeinsamer Merkmale verwandter Klassen
• Wiederverwendung von existierendem Programmcode
Vorgehensweise:
• Neue Klassen werden auf Basis vorhandener Klassen definiert
• Die Merkmale der existierenden Klassen werden dabei
o Übernommen
o Überschreiben
o Ergänzt
Vererbung in Java:
• Jede neue Klasse wird von genau einer anderen Klasse abgeleitet
• Die oberste Basisklasse ist die Klasse Object
• Es gibt genau eine Klassenhierarchie

In Java kann jede Klasse nur von einer einzigen Oberklasse erben. Diese Art der Vererbung wird auch
als Einfachvererbung bezeichnet.

Kann eine Klasse mehrere Oberklassen besitzen, spricht man von Mehrfachvererbung
Vererben von Attributen:
• Instanzvariablen und Klassenvariablen der Basisklasse werden an die Unterklasse vererbt

Verdecken von Attributen:


• Instantvariablen und Klassenvariablen in Unterklassen verdecken Variablen gleichen Namens
in der Basisklasse
• Auf verdeckte Variablen in der unmittelbar übergeordneten Basisklasse kann mit der
Referenz super() zugegriffen werden
Das verdecken von Variablen hat meist keinen praktischen Nutzen und sollte nur in
Ausnahmefällen angewendet werden

Vererben von Methoden:


• Instanz Methoden und Klassenmethoden der Basisklasse werden an die Unterklasse vererbt

Überschreiben von Methoden:


• Instanz Methoden und Klassemethoden in Unterklassen überschreiben Methoden mit der
gleichen Signatur in der Basisklasse
• Auf überschriebene Methoden in der unmittelbar übergeordneten Basisklasse mit der
Referenz super() zugegriffen werden
• Das Überschreiben von Methoden wird in Klassenhierarchien genutzt, um bestehende
Methoden in den Unterklassen an weitergehende Anforderungen anzupassen

Sichtbarkeit von Klassen:


• Public class ist außerhalb des Paktes sichtbar

Sichtbarkeit von Variablen und Mehtoden:


Konstruktoren und Vererbung:
• Konstruktoren können nicht vererbt werden
• Konstruktoren werden von oben nach unten ausgeführt
• Mit der Anweisung super() kann der Konstruktor der unmittelbar übergeordneten Basisklasse
explizit aufgerufen werden

Eigenschaften on super()
• Ein explizit Aufruf von super() muss als erste Anweisung in einem Konstruktor stehen
• Bei einem expliziten Aufruf von super() sind Parameter zulässig
• Wird super() nicht explizit aufgerufen, dann wird ein implizit super() ohne Parameter
aufgerufen
• This() und super() dürfen nicht beide im gleichen Konstruktor verwendet werden
• Ein Aufruf von this() verhindert due Ausführung eines impliziten super() im aktuellen
Konstruktor

Typkompatibilität:
• Jedes Objekt einer Unterklasse ist auch ein Objekt der Basisklasse
• Eine Variable vom Typ einer Basisklasse darf auch Objekte der Unterklassen referenzieren
• Es kann nur auf Methoden und Variablen aus der Klasse des Typs der Variable zugegriffen
werden
• Mit Hilfe der Typkonvertierung kann man au Methoden eines Objekts einer Unterklasse des
Typs einer Variable zugreifen
• Die Verwendung der Typkonvertierung kann zu Laufzeitfehlern führen

Dynamische Bindungen:
• Entscheidung, welche von mehreren Methoden mit gleicher Signatur ausgeführt wird, erfolgt
erst zur Laufzeit
• Alle Instanz Methoden werden in Java dynamisch gebunden
• Klassenmethoden existieren unabhängig von Objekten und werden nicht dynamisch
gebunden
• Auswirkungen der dynamischen Bindung sind beobachtbar,
o wenn Variablen vom Typ einer Basisklasse auf Objekte von Unterklassen verweisen
o und über diese Variablen überschriebenen Methoden aufgerufen werden
• Dynamische Bindungen erfordert Aufwand und erhöht die Rechnerzeit bei
Methodenaufrufen

Klassen und der Modifikator finale:


• Von Klassen mit dem Modifikator final können keine Unterklassen abgeleitet werden
• Die dynamische Bindung entfällt bei Methodenaufrufen aus diesen Klassen folglich

Methoden mit dem Modifikator final können in Unterklassen nicht überschrieben werden

Variablen mit dem Modifikator final sind nicht veränderbar, somit eine Konstante
Das Verdecken der Variable in einer Unterklasse kann mit dem Modifikator final nicht verhindert
werden

Eigenschaften der Klasse Object:


• Alle Klassen in Java sind Unterklassen der Klasse Object
• Alle Objekte in Java sind auch Objekte der Klasse Object
• Alle Objekte können die Methoden der Klassen Object aufrufen

Wichtige Methoden der Klasse Object


toString( ):
• liefert die String-Darstellung eines Objektes zurück
• Die zurückgegeben Zeichenkette besteht aus de Klassennamen und der Speicheradresse des
Objekts
• Durch Überschreiben der Methode kann eine andere Darstellung der Objekte einer Klasse
erreicht werden

Equals(Object o)
• Untersucht, ob zwei Objekte „gleich“ sind
• Implementierung von equals() in Klasse Object überprüft, ob zwei Referenzen das gleiche
Objekt referenzieren
• Um den „Inhalt“ zweier Objekte zu vergleichen, muss die Methode equals() überschrieben
werden

hashCode()
• liefert einen hashCode für ein Objekt
• Beim Überschreiben von equals() muss auch hashCode() angepasst werden, so dass für
gleiche Objekte ein gleicher Hash-Code zurückgegeben wird

finalize()
• Wird vom Garbage Collector aufgerufen, wenn Objekt nicht mehr gebraucht wird
• Durch Überschreiben dieser Methode in einer Klassen könne Anweisungen vor dem Löschen
der Objekte ausgeführt werden

getClass():
• gibt ein Objekt zurück, das die Klasse des aktuellen Objekts repräsentiert

clone()
• kopiert bei Referenzdatentypvariablen nur die Referenz, nicht Objekt
• Kopie des Objekt erhält man über Methode main()
• Objekt kann nur mittels clone() kopiert werden, wenn es die Schnittstelle Cloneable
implementiert
• Enthält ein Objekt selbst Referenzatentypvariablen, so unterscheidet man zwischen einer
flachen Kopie, bei der nur die Referenzen kopiert werden, und einer tiefen Kopie, bei der
auch die referenzierten Objekte kopiert werden

Nur wenige Klassen des Java API lassen sich mittels clone() kopieren, zum Beispiel java.util.Vector

Abstrakte Klassen:
• Mit Modifikator abstract erstellt
• Von abstrakten Klassen könne keine Objekte gebildet werden
• Referenztypvariablen vom Typ abstrakter Klassen sind erlaubt
• Abstrakte Klassen können neben abstrakten Methoden auch konkrete Methoden und
Variablen besitzen
• Die Methode und Variablen abstrakter Klassen werden an abstrakte und konkrete Klassen
vererbt
• In konkreten Klassen, die Unterklassen von abstrakten Klassen sind müssen alle geerbt
abstrakten Methoden durch die konkrete Methode überschreiben werden

Abstrakte Methoden
• Abstrakte Methoden werden mit dem Modifikator abstract erstellt
• Abstrakte Methoden enthalten keinen Methodenrumpf

Schnittstellen (Interfaces)
• Interfaces sind in Java eine Zusammenfassung von abstrakten Methoden und Konstanten
• Von Interfaces können keine Objekte gebildet werden
• Referenztypvariablen vom typ eines Interfaces sind erlaubt
• Mehrfachvererbung wird mittels Interfaces realisiert, denen man eine Standart-
Implementierung mitgibt

Definierten von Schnittstellen:


• Definition erfolgt mit dem Schlüsselwort interface
• Jede in einem Interface deklarierte Methode ist implizit abstract und public
• Jede in einem Interface deklarierte Variable ist implizit static, final und public

Vererben von Schnittstellen


• Ein Interface kann von einem bestehenden Interface durch Vererbung abgeleitet werden
• Mehrfachvererbung ist bei Interfaces, im Gegensatz zu Klasse, erlaubt
• Analog zu einer Hierarchie von Klassen entsteht so durch Vererbung eine Hierarchie von
Interfaces

Implementierung von Schnittstellen


• Soll eine Klasse ein Interface implementieren, so wird dies mit dem Schlüsselwort
implements festgelegt
• In einer konkreten Klasse müssen alle (abstrakten) Methode aus den implementieren
Interfaces überschreiben werden

Interfaces Zusammenfassung:
• Interfaces verhalten sich ähnlich wie abstrakte Klassen
• Interfaces bestehen im Gegensatz zu abstrakten Klassen ausschließlich aus abstrakten
Methoden und Konstanten
• Durch Vererbung kann eine Hierarchie von Interfaces erstellt werden
• Eine Klasse kann mehrere Interfaces implementieren
• Interfaces sind ein „Ersatz“ für Mehrfachvererbung in Java
• Interfaces unterstützen Abstraktion, Kapselung und Modularisierung

Verschachtelung:
Einteilung:
• Innere Klassen werden in einer umgebenen Klasse definiert
• Lokale Klassen werden in einer Methode oder einem Block definiert
• Anonyme Klassen sind lokale Klassen ohne Bezeichner
• Statische innere Klassen sind eine Spezialform der inneren Klassen

Anwendungsgebiete:
• Einsatz als Hilfsklasse, die von abstrakten Klassen erben oder Interfaces implementieren
• Ergebnisbehandlung in graphischen Bedienoberflächen
Eigenschaften innerer Klassen:
• Für innere Klassen sind die Modifikatoren public, protected und private erlaubt
• können auf Methoden und Variablen der äußeren zugreifen
• dürfen weder eine abstrakte Klasse noch ein Interface sein
• können abstrakte Klassen erweitern und Interfaces implementieren
• können geschachtelt werden
• Sind über die äußere Klasse erreichbar

Das Interface java.until.Enumeration:


• Enumeration definiert eine Schnittstelle zum sequenziellen Zugriff auf eine Liste von
Elementen
• Das Interface Enumeration definiert zwei Methoden:
o Public boolean hasMoreElements()
o Die Methode zeigt an, ob weitere, noch nicht aufgerufenen Elemente vorhanden sind
• Public Object nextElement
• Die Methode gibt eine Referenz vom Typ Object auf das aktuelle Element zurück.
Anschließend wird das nächste, noch nicht abgerufene Element gesucht

Unterschied lokale Klassen zu inneren Klassen:


• Lokale Klassen werden innerhalb einer Methode oder innerhalb eines Blocks definiert
• Lokale Klassen erhalten keine Zugriffsmodifikatoren
• Sind nur innerhalb der Methode oder innerhalb des Blocks erreichbar, in der/ dem sie
definiert werden
• Können nicht nur auf Methoden und Variablen der umgebenen Klassen zugreifen, sondern
auch auf lokale Konstanten des umgebenden Blocks

Klasse SequenceEnum wird nur in der Methode getSequenceEnum() verwendet

Eigenschaften anonymer Klassen:


• Anonyme Klassen sind eine Spezialform der lokalen Klassen, die keinen eigenen
Klassennamen besitzen
• Können an jeder Stelle definiert werden
• Erfolgt zusammen mit dem Konstruktoraufruf, der eine Instanz zurückliefert

Mehrfachvererbung:
• Bei der Objektorientierten Programmierung handelt es sich im Mehrfachvererbung, wenn
eine abgeleitete Klasse direkt von mehr als einer Basisklasse erbt
• Ein sequentielles, mehrstufiges Erben wird dagegen nicht als Mehrfachvererbung bezeichnet

Diamond-Problem:
• Entsteht durch die Mehrfachvererbung in der Objektorientieren Programmierung
• Kann auftreten, wenn Klasse D auf zwei verschiedene Vererbungspfaden (B und C) von eine
und derselben Basisklasse A abstammt

Alternativen:
• Java lässt keine Mehrfachvererbung zu, jedoch abstrakte Klassen und Interface-
➔ nur Deklaration wird geerbt, nicht die Implementierung und keine Instanzvariablen
➔ Ab Java 8: Interface kann auch Methoden beinhalten aber keine Instanzvariablen

Unterschied Interface und Mehrfachvererbung:


• Durch Interface können ausschließlich abstrakte Methoden und Konstanten vererbt werden
• Abstrakte Methoden müssen dann in der Klasse ausprogrammiert werden
• Bei Mehrfachvererbung kann eine Klasse beliebige Datenelemente und „gebrauchsfertige“
Methoden von mehreren Klassen erben

Streams:
Sequenzielle Programme und Mehrkernarchitekturen
• Wichtiger Aspekt bei der effizienten Verarbeitung großer Datenstrukturen auf nebenläufigen
Architekturen wie Mehrkernprozessoren oder Rechnerclustern ist die Möglichkeit, einen
geeigneten Programmabschnitt echt nebenläufig ablaufen zu lassen
• Nicht jede Operation eines Algorithmus lässt sich parallelisieren, manche
Anweisungsabschnitte können nur sequenziell erfolgen

Ein Datenstrom (Stream) ist eine Folge gleichstrukturierter Elemente, deren Ende nicht im Voraus
festgelegt ist
• Auf Datenströme sind sequenzielle und parallele Auswertungen anwendbar
• Aber keine direkten und gezielten Zugriffe auf einzelne Elemente

Gesetz von Amdahl:


• Die Beschleunigung durch nebenläufige Ausführung wird durch die sequentiellen Anteil
beschränkt

Gesetz von Gustafson-Barsis:


• Ein genügend großes Problem kann effizient parallelisiert werden

Der entscheidende Unterschied zu Amdahl ist, dass der parallele Anteil mit der Anzahl der
Prozessoren wächst. Der sequenzielle Teil wirkt hier nicht beschränkend, da er mit zunehmendem N
unbedeutender wird. Geht N gegen unendlich, so wächst der SpeedUp linear mit der Anzahl der
Prozessoren N
Gustafsins Gesetz lässt sich gut auf Probleme anwenden, die in Echtzeit verarbeitet werden
Pipes und Piplines:
• Eine Pipe bezeichnet einen gepufferten Datenstrom zwischen zwei Prozessen oder Threads
nach dem FIFO Prinzip
• Eine Pipeline ist einer Abfolge von Operationen auf einem Datenstrom
• Sie besteht aus:
o Quelle (source)
o Mehrere intermediären Operationen (intermediate operations)
o einer terminalen Operation (terminal operation)
• Die Quelle kann eine Datenstruktur, eine Daten erzeugende Funktion („Generator“) oder I/O-
Kana sein
• Aus der Quelle wird der Stream dann erzeugt, technisch ist der oft lediglich ein Verweis auf
die Quelle

Auswertung von Datenströmen: lazy und eager


• In intermediären Operationen werden Ausdrücke üblicherweise lazy ausgewertet
• Eine lazy Evaluation bezeichnet eine Art der Auswertung, bei der das Ergebnis des
auszuwertenden Ausdrucks nur insoweit berechnet wird, wie es benötigt wird
• Bei eager Evaluation wird jeder Ausdruck sofort ausgerechnet oder verarbeitet
• Intermediäre Operationen sind stehts lazy, da der Inhalt des Streams erst dann verarbeitet
wird, wenn die terminale Operation beginnt

Streams in Java:
• Streams sind das zentrale Interface in Java, das parallelisierbare Operationen für Collections
und Arrays ermöglicht, aber auch unendliche Folgen von Elementen
• Streams werden anhand ihrer Quelle unterschieden

Unterschied Stream und Collections:


• Collections sind flexible Datenstrukturen die im Arbeitsspeicher gehalten werden und deren
Wert berechnet werden muss, bevor sie der Collection zugefügt werden
• Streams sind fixe Datenstrukturen, bei der die Werte der Elemente erst bei Bedarf berechnet
werden

Terminale Funktionen liefern als Ergebnis einen Datentyp zurück


Intermediäre Funktionen verarbeiten den Datenstrom und liefern den (modifizierten) Datenstrom als
Ergebnis zurück

Grundlagen der Fehlerbehebung:

Mögliche Laufzeitfehler:
• Division durch Null
• Verwendung unzulässiger Parameter für Methoden
• Zugriff auf ein Array außerhalb seiner Grenzen
• Fehlerhafte Benutzereingabe
• Fehlende Zugriffsberechtigung auf Dateien

Vorgehensweise bei defensiver Programmierung:


• Mögliche Laufzeitfehler vorhersehen
• An relevanten Stellen auf Laufzeitfehler prüfen
• Bei Eintritt eines Fehlers geeignete Aktionen durchführen

Mögliche Reaktion auf einen Laufzeitfehler:


• Wiederholen der Operationen in anderer Form
• Beseitigen oder Vermeiden von Fehlern
• Informieren umgebender Programmteile über den Fehler
• Informieren der Programmumgebung über den Fehler
• Informieren des Benutzers über den Fehler
• Programmabbruch

Fehlerbehandlung mit Rückgabewerten:


• Vorteil:
o Einfache Implementierung
• Nachteile:
o Rückgabe eines Funktionswert wird erschwert
o In großen Programmen unübersichtlich

Ausnahmebehandlung in Java
• Ausnahme werden in Java durch Objekte der Klasse Throwable oder einer ihrer Unterklassen
repräsentiert
• Das Auslösen einer Ausnahme heißt Werfen (Throw)
• Das behandeln einer Ausnahme heißt Fangen (catch)
• Eine Ausnahme kann von Java-Laufzeitsystem oder explizit durch eine spezielle Anweisung
ausgelöst werden
• Eine geworfene Ausnahme muss:
o Entweder im gleichen Anweisungsblock behandelt werden
o Oder an die aufrufende Umgebung weitergereicht werden
• Wird eine Ausnahme an keiner Stelle des Programms aufgefangen, so wird das Programm
abgebrochen

Wichtige Standart-Laufzeitausnahmen:
• ArithmeticExeption: Division durch Null
• IllegalArgumentExeption: Verwendung falschen Parameters
• IndexOutOfBoundsExeption: Überschreitung des Indexbereichs bei einem Feldzugriff
• NegativArraySizeExeption: ungültige Größenangabe beim Anlegen eines Arrays
• NullPointExeption: Objekt-Zugriff über eine null-Referenz
• ClassNotFoundExeption: Verwendung einer nicht vorhandenen Klasse

Eigenschaften der Ausnahmeklassen:


• Ausnahmen vom Typ Error sind schwerwiegende Fehler und führen um Programmabbruch
• Ausnahmen vom Typ Exception sollten vom Programm angemessen behandelt werden
• Ausnahmen vom typ Error und RuntimeError müssen nicht zwingend behandelt werden. Sie
werden als ungeprüfte (unchecked) Ausnahmen bezeichnet
• Alle Ausnahmen erben von der Klasse Throwable folgende Methoden:
o Public String getMessage() liefert die bei der Erzeugung festgelegte Beschreibung
zurück
o Public String toString() liefert den typ und die Beschreibung der Ausnahme zurück
o Public void pirntStackTrace() gibt Typ, Beschreibung und die bei der Erzeugung
aktiven Methodenaufrufe auf stderr aus

Auffangen von Ausnahmen mit try… catch


• Tritt im try-Block eine Ausnahmesituation auf, so wird ein Ausnahmeobjekt erzeugt und
geworfen
• Anhängig vom Typ des geworfenen Ausnahmeobjektswird der erste passende catch-Block
ausgeführt
• Passt kein catch-Block, so wird die Ausnahme an die dynamische Umgebung weitergereicht
• Ein abschließender finally-Block wird immer ausgeführt, unabhängig davon, ob eine
Ausnahme geworfen und/ oder gefangen wurde

Werfen von Ausnahmen mit throw


• Ausnahmen können mit der throw-Anweisung auch explizit geworfen werden
• Solche Ausnahme werden genauso behandelt wie vom Laufzeitsystem geworfene
Ausnahmen

Weiterreichen von Ausnahmen mit throws:


• Geprüfte Ausnahme sind alle Ausnahmen, die nicht vom Typ Error oder RuntimeError sind
• Geprüfte Ausnahmen müssen prinzipiell in der Methode, in der auftreten, behandelt werden
• Geprüfte Ausnahme können aber auch weitergereicht werden. Dazu muss die Methode, in
der die Ausnahme nicht behandelt wird, dies mit der throws-Klausel bekannt geben

Definition eigener Ausnahmeklassen:


Eigene Ausnahmeklassen könne genauso wie andere Klassen definiert werden
• Können zusätzliche Informationen über den aufgetretenen Fehler speichern
• Können in spezialisierten catch-Blöcken gefangen werden

Pakete:
• Zusammenstellung der Definitionen von zusammengehörigen Klassen (und Schnittstellen)
• Jede Klasse in Java ist in einem Paket zugeordnet
• Alle anderen Pakete besitzen einen Namen, der hierarchisch aufgebaut sein kann (Bsp.
java.lang, java.until.Date)
• Der Paketname definiert einen Namensraum für die in den Pakten vorkommenden
Klassendefinitionen

Festlegen der Paketzugehörigkeit:


• Das Schlüsselwort package definiert das Paket, dem die Klassen einer Quelltextdatei
angehören
• Package steht immer am Anfang der Quelltextdatei

Verwenden von Paketen:


• Zugriff auf eine Klasse, die in einem anderen benannten Pakte definiert wurde:
paketname.klassenname
• Alternative: Bekanntmachung des Klassennames mittelts import; dann ist die Angabe des
Klassennames ausreichend
• Klassen des Pakets java.lang werden automatisch importiert

Pakete und Dateipfade:


• Jedem Paketnamen entspricht einem Dateipfad
• Dateien eines Paktes befinden sich im zugehörigen Verzeichnis
• Das Verzeichnis ist entweder das aktuelle Verzeichnis, oder es muss mit der
Umgebungsvariable CLASSPATH bekannt gemacht werden
• Mehrere Basisverzeichnisse möglich
• Der Compiler javac und der Interpreter java unterstützen auch die Angabe von CLASSPATH
als Kommandozieloption

Vorteile des Paketkonzepts:


• Einfache Handhabung von Programmbibliotheken durch die Zusammenfassung
zusammengehöriger Klassen
• Vermeidung von Namenskonflikten (insbesondere in gröberen Projekten) durch eigene
Namensräume der Pakete
• Weiter Ebene der Kapselung innerhalb der Kapselung von Attributen und Methoden in
Klassen
• Kapselung auf Ebene der Module, welche hier als Verbund von Klassen mit einer definierten
Schnittstelle zu ihrer Umgebung zu verstehen sind

Begriffserklärung API:
• Application Programming Interface (Schnittstelle zur Anwendungsprogrammierung)
• Definiert die von einem Softwaresystem (z.B. Betriebssystem) bereitgestellte Funktionalität
auf Quelltextebene
• Java-API: Klassenbibliothek (Sammlung vordefinierter Klassen) zur Verwendung der
Programmierung eigener Programme/ Bibliotheken
• Alternative Bezeichnungen: JDK-API, SDK-API
• Das Java-API ist in Pakete eingeteilt, deren Namen mit java oder javac beginnen

Liste oft verwendeter API-Pakete


• Java.lang: Paket für fundamentaler Programmieraufgaben in Java, umfasst unter andere
Klassen zur Typkonvertierung, String-bearbeitung und für mathematische Funktionen, ohne
import Anweisung verfügbar
• Java.util: Klassen für verschiedene Hilfsaufgaben, z.B. Klassen für Arrays, Listen, Datum,
Kalender
• Java.io: Klassen zur Ein-/Ausgabe in Dateien
• Java.net: Klassen zur Netzwerk-programmierung
• Java.sql: Anbindung relationaler Datenbanken
• Java.applet: Basisklassen Applet zur Anzeige von Java-Applets in Webbrowsern
• Java.awt: Basisbibliothek für graphischer Flächen, leistungsfähiger als AWT und baut
teilwiese auf diesem auf

Ein- und Ausgabe mit der Klasse System:


• Die Klasse System enthält folgende Klassenvariablen:
o In
o Out
o Err
• Out ist ein Objekt der Klasse java.io.PrintStream
• Println() ist eine Methode der der Klasse java.io.PrintStream
• Die Methoden print/ println() sind überladen, um verschiedene primitive Datentypen
ausgeben zu können

Wichtige Methoden in der Klasse System:


• Public static long currentTimeMillis() (aktuelle Systemzeit in Millisekunden seit 1.01.1970)
• Public static void exit (int status) (beendet Programm, fehlerfreier Programmablauf wird mit
dem Status 0 angezeigt)

Bearbeitung der Klasse java.lang.String:


• Standartklassen von Java zur Bearbeitung von Zeichenfolgen
• Die Klasse String verwaltet die zur Programmzeit vorhandenen Objekte in einem internen
Pool
• Jede in Hochkommata (““) eingeschlossene Zeichenfolge wird als Objekt des Typ Sting
behandelt
• Alle Variablen des Typen String sind Referenzdatenvariablen

Wichtige Methoden der Klasse String:


• Public char charAt (int index): liefert das Zeichen and er Position im index; erstes Zeichen des
Index ist 0
• Public int indexOf( int ch): liefert die Position, an der das Zeichen ch zum ersten Mal in der
zeichenkette auftritt, -1 falls das Zeichen nicht enthalten ist
• Public int length(): liefert die Länge der Zeichenkette
• Public String substring( int b, int e): Gibt eine Teilzeichenkette zurück, die an Position b der
ursprünglichen Zeichenkette beginnt und an Position e -1 endet, die Länge dieser
eilzeichenkette ist folglich e-b
• Public String toLowerCase(), public String toUpperCase(): konvertiert alle Zeichen in Klein-
bzw. Großbuchstaben, Rückgabewert ist einer neuer String mit der konvertierten
Zeichenkette
• Public String replace( char oldChar, char newChar): liefert eine Zeichenfolge zurück, bei der
jedes Vorkommen des Zeichens oldChar in der aktuellen Zeichenfolge durch das Zeichen
newChar ersetzt wurde
• Public String trim(): liefert die Zeichenfolge zurück, die durch Entfernen aller Leerzeichen am
Anfang und Ende der aktuellen Zeichenfolge entsteht
• Insgesamt über 50 Methode gibt es in der Klasse String

Eigenschaften der Klasse java.lang.StringBuffer:


• Klasse für dynamische Operationen auf Zeichenketten
• StringBuffer sollte bei umfangreichen Zeichenketten-Operationen aus Performancegründen
an der Stelle von String eingesetzt werden
• Das Hava-Laufzeitsystem benutzt intern StringBuffer-Objekte zur Verkettung von
Zeichenfolgen

Wichtige Methoden der Klasse StringBuffer:


• Public StringBuffer(int n): Konstruktor zu r Erzeugung eines StringBuffer-Objekts mit einer
Kapazität von n Einzelzeichen
• Public StringBuffer append(String s)
• Hängt die Zeichenkette an das StringBuffer-Objekt an
• Public StringBuffer insert (int p, String s): fügt die Zeichenkette s an Position o in das
StringBuffer-Objekt ein
• Public Strung toString(): gibt die im StringBuffer-Objekt gespeicherte Zeichenkette als String
zurück

Wichtige Variablen und Methoden der Klasse java.lang.Math

Objekte serialisieren:
• Objekte einer Klasse können gespeichert werden, wenn diese das Interface Serializable
implementiert
• Dieses Interface hat keine Attribute oder Methoden!
• Standartmäßig wird gespeichert:
o Klassenname und -signatur
o Werte der Objektattribute inklusive der referenzierten Objekte
• Ausgenommen sind Klassenattribute
• Werden referenzierte Objekte serialisiert, kann es passieren, dass deren Namen nicht das
Interface Serializable implementieren

Klassenhierarchie:

Objekte mehrere Klassen können kombiniert werden:

Ereignisbehandlung:
• Ereignis: alle Benutzeraktionen, wie z.B. Mausklick
• Ergebnisquelle: Alle Komponenten einer grafischen Oberfläche, an denen Ereignisse
auftreten können, z.B. Buttons
• Ergebnisempfänger: Objekte, die auf die Ergebnisse reagieren können

Ereignisempfänger:
• Damit Klasse auf Ereignisse reagieren kann, muss sie ein sogenanntes EventListener-Interface
implementieren

Registrierung:
• Ereignisempfänger muss sich bei der Ergebnisquelle registrieren. Dafür stellt jede
Ergebnisquelle entsprechende addEventListener-Methoden zur Verfügung

Die Abstrakte Klasse java.awt.Component ist die Basisklasse für alle Klassen, die am Bildschirm
grafisch dargestellt werden können und mit dem Anwender interagieren können
Alle Klassen, die direkt oder indirekt von der Klasse java.awt.Component abgleitet sind, sind
Ereignisquellen

Ein Applet kann sowohl Ereignisquelle als auch Ereignisempfänger sein

Kriterien für die Qualität von Algorithmen:


• Korrektheit
• Verständlichkeit
o Erhöht die Chance, dass der Algorithmus korrekt ist
o Erleichtert die Implementierung und spätere Änderung ´/ Erweiterung
• Einfache Implementierbarkeit
o Zeitbestimmende Komponente: schreiben und Debuggen
o Besonders wichtig, wenn das Programm nur selten verwendet werden wird
• Effizienz bzgl. Laufzeit und Speicherbedarf
o Aufwandsanalyse

Messung der Rechenzeit:


• z.B. in Millisekunden
• Abhängig von verwendeter Hardware, Compiler, Betriebssystem, …
• Nur für Implementierung von Algorithmen messbar, nicht für Algorithmen selbst

Lösung:
• Anzahl der durchgeführten Elementaroptionen:
o Funktion: Anzahl der durchgeführten Elementaroptionen in Abhängigkeit der
Komplexität der Eingabe (i-d-R. der Kardinalität der Eingabemenge)

Elementaroperationen:
• Von Programmiersprachen üblicherweise angebotene Primitive
• Abbildung auf eine feste, kurze Folge von Maschineninstruktoren
• Abstraktion realer Rechner: mathematische Maschinenmodelle
• Kostenfunktionen

Abstraktionsschritte:
1. Keine Unterscheidung von Elementaroperationen
• Konzentration auf diejenigen Operationen, die die Laufzeit um Wesentlichen bestimmen,
oder
• Zusammenfassung aller Elementaroperationen unter der Annahme, dass alle
Operationen gleich lange dauern
2. Aufteilung der Menge der Eingaben in Komplexitätsklassen
• Nicht mehr jede mögliche Eingabe wird betrachtet
• Sondern nur noch Komplexitätsklassen werden analysiert
• Einfachster Fall. Größe der Eingabe bestimmt die Komplexitätsklasse
3. Abstraktion innerhalb einer Komplexitätsklasse durch
• Betrachtung von Spezialfällen
o Der beste fall: best case
o Der schlimmst Fall: worst case
• Betrachtung des Durchschnittsverhaltens (average case)
• Gewichtetes Mittel unter Berücksichtigung der Auftrittswahrscheinlichkeit der Eingaben
• Problem: Annahmen über die Anwendung nötig
• Einfachster Fall: Annahme der Gleichverteilung (nicht immer realistisch)
4. Betrachtung des Wachstums der Laufzeitfunktion
• Weglassen von multiplikativen und additiven Konstanten
• Einführung der O Notation

O-Kalkül:
• Charakterisierung der Größenordnung des Aufwandswachstums:
• Vergleich des asymptotischen verhalten des Aufwandsfunktion mit einem Repräsentanten
einer bestimmten Funktionsklasse

Aufwandsklassen:

Faustregel:
• Komplexität bis O(n^2) kann toleriert werden
• Höhere Aufwände bereits bei mittleren Eingabegrößen problematisch
• Exponentiell wachsende Aufwände i.d.R. nicht mehr z verarbeiten
Definition:
O-Notation Gegeben sei eine Funktion g : N → R + und die wie folgt definierte Funktionenmenge
O(g):

Anmerkungen:
• f ∈ O(g) heißt, f wächst höchstens so schnell wie die Funktion g.
• Bei der Analyse von Algorithmen sind i. d. R. f, g : N → N definiert:
• Das Argument ist die Größe der Eingabe.
• Der Funktionswert gibt die Anzahl der durchgeführten Elementaroperationen an.
• U. a. wegen Durchschnittsanalysen kann der Wertebereich auch R + sein.

Rechenregel:

Für Funktionsklassen:

Aufwandsabschätzung für Ablaufstrukturen:


Annahmen:

Elementaroperationen:
Sequenzen S1, S2:

Schleifen:
Jeder Schleifendurchlauf kann eine andere Laufzeit haben
➔ Laufzeit aufsummieren
Oft gut abschätzbar:
Fall 1: Anzahl der Schleifendurchläufe ist eine Konstante c und hängt nicht von n ab


Fall 2: Die Anzahl der Schleifendurchläufe ist O(f(n))

Bedingte Anweisungen if B then S1 else S2:


➔ O(max{f(n), g(n)})
➔ Laufzeit wird durch die dominante Operation bestimmt
➔ Sind die beiden Laufzeitfunktionen nicht vergleichbar, lässt sich auch mit der Summe
weiterrechnen, da diese immer die obsterste Schranke darstellt

Unterprogramme:
• Können getrennt analysiert werden und ihre Laufzeit bei Aufrufen entsprechend eingesetzt
werden
• Sonderfall: rekursive Unterprogramme:
• Laufzeit wird durch eine Rekursionsgleichung beschrieben, die zu lösen ist

Zusammenfassung von Aufwandsfunktionen in größeren Gruppen:


• P („polynomial time“): Alle Probleme, die in polynomieller Zeit gelöst werden können
(z.B. O(log n), O 8n), O(n^2),…)
-Sortieren von Elementen
-Beziehungsentfernung: Über wie viele Ecken kenne ich Person Y
-Die schnellste Route
-Längste aufsteigende Sequenz

• NP: Alle Probleme, die in exponentieller Zeit gelöst, aber in polynomialer Zeit verifiziert
werden können (B. O(2^n))
-Clique: Kann ich ein Beziehungsnetzwerk in k Gruppen teilen, innerhalb derer jeweils jeder
jeden kennt?
Travelling Salesman
Subste-sum: Gibt es Zahlen in einer n-elementigen Liste, die summiert t ergeben?

• PSPACE: Probleme, die in exponentieller Zeit gelöst und verifiziert werden könne und deren
Lösung in polynomieller Zeit hingeschrieben werden kann:
-Tic Tac Toe
-Reversi
-Mahjong

• EXPTIME: alle Probleme, die innerhalb von O(2^f(n)) gelöst werden


-Schach
-Dame
-Go

P, NP, PSPACE; EXPTIME bilden eine Hierarchie, alle P-Probleme sind in NP enthalten
Viele Probleme in P sind für die Praxis beriet zu aufwendig

Für viele Probleme gibt es approximative Algorithmen, diese können oftmals eine Lösung anbieten,
die nur in einem garantierten Faktor schlechter ist als die exakte Lösung

Für manche Probleme ist es unmöglich eine Lösung zu finden:


• Halteproblem: Programm, das für ein anderes Programm und eine Eingabe für das
Programm, entscheidet, ob das Programm hält oder unendlich laufen wird
➔ Unmöglich Programm zu schreiben, das aussagt, ob zwei Programm immer dieselbe Ausgabe
berechnen
➔ Unmöglich, Virenscanner zu schreiben, der aussagt, ob ein Programmein bisher unbekanntes
Schafprogramm enthält

Omega ist mindestens


Kreis ist genauso schnell

Rekursion:
Ein Unterprogramm heißt rekursiv, wenn es sich direkt oder indirekt selbst aufruft

Vereinfachte Darstellung:
• Aufrufparameter auf dem Stack legen
• Platz für Rückgabewert auf dem Stack anlegen
• Rücksprungadresse auf dem Stack anlegen
• Gesamte „Methodenschachtel“ am Ende der Methode
o wieder vom Stack entfernen

Rekursive Aufrufe „stören“ sich nicht gegenseitig:


• Immer, wenn eine Methode aufgerufen wird, bekommt sie für den aktuellen Aufruf ihren
„privaten“ Speicherbereich
• Damit sind rekursive Aufrufe derselben Methode sauber getrennt:
o In dem aktuellen Lauf der Methode werden lokale Variablen zwischengespeichert
o Egal, wie lange es dauert, um aus einem Unteraufruf in den aktuellen Methodenlauf
zurückzukehren, liegen damit nach wie vor die lokalen Variablen vor, und zusätzlich
die Rückgabewert aus dem Unteraufruf

Im Vergleich zu iterativen Lösungen sind rekursive Funktionsaufrufe teuer bzgl. Speicherbedarf und
benötigten Rechenzeit

Man unterscheidet verschiedene Typen von Rekursion:


• Lineare Rekursion:
• Repetitive Rekursion
• Kaskadenartie Rekursion
• Geschachtelte Rekursion
• Verschränkte Rekursion

Lineare Rekursion:
• Eine Funktion f heißt linear rekursiv, wenn in jedem Zweig einer Fallunterscheidung die
Funktion f höchstens einmal abgerufen wird
• Ein Spezialfall der linearen Rekursion ist die repetitive Rekursion, ei der der rekursive Aufruf
als allerletzte äußere Funktionsanwendung im Terminalfunktional steht

Repetitive Rekursion
• Bei repetitiv rekursiven Funktionen steht der zu brechende wert bereits fest, wenn die tiefste
Rekursionsebene erreicht ist
• Ergebnis wird dann nur noch sukzessive an die aufrufenden Funktionen zurückgeben
• Eine De-Rekursivierung ist daher einfach

Kaskadenartige Rekursion:
• In einem Zweig einer Fallunterscheidung können mehrere rekursive Aufrufe der Funktion
vorkommen

Eigenschaften:
• Baumartige Aufrufstruktur
• Lawinenartiges Anwachsen der rekursiven Funktionsaufrufe
• Kaskadenartige Rekursion lässt sich leicht in Iteration umwandeln

Verschachtelte Rekursion
• Treten Funktionsaufrufe auch in den Parameter der rekursiven Funktion auf

Verschränkte Rekursion:
• Ruft eine Funktion f eine Funktion g auf, die wiederum die Funktion

Fazit:
• Viele Probleme lassen sich auf kleinere Probleme desselben Typs zurückführen
• Die Rekursion stellt ein äußerst elegantes Mittel der Programmierung dar
• Eine Rekursive Lösung ist in einigen Fällen einfacher zu finden als eine iterative Lösung
• Jeder rekursive Algorithmus lässt sich in einen iterativen Algorithmus umformuliert und
umkehren
• Iterative Lösungen sind bzgl. Der benötigten Ressourcen günstiger

Abstrakte Datentypen:
• Spezifikation der wesentlichen Eigenschaften und Operationen einer Datenstruktur
(Schnittstelle nach außen) unabhängig von ihrer Implementierung in einer konkreten
Programmiersprache
• Wiederverwendbarkeit von Programmcode
• Abstrahierung von unnötigen Details
• Softwaretechnik: Realisierung von ADTs entsprechen Softwaremodulen
• Implementierung jederzeit änderbar ohne Auswirkungen auf den Anwender (Geheimprinzip)
• Benutzung eines ADT-Moduls nur über seine Schnittstelle (Verkapselung)

Unterscheidung:
• Abstrakte Datentypen:
o Spezifikation der Schnittstelle nach außen: Festlegung der Operationen und ihrer
Funktionalitäten
o Nicht direkt in einem Programm anwendbar
• Konkrete Datentypen:
o Primitive Datentypen oder Java-Klassen
o Direkt in einer Implementierung einsetzbar
Zusammenfassung mehrere konkreter Datentypen zu einem abstrakten Datentyp möglich
Abstrakte Datentypen:
• Festlegung der Signatur Σ = (S, Ω)
• Menge S von Objektsorten
o Menge Ω = {fS∗,S} von Operationen:
o definiert Funktionssymbole für Funktionen f : s1 × · · · × sn −→ s
o mit Parametersorten s1, . . . , sn und
o Ergebnissorte s
o Funktionen ohne Parameter heißen Konstanten.
• Festlegung der Syntax

Spezifikation als Algebra:


• Vorteile:
o Leicht zu lesen, anschaulich
o Relativ einfach zu spezifizieren
• Nachteile u.U.
o teilweise Festlegung der Datenstruktur

Spezifikation als Abstrakter Datentyp:


• Vorteile:
o Keine Vorwegnahme von Implementierungsdetails
o Sprache zur Beschreibung von Axiomen folgt formaler Regeln
• Nachteile:
o Große Anzahl an Axiomen bei komplexen Anwendungen
o Intuitive Bedeutung des Datentyps manchmal nicht leicht anhand der Axiome zu
erkennen
o Spezifikation der Axiome schwierig
o Vollständigkeit de Menge der Axiome schwierig zu prüfen

Implementierung in einer objektorientierten Programmiersprache:


• Implementierung eine ADTs durch eine Klasse:
o Objektsorten/ Typen entsprechen Klassen und Interfaces
o Funktionen entsprechen Methoden
o Create wird auf Konstruktoren abgebildet
o Axiome: Implementierung von Einschränkungen in Form von Java-Code
• Kapselung:
o Anwender der Klasse muss lediglich die Schnittstelle nach außen kennen: alle als
public deklarierten Attribute und Methoden
o Implementierungsdetails werden verborgen und könne ohne Auswirkungen auf den
Anwender geändert werden
Verwendung unterschiedlicher Implementierung auf dieselbe Art und Weise durch Interfaces und/
oder Vererbung

Verkette Listen
• Oft werden Felder mit unbekannter Elementzahl benötigt
• Probleme bei der Verwendung von Standartfelder (Arrays)
• Feld ist zu klein
• Feld ist zu groß
• Feld vergrößern
• Feld besitzt bestimmte Reihenfolge
• Schnellste Sortiermethode ist Merge Sort
Typische Methoden:
Advance, get, delete, insert

Verkette Listen vermeiden die Nachtteile:


• Enthalten nicht mehr Elemente als nötig
• Können beliebig wachse
• An beliebiger Stelle Elemente einfügen und löschen

Grund:
• Die Elemente werden im Speicher nicht aufeinander folgend, sondern an beliebigen freien
Stellen im Speicher abgelegt

ABER: es existiert kein Indizierungsoperator für verkette Listen

Unsortierte Liste: elementare Operationen


• Create: erzeugt neue, leere, Liste
• Add: hängt Element vom Typ T vorne an Liste an
• Head/first: gibt erstes Element zurück
• Tail/ rest: letztes Element zurück
• Length: Länge zurück
• isEmpty: überprüft ob leer

Listen mit Zugriff auf beliebiges Element:


• Create: erzeugt neue, leere, Liste
• Reset: setzt Positionszeiger auf Anfang der Liste
• Advance: rückt Positionszeiger ein Element weiter
• Get: liefert Element, auf das Positionszeiger zeigt
• endPos: testet, ob Positionszeiger auf letztes Element zeigt
• insert: fügt hinter Stelle des Positionszeiger ein Element ein
• delete: löscht Element auf das Positionszeiger zeigt
• Length: Länge zurück
• isEmpty: überprüft ob leer

Prinzip von Stapeln (stacks)


• Zugriff nur auf das oberste Element:
• Elemente können nur oben auf den Stapel gelegt werden
• Nur das oberste Element kann entnommen werden
• Zuletzt eingefügtes Element wird als erstes wieder entnommen (LIFO- Prinzip)
• Spezialfall von Listen
• Werden auch als Keller bezeichnet

Anwendung:
• Sichern von lokalen Variablen bei (geschachtelten) Funktionsaufrufen
• Implementierung von Rekursion
• Syntaxanalysen
Operationen auf Stapeln:
• Create: erzeugt einen neuen, leeren Stapel
• Push: legt Element vom Typ T oben auf den Stapel
• Top: gibt oberste Element des Stapels zurück
• Pop: entfernt das oberste Element Stapel
• isEmpty: testet, on Stapel leer ist

Implementierung:
• Stapel können entweder mit Felder oder mit einfach verketteten Listen realisiert werden.
Dabei müssen die Elemente am gleichen Ende angefügt und entfernt werden

Kann zur Auswertung von arithmetischen Ausdrücken in Postfix-Notation verwendet werden

Idee:
• Operanden nacheinander auf einen Stapel
• Bei einem Operator: Verknüpfung der obersten beiden Operanden auf dem Stapel
• Das Resultat wieder auf den Stapel legen
• Am Ende steht das Resultat an oberster Stelle

Vereinfachte Darstellung:
• Aufrufparameter auf dem Stack legen
• Platz für Rückgabewert auf dem Stack anlegen
• Rücksprungadresse auf dem Stack legen
• Platz für lokale Variablen auf dem Stack anlegen
• Gesamte „Methodenschachtel“ am Ende der
Methode wieder vom Stack entfernen

Prinzip von Warteschlangen (queues):


• Elemente werden an einem Ende eingefügt und am anderen Ende entnommen
• Nur das vorderste Element kann entnommen werden
• Zuerst eingefügtes Element wird auch als erstes wieder entnommen, FIFO-Prinzip
• Spezialfall von Listen

Operationen auf Warteschlangen:


• Create: neue, leere Warteschlange
• Enqueue/ append: stellt Element vom Typ T hinten in Warteschlange
• Fornt/ first: gibt das vorderste Element zurück
• Dequeue/ rest: entfernt vorderste Element
• isEmpty: ob leer

Implementierung:
• Verkette Liste
o Effizientes Anhängen am Ende: zusätzlicher Zeiger auf das Schlusselement
o Leere Warteschlange: Zeiger auf erstes und letztes Element null
o Einelementige Schlange: beide Zeiger zeigen auf das einzige Element
• Arrays:
o Standart-Array: dequeue-Operationen erfordert ein Umkopieren der Elemente
(ineffizient)
o Alternative: Elemente werden nur als entfernt markiert

Implementierung als Array:


• Elemente werden nur als entfernt markiert
• Nachteil: Schlange „durchwandert“ das Array und stößt sehr schnell am Ende des Feldes an

Implementierung als Ringbuffer:


• „zirkuläres Array“: auf das letzte Element folgt wieder das erste
• Nächste Position: Inkrement Modulo der Buffergröße n
• Ringbuffer voll: Index last läuft auf Index first auf
• Sonderfall: Ringbuffer leer

Prinzip von Prioritätswarteschlangen:


• Jedes Element besitzt zusätzlich eine Priorität
• Element mit höchsten Prioritäten entschiedet die Wartedauer:
• Zuerst wird das Element ausgelesen, da s sich bereits am längsten in der Warteschlange
befindet

Hilfsoperation: maxPriority: PQueue → int


Nicht Teil der Signatur: aktuell höchste Priorität für Benutzer unwichtig

Einführung eines Typplatzhalters:


Vorteil: keine Duplizierung des Programmcodes →weniger Code, bessere Wartbarkeit, ….

Typplatzhalter: keine primitiven Datentypen


Verwendung von Typparametern: innerhalb der generischen Klasse als…
• Typ von Variablen,
• Rückgabewerten
• Parametern
• Instanziierung von (anderen) parametrisierten Klassen
• Ableitung von generischen Oberklassen

Mehrere Typparameter:
Trennung durch Komma

Nicht möglich: Verwendung von Typparametern in…


• Statischen Attributen
• Statischen Methoden
• Statischen Initialisieren

Begründung: verschiedene Instanzen einer Klasse mit unterschiedlichen generischen Typen teilen
sich dieselben statischen Elemente

Nicht möglich:
• Erzeugung von Objekten des generischen Typs
• Aufruf von Methoden des generischen Typs, die nicht von Object geerbt wurden
• Begründung: Lautzeittyp unbekannt

Einschränkung des generischen Typs: E muss das Interface Comparable implementierten

Vergleich von einfach verketteten Listen und statischen Arrays

Dynamische Arrays:
• Kombinieren die Vorteile beider Datentypen
• Feldgröße passt sich dynamisch an den tatschlichen Bedarf an
Prinzip:
• Speicherung einer (kleinen) Teilmenge von Elementen aus einem (beliebigen) großen
Wertebereich D
• Abbildung des Werts eines zu speichernden Mengenelementes auf eine Speicheradresse+
Speicherung von Objekten mit einer komplexen inneren Struktur
o Wahl einer Komponente oder einer Kombination von Komponenten
o Die numerische Wert dieses Schlüssels (key) kontrollierst die Abbildung auf die
Speicheradresse

Hashtabelle:
• Menge von Behältern (buckets): b0, b1, … b,-1
• Array der Größe m

Hashfunktion: (Schlüsseltransformation)

Eigenschaften:
• H im Allgemeinen nicht Injektiv; Behandlung von Kollisionen
• Möglichst surjektive Abbildung: Abbildung auf alle Behälter: Vermeidung von Lücken in der
Hashtabelle
• Möglichst gleichmäßige Verteilung der Schüssel auf die vorhandenen Behälter
• Effiziente Berechnung

Verschiedene Hashverfahren
• Offenes Hashing:
o Jeder Behälter kann belieibig viele Schlüssel aufnehmen
o Kollision sind in diesem Fall kein Problem

• Geschlossenes Hashing:
o Jeder Behälter kann eine kleine konstante Anzahl b an Schlüssen aufnehmen
o Bei mehr als b Schlüssel kommt es zu einem Überlauf (overflow) des Behälters
o Gewöhnlich betrachteter Spezialfall: b=1

Wahrscheinlichkeit von Kollision:


• Annahmen: Ideale Hashfunktion
Wahrscheinlichkeit, dass bei n Schlüsselwerten mindestens eine Kollision auftritt:
PK (S1,…Sn)= 1-P Gegenteil K (S1,Sn)

• Wahrscheinlichkeit, das bei n Schlüsselwerten eine Kollision auftritt:


((m(m-1)*(m-2)….(m-n+1))/mn)

Offenes Hashing:
• Vorteile:
o Kollision kein Problem
o Gesamtzahl der speicherbaren Schlüssel nicht begrenzt
o Einfaches Löschen von Schlüsseln
o Hashtabelle funktioniert (mit Geschwindigkeitseinbußen) auch bei zu klein gewählten
Tabellengrößen

• Nachteile:
o zusätzlicher Speicherplatz für Verzeigerung nötig
o Listen eventuell sehr lang

Aufwandsabschätzung:
• Average case: Suchen, Einfügen, Entfernen
• Durchschnittliche Listenlänge: n/m
• Wählt man n ca. wie m, erhält man für eine gleichmäßige verteilende Hashfunktion:
O(1+n/m) also O(1)
• Worst case: alle Schlüssel werden auf denselben Behälter abgebildet → O(n)
• Best case: keine Kollisionen O(1)
• Speicherplatz: O(n+m)

Geschlossenen Hashing:
• Spezialfall: b=1
• Jeder Behälter fasst genau einen Schlüsselwert
• Implementierung durch Array
Analoge Techniken für b> 1
• Kollisionsbehandlung: rehashing, offenen Adressierung
• Folge von Hashfunktionen h1, h1, …, hm-1
• Sucher nach einer freien oder als gelöscht markierten Zelle
• Wahl der Hashfunktionen: Inspizierung aller m Zellen der Tabelle
• Sucher nach einem Schlüsselwert:
• Inspiziere Zellen in derselbe Reihenfolge wie beim Einfügen
• Bis der Schlüsselwert gefunden wurde oder
• Eine Zelle frei ist

Konsequenz:
• Elemente x können nicht einfach gelöscht und die Zellen als frei markiert werden
• Elemente, die durch Kollision beim Einfügen an x vorbeugeleitet wurden, wären sonst nicht
wieder auffindbar
• Beim Löschen müssen Zellen als gelöscht markiert werden
• Diese können bei Einfügen neu belegt werden, führen bei der Suche nach einem Element
aber nicht zum Abbruch der Suche
• Nicht geeignet für sehr „dynamische“ Anwendungen mit vielen Einfügung und
Löschoperationen

Lineares Sondieren:
• Der Reihe nach werden alle Folgezellen betrachtet:
• Hi(x)=(h(x)+) mod m 1<=1i<m
Verallgemeinerung:
• H(x)=(h(x)+c*i) mod m 1<= i< m

Konstante c und m sollten teilerfremd sein, um alle Zellen zu treffen


Keine Verbesserung gegenüber c=1
Tendenz: Bindung langer Ketten im Abstand c

Lineares Sondieren 2:
Lineares Sondieren in beide Richtungen: 1<=i<(m-1)/ 2

H2-1=(h(x)+c*i) mod m
H2i(x)=(8(x)-c*i+m^2) mod m

Arten von Kollisionen:


• Primärkollision: h0(x)=h0(y)
o Zwei Schlüsselwerte x und y werden durch die Hashfunktion h0 auf denselben
Behälter abgebildet
• Sekundärkollision: h0(x) = hk(y), k>0
o Schlüsselwert x wird durch Hashfunktion h0 auf einen Behälter abgebildet, der durch
ein Überlaufelement y bereits belegt ist

Quadratisches Sortieren:
• In eine Richtung: 1<0i<m
• Hi(x)=(h(x)+i^2) mod m
• In beide Richtungen: 1<=i<=(m-1)/ 2
• H2-1=(h(x)+c*i) mod m
• H2i(x)=(8(x)-c*i+m^2) mod m

Keine Verbesserung bei Primärkollision


Vermeidung von Clusterbildung bei Sekundärkollisionen (geringe Wahrscheinlichkeit für die Bildung
von längeren Ketten)

Doppel-Hashing
• Hi(x)=(h(x)+(h´(x)*i^2) mod m
• In der Praxis sehr gute Methode
• Beweis der Unabhängigkeit zweier Hashfunktionen schwierig

Mögliche Basis_Hashfunktion:
1. Divisionsrest-Methode: h(x)=x mod m
Einfache und effektive Hashfunktion wenn m eine Primzahl ist
Oft ist die Größe der Hashtabelle jedoch eine Zweierpotent: m= 2kl
Hashwert hängt nur von den niederwertigen Bits ab!
In diesem Fall eignet sich: h(x)=(x mod p) mod p

2. Mittel-Quadrat-Methode:
Bilde das Quadrat des Schlüsselworts: x2
Hashwert h(x) mittlerer Block dieser Ziffernfolge
H(x) hängt von allen Ziffern von x ab
Gute Streuung aufeinanderfolgender Schlüsselwerte

Reorganisation:
• Globale Reorganisation:
o neue Hastateblle von geeigneter Größe
o Umspeichern aller Einträge mit neuer Hashfunktion
o Nachteil: hoher Zeitaufwand und doppelter Speicherplatzbedarf während der
Reorganisation
• Dynamische Reorganisation

Zusammenfassung:
• Sehr schlechtes worst-case-Verhalten beim Einfügen, Suchen und Loschen : O(n)
• Oft jedoch sehr gutes Durchschnittsverhalten: O(1)
• Einfach zu implementieren
Offenes Hashing: bei allgemeinen dynamischen Anwendungen
• Reorganisation, wenn Tabellengröße um ein Vielfaches überschritten wird
Geschlossenen Hashing:
• Gesamtzahl einzufügender Elemente sollte von vornherein bekannt sein
• Löschung keiner (oder nur sehr weniger) Elemente
• Auslastung von 80% sollte nicht überschritten werden

Bäume:
Grundkonzept:
• Boome (trees) können als eine Verallgemeinerung von Listen angesehen werden:
• Bei einem Baum hat jeder Knoten potenziell mehrere Nachfolger (bei Listen einen)
• Nachfolger werden Kinderknoten (child, children) genannt
• Analog zu Listen hat jeder Knoten (bis auf die Wurzel) genau einen Elternknoten (parent)
• Der Knoten ohne Elternknoten wird Wurzel genannt (root)
• Knoten ohne Kinderknoten heißen Blätter (leaf, leaves) oder Terminalknoten
• Knoten mit Kinderknoten heißen auch innere Knoten (inner nodes)
• Jedem Knoten ist eine Ebene (level) im Baum zugeordnet. Die Ebene eines Knotens ist die
Länge des Pfades von diesem Knoten bis zu Wurzel
• Die Höhe (height) eines Baumes ist die maximale Ebene, auf der sich Konten befinden
• Der Verzweigungsgrad (out degree) eines Knotens ist die Anzahl seiner Kinder
Ein Binärbaum (binary tree) ist ein Baum, dessen Knoten höchstens den Verzweigungsgrad 2
haben

Visualisierung von Bäumen:


• Bäume kann man dadurch visualisieren, dass Verbindungen zwischen Eltern und ihren
Kindern eingezeichnet werden.
• In der Informatik wachsen Bäume von oben nach unten

Anwendung:
• Ableitung in einer Grammatik
• Aufrufstruktur von rekursiven Algorithmen
• Mögliche Züge in einem ein- Zweispielerspiel
Transferieren von Bäumen:
• Ein fundamentales Konzept ist das Durchlaufen (transferieren) von Bäumen. Dabei könne die
Knoten in drei verschiedene Reihenfolgen durchlaufen werden:
• Verordnung: preorder
Reihenfolge: Wurzel, linker Teilbaum, rechter Teilbaum

• Nachordnung (postorder)
Linker Teilbaum, rechter Teilbaum, Wurze

• Inorder (insorder)
Linker Teilbaum, Wurzel, rechter Teilbaum

Definition:
• Ein Binärbaum heißt voll, falls alle inneren Knoten den Verzweigungsgrad zwei besitzt
• Ein voller Binärbaum heißt vollständig, falls alle Blätter auf der gleichen ebene liegen
• Ein vollständiger Binärbaum ist ebenso voll

Lemma:
Ein vollständiger Binärbaum B der Höhe n hat 2n Blätter

Beweis:
Induktionsanker: B besitzt die Tiefe 0 und besteht daher nur aus der Wurzel. Die Behauptung hält:
20=1
Indunktionsannahme: B besteht aus der Wurzel W, einem linken Teilbaum L und einem rechten
Teilbaum R mit Tiefe 2n-1. Für L und R gilt die Behauptung. Sie haben jeweils 2n-1 Blätter
Induktionsschritt: Daraus folgend hat B 2*2n-1= 2n Blätter
Ein vollständiger Baum B der Höhe n hat 2n+1 -1 Knoten

Elementare Operationen:
• Create: erzeugt leere binärbäume
• makeTree: erzeugt aus einem linken und einem rechten Teilbaum und einem Wert Typ T
einen neuen Binärbaum
• Leaf: erzeugt aus einem Wert von typ T einen Binärbaum bestehend aus nur einem Blatt
• Value: Wert der Wurzel des Binärbaums
• Left: linker Ast des Binärbaums
• Right: rechter Ast des Binärbaums
• isEmpty: testet, ob der Binärbaum leer ist

Knotenmarkierung:
• Eine Knotenmarkierung ist eine Abbildung s, die Knoten auf einen geordneten Wertebereich
D abbildet.
• Direkter Vergleich möglich, wenn ein Knoten nur einen Wert speichert, dessen Werteberich
geordnet ist (Bsp. numerische Werte, Strings)
• Mehrere Werte pro Knoten: Auswahl eines geeigneten Elements als Schlüssel
• Direkter Vergleich nicht möglich (z-Bsp. Symbolen): Definition einer Knotenabbildung s

Binärer Suchbaum:
• Ein binärer Suchbaum T ist ein knotenmarkierter Binärbaum, für den für jeden Teilbaum T´=
(Ti, x, Tr) von T gilt:
• Alle Knoten im linken Teilbaum Ti haben einen kleineren Wert als die Wurzel x
• Alle Knoten im rechten Teilbaum Tr haben einen größeren Wert als die Wurzel x

Elementare Operationen:
• Create: erzeugt leeren binären Suchbaum
• Insert: fügt einen Wert vom typ T in den binären Suchbaum ein
• Find: überprüft, ob ein Wert vom Typ T im Baum vorhanden ist
• Delete: löscht einen Wert vom typ T aus dem Suchbaum

Vorgehen bei der Suche in binären Suchbäumen:


• Vergleich des Suchschlüssels mit dem Wert der Wurzel: Sind beide gleich, ist der Eintrag
gefunden
• Suchschlüssel kleiner als der Wert der Wurzel: reflektive Suche im linken Teilbaum, gibt es
keinen linken Teilbaum, so existiert der gesuchte Eintrag im binären Suchbaum nicht
• Suchschlüssel größer als der Wert der Wurzel: entsprechende rekursive Suche im rechten
Teilbaum

Komplexität:
• Da die Suchoperation entlang eines Weges von der Wurzel zu einem Blatt verläuft, hängt die
aufgewendete zeit im schlechtesten Fall linear von der Tiefe h des Suchbaums ab
(Komplexität O(h))

Vorgehen beim Einfügen in binären Suchbäumen:


• Das Einfügen funktioniert nach dem gleichen Prinzip wie das Suchen
• Endet die Suche nach dem einzufügenden Element an einem Knoten erfolglos, lässt man
diesen Knoten auf neues Element verweisen
• Die Einfügeoption hat dieselbe Komplexität wie die Suchoperation

Vorgehen beim Löschen in binären Suchbäumen:


• Kein Nachfolger: Der zu löschende Knoten wird einfach entfernt
• Ein Nachfolger: Der Nachfolger wird an die Stelle gesetzt, an der der zu löschende Knoten
war
• Zwei Nachfolger: Der zu löschende Knoten wird durch den am weitesten rechts liegenden
Knoten liegenden Knoten im linken Teilbaum (oder den am weitesten links liegenden Knoten
im rechten Teilbaum) ersetzt
Dieser Knoten muss aus dem entsprechenden Teilbaum entfernt werden

Problem:
Entartete Suchbäume
• Binäre Suchbäume können zu Listen entarten (Bsp. beim Einfügen von Zahlen in sortierter
Reihenfolge)
• Die Komplexität der Suche ist damit O(n) wobei n die Anzahl der Einträge darstellt

Lösung:
• Durch Umstrukturieren des Suchbaums, während des Einfügens von Elementen kann ein
balancierter Suchbaum generiert werden
• Balancierte Bäume stellen für jeden Knoten sicher, dass die Tiefe des linken Unterbaumes
und die Tiefe des rechten Unterbaumes nur um ein bestimmte Verhältnis oder eine
bestimmte Differenz voneinander abweichen

AVL-Bäume:
• Ein AVL-Baum ist ein binärer Sortierbaum, bei dem für jeden Knoten gilt:
o Die Höhe des linken Teilbaums und die Höhe des rechten Teilbaumes unterscheiden
sich um maximal eins
Eigenschaften:
• Komplexität der Suche in AVL-Bäumen: O(log(n))

Rotation in binären Suchbäumen:


• Um beim Einfügen und Löschen von Elementen das Entarten des Baums zu vermeiden,
werden Rotationen auf Teilbäumen durchgeführt
• Rotation behalten die Sortierreihenfolge bei
• Unterscheidung zwischen Rechtsrotationen und Linksrotation und Einfachrotation und
Doppelrotation
• 4 Typen von Rotationen sind ausreichend
Halde:
Eine Hald/ Haufen (Heap) ist ein Binärbaum T mit Knotenmarkierung s, in dem für jeden Teilbaum
T´gilt:
• Variante 1: die Wurzel x enthält das Minimum dieses Teilbaums
• Variante 2: die Wurzel x enthält das Maximum dieses Teilbaums

Hinweis: partiell geordnete Bäume seien im Folgenden links-vollständig:


• Alle Ebene bis auf die letzte ebene voll besetzt
• Blätter auf der letzten ebene soweit links wie möglich

Entnahme:
• Maximum: Eintrag der Wurzel
• Kopiere den Eintrag der letzten Position im Baum in die frei gewordene Wurzel und lösche
die letzte Position
• Betrachte die Wurzel x und deren beide Söhne l und r
• Solange einer der beiden Söhne existiert und bei mindesten einem der vorhandenen Söhne
die Halden-Eigenschaft verletzt ist, das heißt s(x) < s(l) oder s(x) < s
• Vertausche x mit dem größeren der beiden Söhne
• Setze x auf den getauschte Knoten l und r auf dessen Söhne

Einfügen in eine Halde:


• Erzeuge neuen Knoten k für einzufügendes Element
• Füge diesen Knoten auf der ersten freien Position des links-vollständigen Baums ein
• Betrachte den knoten k und dessen Vater v
• Solange der Vater existiert und die Halden-Eigenschaft verletzt ist, d. h. s(v) < s(k)
o Vertausche den Knoten k mit seinem Vater v
o Setze k auf den Vater v und v auf dessen Vater

Anwendung: Effiziente Implementierung einer Prioritätswarteschlange

Vergleich zwischen Halden- und Listen-Implementierung:


Implementierung mithilfe einer Halde (Array-Implementierung)
• Entnahme eines Elements (Maximum!): O(log n)
• Einfügen eines Elements: O(log n)
• Begründung: Beide Operationen erfolgen entlang eines Pfades in einem balancierten Baum
Implementierung einer verketteten Liste:
• Entnahme eines Elementes (Maximum): O(1)
• Einfügen eines Elements: O(n)

Die Folge von Datenelementen wird linear durchlaufen, um das gesuchte Datenelement zu finden
• Entwurfsschema: greedy (viele Vergleiche machen müssen O(n)
• Funktioniert bei Feldern und verketteten Listen
• Funktioniert mit und ohne lineare Ordnung auf den Datenelementen
• Anzahl der Schleifendurchgänge (n+1)/2
• Komplexität: O(n)

Idee:
Prinzip:
• Nachschlagen im Telefonbuch
• Wähle eine Position b, die die Folge in eine linke und rechte Teilfolge zerlegt
• Wenn gesuchtes Element an der Position p steht, dann prüfe abhängig von der Sortierung,
welche teilfolge weiter durchsucht werden muss
Eigenschaften:
• Entwurfsschema: Divide and Conquer
• Sinnvoll: Folge ist sortiert, da dann nur in einer teilfolge weitergesucht werden muss
• Komplexität: O(log n)

Crossover Point
• Divide-and-Conquer-Verfahren haben relativ großen Verwaltungsaufwand (overhead) bzw.
Große Proportionalitätskonstanten O(*)Komplexitäten
• Greedy-Verfahren haben nur kleine Overhead
• Es gibt in der Regel einen Übernahmepunkt (crossover point) zwischen beiden verfahren

Sortieralgorithmen:
Sortieren durch Auswählen (SelectionSort)
Sortieren durch Einfügen (InsertionSort)
Sortieren durch Austauschen (BubbleSort)
Sortieren mit einer Halde (HeapSort)
Sortieren durch Mischen (MergeSort)
Sortieren durch Zerlegen (QuickSort)

Problemstellung:
• Gegeben:
o Eine Folge von Daten
o Auf den Schlüsseln der Daten ist eine lineare Ordnungsrelation definiert
• Gesucht:
o Permutation der Daten, so dass sie mit ihren Schlüsseln aufsteigend (absteigend)
sortiert sind
o Wichtige Gesichtspunkte:
• Asymptotische Laufzeitkomplexität
o Speicherplatz (Platz für Parameter und Variablen/ bei Reihung in place bevorzugen)

Greedy:
Sortieren mittels Auswahl (selection sort)

Sortieren mittels Einfügen (insertion sort)


Sortieren mittels Austauschen (bubble sort)

Sortieren mittels Halde (heap sort)

Divide-and-Conquer:
Quicksort

Sortieren durch Mischen (merge sort)


Komplexität Misch Verfahren:

Idee:
• Aufsteigend sortieren: Lösche nacheinander die Maxima aus einer Folge F fügt sie vorne an
ein anfangs leere Ergebnisfolge L an
• Absteigend sortieren: Lösche nacheinander die Minima aus einer Folge F und fügt sie vorne
an eine anfangs leere ergebnisfolge L

Eigenschaften:
• geeignet für verkette Listen
• Maximale Zahl der Vergleiche: (n-1)+(n-2)…+1
• Komplexitätsklasse: O(n2)

Idee:
Typisches Verfahren beim Sortieren von Spielkarten:
• Starte mit der ersten Karte eine neue Sequenz
• Nimm jeweils die nächste karte vom Stapel und füge diese an der richtigen Stelle in die
Sequenz ein
• Annahme: wir können n-1 Werte sortieren. Dann können wir den n-ten Wert einsortieren,
indem wir seinen Platz in der sortierten Folge finden und die restlichen Elemente nach hinten
verschieben
Eigenschaften:
• Geeignet für verkettete Listen
• Maximale Zahl der Vergleiche: 1+2+…+(n-2)+(n-1)
• Komplexitätsklasse: O(n2)
Idee:
• Die Folge wird immer wieder durchlaufen und dabei werden benachbarte Elemente in die
richtige Reihenfolge gebracht
• Größere Elemente überholen so die kleineren und drängen sie an das Ende der Folge
(=aufsteigende Blasen)
Eigenschaften
• Geeignet für Felder (arrays)
• Keine Kopie der Felder notwendig (L=F)
• Maximale Zahl der Vergleiche: (n-1)+(n-2)+…
• Komplexitätsklasse: O(N2)

Idee zur Verbesserung von SelectionSort:


• Geeignetere Datenstruktur zur Auswahl des Maximums: Halde
• Einfügen eines Elements in die Halde: O(log n)
• Entnahme des Maximums mit Wiederherstellung der Heap-Eigenschaften O(log n)

HeapSort:
1. Einfügen der n zu sortierenden Elemente in eine Halde: O(n log n)
2. N-malige Entnahme des Maximums aus der Halde O(n log n)

Teilhalde:
Ein Teil eines Feldes heißt Teilhalde genau dann, wenn gilt:

Anmerkung:
Ist das gesamte Feld F(0…n-1) eine Teilhalde, so erfüllt F die Haldeneigenschaft

Aufbau der Halde:


• Die zweite Hälfte des Feldes (n/2..n-1) ist bereits trivialerweise eine Teilhalde, da diese
Knoten keine Nachfolger haben
• Elementweises Einsinkenlassen der Elemente aus der vorderen Hälfte

Aufbau der sortieren Folge:


• Aufbau der sortierten Folge am hinteren Ende des Feldes
• Vertauschung der Elemente F(0) und F8k-1):
• Entnahme des Maximums F(0) aus dem Haldenbereich
• Einfügen am Anfang in die aufsteigend sortierte Folge
• F(1…k-2) ist weiterhin eine Teilhalde
• Wiederherstellung der Haldeneigenschaft von F(0…k-2) durch Einsinkenlassen von F(0)

Das abstrakte Divide-and-Conquer-Prinzip:


1. Teile das Problem (divide)
2. Löse die Teilprobleme (conquer)
3. Kombiniere die Teillösungen (join)
Beim Sortieren gibt es zwei Ausprägungen:
• Easy split/ hard join: Dabei ist die Aufteilung F= F1F2 trivial und die ganze Arbeit liegt beim
Zusammensetzen von L1 und L2 zu L. Dieses Prinzip führt zum merge sort-Algorithmus
• Hard split/ easy join: Dabei wird die gesamte Arbeit beim Teilen des Problems verrichtet und
die Kombination ist trivial, das heißt, F wird so in F1 und F2 partitioniert, dass S=S1S2. Dieses
Prinzip führt zum Quicksort-Algorithmus

Merge sort
Idee:
• Zerlegung einer Liste F der Länge n=2m, m>0, mit geringem Aufwand in zwei gleich große
Teillisten der Länge der Länge n/2 =2m-1 mit F= F1F2 tzerlegen (divide)
• Die Teilfolgen F1 und F2 werden rekursiv sortiert mit dem Ergebnis L1 und L2 (conquer)
• Die sortierten Folgen L gemischt, indem man das jeweils kleinste (bzw. größte) Element von
L1 und L2 an L anfügt (join)

Idee:
• Zerlegung einer Liste F der Länge n=2m, m>0, mit geringem Aufwand in zwei gleich große
Teillisten der Länge n/2 = 2m-1 mit F=f1F2 zerlegen (divide)
• Die Teilfolgen F1 und F2 werden rekursiv sortiert mit dem Ergebnis L1 und L2 (conquer)
• Die sortierten Folgen L1 und L2 werden dann in einem linearen Durchgang zur sortierten
Folge L gemischt, indem man das jeweils kleinste (bzw. größte) Element von L1 und L2 an L
anfügt (join)
• Leere oder einelementige Listen sind sortiert

Eigenschaften:
• Schnellstes Verfahren auf verketteten Listen wegen der geringsten Zahl an Vergleichen
• Komplexität klasse: O(n log n)
• Sequentieller Zugriff auf Teillisten → externes Sortierverfahren für große Listen, die nicht
mehr in den Hauptspeicher passen:
o Unterteile Datei in Abschnitte
o Sortiere jeden der Abschnitte in Hilfsdatei
o Speichere sortierte Abschnitt in Hilfsdatei
o Verschmelze die Hilfsdatei mit Reißschlussverfahren
Hilfsdatei sequenziell von links nach recht verarbeitet, das geht schnell und ohne wahlfreien Zugriff

Prinzip des Mischens:


Der Mischvorgang zweier sortierter Teillisten funktioniert nach dem Reißschlussverfahren:
gegeben: sortierte Liste L1 und L2
• Gesucht: sortierte Ergebnisliste L
• Vorgehen:
1. L1 ungleich leere Menge und L2 ungleich leere Menge
Hänge das kleinere Element an L an
Entferne das eingefügte Element aus L1, bzw. L2
2. Wenn L1 ungleich leere Menge: L = L + L1
3. Wenn l2 ungleich leere Menge: L = L+L2
4. Gib L zurück
Quicksort
Idee:
• Zerlege Gesamtaufgabe in zwei Sortieraufgaben:
• Linker Teilbaum der Reihung kleine Werte
• Rechter Teilbaum der Reihung großer Werte
• Diese Anordnung wird hergestellt, indem im Rahmen der Zerlegung zunächst einige
Reihungselemente ihre Plätze tauschen
• Zusammenfügen der Teillösungen ist trivial: Durch das Platztauschen ist die Konkatenation
der Teillösungen bereits sortiert
Eigenschaften:
• Kein zusätzlicher Speicherplatz notwendig. Algorithmus sortiert Reihung in place
• Komplexitätsklasse: O(n log n)

Algorithmus: Zerlegen
• Gegeben: Reihung F von Elementen mit Totalordnung
o Ausschnitt F(m:n) zwischen Indizes m, n (inklusiv)
o In diesem Ausschnitt vorkommendes Element p (Pivotelement)
Vorgehen:
• Wähle und entferne Pivotelement p aus F(m:n)
• Zerlege F in Teilfolge
o Sortiere (rekursiv) F1 zu L1 und F2 zu L2
o Leere oder einmenge Listen sind sortiert
• Gesamtlösung L ist L1pL2

Idee 1:
• Für alle k mit m<0 k<i soll gelten: F(k) <p
Dies legt eine Schleife nahe, in welcher i von links nach rechts durch F(m:n) wandert und im
Schleifenrumpf durch geeignete Vertauschung die Bedingung F8k)<p hergestellt wird
• Weiterhin soll für alle k mit j<= k <n gelten: p<= F(k)
Dies legt ein Durchlaufen von rechts nach links nahe

Idee 2:
• Lösung: Lasse i von links nach rechts laufe und führe zweiten Zähler j ein, der von rechts nach
links wandert
• Werte im Bereich F(m:i) und F(j:n) sind richtig angeordnet
• F(i:j) wurde noch nicht behandelt

Sortieren durch Zerlegen (quick Sort)


Eingabe: Folge F= a0…an-1 mit n Elementen
Ausgabe: Umordnung der Folge derart, dass alle Elemente a0…ai kleiner als die Elemente aj, …an-1
sind (i<j)
Methode:
1. Wähle das mittlere Element der Folge als Vergleichselement p
2. Setze i=0 und j=n-1
3. Wiederhole so lange i<= j:
a. Suche von links nach rechts das erste Element ai mit ai>= p
b. Suche von rechts nach links das erste Element aj mit aj<= p
c. Falls p i< j: Vertausche ai und aj
d. Falls <= setze i = i+1 und j= j-1

Die Ergebnisteilfolgen werden rekursiv sortiert


Das zusammenfügen der Teilfolgen ist dann trivial

Aufwandabschätzung:
• Mittlerer Aufwand: Halbiert das Pivot Element den zu sortierenden Ausschnitt (die Hälfte der
Werte ist kleiner und die andere Hälfte ist größer als das Pivot Element); müssen maximal
n/2 Tauschoperationen durchgeführt werden

• Pathologischer Fall: Alle Werte des Ausschnitts sind kleiner (oder alle größer) als das
Pivotelement
o Alle Elemente des Ausschnitts müssen für den Platztausch untersucht werden
o Im nächsten Rekursionsschritt ist der Ausschnitt nur um ein Element verkürzt
o Damit: n+ (n-1) # (n2-2)+…+1

Aufwandsabschätzung:
Fazit: Für jedes Verfahren, das nach einer feststehenden Regel das Pivotelement aussucht (z. B . in
der Mitte des Ausschnitts) kann eine Eingabe gefunden werden, die zu einem schlechten Aufwand
führt.
Im Schnitt ist quicksort aber extrem schnell

Theoretische untere Schranke:


Gegeben: Folge von n Eingabewerten
Sortierverfahren: Wahl derjenigen Permutation, die der sortierten Folge entspricht
Anzahl an möglichen Permutationen: n! Permutationen
Entscheidungsbaum: Auswahl der richtigen Permutation

Entscheidungsbaum:
• Blätter: alle möglichen n! Permutationen
• Höhe: h = (log(n)
• Innere Knoten: Vergleich der Werte zweier Positionen

Stirling Formel:
• N! = (Wurzel 2 pi n) *(n/e)n

• Abschätzung von log(n!): O(n log n)


• Folge Vergleichsbasierte Sortierverfahren benötigen mindestens O(n log n)

BucketSort:
Idee:
• Sortierung ohne Schlüsselvergleiche
• Schlüsselwerte unterliegen bestimmte Einschränkungen, z.B. ganze Zahlen zwischen 0 und
m-1, keine Duplikate
Verfahren:
• Verwendung von m Behältern
• Verwaltung der Behälter in einem (zweiten) Array: out of place
• Platzieren eines Elements in einem Behälter: O(1)
• Gesamtaufwand: O(n+m)
nicht praktikabel, wenn n größer m

Erweiterung:
Zulassung von Duplikaten
Bsp. Verteilung von n Briefen auf m Postfächer (Jeder Behälter wird als Liste von Elementen
implementiert, vgl. offenes Hashing)

Radix Sort:
Verfahren:
• Einsparung von Behältern: Nicht mehr jeder mögliche Schlüsselwert bekommt seinen
eigenen Behälter
• Aufteilung des Schlüssels in Segmente
Ablauf:
• Sortierung gemäß des rechten Schlüsselelements mit BucketSort
• Sortierung des Ergebnisses gemäß des nächsten Schlüsselsegments
• Wiederholung für alle Segmente von rechts nach links
Aufwandsabschätzung:
• Aufwand pro Sortierung gemäß einem Segment: O(n)
• Gesamtaufwand für k Segmente: O(k*n)

Warum funktioniert das Verfahren?


(Wichtig: Implementierung der Behälter mit Warteschlangen)
• Stabilität des Verfahrens. Die relative Reihenfolge von Elementen, die bzgl. Der Ordnung
äquivalent sind. Bleibt unverändert
• Sei i< j und ei= ej
• Dann gilt für die neuen Positionen i´und j´ dieser beiden Elemente: i´< j´

Korrekt beweis:
• Voraussetzung:
• Daten lassen sich mit Hilfe von Bucket Sort gemäß eines Schlüsselsegments korrekt sortieren
• Sortierverfahren ist stabil
Induktionsanfang:
• Korrekte Sortierung gemäß des rechten Schlüsselsegments
Induktionsschritt:
• Daten sind gemäß der rechten k-1 Segmente korrekt sortiert
Induktionsschluss: k- 1 → k
• Fall 1: zwei Elemente unterschieden sich im k-ten Segment:
• Sortierung gemäß des k-ten Segmentes führt zur korrekten Reihenfolge
• Fall 2: zwei Elemente sind bezgl. des k-ten Segments gleich:
nach Induktionsannhame sind diese Elemente in den k- 1 Segment rechts davon korrekt
sortiert
• Da das Sortierverfahren stabil ist, blieb die relative Ordnung der beiden Elemente erhalten
• Die Reihenfolge ist auch nach der Sortierung gemäß des kten Segmentes korrekt
Verfahren:
• Sortierung nach dem linken Schlüsselelement
• Rekursive Anwendung auf jeden Behälter Sortierung nach dem nächsten Segment

Beispiel Postleitzahl:
• Sortierung nach der ersten PLZ
• Sortierung eine jeden Behälters nach der zweiten Stelle der PLZ in 10 weitere Behälter
• …
• Bedarf 50 statt 100.000 Behälter

Graphen:
• Problemabhängige Beschreibung von Objekten und deren Beziehungen
• Anschauliche Darstellung durch Diagramme möglich
• Graphalgorithmen: problemunabhängig formulierte Algorithmen zur Lösung vieler Aufgaben
• Unterscheidung zwischen gerichteten und ungerichteten Graphen

• Ein gerichteter Graph ist ein Paar G= (V, E)


V ist eine endliche Menge von Knoten (vertex)
• E ist eine zweistellige Relation auf V ist, d.h. E (edges) ist (ist Paare aus V´s)
• Die Elemente von E werden gerichtete Kanten genannt
• Ein Graph heißt schlicht, wenn es keine Kanten mit identischen Anfangs und Endknoten gibt
(keine loops)

Ist (vi, vj) aus E, so heißt,


• vi direkter Vorgänger oder Elternknoten von vj
• vj direkter Nachfolger oder Kind (knoten) von vi

Existiert ein Pfad von vi nach vj, so heißt


• Vi Vorgänger oder Vorfahre von vj
• Vj Nachfolger, Nachfahre oder Nachkomme von vj

Ein ungerichteter Graph ist ein Graph G=(V, E) für den gilt:
Für alle vi, vk aus V: (vi, vj) aus E → (vj, vi) aus E
• E ist symmetrisch, Richtung spielt keine Rolle, man kann Plätze tauschen
• In der graphischen Darstellung gehört zu jedem Pfeil ein Pfeil in die Gegenrichtung. Statt
Pfeilen zeichnet man daher spitzenlose Linien
• In der Mengenschreibweise schreib man [vi, vj] für beide Paar (keine runden Klammern
mehr)

Geometrische Repräsentation:
Bestimmt eindeutig einen Graphen, aber
Es gibt (unendlich) viele Möglichkeiten einen Graphen durch ein Diagramm darzustellen
(man muss alle Knoten sehen und alle Graphen, ob die gerichtet oder ungerichtet sind, …)

Adjazenz:
• Zwei Knoten in einem ungerichteten Graphen heißen adjazent oder benachbart, wenn es
eine Kante gibt, die beide Knoten verbindet
• Die Nachbarn eines Knoten v sind diejenigen Knoten, die zu v benachbart sind
• Zwei Kanten heißen adjazent oder benachbart, wenn sie einen gemeinsamen Knoten
besitzen

Inzidenz:
• Ein Knoten in einem ungerichtete Graphen ist inzident mit einer Kante des Graphen, wenn er
ein Endknoten dieser Kante ist
• Eine Kante ist inzident mit einem Knoten, wenn dieser Knoten ein Endknoten der Kante ist
• Der Grad eines Knoten v ist die Anzahl der Kanten, die mit v inzident sind
• Delta (G) bezeichnet den maximalen Knotengrad G, kleinen Delta (G) den minimalen
Knotengrad
• Knotengrad bei loops werden zwei Mal gewertet

Ungerichteter Graph:
• Jede Kante trägt zum Knotengrad von zwei Knoten bei
• Folglich ist die Summe der Grade alle Knoten gleich der zweifachen Anzahl der Kanten
• Summe von i=1 bis n von g(vi) =2m (n: Anzahl der Knoten, m : Anzahl der Kanten)
• Nützliche Folgerung: In einem ungerichteten Graphen gibt es eine gerade Anzahl an Knoten
mit ungeraden Knotengrad

Bei gerichteten Graphen unterscheidet man bei einem Knoten v zwischen


• Ausganggrad g+ (v) Anzahl der Kanten mit Anfangsknoten v und
• Eingangsgrad g– (v) Anzahl der Kanten mit Endknoten v

Komplement:
• Bei ungerichteten Graphen
• Wenn zwischen zwei Knoten eine Kante existiert, gibt es die im Komplement nicht
• Gibt es keine Kante zwischen Knoten, existiert die im Komplement
• (aus 0 wird 1, aus 1 wird 0)

Untergrad:
• Der von der Knotenmenge V´={v1, v2, v3, v4, v5] induzierte Untergraph wir behalten nur alle
Knoten aus der Untermenge und deren Ursprüngliche Verbindungen, alle andern werden
weggelassen

Planare Graphen:
• durch Umformung keine Kanten sich überschneiden
• Geht nicht immer

Pfad:
• Ein Pfad (Weg, Kantenzug, path) von vi nach vj ist eine endliche Folge von Knoten vi= a0, a1,
…ap=vj
• , wobei (ai, ai+1) aus E für 0<= i < p
• Der Pfad verbindet vi und vj
• Vj ist von vi aus erreichbar
• In einem einfachen Pfad kommt jeder Knoten höchstens einmal vor
• P ist die Anzahl der Kanten im Pfad und heißt Länge des Pfades
• Der kürzeste Pfad von einem Knoten vi zu einem Knoten vj ist derjenige Pfad von vi nach vj
dessen Länge minimal ist

Minimaler Zyklus:
• Außer vi kommt kein anderer Knoten mehr als einmal vor

Zusammenhängende ungerichtete Graphen:


• Ein ungerichteter Graph G=(V,E) heißt zusammenhängend, falls es für jedes Knotenpaar (vi,
vj) einen ungerichteten Pfad von vi nach vj gibt
• Ein maximaler zusammenhängender Teilgraph eines Graphen G heißt
Zusammenhangskomponente von G
• Ein unzusammenhängender Graph zerfällt in seine Zusammenhangskomponenten

Zusammenhängende gerichtete Graphen:


• Ein gerichteter Graph G=(V,E) heißt strak zusammenhängend von einem Knoten aus v, falls es
für alle vj aus V einen Pfad von v nach v gibt
• G heißt stark zusammenhängend, falls G von jeden Knoten vi stark zusammenhängend ist
• der Knoten ist von jedem anderen Knoten aus erreichbar
• Ein gerichteter Graph heißt schwach zusammenhängend, falls der ungerichtete Graph, der
entsteh, wenn jede gerichtete durch eine ungerichtete Kante ersetz wird, zusammenhängend
ist

Spannbaum:
• Ein zusammenhängender, Zyklen freier Teilgraph des Graphen G, der alle Knoten G enthält
• Existieren nur in zusammenhängenden Graphen

Kantengewichtete Graphen:
• Erweiterung von Graphen : G=(V, E, f) mit f: E→ R
• In der Praxis beschreiben diese Gewichte die Kosten eines Übergangs von einem Zustand vi
nach vj mit (vi, vj) aus E
• Auch die Knoten kann ein Gewicht zugeordnet werden. In diesem Fall spricht man von einem
knotengewichteten Graphen

Problem des Handlungsreisenden:


• Ein Handlungsreisender muss eine Rundreise durch N vorgegebene Städte machen. Um Zeit
und Berzin zu sparen, sucht er dazu nach demjenigen Weg, der unter allen denkbaren Routen
die kürzeste Gesamtlänge aufweist
Formulierung als Graph:
• Knoten V: vi entspricht der i-ten Stadt
• Kanten E: [vi, vj] aller Wege ca. 2 Millionen Jahre dauern] entspricht einer Verbindung
zwischen beiden Städten
• Gewichte f: F([vi, vj]) entspricht der Entfernung zwischen den beiden Städten, wobei [vi, vj]
aus E

Problem:
• Schon bei nur N=20 Städten gibt es (N-1) !/2 unterschiedliche Reiserouten. Damit würde bei
einer Millisekunde pro Weg die Berechnung

Datenstrukturen für Graphen


Wichtige Operationen auf Graphen:
• Feststellung, ob zwei Knoten benachbart sind
• Bestimmung der Nachbarn eines Knoten in einem ungerichteten Graphen
• Bestimmung der Nachfolger und Vorgänger eines Knoten in einem gerichteten Graphen
• dynamische Anwendung: Löschen und Einfügen von Knoten und Kanten Kriterien zur Wahl
der

Darstellungsform:
• Effizienz der Ausführung von Operationen
• Speicherplatzbedarf

Darstellungsformen:
• Adjazenzmatrix
• Adjazenzliste
• Kantenliste
• Einbettung in ein Feld

Matrix A ∈ {0, 1} n×n spiegelt direkt die Nachbarschaft der Knoten wider
ungerichtete Graphen: A ist symmetrisch

Adjazenzmatrix für kantengewichtete Graphen: Beispiel Entfernungstabelle



Komplexität von Graphoperationen auf Adjazenzmatrizen
• Feststellung, ob eine Kante von Vi nach Vj existiert:
o Überprüfung des Eintrag aij
o 1 Schritt unabhängig von der Größe des Graphen: O(1)
• Bestimmung der Nachbarn eines Knoten Vi in einem ungerichteten Graphen
o Durchsuchung aller Einträge der i-ten Zeile oder i-ten Spalte
o n Überprüfungen unabhängig von der Anzahl der Nachbarn: O(n)
• Bestimmung der Vorgänger in einem gerichteten Graphen
o Durchsuchung aller Einträge der i-ten Spalte: O(n)
• Bestimmung der Nachfolger in einem gerichteten Graphen Du
o Durchsuchung aller Einträge der i-ten Zeile: O(n)

Speicherbedarf von Adjazenzmatrizen


• n2 Speicherplätze unabhängig von der Anzahl der Kanten
• In Graphen mit wenigen Kanten sind die meisten Einträge aij gleich 0.
• Bei ungerichteten Graphen genügt es, die Dreiecksmatrix zu speichern: n(n + 1)/2
Speicherplätze

Adjazenzliste:

• Für jeden Knoten wird eine verkettete Liste der Nachfolger gespeichert.
• Speicherbedarf direkt abhängig von der Anzahl der Knoten n und Kanten m: n + m bei
gerichteten Graphen, n + 2m bei ungerichteten Graphen
• Kantengewichte lassen sich in den Listenelementen speichern.

• Die Nachbarn eines jeden Knoten werden der Reihe nach lückenlos in
einem Feld A abgelegt.
• Ein zweites Feld N verwaltet die Indizes der Anfänge der
Nachbarlisten.
• Nachbarn des Knoten Vi sind in den Einträgen A[N[i]] bis A[N[i + 1] −
1] zu finden, falls N[i + 1] > N[i]

Speicherbedarf:
• Feld mit n Einträgen zur Verwaltung der Indizes der Anfänge der Nachbarlisten
• Feld der Nachbarn mit m Einträgen bei gerichteten Graphen
• Feld der Nachbarn mit 2m Einträgen bei ungerichteten Graphen

Fazit:
• Speicherbedarf deutlich geringer als bei Adjazenzmatrizen, wenn die Anzahl der Kanten m
viel kleiner ist als die maximal mögliche Anzahl an Kanten.
• Dynamische Anwendungen: Verwendung von Adjazenzlisten basierend auf verketteten
Listen

Komplexität von Graphoperationen auf Adjazenzlisten:


• Feststellen, ob eine Kante von Vi nach Vj existiert:
o Durchsuchung der Nachbarschaftsliste von Knoten Vi
• Bestimmung der Nachbarn eines Knoten Vi in einem ungerichteten Graphen
o Die gesuchte Liste liegt direkt vor.
• Bestimmung der Nachfolger in einem gerichteten Graphen
o Die gesuchte Liste liegt direkt vor.
• Bestimmung der Vorgänger in einem gerichteten Graphen
o erfordert die Durchsuchung sämtlicher Nachbarschaftslisten
o Abhilfe: Speicherung der Vorgängerlisten in zwei weiteren Feldern (redundante
Information)

• explizite Speicherung aller Kanten in einem 2 × m-Feld K


• i-te Kante: (K[1, i], K[2, i])
• Speicherbedarf: 2m Speicherplätze
• dynamische Anwendungen: Realisierung über verkettete Listen

Kantenliste gewichtete Kanten

• explizite Speicherung aller Kanten in einem 3 × m-Feld K


• i-te Kante: (K[1, i], K[2, i])
• Gewicht der i-ten Kante: K[3, i]
• Speicherbedarf: 3m Speicherplätze

Komplexität von Graphoperationen auf Kantenlisten:


• Feststellen, ob eine Kante von Vi nach Vj existiert:
o Durchsuchung der gesamten Kantenliste
o Sortierte Kantenliste: binäre Suche
o ungerichteter Graph: Kanten doppelt aufführen, einmal für jede Richtung
• Bestimmung der Nachbarn eines Knoten Vi in einem ungerichteten Graphen
o ähnliche Aussagen
• Bestimmung der Vorgänger/Nachfolger in einem gerichteten Graphen
o ähnliche Aussagen

Einbettung in ein Feld:

Einbettung in ein Feld


• Vorteile:
o geringer Speicherbedarf: n + m Einträge
o noch kompakter als verkettete Listen: kein Speicherplatz für Zeiger notwendig
• Nachteile:
o Einfügen und Löschen: aufwändige Änderungen des Feldes notwendig

Traversierung
• Durchlaufen des Graphen, so dass jeder Knoten einmal besucht wird
• Bestandteil vieler Graphalgorithmen
Verfahren:
• Tiefensuche, depth-first search (DFS)
• Breitensuche, breadth-first search (BFS)

Expansion eines Graphen in einem Knoten


Unter der Expansion X(V) eines Graphen G in einem Knoten V versteht man einen Baum, der wie folgt
definiert ist:
• Hat V keine Nachfolger, besteht der Baum X(V) lediglich aus dem Knoten V.
• Hat der Knoten V die Nachfolger V1, …, Vk , so besteht der Baum X(V) aus der Wurzel V und
den Teilbäumen X(V1), . . . , X(Vk ).
• Enthält der Graph Zyklen, ergibt sich ein unendlicher Baum.
• Der Baum X(V) stellt die Menge aller Pfade dar, die vom Knoten V ausgehen.

Zykelgraph
Tiefensuche:
• Traversierung der Expansion von G in pre-order-Reihenfolge
• Abbruch in einem bereits besuchten Knoten

Tiefensuche (Forts.)
• Mit der Tiefensuche erreicht man alle Knoten in einem Graphen, die von einem Startknoten
aus erreichbar sind.
• In einem ungerichteten Graphen werden alle Knoten einer Zusammenhangskomponente
erreicht.
• Um alle Knoten von G zu erreichen, muss der Algorithmus für alle noch unbesuchten Knoten
im Graph ausgeführt werden.
• Die Knoten eines Graphen werden in einer bestimmten Reihenfolge durchlaufen. → DFS-
Nummerierung
• DFS-Nummern sind für viele Graphalgorithmen nützlich.

DFS-Baum
• Blaue Kanten verbinden Knoten mit denjenigen Knoten, die der Algorithmus als noch
unmarkiert vorgefunden hat.
• Daraus entsteht bei zusammenhängenden Graphen der sogenannte DFS-Baum
(Tiefensuchbaum).
In einem zusammenhängenden ungerichteten Graphen G = (V, E) mit seinem DFS-Baum T = (V, E 0 )
gilt für alle Kanten e ∈ E:
• e ist eine Kante des DFS-Baums: e ∈ E 0 , oder
• e verbindet zwei Knoten von G, von denen einer Vorfahre des anderen in T ist.
➔ Es gibt keine Querkanten im Baum

Beweis:
• Sei (v, w) eine Kante von G und Knoten v bereits besucht.
• Wird w als erster Nachfolger besucht, ist (v, w) eine Kante von E 0.
• Ansonsten wird w besucht, ehe der Algorithmus zum Vorgänger von v zurückkehrt. In diesem
Fall ist w Nachfolger von v in T.

DFS-Baum
In einem zusammenhängenden gerichteten Graphen G = (V, E) mit seinem DFS-Baum T = (V, E 0 ) gilt
für jeden Kante (vi , vj) ∈ E:
• Falls die DFS-Nummer von vi kleiner ist als diejenige von vj , dann ist vi ein Vorfahre von vj in
T.

Kantenarten:
• Teil des DFS-Baums:
o Baumkanten: Kanten des DFS-Baums
• Nicht Teil des DFS-Baums:
o Vorwärtskanten: Kante zwischen einem Knoten und einem Nachfahren
o Rückwärtskanten: Kante zwischen einem Knoten und einem Vorfahren
o Querkante: Verbindung zwischen “nicht direkt verwandten” Knoten
Detektion von Zyklen
Zyklus: Auf dem Pfad von der Wurzel nach unten trifft man auf einen Knoten, der bereits auf diesem
Pfad liegt.

Vorgehen:
• Einführung einer Variablen, die speichert, ob ein Knoten bereits auf dem Pfad liegt oder nicht
• Wird ein Knoten besucht, wird diese Variable auf true gesetzt.
• Trifft man bei der Expansion der Nachfolger auf einen Knoten, dessen Variable bereits den
Wert true hat, hat der Graph einen Zyklus.
• Nachdem die Nachfolger alle abgearbeitet wurden, wird der Wert der Variable für diesen
Knoten wieder auf false gesetzt.

Breitensuche:
• Besuch der Knoten der Expansion von G ebenenweise
• Abbruch in einem bereits besuchten Knoten

Kürzester Pfad im Graph:

Ziel: single source shortest path-Problem


• In einem gerichteten Graphen, dessen Kanten mit positiven reellen Kosten gewichtet sind,
sollen die kürzesten Pfade von einem beliebigen Startknoten v aus zu allen anderen Knoten
des Graphen berechnet werden. Länge eines Pfades: Summe der Kantenkosten

Idee von Dijkstra:


Schrittweise Erkundung des Graphen
• Ausgehend vom Startknoten lässt man einen Teilgraphen wachsen.
• 2 Arten von Knoten:
o dunkelblaue Knoten, bei denen bereits alle Kanten zu Nachfolgern betrachtet
wurden; zu diesen Knoten ist der kürzeste Pfad bereits berechnet.
o hellblaue Knoten, bei denen die ausgehenden Kanten noch nicht betrachtet wurden;
diese Knoten bilden den Rand des Teilgraphen.
• 2 Arten von Kanten:
o blaue durchgezogene Kanten: bilden innerhalb des Teilgraphen einen Baum der
(bisher gefundenen) kürzesten Wege
o blaue gestrichelte Kanten: können als Teil des Baums der kürzesten Wege
ausgeschlossen werden

Expansion des Teilgraphen:


• Wahl des hellblauen Knoten w mit dem kleinsten Abstand zum Startknoten v
• Färbe Knoten w dunkelblau.
• Nachfolger wi von w, die noch nicht im Teilgraphen liegen, werden als hellblaue Knoten in
den Teilgraphen übernommen:
o (w, wi) wird eine blaue durchgezogene Kante.
o dist(wi) := dist(w) + cost(w, wi)
• Ist Nachfolger wi bereits hellblau, muss ggf. der bisher bekannte kürzeste Pfad angepasst
werden.
• Ist der Nachfolger wi bereits dunkelblau, wird die Kante (w, wi) gestrichelt gezeichnet.

Lemma 1:
• Zu jedem Zeitpunkt gilt für jeden hellblauen Knoten w, dass der blaue durchgezogene Pfad
von v zu w minimal unter allen blauen Pfaden zu w ist.
• (Blaue Pfade bestehen ausschließlich aus blauen gestrichelten und/oder blauen
durchgezogenen Kanten.)

Beweis:
• Induktionsanfang:
o Die Behauptung gilt für v, da noch keine Kanten blau gefärbt sind
• Induktionsannahme:
o Die Behauptung gilt für hellblaue und dunkelblaue Knoten.
• Induktionsschritt:
o Ein hellblauer Knoten w wird dunkelblau gefärbt.
o Unterscheidung folgender zwei Fälle für die Nachfolger wi von w:
▪ 1. Nachfolger wi wird zum ersten Mal erreicht:
Alle blauen Pfade zu wi haben die Form v w→wi.
Nach Induktionsannahme gibt es einen blauen durchgezogenen Pfad v w, der
unter allen blauen Pfad von v nach w minimal ist. Folglich ist v wi ebenfalls
minimal
▪ Nachfolger wi ist bereits hellblau und wird erneut erreicht:
Bisher kürzeste Pfad zu wi führt über Knoten u: Pfad 1: v u → wi Der neue
blaue durchgezogene Pfad über Knoten w ist kürzer: Pfad 2: v w → wi
Pfad (1) ist minimal unter allen blauen Pfaden, die nicht über w führen.
Pfad (2) ist minimal unter allen blauen Pfaden, die über w führen (s. 1. Fall).
→Folglich ist der neue Pfad minimal unter allen blauen Pfaden.

Lemma 2:
Wenn ein Knoten w dunkelblau gefärbt wird, dann ist der blaue durchgezogene Pfad zu w der
kürzeste aller Pfade zu w im Graphen.

Beweis:
• Induktionsanfang:
o Der Startknoten v wird zuerst dunkelblau gefärbt; es gibt noch keinen blauen
durchgezogenen Pfad zu v.
• Induktionsannahme:
o Die Behauptung gilt für hellblaue und dunkelblaue Knoten.
• Induktionsschritt:
o Hellblauer Knoten w mit minimaler Entfernung zu v wird dunkelblau gefärbt. Der
blaue durchgezogene Pfad zu w ist nach vorherigem Lemma minimal unter allen
blauen Pfaden.

Ziel:
all pairs shortest path-Problem
• In einem gerichteten Graphen, dessen Kanten mit positiven reellen Kosten gewichtet sind,
sollen die kürzesten Pfade zwischen allen Knotenpaaren des Graphen berechnet werden.
Länge eines Pfades: Summe der Kantenkosten

Voraussetzung:
Knoten sind von 1 bis n durchnummeriert

Idee von Floyd:


• Verläuft der kürzeste Pfad von Knoten u nach Knoten v über Knoten w, dann sind die beiden
Teilpfade von u nach w und von w nach v ebenfalls minimal.
• Annahme: Die kürzesten Pfade zwischen allen Knotenpaaren, die nur über Knoten mit Index
kleiner als i verlaufen, sind bereits bekannt.
• Iterationsschritt: Suche alle kürzesten Pfade über Knoten mit Index höchstens i.
• Hierzu gibt es nur 2 Möglichkeiten:
• Der Pfad verläuft über Knoten i. Die Teilpfade von u nach i und von i nach v sind in diesem
Fall bereits bekannt.
• Der kürzeste Pfad von u nach v ist der bereits bekannte Pfad, der nur über Knoten mit Index
kleiner i verläuft.

Transitive Hülle:
• U. U. ist nur von Interesse, ob ein Pfad (mit Länge ≥ 1) zwischen zwei Knoten existiert.
• Die Kosten spielen dabei keine Rolle.
• Transitive Hülle G = (V, E) von G = (V, E): (v, w) ∈ E ⇐⇒ Es existiert in G ein Pfad von v nach
w.

Algorithmus von Warshall:


• Vereinfachte Version des Algorithmus von Floyd
• Darstellung des Graphen G als Adjazenzmatrix W ∈ R n×n

Algorithmus von Prim:

Ist dieser Algorithmus korrekt?


Überlegungen:
• Sei (u, w) die Kante mit geringsten Kosten, die einen Knoten von T mit einem Knoten
außerhalb von T verbindet.
• Gehört diese Kante zum minimalen Spannbaum?
• Wenn nicht, gäbe es einen Pfad von einem Knoten u 0 zu w mit niedrigeren Kosten.
• Dieser Pfad muss T verlassen; er hat mindestens die Kosten der ersten Kante e, die T verlässt.
• Die Kosten dieser Kante e sind mindestens so groß wie die Kosten der Kante (u, w).
• Der Spannbaum wird folglich kleiner (oder bleibt gleich groß), wenn man die Kante e durch
(u, w) ersetzt.

Lemma 3:
• Für einen Graphen G = (V, E) sei (U, W) eine Zerlegung seiner Knotenmenge V.
• Sei (u, w) ∈ E eine Kante mit minimalen Kosten unter allen Kanten {(u 0 , w 0 )|u 0 ∈ U, w 0 ∈
W}.
• Dann gibt es einen minimalen Spannbaum für G, der diese Kante enthält.

Algorithmus von Kruskal:


• Sei T = (V, ∅) der Graph, der nur aus den Knoten von G besteht und keine Kanten enthält.
• Betrachtung der Kanten von G in der Reihenfolge aufsteigender Kosten
• Verbindet eine Kante zwei getrennte Komponenten von T, wird sie in den Graphen T
eingefügt und die beiden Komponenten dadurch verschmolzen. Andernfalls wird die Kante
ignoriert.
• T ist ein minimaler Spannbaum für G, sobald T nur noch aus einer einzigen Komponente
besteht.

Reihenfolge Komplexitätsklasse

Implizit: int zu Double


Explizit: double zu int

Das könnte Ihnen auch gefallen