Sie sind auf Seite 1von 86

§2 Lineare Datenstrukturen

© 2014 Prof. Dr. Franz J. Brandenburg

Übersicht
2.1 elementare Datentypen (Wiederholung Programmierung I)
2.2 Lineare Datenstrukturen (Wiederholung Programmierung I)
Klassifikationsmerkmale
Arrays
Listen
Java API (applications programmer interface)
2.3 Abstrakte Datentypen
allgemein
Stacks und Queues
2.4 Sortieren
elementare Verfahren in O(n2)
fortgeschrittene Verfahren in O(n logn)
BucketSort in O(N), N = max-min bei n Elementen

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
elementare Datentypen
• Zahltypen, numerische Werte
– ganzzahlig: int, sowie byte, short, long
– floating point: double und float

• boolesche Werte
boolean mit den Werten {false, true}

• Zeichen, char

• Referenzdatentypen: alles andere (der Zugriff auf…)


– Object die Wurzel der Vererbungshierarchie; das Allgemeinste
– Strings Zeichenreihen
– arraysFelder
– class eigene, selbst definierte Klassen
– Wrapper z.B. Integer
– null die leere Referenz

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
int
ganzzahlig: int (Default) und außerdem byte, short, long
(außerdem: BigInteger als Referenztyp)
– Wertebereich:
eine Teilmenge der ganzen Zahlen Z von –2b-1 bis 2b-1-1
interne Darstellung: Zweierkomplement mit b Bits, b=8,
16,32,64
– Operationen: alle Standardoperationen
int x,y,z; x = 1; y = x+x; x= x+y; if (x<y) x=y; z = z++;
arithmetisch:
unär: +, - (Vorzeichen), ++, -- (In-, Dekrement)
binär: +,–,*, / (ganzzahlig), % (modulo)
Vergleiche: ==, !=, <, <=, >, >=
Bitoperationen: Bitshifts <<, >>,... und Bitkomplement ~
Transfer- und Castfunktionen:
z.B. Math.ceil, Math.floor (double -> int)
Math.round (double -> long)
int vier = (int) Math.floor(Math.PI));
int drei = (int) Math.PI;

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
float, double
reell wertig: float und double (Default) z.B. double x = -0.123;
– Wertebereich:
eine Teilmenge der reellen Zahlen R
genauer: Dezimalzahlen mit beschränkter Stelligkeit (Taschenrechner)
Zahlen haben eine beschränkte Genauigkeit
intern: Binärdarstellung für Mantisse und Exponent
daher: Probleme mit numerischer Exaktheit
z.B. 0.1+ ...+0.1 (100 mal) ≠ 10.0
z.B. (1.0 / 7.0) * 7.0 ≠ 1.0
float: 32 Bit, 7 Stellen Genauigkeit, ± 1.4 E-45 ... ±3.4 E+38
double: 64 Bit, 15 Stellen Genauigkeit, ± 4.9 E-324 ... ±1.7 E+308
(„fast 0, ... sehr groß/klein“)

– Operationen: alle Standardoperationen


arithmetisch: unär: Vorzeichen +, -
binär: +,–,*, /
eingebaute Funktionen in Math package
z.B. Math.sin(x), Math.sqrt(x), Math.log(x),...
Vergleiche: ==, !=, <, <=, >, >=
Transferfunktionen: z.B. Math.ceil, Math.round, Math.floor
Castoperationen: float shortpi = (float) Math.PI; // Wert: 3.141593
§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
boolean und char
boolesche Werte: Typ boolean
– Wertebereich: {false, true} mit false < true
der Wertebereich (Ergebnis) von Vergleichen
Anwendung insbesondere bei if ( ), while ( )
– Operationen:
die booleschen Operationen
! (not),
& bzw. && (und), | bzw. || (oder), ^ (xor) z.B. (x & y)

Zeichen, Character: Typ char


– Wertebereich: Unicode, 65536 Zeichen
mit speziellen Charactersets, UTF-8, UTF-16
– Operationen: keine (außer Zuweisung)
Notation: einfache Hochkommas, ’a’ oder ’A’ oder ’@’ ...
4-stellig hexadezimal (0000-FFFF) mit \u000a
Besonderes: Escape ’\\’, ’\’’, ’\b’ (backspace), ’\n’ (new line)

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Referenzen
eine Referenz (pointer, Zeiger)
verweist auf ein Element eines bestimmten Typs.
Sie liefert die Speicheraddresse, wo das Element gespeichert ist.
null ist die Referenz auf das leere Element, der Zeiger ins Nichts

Beispiel: die Signatur 80 ST 250 J35


sagt wo das Buch „Algorithms in Java“ normalerweise steht.

Derefenzierung: mit „Punkt“ greift man auf den Wert hinter der Referenz zu.
class Point {
double x; double y; x = 0.0
Point(double x, double y) { y = 0.0
this x = x; this.y = y;
} p
x = 1.0
} y = 2.0
.... q
Point p = new Point(0.0, 0.0);
Point q = new Point(1.0, 2.0);
p = q;
... dann hat p.x den Wert 1.0 und p.y den Wert 2.0
if (p.x != p.y) ...else ...;
§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
class
Klassen (class) ist das zentrale Sprachelement in Java
class beschreibt Objekte (=konkrete Elemente einer Menge) und Methoden,
Methoden, die auf die Objekte einwirken (invoke a method)
und dadurch Veränderungen hervorrufen
z.B. kreieren / konstruieren / Instanziieren von neuen Objekten
mit den jeweiligen Konstruktoren unter Verwendung von new.
z.B. ändern von Attributwerten.

eBNF für class (bei eBNF steht [ ] für 0,1-mal und { } für beliebig oft)
<class_Definition> ::=
[ <class_modifier> ] class <class_name>
[ extends <superclass_name> ]
[ implements <interface> {, <interface>} ]
<class_body>

<class_body> ::=
{ { <Deklaration der KlassenAttribute> }
{ < KlassenKonstruktoren> }
{ < KlassenMethoden > }
}

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
struct
Kombination von verschiedenartigen Typen, z.B. int, double, String,…
(im Gegensatz zu Arrays oder Listen)

als class (struct in C++, record in Pascal)


mit den entsprechenden Attributen

Wertebereich: das kartesische (oder Kreuz-) Produkt der Komponenten

Beispiel: class Person


{
String name;
String vorname;
int handynummer;
double kontostand;
}
Person p = new Person(); // der Standardkonstruktor
p.name = ”Brandenburg”;
p.vorname = ”Prof. Dr. Franz J.”;
p.handynummer = ....; // top secret
p.kontostand = -1000.00;
§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Strings
Strings (Worte oder Zeichenreihen)
– Wertebereich: ∑*, die Menge aller Worte über dem Alphabet ∑
ein statischer (static) Referenztyp im Java
– Notation: im Doppelhochkommas ”ein string”
– Operationen:
+ für die Konkatenation
”PA” + ”S” + ”SAU” ergibt ”PASSAU”
length(), z.B. int wortlaenge = ”Passau”.length();

ausgewählte public Methoden für Strings (API Dokumentation)


String concat(String t); s.concat(t)
// die Konkatenation (Anhängen) von String t an den String s
boolean equals (Object obj); s.equals(t);
// liefert true, wenn obj ein String ist und wertmäßig gleich s
int compareTo(String t) s.compareTo(t);
// liefert den lexikographischen Vergleich, also 0 bei s==t.

und andere Methoden nach der Java API.

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Stil und Namen
• guter Programmierstil
sprechende Namen (mnemotechnische Namen),
nicht a,b,c z.B. class A = ...
Großschreibung für class
Kleinschreibung für Methoden

• get Methoden für den Zugriff auf ein Objekt


• set Methoden für Änderungen / Updates
• remove Methode zum Löschen
• insert Methode zum Einfügen eines neuen Objekts

Siehe Empfehlungen in Java API


Programmierung I und II

