Beruflich Dokumente
Kultur Dokumente
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.
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
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
Kommentare:
• Programm dokumentieren
• Teile auszukommentieren
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.
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
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
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 () ;
• 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
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
Dezimalsystem:
Vereinbarung: Um Verwechslungen zu vermeiden, wird die Basis als Index rechts neben der Zahl
geschrieben
Polyadische Zahlensysteme:
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
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:
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
➔ Assoziativgesetz
➔ Absorptionsgesetz (assoziativ mit gewechselten Zeichen)
➔ Idempotenz (a oder a=a ist gleich a und a=a)
➔ De Morgan
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
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
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
Klassendefinition:
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:
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
Operationen:
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
Aggregationen:
Komposition:
• Ist ein Spezialfall der Aggregation, bei der ein Bestandteil zu genau einem Ganzen gehört und
nicht ohne das Ganze existieren kann
Begriffsdefinition:
Enthält eine Klasse mehrere Methoden mit gleichem Namen, aber unterschiedlichen Signaturen, so
spricht man von überladenen Methoden
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
• 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
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
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
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
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
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
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
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
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
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
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
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
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
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:
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
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:
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))
➔
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
• 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
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
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
Im Vergleich zu iterativen Lösungen sind rekursive Funktionsaufrufe teuer bzgl. Speicherbedarf und
benötigten Rechenzeit
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
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
Grund:
• Die Elemente werden im Speicher nicht aufeinander folgend, sondern an beliebigen freien
Stellen im Speicher abgelegt
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
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
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
Mehrere Typparameter:
Trennung durch Komma
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
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
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
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
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
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
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
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))
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))
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
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)
Divide-and-Conquer:
Quicksort
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)
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
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
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
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
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
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)
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 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
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
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:
• Schon bei nur N=20 Städten gibt es (N-1) !/2 unterschiedliche Reiserouten. Damit würde bei
einer Millisekunde pro Weg die Berechnung
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
➔
→
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)
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
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)
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
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
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.
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.
Reihenfolge Komplexitätsklasse