Lehrbücher, zB D. Abts, Grundkurs Java ST 250 J35

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Arrays
Definition
Ein Array a
ist ein Feld von Elementen eines Typs ...
mit fortlaufenden Indizes von 0 bis a.length - 1
umgekehrt
zu jedem Typ T (oder AnyType, insbesondere zu jeder selbst definierten Klasse)
gibt es entsprechende Array-Datentypen (der Dimension 1,2,...)
T [ ] array a = new T [LENGTH]; // ein Array der Größe LENGTH für T-
Elemente

Wesentlich (und immer wieder benötigt)


Direktzugriff auf das i-te Element; man kann beliebig hin und her springen.

Beispiel:
static int MAXSIZE = ...; // eine Konstante für die Größe, zB 10
int [ ] a = new int [MAXSIZE]; // eine Arraydefinition
for (int i = 0; i < a.length; i++) a[i] = i+1; // Durchlaufen des Arrays mit for-Schleife
for (int i = 0; i < a.length; i++)
a[i] = a[a.length–1–i]; // VORSICHT! Palindrom!
§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Arrays
in Java
Arrays sind vom Referenztyp.

Konsequenzen
call by reference beim Methodenaufruf;
der Verweis auf das Array wird übergeben.
void sort(int [ ] a) { ....} ;

die Zuweisung a = b
kopiert „nur“ die Referenz, nicht die Werte
int [ ] original = {3,10,-1,0, 5};
int [ ] kopie = original;
original[0] = 1000;
System.out.println(kopie[0]); // druckt 1000,
denn kopie zeigt auf original

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Array Operationen
Direktzugriff auf die Array Elemente
über den Index mit 0 ≤ index < arrayName.length
oft in Kombination mit einer for-Schleife

Dabei sind beliebige Sprünge erlaubt, i=0,17,4,12,....


Es muss nicht strikt sequentiell sein wie beim üblichen for (int i=0;...; i++)

Bestimmung der Größe


arrayName.length (ohne Klammern; length ist ein Array Attribut)
length gibt die statische Größe an.
oft sind nur size viele Elemente tatsächlich gespeichert mit size ≤ length.
Bei size < length wird (viel) Speicherplatz verschwendet.

Kreieren von Arrays


mit dem new Operator. int [ ] a = new [100];
new legt das Array an und
reserviert den erforderlichen Speicherplatz
der Größe length * (Speicherbedarf pro Element)
Beachte, dass das Anlegen von (viel) Speicher (viel) Zeit kostet.
§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Durchlaufen
eine for Schleife for (int index=0; index<a.length, index++)
vor oder rückwärts
mit Schrittlänge 1 (i++ bzw. i--) oder anders
die for-Schleife ist der Standard bei Zugriffen auf fortlaufende Elemente.

eine while Schleife


eher ungewöhnlich, da das mächtige while meist gar nicht nötig ist.

Fehlerquellen: an den Indexgrenzen (man verrechnet sich bei 0 bzw. a.length-1

iterieren mit der for_each Schleife


mit dem Anlegen eines Iterators für ein Array a for (int index : a)
aber
Array Iteratoren unterliegen Einschränkungen
u.a. wird der Laufindex verdeckt.
das Geheimnisprinzip im Software Engineering

for_each ist sehr bequem als Pseudocode oder High-Level


Man unterstellt, dass der Iterator
richtig implementiert ist und jedes Element genau einmal erwischt.
§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Kopieren
Kopieren von Arrays
Vorsicht! Was wird kopiert? Die Referenz oder die Inhalte

a) Anlegen von zwei Arrays und elementweises kopieren


int [ ] original = new {1,2,3,4,5};
int [ ] kopie = new int [original.length];
for (int i = 0, i < original.length, i++) kopie[i] = original[i];
(bei Referenztypen hier „echte“ Kopie mit new)

b) mit der vordefinierten Methode System.arraycopy


System.arraycopy(original, from, kopie, to, count);
kopiert count viele Elemente vom Originalarray ab dem Index from
in das Zielarray Kopie ab dem Index to (über ein Hilfsarray temp)
Bei Referenztypen als Elemente werden nur die Referenzen kopiert.
z.B. System.arraycopy(original, 0, original, 2, 3);
ergibt beim obigen Array [1,2,1,2,3]

c) mit der Methode clone aus der Basisklasse Object


int [ ] kopie = original.clone(); (Cast (auf int []) bei clone unnötig.)
§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Vergleich von Arrays
Shallow, auf den Referenzen
a== b ist nur der Vergleich der Referenzen, nicht der Inhalte!
z.B. true nach der Zuweisung a = b;
false für original == kopie (nach a oder c der vorhergehende Seite)
obwohl beide Arrays wertmäßig identisch sind
a.equals(b) mit der von Object ererbten equals Methode
testet auch nur die Referenzen wie ==

Deep, auf allen Werten


mit einer selbst programmierten Methode mit dem Rumpf
boolean eq = false;
if (a.length == b.length) { // Vergleich der Längen
eq = true;
for (int i = 0; i < a.length; i++){
if (a[i] != b[i]) eq= false; break; für primitive Typen wie int, double, char
} für Objekte !(a[i].equals(b[i]);
System.out.println(eq? ”gleich” : ”ungleich”);

mit der vordefinierten Methode Arrays.equals(a,b)


aus der Java Arrays Klasse.
§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
komplexere Arrays
ein zweidimensionals Array ist in Java:
ein Array von Arrays
was Zeilen und was Spalten sind legt der Benutzer fest.
Konvention: Zeilen von Spalten a[i][j] entspricht aij
Beispiel
int[ ] [ ] myMatrix = new int [10] [20];
alternativ int [ ] [ ] myMatrix = new int [10] [ ];
for (int i = 0; i < myMatrix.length; i++){
myMatrix[i] = new int [20]; // auch unterschiedliche Längen sind erlaubt
}
allgemein: eine n×m Matrix
eine Tabelle mit m gleichlangen Zeilen (Spalten) mit n•m Elementen
(es gibt auch „schräge“ Matrizen mit Zeilen (Spalten) unterschiedlicher Länge)

Anwendungen
• für lineare Abbildungen vom Rm in den Rn.
• in der Robotik zur Beschreibung von Bewegungen
• als Adjazenzmatrix für Graphen (siehe §4)

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Syntax
Deklarationen von Arrays haben die Form
<Typ> [ ] <Arrayname> ;
Dadurch werden der Name und der Typ der Elemente festgelegt.
Nicht festgelegt ist so die Größe (oder die Werte der Elemente).

Sprechweise: int [ ] a liest man als int array a


(andere Schreibweisen sind möglich, z.B. int a [ ])

Erzeugung von Arrays


durch ihre Instanziierung mit new
und die Festlegung der Anzahl der Felder, a.length (oder Grösse)
Beispiel: int [ ] a = new int [1000];
eBNF: <Typ> [ ] <Arrayname> = new <Typ> [ <Groesse> ] ;
<Arrayname> = new <Typ> [<Groesse>] ; (Typ wie oben)
durch die Initialisierung per Aufzählung
int [ ] einszweidrei = {1, 2, 3};
String [ ] umlaute = {”ae”, ”oe”, ”ue”};

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
java.util.Arrays
die vordefinierte Java Klasse für Arrays in java.util
import java.util.Arrays;

void Arrays.fill (<arrayname>, Wert);


void Arrays.fill (<arrayname>, from, to, Wert);
mit der Wirkung a[i]=x für jedes i (for-Schleife for (int i=from; i< to, i++)...)
für das aktuelle Array a
Nebenbedingung: Typkompatibilität zwischen Wert und Array
void Arrays.sort(a);
sortiert das Array a aufsteigend.
Nebenbedingung: die Elemente sind primitiv oder implementieren
Comparable.
boolean Arrays.equals (a,b);
elementweiser Vergleich der beiden Arrays a und b
boolean Arrays.deepEquals(a,b);
elementweiser Vergleich für mehrdimensionale Arrays a[ ] [ ]
int Arrays. binarySearch(a, x);
sucht das Element x im sortierten Array a und gibt einen Index aus mit a[i]=x
bzw. einen negativen Wert, falls x nicht in a vorkommt.
Die Elemente von a müssen primitiv oder Comparable sein.

und andere, siehe API §2/ *


© 2014 Prof. Dr. Franz J. Brandenburg
Zusammenfassung
das besondere an Arrays
– random access Zugriff in O(1) über den Index, a[i]
Dieser effiziente Zugriff ist der Pluspunkt für Arrays.
– dieselbe Struktur wie der Aufbau der Speichers bei einer RAM
– statisch, mit dem Anlegen erhält das Array seine Länge
• schlecht, wenn das Array nicht „voll“ ist
• ggf. Speicherverschwendung und Speicher anlegen kostet Zeit
– feste, fortlaufende Felder
• gut für die direkte Adressierung
• schlecht beim Einfügen / Löschen (dies erfordert Shifts)
– Referenztyp
• call by reference,
• Zuweisung a=b ist keine Kopie
• Gleichheitstests

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Listen
Definition
Eine Liste besteht aus einer sequentiellen Folge von Elementen desselben Typs
abstrakt: (x1,x2,...,xn)
Jedes Element xi (außer am Rand)
hat einen direkten Nachfolger next(xi) = xi+1
(und ggf. einen direkten Vorgänger prev(xi) = xi-1 )
Wesentlich:
der sequentielle Zugriff auf Elemente, auf das nächste (oder vorhergehende)

in Java
Konstruktion von Listen durch eine Verkettung von Knoten (Basisversion)
public class Node {
public int data; // der Wert der Datenelemente
public Node next; // Verzeigerung der Knoten vorwärts
(public Node prev; // Verzeigerung der Knoten auch rückwärts)
}
public class IntList {
public Node front; // Verweis auf den Listenanfang
(public Node rear; ) // Verweis auf das Listenende; falls nützlich
}

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
wichtige Methoden
Einfügen
für das Datenelement (hier der Einfachheit halber vom Typ int)
erzeugt man mit einem Konstruktur new Node (d)
einen Knoten mit dem Datenelement
und hängt den Knoten an der gewünschten Stelle in die Liste ein.
Dazu gibt es viele Vairanten (am Anfang, am Ende, ...)

Löschen
der Knoten mit dem Datenelement wird entfernt
aber die Liste darf dadurch nicht kaputt sein (zB Zerstören der Verkettung)

Get Methoden für den Zugriff auf Datenelemente


Set Methoden für das Ändern (Update) von Datenelementen

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Basis Operationen
Was kann man mit einer Liste/Folge (x1,...,xn)
und einem neuen Element x sinnvoll machen?

Konstruktoren (für Listen von einem beliebigen (generischen) Typ T)


List<T> () die leere Liste der Länge/Größe 0 (das neutrale Element für Listen)
List<T>(T x) aus dem Element x mache die Liste (x) der Länge/Größe 1.

Zugriff auf Elemente


am Anfang, am Ende,
von xi unterm Cursor: auf das nächste xi+1, auf das vorhergehende xi-1
auf das i-te Element (i beliebig, zufällig), d.h. über den Index
über den Wert (das erste / letzte / alle Vorkommen eines Wertes x)

Einfügen und Löschen: dynamisches Veränderungen der Liste


am Anfang
am Ende
hinter dem aktuellen Element unterm Cursor
vor dem aktuellen Element unterm Cursor
über den Wert: lösche das erste (letzte, alle) Vorkommen von x in der Liste
über den Index: vom j-ten bis zum k-ten Element

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Listenoperationen
weitere Operationen:
systematischer Durchlauf durch die Liste mit einem Iterator
Teilfolge von j bis k
Konkatenation (Aneinanderhängen) von zwei Listen
Transferfunktionen in Arrays
Überscheiben/Ändern des Elements an der Stelle i
Transfer in Strings, toString()
Abfragen
auf leer, isEmpty()
der Größe, size()
Test auf Gleichheit zwischen zwei Listen x.equals(Object o)
Sortieren (wenn das bei den Elementen möglich ist)
remove_duplicates() aus der Folge (...) wird eine Menge {...} (Set in Java)
.....

im Detail:
das Java Collection Framework
insbesondere die API für LinkedList, ArrayList
Ähnliche Operationen verwendet man für Strings

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Bewertung
Wie teuer ist eine Operation auf einer (sehr langen) Liste der Länge n?

.... das hängt von vorliegenden Fall ab!

• auf welches Listenelement habe ich gerade den direkten Zugriff?


– oft nur auf das erste (und das letzte)
– oder auf das beim zeitlich letzten Zugriff

• Wie kann ich in der Liste navigieren?


– nur nach „rechts“ mit next oder auch nach „links“ mit prev
also einfach oder doppelt verkettet (oder sogar zyklisch)

• Wenn ich mich erst durch die (möglicherweise gesamte) Liste


durchhangeln muss, dann wird‘s teuer, nämlich O(n) Zugriffe

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Arten von Listen
Verkettung
einfach verkettet, mit next
doppelt verkettet, mit next und prev
zyklisch verkettet (meist mit doppelter Verkettung und cursor)

Verankerung
front (head) der Zeiger auf den Anfang der Liste, auf das erste Element
rear (tail) der Zeiger auf das Ende der Liste, auf das letzte Element
cursor ein Zeiger auf das aktuelle, zuletzt bearbeitete Element

Einfügen und Löschen


allgemein, an beliebiger Stelle
nur an den Enden

ListenAttribut(e)
int size die aktuelle Größe der Liste (Anzahl der Elemente)

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
einfache Verkettungen
einfach verkettete Listen mit (aktuell oder maximal) n Elementen
sind vollständig (oder universell)
d.h. alles was man mit Listen machen kann,
kann man (schon) mit einfach verketteten Listen machen
d.h. man kann jede sonstige Operation auf Listen simulieren,
man muss dazu die andere Operation implementieren.

aber: manche Operationen sind zu aufwendig, in O(n)


und erfordern ein Durchlaufen der Liste vom Anfang an.
z.B. der Zugriff auf das vorherige Element
z.B. Einfügen (Löschen) vor dem aktuellen Element
z.B. Berechnung der Größe (for Scheife oder Iterator)

gute Spezialfälle: Realisierung mit einfach verketteten Listen


stack ein Keller, das LIFO (last in, first out) Prinzip
queue eine Schlange, ein Puffer, das FIFO (first in , first out) Prinzip

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
doppelte Verkettungen
doppelt verkettete Listen mit (aktuell oder maximal) n Elementen
sind vollständig (oder universell)
weil man einfach verkettete Listen mit O(1) je Operation simulieren
kann
aber: das ”O“ hat nicht den Wert ”1“
man muss den zweiten Zeiger mitschleifen und updaten.
Die effektiven Kosten sind u.U. doppelt so hoch (z.B. Stack,
Queue)

Wann verwendet man doppelt verkettete Listen?


Wann lohnt sich der pred-Zeiger auf das Element „links“?
bei Einfügen und/oder Löschen vor dem aktuellen Element

gute Spezialfälle: Realisierung mit doppelt verketteten Listen


deque (double queue)
„Alles hat ein Ende nur die Wurst hat zwei“

§2/ *
zyklisch verkettete Listen
© 2014 Prof. Dr. Franz J. Brandenburg

meist doppelt verkettet


sind vollständig (oder universell)
.. weil man einfach verkettete Listen simulieren kann.

aber:
Gefahr von ∞-Schleifen beim Durchlaufen
while (n.next != null) { .....} für den aktuellen Knoten n
oft
ein blinder Knoten als Trennung und Anker für den Cursor.

Beispiel
das Josephus Problem (auch hot potato oder Reise nach Jerusalem)
zyklisch scheidet nach jeder Runde der k-nächste aus
(k fest oder mit int k = (int) random()*scale)

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Gegenüberstellung
Arrays Bewertung
random access Zugriff
Direktzugriff in O(1) je Element über den Index +
in der Form a[i] oder a.get(i)
statische Größe, nachfolgend n –
festgelegt bei der Instanziierung mit new
Einfügen und Löschen –
erfordert (außer am rechten Ende) extra Shifts in O(n)

Listen
dynamisch veränderbare Größe (add, remove) +
Einfügen und Löschen (bei gegebener Stelle) in O(1) +
z.B. an den beiden Enden oder nach dem Cursor
rein sequentieller Zugriff über die Verkettung –
die Suche eines Elements dauert lange,
weil man durch die Liste iterieren muss
es gibt viele Varianten (Verkettung und Verankerung s.u.) –

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Bewertung
Wer ist der Sieger? keiner
Man braucht Arrays und Listen

Entscheidungskriterien
Welche Operationen werden vorwiegend gebraucht?
Daraus ergeben sich die Gesamtkosten (Laufzeit)
Die wichtigen/häufigsten Operationen in O(1)!

=> die Entscheidung fällt zwischen


oft Direktzugriff auf das i-te Element, dann Array und ArrayList
nur sequentieller Durchlauf
und viele dynamischen Veränderungen (Einfügen und Löschen),
dann (verkettete) Liste und LinkedList

der Kompromiss (die Kombination der guten Eigenschaften mit einer optimierten Implementation)
Java API
import java.util.Iterator
import java.util.ArrayList; ein dynamisch veränderbares Array

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Generische Typen
Motivation
bei Listen und Mengen ist der Typ der verwalteten Datenelemente oft egal
Die Operationen sind immer dieselben, insbesondere stack, queue.
es gibt IntListen, DoubleListen, StringListen, uvam.

ein Software technisches Prinzip:


die Wiederverwendung von Software
derselbe Code für verschiedene Datentypen.

Definition
eine generische Klasse (generics) ist eine mit dem Typ T parametrisierte Klasse.
Schreibweise mit spitzen Klammern: class Name<T>

Behandlung
wie andere Typen (class) auch

Analogie
ein Stack: man stapelt Kisten vom generischen Typ T
Was in den Kisten ist, ist für das Stapeln egal.

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Beispiel

class Node<T> { // Knoten vom Typ T


kurz ein T-Knoten
private T info; // der Inhalt ist vom Typ T private
Node<T> next;

Node (T x, Node<T> p){


info = x; next = p; } // ein Konstruktor

T getInfo(){
return info; } // Zugriffmethoden
Node<T> getNext () {
return next;}

void setInfo(T x) {info = x;} // Änderungen


void setNext(Node<T> p) {next =p;}
}

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Generische Typen
Anwendung von class AllTypes<T>
der Typvariablen T wird ein konkreter Typ übergeben.
Aber: keine einfachen Typen wie int, long, float, double, char, boolean
dafür: Wrapper oder Hüllenklassen
Integer, Long, Float, Double, Character, Boolean

Beispiel
Node<Integer> n1 = new Node<Integer>(new Integer(1), null);
Node<String> s1 = new Node<String>(”abrakadabra“, null);

Beachte:
kein Mix von verschiedenen Typen;
Node<Integer> und Node<String> sind total verschieden und
inkompatibel
z.B. Node<String> s2 = new Node<Integer> (17, null); // Fehler

Mehr dazu
Programmierung 2
fortgeschrittene Vorlesungen/Bücher über Java

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Iteratoren
Anwendung
das systematische Durchlaufen (einer Collection oder einer Liste)
Kurzform: for_each(x in X)

Definition
Iteratoren sind Verallgemeinerungen von Indizes (1. 2. 3....)
Der interne Aufbau bleibt verborgen (Geheimnisprinzip).
Der Iterator durchläuft die Collection/Liste von vorne bis hinten
wie eine for-Schleife (for (int i=0; i<a.length;i++))
Iteratoren sind generisch; der jeweilige Typ wird mit <T> nachgestellt.

Der Iterator steht jeweils zwischen seinem letzten und dem nächsten Element.

••

Start Ende

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Iteratoren
Iteratoren implementieren das java Interface Iterator.
Sei list ein geeignetes Objekt, allgemein Collection
spezieller z.B. LinkedList<T>, ArrayList<T> vom generischen Typ T

Iterator<T> iter = list.iterator(); Konstruktion des Iterators iter auf list


boolean hasNext(); Gibt es noch ein weiteres Element
T next(); Fortschaltung auf das nächste Element
T remove(); Löschen des zuletzt überschrittenen Elements
egal ob in der Vor- oder Rückwärtsbewegung
Nicht möglich am Start vor next
nach remove() erst ≥1mal next()
also nicht remove(); remove();
wirft evtl. eine unsupportedOperationException
Listen-Iteratoren (über Listen wie ArrayList, LinkedList) haben zusätzlich
boolean hasPrevious() Gibt es noch ein vorangehendes Element
T previous() Zugriff auf das vorhergehende Element
int previousIndex() der Index des vorhergehenden Elements (ab 0)
int nextIndex() der Index des nachfolgenden Elements

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Abstrakte Datentypen
Definition
ein abstrakter Datentyp, ADT
oder Rechenstruktur oder class oder partielle heterogene Algebra
besteht aus
(Träger- oder Daten) Mengen Ms, Mengen sind getypt
Operationen über diesen Mengen fr, partielle Funktionen
Axiomen Ax, die Eigenschaften der Operationen beschreiben.

Programme
zur Bearbeitung des ADT
verwenden Konstante und Variable für Werte der Mengen
und die üblichen Sprachelemente wie
Zuweisungen (x=x+y), if, Schleifen (for, while), Rekursion

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Beispiele für ADTs
• die boolesche Algebra
Trägermenge: {false, true}
Operationen not, and, or, xor und andere (!, &,|, ^)
Axiome: die Gesetze der booleschen Algebra
z.B. !x ≠ x, x&y = y&x, deMorgan, distributiv,...

• die natürlichen Zahlen


Trägermengen: N = {0,1,2,....} und {false, true}
Operationen +, -, *, /, % (modulo), und ==, !=, <, >, <=,
>=
Axiome: die bekannten Rechenregeln
z.B. x+0=x, x-0=x
x+y = x+(y-1) +1 für y > 0
x-y = x-(y-1)-1 für y > 0 und x ≥ y
x < y => x+z < y+z
Programme: z.B. isprim(p) Primzahltest)
gcd(x,y) größter gemeinsamer Teiler
§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Beispiele für ADTs
• die ganzen (rationalen, reellen) Zahlen
das Übliche

int in Java
Trägermenge: {–231,...+231-1}∪{underflow, overflow} und boolean
Operationen die Standardoperationen mit Über- bzw. Unterlauf
Axiome: die Rechenregeln auf beschränkten Zahlbereichen

BigInteger aus der Java API, import java.lang.BigInteger


die ganzen Zahlen mit beliebig vielen Stellen
Trägermenge: Z (oder eine Teilmenge nach Speichergröße ihres PCs)
Operationen: die Methoden der Java API
BigInteger(String s) konstuiert Biginteger Objekt aus dem String s.
BigInteger x = new Biginteger(”123456789012345”)
BigInteger add(BigInteger x) entsprechend subtract, multiply, divide, mod
boolean isProbablyPrime(int p)
ist Biginteger mit Wahrscheinlichkeit > (1-0.5p) prim ?
z.B. myBig.isprobablyPrime(6) ... mit einem Irrtum < 10-6.

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Beispiele für ADTs
• Strings
Trägermenge: ∑* und int und boolean
ein Referenztyp in Java
Operationen: die klassischen String Operationen nach Java API
z.B. String emptystring = new String( ); erzeugt das leere Wort
int laenge = s.length(); liefert die Anzahl der Zeichen
außerdem
String concat ( String s) die Konkatentation
s+t die Konkatentation
String substring ( int a, int b) das Teilwort von a bis b-1 der Länge b-a
boolean equals ( Object o) true, falls o ein String und identisch
int compareTo ( String s) 0 falls Gleichheit,
negativ, falls s lexikographisch größer
positiv, falls s lexikographisch kleiner

Axiome: z.B. Assoziativität der Konkatentation


z.B. Konkatenation und Länge (s+t).length() = s.length()+t.length()

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Implementierung
Definition
eine Implementation J eines ADT D über einem ADT D‘
beschreibt, wie man Probleme über D in D‘ löst.

Probleme werden durch Algorithmen gelöst,


die durch Programme beschrieben werden.

konkret: Was ist zu leisten?


Wie werden die (Daten)-Elemente von D in D‘ beschrieben?
Wie speichert man die Elemente von D in D‘?
Formal: Umsetzung der Konstruktoren für D in solche von D‘
Wie werden die Operationen von D durch Programme
mit den Operationen von D‘ beschieben?
Was kostet jede Operation von D in D‘?
in D: je 1
in D’: abhängig von D und D’ (meist O(1) bis O(n))

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Dominanz

Definition
ein ADT D‘ dominiert einen ADT D, kurz D ≤ D‘
wenn jede Operation von D in O(1) in D‘ implementiert wird
werden kann.

Beispiele
– D einfach verkettete Liste, D‘ doppelt verkettet,
aber bei jedem Einfügen oder Löschen muss man auch den
Vorgängerzeiger pred updaten
Wir müssen den zweiten Zeiger „mitschleifen“.
– D Liste ohne size Attribut, D‘ mit size Attribut.
Das size Attribut ist nützlich z.B. für size() und isEmpty(),
aber size muss bei jedem add und remove geändert werden.
Wir müssen size „mitschleifen“.
– D = Stack und D‘ array (oder einfach verkettete Liste)
- D = Queue und D‘ array oder einfach verkettete Liste

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Nicht-Dominanz
... ist der Normalfall
Jeder Datentyp, jede Datenstruktur hat Stärken und Schwächen.
Stärke: eine gut unterstützte Operation, oft O(1)
Schwäche: eine nicht unterstützte Operation, meist O(n)

Beispiele
Arrays sind schlecht für Einfügen und Löschen (außer am rechten Ende)
Einfügen (oder Löschen) auch ganz vorne kostet O(n),
sei size die Anzahl der „echten“ Elemente in a,
denn es müssen alle anderen Elemente im Array geshiftet werden.
for (int i = size-1; i >= 0; i--) a[i+1] = a[i]; size ++; //Rechtsshift um ein Feld
for (int i = 1; i< size; i++) a[i-1] = a[i]; size--; // Linksshift um ein Feld

Arrays sind gut für den Zugriffe (Suchen, Ändern des Wertes) auf das i-te Element

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Nicht-Dominanz
Beispiele
einfach verkettete Listen sind schlecht für das
Einfügen und Löschen vor dem aktuellen Element
Das aktuelle Element ist das Listenelement xi unterm Cursor
denn man muss wieder von vorne bis zu i-1 laufen.
Das kostet O(i-1) und das ist worst case O(n)

Listen (allgemein) sind schlecht für den wahlfreien (random access) Zugriff
auf das i-te Element.
Es dauert (im Mittel) O(n),
bis man mit einem Cursor an der richtigen Stelle ist, denn
bei n Elementen laufe bis zur Stelle i, 1≤i≤n
im Mittel (Durchschnitt)

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Spezialfälle
Es gibt Spezialfälle von listenähnlichen ADTs,
bei denen das dynamische Verändern durch Einfügen und Löschen
nur an den Rändern vorgenommen wird.

Diese treten (glücklicherweise) sehr oft auf.

Dann gibt es effiziente Implementationen mit Arrays und Listen

nämlich
Stack, Keller LIFO last in first out (als Regenbogen)
Queue, Puffer FIFO first in first out (mit Zylinder-Darstellung)
Deque „alles hat ein Ende nur die Wurst hat zwei“
Doppelstack zwei Stacks (im Array mit „Luft“ in der Mitte)

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Stack
Definition
Ein Stack (pushdown stack) oder Keller (oder Stapel)
ist ein abstrakter Datentyp für beliebige Elemente
Elemente sind vom Typ Object, oder speziell oder generisch <T>
mit den Operation: push(T e); // Einfügen des Elements e vom Typ T
und pop(); // Löschen des obersten Elements
und zusätzlich Stack<T>(), isEmpty(), top()

push legt das Element e „oben“ auf den Stack


pop nimmt (und löscht) das oberste Element vom Stack, falls !s.isempty();
pop ist zerstörendes Lesen. Das Element ist „weg“.

Axiom: s.push(e); s.pop(); ergibt den Originalstack.


formal gilt die Gleichung: s = s.push(e); s.pop();
d.h. pop ist das Inverse zu push (aber nicht umgekehrt)

Ein Stack operiert nach dem LIFO Prinzip, last in, first out.

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Stacks
Entdecker
für die Informatik (Anwendungen z.B. im Compilerbau)
hat Prof. F.L. Bauer (Emeritus, TU München) um 1962
den Stack und seine Bedeutung und besonderer Rolle entdeckt.

Beispiele:
Folge der Aktionen beim Stapeln von Tellern
korrekte Klammerungen ((([[ () ] ([]) ] )) ())
Traversieren (Durchlaufen) von Bäumen
Auswertung von Ausdrücken, postfix Notation
Rekursion (der Aufrufkeller)
Rangieren von Waggons („Keller-Permutationen“)

nicht: in der Mensa: „Wer zuletzt kommt, wird als Erster bedient.“

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Visualisierung
Ein Stack ist ein Regenbogen.

Für jede Aktion (Einfügen von x) und Löschen von y zum Zeitpunkt t
gibt es einen Knoten oder Punkt auf der X-Achse;
vereinfacht: die Zeitpunkte 1,2,…,2n
für das Einfügen/Löschen von n Elementen
Verbinde die Zeitpunkte von Einfügen und Löschen durch einen Bogen,
der oberhalb der X-Achse verläuft.

Die Aktionen sind LEGAL für einen Stack ⬄ es gibt einen Regenbogen.

§2/ *
Implementierung von Stacks
© 2014 Prof. Dr. Franz J. Brandenburg

– im Array
mit einem Kellerpegel p
gefüllt von a[0] bis a[p-1]
p zeigt auf das erste freie Arrayfeld
isempty == (p=0)
einfügen bei a[p]; p++;
löschen durch p--
ohne Überschreiben von a[p] Gefahr!!
mit vorherigem Überschreiben von a[p]

Shifts
keine
Überlauf
nur wenn das Array total voll ist

Kosten je Operation
je O(1) (genauer: 2 Aktionen)

§2/ *
Implementierung von Stacks
© 2014 Prof. Dr. Franz J. Brandenburg

– als Liste
es reicht eine einfach verkettete Liste (und diese generisch <T>)
denn Einfügen und Löschen erfolgt am Kopf der Liste
mit O(1) (genauer: 2) je Operation

public class Node // ich behalte die Kontrolle, alle Zugriffe


{
int data;
Node next;
Node(int dataValue, Node toNode) // Konstruktor für Einzelknoten und
Zeiger
{ data = datavalue; next = toNode;
}
}

public class IntStack // public, damit von außen nutzbar


{
public Node head; // der Zugriff auf die Knoten
boolean isEmpty ()
{ return head == null};
void push(int data)
{ head = new Node(data, head); }
int pop()
{ int d = head.data; Node t = head.next; head = t; return d;}
} §2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Diskussion
weitere Methoden für Keller (stacks)
boolean isEmpty(),
void push(T x) (Einfügen),
T pop() (Löschen des obersten Elements)
T peek() (Zugriff auf das oberste Element)

Kosten je Operation:
je O(1), genauer 2 Aktionen bei „guter“ Implementierung

Hinweis:
doppelt verkettete Liste ist „Overhead und Verschwendung von Ressourcen“

weitere Ergänzungen
size ist ok

aber
boolean contains(T x) Ist x irgendwo im Keller
ist illegal (für Stack)
denn das Kellerprinzip, LIFO und zerstörendes Lesen ist verletzt.

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Java Stack
das Interface java.util.Stack<T>
erbt alle Methoden von der Java Klasse Vector.
(Vector ist sehr ähnlich zu ArrayList)
aber Vector enthält viele Methoden, die das Kellerprinzip verletzen.

Methoden
Stack<T>() Konstruktor für den leeren <T>-Keller
boolean empty()
T push (T item) lege item oben auf den Keller
T pop() das oberste Element; dieses wir gelöscht
EmptyStackException bei einem Fehler
T peek() Zugriff auf das oberste Element, ohne Löschen

int search(Object o) illegal als Kelleroperation


Index des ersten Vorkommens von o

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Beispiel Stack
import java.util.Stack;
....
Stack<String> stringStack = new Stack<String>; // ein generischer Typ, hier Strings
stringStack.push(”erstens”);
stringStack.push(”zweitens”);
stringStack.push(”drittens”);
boolean b = stringStack.isEmpty(); // liefert false

String s;
s = stringStack.pop(); // liefert drittens
s = stringStack.pop(); // liefert zweitens
s = stringStack.peek(); // liefert erstens
s = stringStack.peek(); // liefert erstens
s = stringStack.pop(); // liefert erstens

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Queue
Definition
Eine Queue (Puffer oder Schlange (Achtung ≠ priority queue))
ist ein abstrakter Datentyp für beliebige Objekte vom Typ T
mit den Operation: offer(T e); ein Objekt hinten anfügen, append
poll(); das erste Element löschen, pop
(und zusätzlich create(), isempty(); T peek() Zugriff auf das erste Element)

Eine Queue arbeitet nach dem FIFO Prinzip, first in, first out.

Axiome
Einfügen und dann Löschen erzeugt i.a. keinen Gleichzustand.
Löschen eines Elements x und dann Wieder-Einfügen von x
ergibt fast immer eine andere Queue (geänderte Reihenfolge)

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Puffer
Entdecker:
unbekannt

Beispiele:
Engländer an der Bushaltestelle
first come, first served
Stau auf einer einspurigen Strasse:
auf den Stau auflaufen (einfügen) und rausfahren (löschen)
überholen ist bei einer Schlange (Puffer) verboten
Puffern von Jobs (Scheduling)
Breitensuche in Graphen (später)
Rangieren von Waggons („Puffer-Permutationen“)

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Visualisierung
Queue = Maibaum
man wickelt die Aktionen um einen Zylinder.

Für jede Aktion (Einfügen von x) oder Löschen von y zum Zeitpunkt t
gibt es einen Knoten oder Punkt auf der X-Achse
Diese liegt vorne auf einem rollenden Zylinder.
Alternativ: man schneidet diese Achse auf und erhält
ein oberes und ein unteres Level.
Verbinde die Zeitpunkte von Einfügen und Löschen durch eine Kurve,
die um den Zylinder (Maibaum) läuft
oder nach dem Aufschneiden gerade zwischen den beiden Leveln.
Es gilt: Queue ⬄ legale kreuzungsfreie Zeichnung

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Queue im Array
im Array
betrachte ein Array als Ringpuffer der an den Enden zusammengeklebt ist.
Dies erreicht man durch Rechnen modulo n mit n=a.length.

Der Inhalt der Schlange (des Puffers) steht zwischen zwei Cursorn
front zeigt auf das „letzte“ (zuletzt eingefügte) Element, dort lesen.
rear zeigt auf das „erste“ freie Feld im Array, dort schreiben.

isempty == ((rear - front+n)%n==1); front ist eins voraus


voll == (rear == front); front hat rear eingeholt
Einfügen bei a[rear]; rear ++ %n; nur wenn nicht voll
Löschen bei a[front]; front ++ %n; nur wenn nicht leer
size = (rear - front - 1+n)%n; die Anzahl der Elemente

Shifts: keine

Kosten je Operation
je O(1) (genauer: 2 Aktionen)

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Queue als Liste
• es reicht eine einfach verkettete Liste
mit 2 Cursorn für front und rear
Die Schlange wandert von links nach rechts
rechts einfügen, bei front
links löschen, bei rear

public class Node<T> public class Queue<T>


{ {
public T data; private Node front;
public Node next; private Node rear;

public Node(T dataValue) public Queue()


{ {
data = dataValue; next = null; front = rear = null;
} }
} }

Anmerkung:
hier sind alle Datenobjekte public und damit von außen zugreifbar.
§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Java Queue
das Interface java.util.Queue<T>
erbt alle Methoden von de Java Interface Collection
und erweitert Collection<T>

Methoden
Queue() Konstruktor für den leeren Keller
boolean empty()
boolean offer (T item) Einfügen (falls ok, sonst false)
T poll() Rückgabe und Löschen des ersten Elements
T peek() Rückgabe des ersten Elements (oder null)

außerdem
die ererbten Methoden von Collection (nicht FIFO Prinzip)
wie void add(T item); T remove(); T element(); int size();

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Permutationen
betrachte:
Waggons mit den Nummern 1, 2, 3,.... und rangieren
mit einem Stack (Prellbock)
mit einem Ausweichgleis (queue).
Welche Permutationen sind möglich?

stac queue
k
(1,2,3)->(2,3,1) geht nicht LIFO (1,2,3)->(3,2,1) geht nicht FIFO

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Deque
eine Deque (double ended queue)
ist ein ADT mit Einfügen und Löschen an beiden Enden
insertLeft(T x); insertRight(T x);
T removeLeft(); T removeRight();
T getfromleft(); T getfromright();

Implementierung
doppelt verkettete Liste

Kosten:
O(1) je Operation

Stack ist Spezialfall mit insertLeft(..) für push und removeLeft() für pop();
Queue ist Spezialfall mit insertLeft(..) für offer und removeRight() für poll();

Anwendungen:
keine (außer bei Klausuren)

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Visualisierung
SATZ: DEQUE ⬄ (aufgeschnittener) Zylinder

Eine DEQUE ist eigentlich ein „diebischer Doppelstack“.

Es gibt je einen Stack auf jeder Seite und ab und zu klaut ein Stack
dem anderen Elemente. Diese Elemente verhalten sich wie eine Queue.

Diese Sicht auf Deques gibt weltweit nur in Passau!

Hintergründe:
C. Auer et al. Plane Drawings of Queue and Deque Graphs,
Proceedings Graph Drawing 2010, Springer LNCS 6502, 68-79, 2011,
§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Doppelstack
gegeben: 2 Stacks leftStack, rightStack
Implementierung
in 1 Array mit 2 Kellerpegeln
Operationen: je O(1)
Overflow: Array voll, wenn sich die Kellerpegel kreuzen

Simulation: 1 queue durch 2 Stacks, leftStack, rightStack


Implementierung
geschrieben (einfügen) wird immer auf leftStack
gelesen (löschen) auf rightStack, wenn rightStack nicht leer ist.
Wenn gelesen werden soll, aber rightStack.isempty(),
dann wird leftStack in rightStack umkopiert (falls leftStack nicht
leer).
Kosten:
im Durchschnitt O(1)
denn jedes Element wird je einmal eingefügt, umkopiert, gelöscht.

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Java API
Listenartige Strukturen
sind so wichtig und treten so häufig auf
dass sie in der API vordefiniert und optimiert implementiert sind.

Es gibt mit import java.util.*


LinkedList doppelt verkettete Listen
ArrayListArray Implementation von Listen
mit dynamisch veränderbarer Größe
Vector ein dynamisch veränderbares Array
wachsen und schrumpfen wird über
die capacity in capacityIncrement Schritten gesteuert.

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Klassifikation
konkrete Klassen

Listen Sammlung von Elementen eines Typs, auch doppelte Werte


ArrayList realisiert als dynamisches Array
implementiert das Interface List
LinkedList realisiert als doppelt verkettete Liste
gut für sequentielle Zugriffe und viele Veränderungen
Iteratoren: hasNext(), next(), remove()
Listiteratoren: hasprevious(), previous()

Mengen im mathematischen Sinn, jedes Element tritt höchstens einmal auf


HashSet realisiert das Interface Set mit Hashing
TreeSet realisiert Set über Bäume, unterstützt sortieren
Enumeratoren: hasMoreElements(), nextElement()

§2/ *
Dynamisch veränderbare Arrays
© 2014 Prof. Dr. Franz J. Brandenburg

Trick:
wenn das aktuelle Array a voll ist,
wird ein neues, doppelt so großes Array b erzeugt.
Der Inhalt von a wird nach b umkopiert und a=b gesetzt
Auch wenn man dies wiederholt machen muss, sind die Gesamtkosten
für das Neuanlegen und Umkopieren nur 4*N, N= größes Array am Ende

int size sei die Anzahl der aktuell belegten Array Elemente
int capacity sei die statische Größe (die beim Anlegen des Arrays festgelegte length)

public void expandArray () {


if (size == capacity) {
capacity *= 2;
Object [ ] b = new Object [capacity];
for (int i=0; i<size; i++) b[i] = a[i];
a=b
}
}

Aufruf mit myArray.expandArray() für ein Object Array myArray

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Sortieren
Voraussetzung:
man kann Elemente paarweise vergleichen
einfach: ≤
in Java oft: Comparable
in der Praxis: ≤ auf Zahlwerten
≤ die lexikographische Ordnung auf Strings

Algorithmen
insertionSort O(n2) mit Listen, O(n logn) mit Bäumen
selectionSort O(n2)
bubbleSort O(n2)
mergeSort O(n logn)
quickSort O(n logn) im Mittel, O(n2) worst case
heapSort O(n logn)

bucketSort O(N) für einen kleinen diskreten Wertebereich

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Gütekriterien
Wie kann man Sortierverfahren vergleichen?

Primärkriterium: „Zeit ist Geld“


die Komplexität/Laufzeit T(n) bei n Elementen
• worst case die garantierte Güte
• average case die erwartete Güte
• best case / good cases im Spezialfall
• experimentell
Die Laufzeit setzt sich aus der Summe aller ausgeführten Anweisungen zusammen.
Beim Sortieren sind hier wesentlich (weil häufig und ggf. teuer für den Zugriff)
• die Anzahl der Vergleiche
• die Anzahl der Vertauschungen / Swaps (echte Bewegungen von Daten)

Sekundärkriterien: das Wohlverhalten des Algorithmus


• robust keine Vertauschung von gleichen Elementen
• Speicherplatz in situ, kein extra Speicherplatz, kein Hilfsarray
• Zugriffsart Array (random access) oder Liste (sequentiell)
• Vorsortierung wenn nur wenige Elemente aus der Ordnung fallen
dann geht es deutlich schneller, ggf. in O(n)

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
selectionSort
selectionSort Kosten
Idee:
wähle in jedem Schritt das jeweils kleinste Element O(n-i)
(von links nach rechts in der Liste, dann robust)
lösche es und füge es mit add in O(1) O(1)
in die sortierte Liste/Array ein (eine Hilfsliste oder mit swap)
Implementation:
zwei for-Schleifen daher O(n2)

die Invariante für die Korrektkeit


nach i-Durchläufen wurden die i kleinsten Elemente ausgewählt
Komplexität: O(n2), genauer ∑ O(n-i)=O(i)
worst case, average case, best case, experimentell je O(n2)
Güte: + robust, +/- Speicherplatz, + sequentiell, – Vorsortierung

public static void selectionSort(int [ ] a);

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
insertionSort
insertionSort Kosten
Idee:
wähle das nächste Element (Iterator, for_each) a[i]
füge es an der richtigen Stelle in der sortierten Liste ein O(i)
im Array muss man (nach rechts) shiften
in der Liste die richtige Stelle suchen
im Suchbaum geht das in O(log i)
Implementation:
zwei for-Schleifen

Invariante für die Korrektkeit


nach i-Durchläufen sind die bisherigen i Elemente richtig sortiert
Komplexität: O(n2), genauer ∑ O(i)
O(n logn) beim Einfügen in einen balancierten Baum
Güte: + robust, - Speicherplatz, + sequentiell, – Vorsortierung

public static void insertionSort(int [ ] a);

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
bubbleSort
bubbleSort Kosten
Idee
vertausche zwei benachbarte Elemente, wenn sie „falsch“ stehen
große Elemente „blubbern“ nach oben
falls kein swap mehr nötig ist, break.
Implementation:
zwei for-Schleifen

Invariante für die Korrektkeit


nach i-Durchläufen stehen die i größten Elemente oben im Array
und sind intern richtig sortiert
oder das i.-größte Element steht an der Stelle a[n-i]
Komplexität: O(n2), genauer ∑ O(i)
O(n) bei einer vorsortierten Liste
Güte: + robust, + Speicherplatz, + sequentiell, + Vorsortierung

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
bubbleSort
public static void fastBubbleSort(int [ ] a) {
boolean fertig = false;
for (int i = 0; !fertig; ++i) { // vorzeitiger Abbruch
fertig = true;
for (int j=0; j < a.length-i-1; j++) // die letzten i Elemente sind schon ok
{
if (a[j] > a[j+1])
{
swap(a[j], a[j+1]); // int temp = a[j]; a[j]=a[j+1]; a[j+1]=temp;
fertig = false; // es bleibt noch was zu tun
}
}
}
}

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
bubbleSort
Beobachtungen
– pro Durchlauf (der äußeren Schleife) werden meist mehrere
Elemente vertauscht und nach oben befördert.
– ganz schlecht für bubbleSort:
eine falsch herum sortierte Liste, ≥ statt ≤
Dann bewegt jeder Durchlauf nur ein Element.
– Gegenmaßnahme: Shearsort (oder Shakersort)
abwechselnd wird nach oben (for ...; i++) und swap bei > geblubbert
und dann abwärts (for ...; i--) nach unten (swap) gebubbelt.
- fastTermination, wenn in einer Phase keine Vertauschung vorkommt.
- Invariante für die Korrektheit:
das (jeweils) größte Element wandert nach oben
und das jeweils kleinste Element wandert nach unten
nach i auf+ab Runden hat man an Grenzen for(int j=i; j < a.lenth-i, ...)
– für kleine n ist fastbubblesort „schnell“
und eine gute Alternative auch für die rekursive Endphase von quickSort
– bubbleSort (oder Shearsort) ist sehr gut,
wenn nur wenige Elemente nicht am richtigen Platz stehen.

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
mergeSort(1)
mergeSort Kosten
Idee: ein divide&conquer Verfahren
public static void mergeSort(int [ ] a);
if (a.length <= 1) return a
else (a1, a2) = divide(a) für zwei gleichgroße Teile a1 und a2
b1 = mergeSort(a1); b2 = mergeSort(a2);
merge(b1, b2);

private int [ ] merge(int [ ] a, int[ ] b);


int [ ] buffer = new int[a.length+b.length];
int ac = 0; // der cursor in a
int bc= 0;
while (ac < a.length && bc < b.length) // der kleinere Wert „gewinnt“
if (a[ac] <= b[bc]) { buffer[ac+bc]=a[ac]; ac++;}
else { buffer[ac+bc]=b[bc]; bc++;}
if ac < a.length for (int i=ac, i<a.length, i++) buffer[i+bc] = a[i];
else for (int i=bc, i<b.length, i++) buffer[i+ac] = b[i];
return buffer;

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
mergeSort(2)
Kosten
Berechnung der Kosten nach dem Mastertheorem
T(n) = 2•T(n/2) + O(n) ergibt O(n logn)

Beispiel
[-5,3,17,-1, 0,9,13,1]

[-5,17,0,13] [3,-1,9,1]
Zerlegen
[-5,0] [17,13] [3,9] [-1,1]

-5 0 17 13 3 9 -1 1

[-5,0] [13,17] [3,9] [-1,1]


merge und Zusammenbauen
[-5,0,13,17] [-1,1,3,9]

[-5,-1,0,1,3,9,13,17]

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
mergeSort(3)
Korrektheit
per Indukion und Rekursion
n=0 ok
n/2 --> n die rekursiven Aufrufe für a1 und a2 seien korrekt.
Dann konstruiert merge die korrekte Gesamtsortierung.
Beweis für die Korrektheit von merge:
durch Induktion über die Summe der Länge beider Listen

Güte (bei entsprechender Implementerung)


+ robust
- Speicherplatz, die Rekursion verbraucht extra Speicherplatz
+ sequentiell (auf einfach verketteten Listen)
- Vorsortierung: wird völlig ignoriert.

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
quickSort(1)
Idee
public static void quickSort(int [ ] a,int start, int end]

bestimme ein Pivotelement,


z.B. pivot = a[start] oder „median-of-three“ oder ein zufälliges Element
bewege die Elemente ≤ pivot nach vorne und die ≥ pivot nach hinten.
Es ergeben sich neue Abschnittsgrenzen:
für ≤ von start mit right-1 mit dem Pivotelement bei a[right-1]
für > von right bis end
rekursiv sortiere die beiden Abschnitte
quickSort(a, start, right-2);
quickSort(a, right, end);
Danach ist das Gesamtarray im Abschnitt start ... end korrekt sortiert.

Die Bewegung der Elemente relativ zu pivot:


von aussen nach innen mit zwei cursorn left und right
suche von links a[i] > pivot und von rechts a[j] < pivot
swap ( a[i], a[j])
Dann gilt für die Elemente von start bis right-1 a[..] ≤ pivot
und für die Elemente von right bis end a[..] > pivot
§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
quickSort(2)
Komplexität
wenn pivot das Array jeweils halbieren würden, dann
T(n) = 2•T(n/2)+O(n) = O(nlog n) nach dem Mastertheorem
aber
wenn das Pivotelement immer das kleinste oder größte Element des
jeweiligen Abschnitt ist (der ungünstigste fall; worst case)
dann gilt T(n) = T(1) + T(n-1)+O(n)
mit der Lösung T(n) = O(n2)
im Mittel, der Erwartungswert
das Pivotelement ist ein beliebiges Element (aus dem Abschnitt)
und zerlegt den Abschnitt (der Größe n) in i und n-i-1 Elemente.

Dies führt zur Rekursionsgleichung


T(n) = T(n-i-1) + T(i) + O(n) mit T(0) =T(1) = 1
Im Durchschnitt gilt:

und diese Rekursionsgleichung ergibt (nach etwas Rechnerei)


Tav(n) = O(n logn)
§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
quickSort(3)
Güte
– robust; gleiche Elemente werden über das Pivotelement gespiegelt
Beispiel:
mit dem ersten Element als Pivot ergibt sich
[5, 81, 82,83,11,12,13 ]
[13, 12, 11] und [83, 82, 81, 5]
[13, 12, 11] und [5, 82, 81, 83]
[13, 12, 11, 5, 82, 81, 83 ]
+ in situ: man sortiert auf dem Array, ohne extra Speicherplatz
+ meist im Array, wegen der (fortgeschrittenen) Pivotauswahl
median-of three oder ein Element an einer zufälligen Position
– Vorsortierung... geht unter

Bemerkung
experimentell ist quickSort am schnellsten
Variante mit bubbleSort oder if-else Kaskade für kleine Werte n≤5
Dies beschleunigt die Laufzeit, weil die Rekursion angekürzt wird.
Das ist wie Tuning von F1-Autos.

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Vergleiche
die minimale Anzahl an Vergleichen (Quelle D.E. Knuth, The Art of Computer programming, 1968)
n 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
# 0 1 3 5 7 10 13 16 19 22 26 30 34 38 42 46 50

Hardware mit Vergleichsnetzwerken mit „Vergleichern



Input: x, y Output: min max
Experimentell

Zeit T(n) Vergleiche


quickSort 9 n log n 1.4 n log n
mergeSort 12 n log n 1.0 n log n
heapSort 20 n log n 2.0 n log n

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
untere Schranke
auf großen Zahlen (bei ≤) gehts nicht besser als Ω(n logn)

Definition
ein Entscheidungsbaum (oder Vergleichsbaum) ist ein Binärbaum
für die Eingabe x1,x2,...,xn (an der Wurzel)
mit inneren Knoten für die Prädikate „x≤y“
und ausgehenden Kanten gemäß „ja“ oder „nein“
und jede mögliche Lösung (jede Permutation π) xπ(1),xπ(2),...,xπ(n)
mit xπ(i) ≤ xπ(i+1) für i=1,..,n-1
in mindestens einem der Blätter,
so dass (xπ(1),xπ(2),...,xπ(n)) die Prädikate auf den Kanten erfüllt.

Beispiel
ein Entscheidungsbaum für x,y,z

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
untere Schranke
Satz
jedes allgemeine Sortierverfahren benötigt
im Entscheidungsbaummodel, d.h. gestützt auf ≤ -Vergleiche
Ω(n logn) viele Vergleiche und (damit) Ω(n logn) Zeit.
M.a.W. n logn ist untere (und obere) Schranke für Sortieren.
Beweis
O.b.d.A. seien die Elemente paarweise verschieden, xi ≠ xj.
Jedes Blatt des Entscheidungsbaums beschreibt eine Folge
xπ(1) < xπ(2) < ... < xπ(n)
Es gibt n! Permutationen π,
jede steht mindestens einmal an einem Blatt
⇒ #Blätter ≥ n!
⇒ Höhe(Entscheidungsbaum) ≥ log(#Blätter) ≥ log(n!) ≥ Ω(n logn).
Die Höhe des Entscheidungsbaums
ist die maximale Anzahl der Vergleiche auf einem Berechnungspfad
ist somit eine untere Schranke für die Laufzeit.

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
BucketSort
ein anderes Szenario:
gegeben: viele kleine Zahlen, oBdA 0 ≤ xi ≤ N, 1 ≤ i ≤ n
gesucht: eine Sortierung einer bliebigen Liste solcher Zahlen
Anwendung: Briefe sortieren
per Postfach
nachder Postleitzahl - fünfstellig
allgemein: Hashing mit chaining (§3)
Idee: die Zahlen seien k-stellig, z.B. k=5 für Postleitzahlen
k Runden (for int i =1, i<=k,i++)
1. Runde: sortiere die Zahlen nach der 1. Stelle
gleichwertige Zahlen (1. Stelle) kommen in denselben Sack
i- te Runde, i≥2
alle Zahlen in einem Sack werden nach der i-ten Stelle
sortiert
und in Säcke aufgeteilt
Komplexität: k•n (für n Zahlen)
denn k Runden; je Runde wird jede Zahl „angefasst“
gesamt: O(n) wenn k klein ist (wie bei Postleitzahlen)

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Bucketsort
eine Variante
alle zu sortierenden Elemente xi (einfacher Zahlen)
liegen wertmäßig im Intervall [min, max] mit max-min = N

Man stellt N Kisten auf vom min bis max


und geht die Liste der zu sortierenden Elemente (x1,...,xn) sequentiell durch.
xi kommt in die Kiste mit dem Label xi.
Das kostet je O(1), gesamt O(n)
Anschließend sammelt man von min nach max die Elemente in den
jeweiligen Kisten auf.
Das kostet O(N)
Gesamtkosten: O(n+N)

Bemerkung
die untere Schranke Ω(n log n) gilt hier nicht
denn Bucketsort basiert NICHT auf Vergleichen von Zahlen
sondern auf deren absoluten Werten.
Für große Zahlbereiche oder float/double geht Bucketsort nicht.

§2/ *
© 2014 Prof. Dr. Franz J. Brandenburg
Specials
Definition
Die Inversionsdarstellung zum Sortieren von Zahlen (x1,….,xn)
ist ein Vektor der Länge n (s1,….,sn) mit
si = #Zahlen links von xi, die kleiner sind als xi

Die Summe der si ist die Anzahl der Inversionen oder bubbleSort Schritte

Die Kendall-tau Distanz zwischen zwei Permutationen a und b gibt an, wie
oft a[i] < a[j] und b[j] < b[i] gilt.
D.h. Wie oft widersprechen sich a und b.

Das Ranking Problem


gegeben k Permutationen a1,…,ak über {1,…,n}
gesucht: a* mit Σi #Widersprüche(ai, a*) → MIN
Anwendung: der „optimale“ Kompromiß bei Suchmaschinen.

§2/ *
Sortieren bei DNA-Sequenzen
© 2014 Prof. Dr. Franz J. Brandenburg

Eine DNA-Sequenz ist ein String über {A,G,T,C}.


Beim Sequenzieren entstehen Fehler;
die Sequenzen müssen wieder zusammengefügt werden.

Elementare Operationen sind hier:


Inversion: die Spiegelung einer Teilsequenz einer beliebigen Länge
Transposition: das Verschieben einer Teilsequenz an eine neue Stelle

Frage:
Wie viele Inversionen + Transpositionen braucht man zum Sortieren?
Antwort: das ist ein hartes Problem: NP-schwer (siehe §5)

§2/ *

Das könnte Ihnen auch gefallen