Michael Philippsen

Werbung
13. Verkettete Listen
Algorithmen und Datenstrukturen
13. Verkettete Listen
Prof. Dr. Christoph Pflaum
Department Informatik • Martensstraße 3 • 91058 Erlangen
13.1
13.2
13.3
13.4
13.5
13.6
13.7
13.8
13.9
13.10
Generische Listen und Iteratoren
Verkettete Listen
Exkurs: Geschachtelte Klassen in Java
Mengen ohne Sortierung
Mengen mit Sortierung
Sortieren durch Auswählen
Sortieren durch Einfügen
Fächersortierung
Radix-Sortierung
Sortieren durch Mischen („merge sort“)
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-2
Zur Erinnerung: aus Abschnitt „12.3 Liste“
13.1 Generische Listen und Iteratoren
ADT Liste
ƒ In Java ist die Liste Spezialfall einer Objektmenge
beliebiger Größe:
Typ-Parameter
ƒ Signatur Liste(T)
□ create:
□ append: T x Liste
□ head: Liste
□ tail:
Liste
□ length: Liste
ƒ Axiome
□ A1:
□ A2:
□ A3:
□ A4:
Æ Liste
Æ Liste
ÆT
Æ Liste
Æù
Namen im folgenden Code
head(append(x,l)) = x
tail(append(x,l)) = l
length(create) = 0
length(append(x,l))=1+length(l)
add
entry(idx)
remove(idx)
size
head(create) und
tail(create) sind
nicht spezifiziert.
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-3
© Michael Philippsen
java.util.Collection
Eine Collection ist eine
Ansammlung von Objekten,
zunächst noch ohne bestimmte
Struktur.
java.util.List
List spezifiziert genauer: Sammlung
von Objekten mit folgenden
Eigenschaften: „Reihenfolge wichtig“
und „Duplikate möglich“.
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-4
1
13.1 Generische Listen und Iteratoren
13.1 Generische Listen und Iteratoren
Imperative Lösung mit Java
ƒ Die ADT-Modellierung hat eine „Basis-Liste“ modelliert.
ƒ Hier nun Implementierung in Java mit mehr Operationen:
ƒ Java-Lösung: Das interface java.util.List
realisiert die abstrakte Idee einer Liste.
□
□
□
□
□
□
□ Ein Interface implementiert noch keine konkreten Methoden.
□ Es zeigt die Grundidee einer Liste auf.
□ Die konkrete Implementierung erfolgt erst später (Æ z.B als
java.util.LinkedList).
Erstellen einer neuen Liste.
Einfügen eines neuen Elements an beliebiger Stelle der Liste.
Löschen eines Elements an beliebiger Stelle der Liste.
Überprüfen, ob ein Element in einer Liste enthalten ist.
Überprüfen, ob eine Liste leer ist.
Die Länge einer Liste bestimmen.
ƒ Eigenschaften
□ Duplikate sind möglich.
□ Kontrollierte Reihenfolge der Elemente (über Parameter
index).
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-5
13.1 Generische Listen und Iteratoren
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-6
13.1 Generische Listen und Iteratoren
Einfache Java-Schnittstelle für „Liste“
Allgemeine
Object-Menge
Einfache Java-Schnittstelle für „Liste“
// Entfernt das Element an der Stelle index
Object remove(int index);
public interface List extends Collection {
// Element elem an der Stelle index einfuegen
void add(int index, Object elem);
// Element elem an die Liste anhaengen
boolean add(Object elem);
// Alle Elemente aus der Liste entfernen
void clear();
0 = erstes
Listenelement
// Entfernt das erste Vorkommnis des Objekts o
boolean remove(Object o);
true, falls Liste
geändert wurde.
// Prüfen, ob die Liste leer ist
boolean isEmpty();
// Liefert die Anzahl der Elemente der Liste
int size();
// Ueberprueft, ob Objekt o enthalten ist
boolean contains(Object o);
// Liefert das Element an der Stelle index
Object get(int index);
...
...
}
Problem: Typinformation
geht verloren, denn get
liefert in jedem Fall Object.
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-7
© Michael Philippsen
false, wenn o
nicht vorkommt.
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-8
2
13.1 Generische Listen und Iteratoren
13.1 Generische Listen und Iteratoren
„Obst-Beispiel“: einfache „Liste“
ƒ Mithilfe von generischen Klassenparametern können
Klassen spezialisiert werden.
...
List aepfel = new ListImpl();
// Liste von Aepfeln
aepfel.add(new Apfel("Boskoop"));
get liefert Object,
aepfel.add(new Apfel("Granny Smith"));
aepfel.get(0);
Apfel erster = (Apfel)
aepfel.get(0);
Typkonvertierung
notwendig.
List birnen = new ListImpl();
// Liste von Birnen
birnen.add(new Birne("Williams Christ"));
Kein Übersetzungsbirnen.add(new Apfel("Boskoop"));
Birne zweite = (Birne) birnen.get(1);
...
Fehler zur Laufzeit! Programm erwartet
eine Birne, in der Liste steht aber ein Apfel.
java.util.Collection<E>
java.util.List<E>
fehler! Programmierer
muss aufpassen, das
er nur richtige Objekte
in der Liste speichert.
Die Collection ist eine Ansammlung von Objekten, zunächst noch
ohne bestimmte Struktur.
Der Elementtyp ist als später zu konkretisierender Parameter E realisiert.
Man spricht von einem
parametrisierten Typ.
List spezifiziert genauer: Sammlung von
Objekten (vom Typ E) mit folgenden
Eigenschaften: „Reihenfolge wichtig“ und
„Duplikate möglich“.
Ist es möglich Typ-Prüfung zur Laufzeit zu vermeiden?
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-9
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-10
13.1 Generische Listen und Iteratoren
13.1 Generische Listen und Iteratoren
ƒ Als generische Klassenparameter können Klassen und
Interfaces angewendet werden. Es können keine speziellen
Methoden der generischen Klasse aufgerufen werden:
Generische Java-Schnittstelle für „Liste“
class MySpecialClass<E> {
E f;
// o.k., da hier nur Referenz
...
public void do(E h) {
f = h;
// o.k., da hier nur Referenz
MySpecialClass<E> g = new MySpecialClass<E>(); // o.k.,
// E kann als Parameter verwendet werden
public interface List<E> extends Collection<E> {
// Element elem an der Stelle index einfuegen
void add(int index, E elem);
// Element elem an die Liste anhaengen
boolean add(E elem);
// Alle Elemente aus der Liste entfernen
void clear();
E g = new E(); // Konstruktion eines Objektes nicht
// erlaubt, da Laufzeittyp unbekannt
f.Print();
// Aufruf dieser Methode nicht erlaubt,
// da Laufzeittyp unbekannt
f.toString();
// o.k., da Object die Methode garantiert
}
Typ-Parameter
// Ueberprueft, ob Objekt o enthalten ist
boolean contains(Object o);
Als Platzhalter für
Klasse verwendet.
Nur Elemente v.
Typ E anhängbar.
Auch für Objekte,
die nicht vom Typ
E sind nutzbar.
// Liefert das Element an der Stelle index
E get(int index);
...
}
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-11
© Michael Philippsen
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-12
3
13.1 Generische Listen und Iteratoren
„Obst-Beispiel“: einfache „Liste“
13.1 Generische Listen und Iteratoren
Liste nur für Objekte
vom Typ Apfel.
Generische Java-Schnittstelle für „Liste“
// Entfernt das Element an der Stelle index
E remove(int index);
...
List<Apfel> aepfel = new ListImpl<Apfel>();
aepfel.add(new Apfel("Boskoop"));
get liefert Apfel,
aepfel.add(new Apfel("Granny Smith"));
// Entfernt das erste Vorkommnis des Objekts o
boolean remove(Object o);
false, wenn o
Typkonvertierung
nicht notwendig
Apfel erster = aepfel.get(0);
// Prüfen, ob die Liste leer ist
boolean isEmpty();
List<Birne> birnen = new ListImpl<Birne>();
birnen.add(new Birne("Williams Christ"));
birnen.add(new Apfel("Boskoop"));
Übersetzungsfehler!
Birne zweite = (Birne) birnen.get(1);
...
// Liefert die Anzahl der Elemente der Liste
int size();
birnen.add(..) ist
nur für Birne deklariert.
Niemals Typfehler zur
Laufzeit! Übersetzer prüft
Typ schon beim Einfügen.
nicht vorkommt.
// Liefert einen Iterator für die Liste,
// der Elemente vom Typ E liefert.
Iterator<E> iterator();
...
}
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-13
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-14
13.1 Generische Listen und Iteratoren
13.1 Generische Listen und Iteratoren
ƒ Iterator:
Methoden der Klasse java.util.Iterator<E>:
= Möglichkeit zur Navigation durch eine beliebige Datenstruktur.
□ Das Besuchen der Datenstruktur wird unabhängig von der konkreten
Implementierung der Datenstruktur Æ Geheimnisprinzip.
□ Falls eine Datenstruktur gleichzeitig mehrmals durchlaufen (traversiert)
werden soll, muss der Zustand der Traversierung unabhängig von der
Datenstruktur sein.
public interface Iterator<E> {
// Gibt es noch ein nächstes Objekt?
boolean hasNext();
// Liefert das nächste Element (vom Typ E)
E next();
ƒ Ein Iterator ist ein Objekt für genau diesen Zweck.
// entfernt das Objekt, das der letzte Aufruf von next()
// geliefert hat. (Pro next()-Aufruf ist nur ein
// remove()-Aufruf zugelassen.)
// Achtung: unspezifiziertes Verhalten, wenn die zugrunde
// liegende Datenstruktur auf andere Weise modifiziert
// wird
void remove();
□ Es ist eine Art fortschaltbarer Zeiger, der immer das nächste Element
der Datenstruktur liefert:
next()
Element 0
next()
Element 1
next()
Element 2
...
Element n
next()
Iterator für Reihung: implementierungsunabhängiges Fortschreiten
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-15
© Michael Philippsen
}
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-16
4
13.1 Generische Listen und Iteratoren
13.1 Generische Listen und Iteratoren
Anmerkungen zu den folgenden Code-Beispielen:
Beispiel für die Anwendung eines Iterators:
□ Unsere Implementierung orientiert sich weitgehend an den
Klassen aus java.util.*
□ Einige Sonderfälle und selten genutzte Funktionen werden
nicht betrachtet.
□ Ausnahme-Behandlung wird weitgehend ausgeklammert.
ƒ Vorteil:
□ Kenntnis der Java-Klassenbibliothek wird vertieft.
□ Späterer Praxiseinsatz problemlos möglich.
public class Bla {
void do() { ... };
}
die Klasse
LinkedList<E>
wird später erklärt
public class Test {
LinkedList<Bla> list;
...
Bla e;
for (Iterator<Bla> iter = list.iterator();
iter.hasNext(); e = iter.next()){
e.do();
}
}
Kürzere Schreibweise der Schleife:
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-17
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-18
13.2 Verkettete Liste
13.2 Verkettete Liste
Die Implementierung basiert auf dem Prinzip der CodeWiederverwendung und Spezialisierung durch Unterklassen:
Wurzel der gesamten Objekthierarchie.
Verkettete Liste
java.lang.Object
ƒ Veranschaulichung:
header
java.util.Collection<E>
for (Bla e : list) {
e.do();
}
java.util.
AbstractCollection<E>
Index 0
Index 1
Index size-1
Index 2
null
...
element next
element next
element next
element next
element next
Schnittstellen
java.util.List<E>
Eine grobe Listenimplementierung,
bei der die Zugriffsmethoden über
den Iterator implementiert sind.
Listenimplementierung als
verkettete Liste.
java.util.AbstractList<E>
java.util.
AbstractSequentialList<E>
java.util.LinkedList<E>
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-19
© Michael Philippsen
Klassen
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-20
5
13.2 Verkettete Liste
13.2 Verkettete Liste
Die folgende Implementierung orientiert sich weitgehend
an der Klasse java.util.LinkedList<E>:
//
//
//
//
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E> {
Klasse für Einträge (innere Klasse von LinkedList<E>).
Damit ist die generische Klasse E bekannt!
Jeweils mit einem Elementverweis und
einem Verweis auf das nachfolgende Element.
private final class Entry {
E element;
Entry next;
// Kopfelement erzeugen, Größe auf 0
private Entry header = new Entry(null, null);
private int size = 0;
// Konstruktor der Klasse
public LinkedList() {
// kein Typ-Parameter!
header.next = header;
}
O(1)
...
Entry(E elem, Entry next) {
this.element = elem;
this.next = next;
}
Leere Liste:
Ein Wächterelement vereinfacht
die Implementierung der Listenoperationen.
header
}
null
element next
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-21
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-22
13.2 Verkettete Liste
13.2 Verkettete Liste
// Hilfsfkt. zum Einfügen von elem nach Eintrag e
// Hilfsmethode, die den Eintrag an der Stelle
O(1) private Entry addAfter(E elem, Entry e) {
O(n) // index liefert
assert (elem!=null) && (e!=null);
Entry newEntry = new Entry(elem, e.next);
e.next = newEntry;
size++;
return newEntry;
private Entry entry(int index) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException();
Entry e = header;
for (int i=0; i <= index; i++) { e = e.next; }
return e;
}
}
Entry e
Entry x
element next
element next
Entry e
element next
Entry x
header
element next
null
element next
Entry e
Entry x
element next
element next
Entry newEntry
element next
Entry newEntry
element next
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-23
© Michael Philippsen
Index 0
Index 1
Index size-1
Index 2
...
element next
element next
element next
element next
O(n) // Liefert das Element an der Stelle index
public E get(int index) {
return entry(index).element; O(n)
}
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-24
6
13.2 Verkettete Liste
13.2 Verkettete Liste
// Ein Element am Anfang in die Liste einfügen
public boolean add(E elem) {
addAfter(elem, header);
O(1)
return true;
}
O(1)
// Element elem an der Stelle index einfügen
public void add(int index, E elem) {
O(n)
addAfter(elem,
(index
==
0
?
header
:
entry(index
1)));
O(1)
}
O(n)
index gibt an, wie viele Schritte man braucht, um
das Element zu finden, nach dem einzufügen ist.
Im schlimmsten Fall sind dies n. Daher O(n).
// Überprüft, ob Objekt o enthalten ist
O(n)
public boolean contains(Object o) {
// o == null wird nicht behandelt
for (Entry e = header.next; e != header; e = e.next) {
if (o.equals(e.element))
return true;
}
return false;
}
Man spricht von sequentieller Suche, weil die Liste Element für
Element durchlaufen wird.
Man kann schneller
• Bester Fall:
O(1)
suchen. Liste ist daher
• Schlechtester Fall:
O(n)
nicht die ideale
• Durchschnitt (erfolgreich): O(n/2)
Datenstruktur, wenn
• Durchschnitt (erfolglos):
O(n)
viel gesucht wird.
(boolean-Return-wert von add wird bei Mengen verständlich.)
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-25
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-26
13.2 Verkettete Liste
13.2 Verkettete Liste
// Hilfsmethode zum Entfernen eines Listenelements
private void remove(Entry e) {
Entry p;
for (p = header; p.next != e; p = p.next) { }
p.next = e.next;
size--;
}
Entry y
Entry x
element next
element next
O(n)
Beachte: selbst wenn man schon
die Referenz auf das zu
löschende Objekt hat, muss man
die Liste erneut durchlaufen, um
den Vorgänger zu korrigieren.
Entry e
Entry y
Entry x
element next
element next
element next
Entry e
// Entfernt das Element an der Stelle index
public E remove(int index) {
Entry e = entry(index);
O(n)
remove(e);
O(n)
return e.element;
}
// Entfernt das erste Vorkommen des Objekts o
public boolean remove(Object o) {
...
}
O(n)
O(n)
Einfügen, Löschen und Suchen sind langsam, weil es
nicht möglich ist, gezielt auf ein Listenelement zuzugreifen.
Die Liste muss stets linear durchlaufen werden. Der Aufwand
ist also im schlimmsten Fall in O(n).
element next
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-27
© Michael Philippsen
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-28
7
13.2 Verkettete Liste
// Liefert die Anzahl der Elemente in der Liste
public int size() { return size; }
// Überprüft, ob Liste leer ist
public boolean isEmpty() { return size==0; }
// Alle Elemente aus der Liste entfernen
public void clear() {
header.next = header;
size = 0;
}
// Konkrete Implementierung des Iterators in
// LinkedList.
private class ListItr implements Iterator<E> {
...
„Innere Klasse“
}
// Liefert einen Iterator für die Liste
public Iterator<E> iterator() {
return new ListItr();
}
...
O(1)
O(1)
O(1)
13.2 Verkettete Liste
ƒ Die gezeigte Implementierung nutzte ein Wächterelement
header.
□ Die leere Liste besteht aus diesem Wächterelement.
□ Das letzte Listenelement verweist auf das header-Element, wodurch
die Liste zu einem geschlossenen Zyklus wird.
ƒ Übung:
□ Implementieren Sie eine Liste ohne ein solches Wächterelement.
□ Überzeugen Sie sich davon, dass die Behandlung der Sonderfälle
ƒ
ƒ
ƒ
ƒ
leere Liste
Einfügen am Listenende
Löschen am Listenende
…
aufwändiger zu implementieren ist.
}
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-29
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-30
13.3 Exkurs: Geschachtelte Klassen in Java
13.3 Exkurs: Geschachtelte Klassen in Java
ƒ In Java kann man neben Variablen und Methoden auch
Klassen als Bestandteil anderer Klassen definieren:
Innere Klassen in Java
class EnclosingClass {
...
class ANestedClass {
...
}
}
ƒ Enthaltene Klassen können ebenso unbehindert auf andere
Bestandteile der enthaltenden Klasse zugreifen wie Methoden.
ƒ Genau wie Methoden können auch Klassen statische Bestandteile
der enthaltenden Klasse sein.
ƒ Genau wie statische Methoden können statische enthaltene
Klassen nicht auf Instanzvariablen der enthaltenden Klasse
zugreifen.
ƒ Eine enthaltene Klasse heißt innere Klasse, wenn sie nicht
statisch ist.
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-31
© Michael Philippsen
ƒ Eine innere Klasse I ist zur Laufzeit assoziiert:
□ mit einer Instanz der inneren Klasse (explizit: I.this)
□ mit einer Instanz der sie umgebenden Klasse O (O.this)
ƒ Der Code einer inneren Klasse kann transparent (unmittelbar) auf
die Instanzvariablen (und Methoden) der Instanz der umgebenden
Klasse(n) zugreifen.
Instanz der enthaltenden Klasse
Instanz der
inneren Klasse
(mehrere möglich)
ƒ Der Benutzer eines Objekts einer inneren Klasse kann dadurch
indirekt auf den Zustand des umgebenden Objekts zugreifen,
ohne dessen Implementierung zu kennen.
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-32
8
13.3 Exkurs: Geschachtelte Klassen in Java
13.3 Exkurs: Geschachtelte Klassen in Java
Iterator einer verketteten Liste
Trennung statischer und nicht-statischer Kontexte
Instanzvariablen
private class ListItr implements Iterator<E> {
int currentIdx = 0;
des IteratorEntry currentElement = header; // initial
Objekts speipublic boolean hasNext() {
chern Zustand.
return (currentIdx < size);
Mehrere Itera}
toren möglich.
public E next() {
currentElement = currentElement.next;
if (currentIdx < size) {
Iterator-Code nutzt
currentIdx++;
Instanzvariablen d.
return currentElement.element;
} else {
umschließenden
return null;
Klasse.
}
}
public void remove() { ... }
}
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-33
ƒ Geschachtelte Klassen aus statischen Kontexten können nicht auf
Instanzvariablen der sie enthaltenden Klasse zugreifen.
ƒ Innere Klassen können auch in einem Block deklariert werden (die
Namen sind ab dem Deklarationspunkt sichtbar).
□ Solche innere Klassen können auf die lokalen Variablen, formalen
Parameter, … des deklarierenden Blocks zugreifen, wenn diese als
final deklariert wurden (und bereits initialisiert sind).
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-34
13.3 Exkurs: Geschachtelte Klassen in Java
13.3 Exkurs: Geschachtelte Klassen in Java
Kontext-Trennung am Beispiel
Anonyme (innere) Klassen in Java
statischer Kontext
class Outer {
int i = 100;
keine „innere Klasse“,
static void classMethod() {
sondern „enthaltene Klasse“
final int l = 200;
class LocalInStaticContext {
int k = i; // Fehler (i nicht statisch)
int m = l; // ok (l final und initialisiert)
}
}
nicht-statischer Kontext
void foo() {
int l = 200;
Innere Klasse
class Local {
im Block
int k = i; // ok (i Instanzvariable)
int m = l; // Fehler (l außen nicht final)
}
}
}
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-35
© Michael Philippsen
ƒ Anstatt eine innere Klasse zu definieren, dieser Klasse damit
einen Bezeichner zu geben, und dann Objekte dieses Typs per
„new“-Operator zu erzeugen, kann man auf die Namensvergabe
verzichten:
new BaseClassOrInterface() {... class-def ...}
Sinnvoll, wenn Objekte der inneren Klasse nur an einer Stelle
erzeugt werden und ein Bezeichner eigentlich nicht nötig ist.
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-36
9
13.3 Exkurs: Geschachtelte Klassen in Java
13.4 Menge ohne Sortierung
Statt expliziter Klasse ListItr
hier anonyme Klasse, die
public Iterator<E> iterator() {
Iterator<E> implementiert.
return new Iterator<E>() {
Das return gibt ein Objekt
int currentIdx = 0;
dieser Klasse zurück.
Entry currentElement = header;
public boolean hasNext() {
return (currentIdx < size);
}
public E next() {
currentElement = currentElement.next;
if (currentIdx < size) {
currentIdx++;
return currentElement.element;
} else {
return null;
}
}
public void remove() { ... }
};
Jetzt allgemeiner Fall: Menge von Elementen
□ Duplikate sind nicht mehr erlaubt.
□ Keine feste Reihenfolge der Elemente.
ƒ
Wie in der mathematischen Mengenlehre können …
□ einzelne Werte hinzugefügt oder entnommen werden,
□ Schnitt-, Vereinigungs- oder Subtraktionsmengen gebildet werden
oder
□ Mengen bzw. Elemente miteinander verglichen werden.
Beliebter Fehler: return-Anweisung
muss mit „;“ abgeschlossen werden.
}
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-37
13.4 Menge ohne Sortierung
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-38
13.4 Menge ohne Sortierung
ADT Menge
public interface Set<E> extends Collection<E> {
Signatur Menge(T), für T muss Gleichheitsoperator definiert sein
create:
add:
isIn:
del:
empty:
T x Menge
T x Menge
T x Menge
Menge
single:
union:
intersect:
diff:
equals:
containsAll:
T
Æ Menge
Menge x Menge Æ Menge
Menge x Menge Æ Menge
Menge x Menge Æ Menge
Menge x Menge Æ Boolean
Menge x Menge Æ Boolean
Æ Menge
Æ Menge
Æ Boolean
Æ Menge
Æ Boolean
Namen in folgendem Code
add
contains
remove
isEmpty
Übung: Axiome erstellen.
addAll
retainAll
removeAll
equals
containsAll
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-39
© Michael Philippsen
ƒ
// Liefert die Kardinalität der Menge
int size();
// Überprüft, ob die Menge leer ist
boolean isEmpty();
// Überprüft, ob die Menge das Objekt o enthält
boolean contains(Object o);
// Liefert einen Iterator für die Menge
Iterator<E> iterator();
// Element elem zur Menge hinzufügen
boolean add(E elem);
true, wenn elem wirklich
eingefügt wurde und nicht
schon vorhanden war.
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-40
10
13.4 Menge ohne Sortierung
13.4 Menge ohne Sortierung
// Entfernt das Objekt o aus der Menge
boolean remove(Object o);
Beliebiger
Parametertyp
möglich.
// Behält die Elemente der Menge, die auch in c
// enthalten sind (entspricht Schnittmenge)
boolean retainAll(Collection<?> c);
// Überprüft, ob die Menge alle Elemente aus c enthält
// (entspricht Teilmengenbeziehung)
boolean containsAll(Collection<?> c);
// Fügt alle Elemente von c zur Menge hinzu
// (entspricht Vereinigung der Mengen)
boolean addAll(Collection<? extends E> c);
Nur E und
Untertypen
von E
möglich.
// Leert die Menge
void clear();
// Überprüft die Gleichheit zweier Mengen
boolean equals(Object o);
}
// Entfernt alle Elemente von c aus der Menge, sofern
// enthalten (entspricht Differenz der Mengen)
boolean removeAll(Collection<?> c);
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-41
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-42
13.4 Menge ohne Sortierung
13.4 Menge ohne Sortierung
Implementierung von Set<E> mithilfe von
LinkedList<E>
Implementierung von Set<E> mithilfe von
LinkedList<E>
ƒ universell einsetzbar für alle Mengenklassen.
ƒ besonders flexibel für Mengen unbeschränkter Größe.
Beachte:
Eingabedaten
Set<E>
Transformation
Eingabedaten
Ausgabedaten
Transformation
LinkedList
<E>
Ausgabedaten
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-43
© Michael Philippsen
ƒ Einfügen eines Elements in die Liste hatte den Aufwand O(1).
ƒ Einfügen eines Elements in eine Menge, die mit einer Liste
implementiert wird, ist aufwändiger, weil beim Einfügen überprüft
werden muss, ob das Element schon in der Menge (der Liste) ist.
Das erfordert einen sequentiellen Listendurchlauf Æ O(n).
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-44
11
13.4 Menge ohne Sortierung
13.4 Menge ohne Sortierung
// Liefert einen Iterator für die Menge
public Iterator<E> iterator() {
return list.iterator();
}
public class SetAsList<E> extends AbstractSet<E>
implements Set<E> {
// Zugrunde liegende Liste
protected List<E> list;
// Leere Liste wird erzeugt
public SetAsList() {
list = new LinkedList<E>();
}
// Liefert die Kardinalität der Menge
public int size() {
O(1)
return list.size();
}
O(1)
// Überprüft, ob die Menge leer ist
public boolean isEmpty() {
O(1)
return list.isEmpty();
}
O(1)
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-45
// Fügt alle Elemente von c zur Menge hinzu
// (Vereinigungsmenge)
public boolean addAll(Collection<? extends E> c) {
Iterator<? extends E> i = c.iterator();
boolean setChanged = false;
while (i.hasNext()) {
n-mal
setChanged |= add(i.next()); O(n)
}
return setChanged;
}
O(n)
// Fügt das angegebene Element zur Menge hinzu
public boolean add(E elem) {
if (contains(elem))
O(n)
return false;
O(1)
return list.add(elem);
}
O(n)
// Entfernt Objekt o aus der Menge
public boolean remove(Object o) {
return list.remove(o);
O(n)
}
O(n)
13.4 Menge ohne Sortierung
O(n2)
n-mal
O(n2)
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-47
© Michael Philippsen
// Überprüft, ob die Menge das Objekt o enthält
public boolean contains(Object o) {
return list.contains(o);
O(n)
}
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-46
13.4 Menge ohne Sortierung
// Überprüft, ob die Menge alle Elemente aus c
// enthält (Teilmengenbeziehung)
public boolean containsAll(Collection<?> c) {
Iterator<?> i = c.iterator();
while (i.hasNext()) {
if (!contains(i.next())) return false; O(n)
}
return true;
}
O(1)
// Entfernt alle Elemente von c aus der Menge (Differenz)
public boolean removeAll(Collection<?> c) {
O(n2)
Iterator<?> i = c.iterator();
boolean setChanged = false;
while (i.hasNext()) {
setChanged |= remove(i.next());
n-mal
O(n)
}
return setChanged;
}
ƒ Mögliche alternative Formulierung der Schleife (seit Java 1.5):
for (Object o : c) {
setChanged |= remove(o);
}
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-48
12
13.4 Menge ohne Sortierung
13.4 Menge ohne Sortierung
// Behält die Elemente der Menge, die auch in c
// enthalten sind - Schnitt
public boolean retainAll(Collection<?> c) {
Iterator<?> i = iterator();
boolean setChanged = false;
while (i.hasNext()) {
E a = i.next();
if (!c.contains(a)) { O(n)
n-mal
i.remove();
O(n)
setChanged = true;
}
}
return setChanged;
}
// Leert die Menge
public void clear(){
list.clear();
}
O(1)
// Überprüft die Gleichheit zweier Mengen
public boolean equals(Object o) {
if (o == this) return true;
2
if (!(o instanceof Set)) return false; O(n )
Collection c = (Collection) o;
if (c.size() != size())
return false;
Prinzip: Preiswerte Tests
return containsAll(c);
zuerst durchführen.
}
O(n2)
}
// Zur Aufwandsanalyse betrachten wir nur:
public boolean retainAll(SetAsList<E> c) { ... }
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-49
13.4 Mengen ohne Sortierung
O(1)
O(1)
Um Enthaltensein
zu prüfen, muss
man die ganze
Liste durchlaufen.
boolean contains(Object o)
boolean add(E elem)
O(n)
1 Element: O(n)
n Elemente: O(n2)
boolean remove(Object o)
O(n)
Verkettete Liste
Für weitere Set-Methoden:
hatte nur O(1)
O(n²)
boolean equals(Object o)
bzw. O(n).
boolean addAll(Collection<? extends E> c) O(n²)
O(n²)
boolean retainAll(SetAsList<E> c)
O(n²)
boolean removeAll(Collection<?> c)
Aufwand
Für jedes Element von c muss
this komplett durchlaufen werden,
da keine Duplikate erlaubt sind
reduzierbar?
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-51
© Michael Philippsen
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-50
13.5 Menge mit Sortierung
Aufwände für SetAsList<E>
boolean isEmpty ()
int size()
Einseitiger Test reicht bei Mengen,
da es keine Duplikate gibt.
Wenn die Elemente in der Menge aufsteigend sortiert sind, …
ƒ … dann muss man nicht die ganze Liste durchlaufen, um nach
Duplikaten zu suchen.
□ contains endet im Durchschnitt schneller
□ add endet im Durchschnitt schneller (weil contains benutzt wird)
□ remove endet im Durchschnitt schneller
ƒ … dann sind die Mengenoperationen schneller (Aufwand in O(n)
statt in O(n2)), weil ein Reißverschlussverfahren verwendet
werden kann.
Beispiel: addAll
3 7 10
3 7 10
3 7 10
2 4 5
2 4 5
2 4 5
Ergebnis: 2
2 3
O(n)
2 3 4
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-52
13
13.5 Menge mit Sortierung
13.5 Menge mit Sortierung
Sortieren
ƒ Klassen von Sortierverfahren
□ Internes Sortierverfahren: zum Sortieren von Datensätzen im
Hauptspeicher. Es wird direkter Zugriff auf alle Elemente benötigt.
□ Externes Verfahren: Sortieren von Massendaten, die auf externen
Speichermedien gehalten werden. Der Zugriff ist auf einen Ausschnitt
der Datenelemente beschränkt.
ƒ Gegeben: Grundmenge U, totale Ordnung ≤ hierauf,
Mehrfachmenge M mit Elementen e ∈ U.
ƒ Gesucht: Anordnung der Elemente aus M gemäß ≤.
ƒ Formulierung:
□ Gegeben:
Liste M = [e0, e1,..., en-1].
□ Gesucht: Liste L = [ej0, ej1, ... , ejn-1]
mit Nachbedingung ej0 ≤ ej1 ≤ ... ≤ ejn-1 ∧ perm(M, L).
ƒ Eine Sortierung heißt stabil, wenn gleiche Werte ihre relative
Reihenfolge nicht ändern.
Formal:
Wenn k<j und ek = ej
dann ik=perm(k) < perm(j)=jk und eik = ejk
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-53
ƒ Kosten-/Nutzen-Analyse:
□ Sei m: Zahl der Suchvorgänge über die Lebensdauer der Menge
□ Sortierung ist vorteilhaft, falls
Ts + m⋅T’ < m⋅T
Sortieraufwand,
mit
Ts:
T’:
Suchaufwand sortiert,
T:
Suchaufwand unsortiert.
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-54
13.5 Menge mit Sortierung
13.5 Menge mit Sortierung
Objektvergleiche zur Sortierung in Java
addAll mit Reißverschlussprinzip
ƒ Das Interface java.lang.Comparable<T> definiert eine einzige
Methode:
public int compareTo(T o);
ƒ Alle Implementierungen dieser Schnittstelle sollten
Hinweise:
ƒ Wir verwenden eine LinkedList<E extends Comparable<E>>.
ƒ addAll erzeugt nicht eine neue Liste, sondern fügt c zu this
hinzu.
□ einen negativen Wert zurückgeben, wenn gemäß der gegebenen
Ordnung dieses Objekt „kleiner“ als das übergebene Objekt ist:
this < o
□ 0 zurückgeben, wenn die beiden Objekte „gleich groß“ sind (oder die
relative Sortierung gleichgültig ist):
this = o
□ einen positiven Wert zurückgeben, wenn dieses Objekt „größer“ als
das übergebene Objekt ist:
this > o
ƒ Wir gehen im Folgenden davon aus, dass Elemente von sortierten
Listen dieses Interface implementieren.
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-55
© Michael Philippsen
public boolean addAll(LinkedList<E> c) {
boolean setChanged = false;
ƒ Die Liste c wird mithilfe einer for-Schleife/Iterator abgelaufen.
ƒ Die Liste this wird elementweise durchlaufen, bis der header
wieder erreicht ist.
ƒ Ein Schleppzeiger zeigt auf das „vorletzte“ Element von this.
Nach diesem wird mithilfe von addAfter das nächste Element
von c eingefügt.
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-56
14
13.5 Menge mit Sortierung
13.5 Menge mit Sortierung
Entry currentElement = header.next; //initial
Entry schlepp = header;
Konstruktion zum Durchlaufen der Liste
for (E cElem : c) {
while (currentElement != header &&
currentElement.element.compareTo(cElem) <= 0) {
schlepp = currentElement;
currentElement = currentElement.next;
}
schlepp = addAfter(cElem, schlepp);
bedingt Stabilität
setChanged = true;
}
return setChanged;
Sortieren verketteter Listen (Abschnitte 13.6 – 13.10)
ƒ Betrachtung von Sortierverfahren, für die sich die verkettete Liste
als Datenstruktur besonders eignet.
ƒ Dies ist der Fall, wenn das Verfahren die Elemente der Liste
entlang ihrer Anordnung aufgreifen will.
}
Jedes Listenelement von this
und c wird einmal angefasst.
Daher Aufwand O(n).
Aber Aufwand des Sortierens?
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-57
13.6 Sortieren durch Auswählen
Sortieren durch Auswählen („selection sort“)
Grundidee:
ƒ aufsteigend sortieren: Lösche nacheinander die Maxima aus einer
Liste M und füge sie vorne an eine anfangs leere Ergebnisliste L
an.
ƒ absteigend sortieren: Lösche nacheinander die Minima aus einer
Liste und füge sie vorne an eine anfangs leere Ergebnisliste an.
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-58
13.6 Sortieren durch Auswählen
Beispiel
10
L
10
© Michael Philippsen
2
3
5
8
1
7
5
8
1
4
7
3
2
5
1
4
7
3
2
5
1
4
3
2
1
4
3
2
1
2
1
10
7
8
10
5
7
8
10
4
5
7
8
10
3
4
5
7
8
10
2
3
4
5
7
8
2
4
2
8
1
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-59
3
3
4
5
7
10
1
8
10
M
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-60
15
13.6 Sortieren durch Auswählen
13.6 Sortieren durch Auswählen
Imperative Lösung
Äußere Methode
ƒ
Problemlösung durch Induktion:
□ Induktionsanfang: Leere Liste L und einelementige Liste L sind
sortiert.
□ Induktionsschritt: Sei (n-1)-elementige Liste L sortiert. Nächstes
Element aus M ist kleiner als alle Elemente in L und wird als erstes
Element in L eingefügt. Daher ist n-elementige Liste L sortiert.
□ Induktionsende: Wenn M leer, befinden sich alle Elemente in L.
ƒ
Formulierung als rekursive Methode der Klasse
LinkedList<E>.
ƒ
Wir wollen nicht mit jedem Rekursionsschritt eine neue (Teil-)
Liste erstellen, daher brauchen wir eine äußere Methode zur
Einbettung der rekursiven Methode, in der insbesondere die Liste
L vereinbart wird.
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-61
13.6 Sortieren durch Auswählen
Innere Methode (überladene Methode selsortRec)
LinkedList<E> selsortRec(LinkedList<E> M, LinkedList<E> L) {
if (!M.isEmpty()) {
Übung: Implementieren Sie die
E m = M.maximum();
Methode maximum().
M.remove(m);
L.add(m);
return selsortRec(M, L);
//{I: ∀ v∈M, v'∈L: v≤v' ∧ M∪L = M0 ∧ L sortiert}
} else return L;
}
public LinkedList<E> selsortRec() {
// Kopie der Ausgangsliste
M0 bezeichnet die
LinkedList<E> M = new LinkedList<E>();
zu sortierende Liste
M.addAll(this);
// Erzeuge leere Ergebnisliste L
LinkedList<E> L = new LinkedList<E>();
//{P: ∀ v∈M, v'∈L: v≤v' ∧ M∪L = M0 ∧ L sortiert}
// Aufruf der Rekursion
return selsortRec(M, L);
//{Q: ∀ v∈M, v'∈L: v≤v' ∧ M∪L = M0 ∧ L sortiert ∧ M leer}
}
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-62
13.6 Sortieren durch Auswählen
Wiederholung: Konstruktion einer iterativen Lösung
ƒ Umformulierung der rekursiven Formulierung in folgendes Schema
Bedingung
Initialisierung
T' p(T x) {
A;
if (B) { C; x = f(x); return p(x); }
else D;
}
Abschluss
(Schleifen)invariante
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-63
© Michael Philippsen
¬b
P
Auf dem Weg zur Schleifeninvariante
Veränderung der
Rekursionsvariablen x
Rekursion
ƒ Diese Form der Rekursion heißt Rechtsrekursion.
ƒ Von geringerer Bedeutung sind Linksrekursionen, bei denen der
rekursive Aufruf vor dem C-Teil liegt. Diese lassen sich nicht so
leicht in eine iterative Form überführen.
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-64
16
13.6 Sortieren durch Auswählen
13.6 Sortieren durch Auswählen
Wiederholung: Konstruktion einer iterativen Lösung
Innere Methode ist Rechtsrekursion
ƒ Rekursive Version:
T' p(T x) {
A;
if (B) { C; x = f(x); return p(x); }
else D;
}
ƒ Äquivalente iterative Version:
T' p(T x) {
A;
while (B) { C; x = f(x); A }
D;
}
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-65
Init. A
leer
Bedingung B
LinkedList<E> selsortRec(LinkedList<E> M, LinkedList<E> L) {
if (!M.isEmpty()) {
C ist hier leer!
E m = M.maximum();
M.remove(m);
Anweisung mit Veränderung
L.add(m);
return selsortRec(M, L);
der Rekursionsvariablen M,L
Rekursion//{I: ∀ v∈M, v'∈L: v≤v' ∧ M∪L = M0 ∧ L sortiert}
} else return L;
}
Abschluss D
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-66
13.6 Sortieren durch Auswählen
13.6 Sortieren durch Auswählen
Innere Methode umgewandelt in Iteration
Einbettung der iterativen Lösung
LinkedList<E> selsortIt(LinkedList<E> M, LinkedList<E> L) {
//{P: ∀ v∈M, v'∈L: v≤v' ∧ M∪L = M0 ∧ L sortiert}
while (!M.isEmpty()) {
E m = M.maximum();
M.remove(m);
L.add(m);
}
//{Q: ∀ v∈M, v'∈L: v≤v' ∧ M∪L = M0 ∧ L sortiert ∧ M leer}
return L;
}
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-67
© Michael Philippsen
public LinkedList<E> selsort() {
// Kopie der Ausgangsliste this
LinkedList<E> M = new LinkedList<E>();
„Außenmethode“
M.addAll(this);
// Erzeuge leere Ergebnisliste L
LinkedList<E> L = new LinkedList<E>();
//{P: ∀ v∈M, v'∈L: v≤v' ∧ M∪L = M0 ∧ L sortiert}
while (!M.isEmpty()) {
statt MethodenE m = M.maximum();
aufruf: Rumpf
M.remove(m);
von selsortIt
L.add(m);
offen eingebaut
}
//{Q: ∀ v∈M, v'∈L: v≤v' ∧ M∪L = M0 ∧ L sortiert ∧ M leer}
return L;
}
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-68
17
13.6 Sortieren durch Auswählen
public LinkedList<E> selsort() {
LinkedList<E> M = new LinkedList<E>();
M.addAll(this);
LinkedList<E> L = new LinkedList<E>();
//{P: ∀ v∈M,v'∈L: v≤v' ∧ M∪L=M0
// ∧ L sortiert}
while (!M.isEmpty()) {
E m = M.maximum();
M.remove(m);
L.add(m);
}
//{Q: ∀ v∈M,v'∈L: v≤v' ∧ M∪L=M0
// ∧ L sortiert ∧ M leer}
return L;
}
13.6 Sortieren durch Auswählen
selsort ist korrekt
ƒ Q=I ∧ M leer
ƒ I gilt für die leere Liste L.
ƒ Bei Verlassen der Schleife
gilt
I mit M = Ø:
L = M0 ∧ L sortiert
(”=” steht für Gleichheit von
Mehrfachmengen).
ƒ I ist Schleifeninvariante:
Wiederherstellen von I durch
remove() und add()
ƒ Terminierung, da M monoton
verkürzt wird.
selsort ist stabil
Aufwandsabschätzung
sofern maximum() das in der Reihenfolge von M letzte Maximum nimmt
und remove() auch dieses Element löscht (das ist in unserer
Beispielimplementierung nicht der Fall, dort wird das erste genommen!).
13.7 Sortieren durch Einfügen
Sortieren durch Einfügen („insertion sort“)
Grundidee:
Beispiel
ƒ Angenommen wir können n-1 Werte sortieren. Dann können wir
den n-ten Wert einsortieren, indem wir seinen Platz in der
sortierten Liste finden und die restlichen Elemente nach hinten
verschieben.
ƒ Nimm das nächste Element aus der Menge M und füge es an der
richtigen Stelle in die (anfangs leere) Menge L ein.
L
10
© Michael Philippsen
3
10
2
3
5
8
1
7
5
8
1
4
7
2
5
8
1
4
7
5
8
1
4
7
8
1
4
7
1
4
7
4
7
10
2
3
10
2
3
5
10
2
3
5
8
10
1
2
3
5
8
10
1
2
3
4
5
8
2
4
2
3
1
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-71
durch Merken der
Maximum-Position: O(1)
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-70
13.7 Sortieren durch Einfügen
□ Starte mit der ersten Karte einen neuen Stapel.
□ Nimm jeweils die nächste Karte des Originalstapels und füge diese an
der richtigen Stelle in den neuen Stapel ein.
Idee: Nutze Halde, um
Maximum schnell zu
entfernen. Später mehr!
Schlecht! Lohnt sich nur, wenn über
die Lebensdauer oft gesucht wird.
Daher Tselsort = Θ(n2).
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-69
ƒ Typisches Verfahren beim Sortieren von Spielkarten:
im Mittel sind in
M n/2 Elemente
public LinkedList<E> selsort() {
LinkedList<E> M = new LinkedList<E>();
M.addAll(this);
LinkedList<E> L = new LinkedList<E>();
//{P: ∀ v∈M,v'∈L: v≤v' ∧ M∪L=M0
// ∧ L sortiert}
while (!M.isEmpty()) { O(1)
int m = M.maximum();
n-mal
O(n)
M.remove(m);
O(n)
L.add(m);
O(1)
}
//{Q: ∀ v∈M,v'∈L: v≤v' ∧ M∪L=M0
// ∧ L sortiert ∧ M leer}
return L;
}
3
4
5
7
10
7
8
10
M
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-72
18
13.7 Sortieren durch Einfügen
13.7 Sortieren durch Einfügen
Pseudocode mit Einbettung der iterativen Lösung
Aufwandsabschätzung
konstanter Aufwand c0
public LinkedList<E> insort() {
LinkedList<E> M = new LinkedList<E>();
M.addAll(this);
Schleifeninvariante
LinkedList<E> L = new LinkedList<E>();
//{P: M∪L = M0 ∧ L sortiert ∧ L leer }
while (!M.isEmpty()) {
E m = M.get(0);
M.remove(m);
//suche in L m', m'' aufeinanderfolgend,
//m' ≤ m < m'';
//füge in L m zwischen m', m'' ein
Ergänzung von
}
LinkedList<E>
//{Q: M∪L = M0 ∧ L sortiert ∧ M leer }
um weitere
return L;
Operatoren
}
while (!M.isEmpty()) {
E m = M.get(0); M.remove(m);
Sequentielle Suche:
//suche in L m', m'' aufeinanderfolgend,
mittlerer Aufwand O(k/2)
//m' ≤ m < m'';
schlimmster Fall O(k)
//füge in L m zwischen m', m'' ein
wenn k Länge von L ist.
}
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-73
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-74
erforderlich!
ƒ Damit Gesamtaufwand über alle Durchläufe der äußeren Schleife:
n-1
n-1
k=1
k=1
Tinsort(n) = Σ O(k) ≤ Σ c·k = c·n(n-1)/2 0 O(n2)
ƒ Günstigster Aufwand, falls M bereits entgegengesetzt geordnet ist.
Dann verursacht die innere Schleife einen konstanten Aufwand c1.
Dann ist der Gesamtaufwand im besten Fall in O(n).
13.8 Fächersortierung („bucket sort“)
13.8 Fächersortierung („bucket sort“)
ƒ Poststellen arbeiten häufig mit Fächersortierung.
ƒ Stabil, wenn die Briefe in einem Postfach „gestapelt“ werden,
wenn also zur Implementierung des Fachs eine Schlange
verwendet wird.
□ Pro Person wird ein Postfach angelegt, insgesamt m Fächer,
also endliche Zahl möglicher sogenannter Sortierschlüssel.
□ Jedes Element der eingehenden Post (Menge mit n Elementen)
wird in das Fach des Adressaten gelegt.
□ Also ist eine bijektive Funktion nötig, die jedem Sortierschlüssel
eine Fachnummer zuordnet.
ƒ Aufwand in O(n + m)
□ Jeder der n Briefe ist anzufassen, seine Fachnummer ist zu
bestimmen, dann ist er in dieses Fach zu legen.
□ Am Schluss müssen die Fächer geleert werden.
ƒ Bei m 0 O(n) hat dieses Sortierverfahren linearen Aufwand!
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-75
© Michael Philippsen
ƒ Sortieren einer Reihung erfolgt analog zum „Counting Sort“:
□ Erst zählen, wie viele Daten in jedes Fach fallen (Histogramm).
□ Dann aus diesen Daten pro möglichem Sortierschlüssel einen Offset
in der Ziel-Reihung berechnen.
□ Dann elementweise einsortieren und dabei den zum Sortierschlüssel
gehörenden Offset erhöhen. (Das entspricht dem
Schlangenverhalten.)
ƒ Leider nicht praktikabel bei großem m oder wenn m>>n, wenn es
also viel mehr mögliche Werte/Sortierschlüssel gibt, als in der zu
sortierenden Menge tatsächlich vorkommen.
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-76
19
13.9 Radix-Sortierung
Radix-Sortierung (Verallg. der Fächersortierung)
ƒ Statt für jeden infrage kommenden Schlüssel ein Fach anzulegen,
wird der Schlüssel in Segmente aufgeteilt. Pro Schlüsselteil gibt
es ein Fach.
□ Z.B. die einzelnen Ziffern der Postleitzahl, die Buchstaben eines
Worts fester Länge, ...
13.9 Radix-Sortierung
Radix-Sortierung (Segmente v.r.n.l.) am Beispiel:
503 087 512 061 908 170 897 275 653 426 154 509 612 677 765 703
170 061 512 612 503 653 703 154 275 765 426 087 897 677 908 509
Fach für letzte
Stelle = 3
ƒ Die Elemente der Menge werden gemäß ihres Schlüsselteils per
Fächersortierung sortiert.
□ Wesentlich weniger Fächer (10, 26, ...)
ƒ Für die Sortierung nach den restlichen Schlüsselsegmenten wird
die Fächersortierung rekursiv angewendet.
ƒ Achtung: Segmentbetrachtung von rechts nach links!
503 703 908 509 512 612 426 653 154 061 765 170 275 677 087 897
061 087 154 170 275 426 503 509 512 612 653 677 703 765 897 908
Beachte:
• Nach jeder Partitionierung werden die Daten wieder gesammelt.
• Essentiell ist die Stabilität des Sortierens.
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-77
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-78
13.9 Radix-Sortierung
13.9 Radix-Sortierung
Herleitung durch Induktion
Aufwandsabschätzung
ƒ Induktionshypothese: Schlüssel mit weniger als k Segmenten, die
jeweils maximal d mögliche Werte einnehmen können, können
sortiert werden.
ƒ Induktionsanfang: 1 Segment/vorderstes Segment:
Fächersortierung mit d Fächern erzeugt korrekte Sortierung.
ƒ Induktionsschluss:
ƒ Pro Segment der Radix-Sortierung werden alle n Werte untersucht.
ƒ Bei k Segmenten ergibt sich ein Gesamtaufwand von O(n·k).
□ 1. Fall: zwei Elemente unterscheiden sich im k-ten Segment.
Dann wird der k-te Sortierschritt nach Induktionsvoraussetzung zur
korrekten Sortierung der Elemente führen.
□ 2. Fall: zwei Elemente sind im k-ten Segment gleich.
ƒ Dann sind sie nach Induktionsvoraussetzung in den k-1 Segmenten
rechts davon korrekt sortiert.
ƒ Ein stabiles Sortierverfahren behält die relative Ordnung der beiden
Elemente bei.
ƒ Damit sind sie auch noch korrekt sortiert, wenn nach dem k-ten Segment
sortiert worden ist.
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-79
© Michael Philippsen
ƒ Bei Betrachtung der Binärdarstellung gibt es k=log2n Segmente (die
jeweils 2 Werte 0 oder 1 haben können).
Damit ist der Gesamtaufwand in O(n·log2n)
□ Das ist besser als der quadratische Aufwand der bisher gezeigten
Sortierverfahren. (Verbesserungsmöglichkeit: Betrachtung von
Segmenten, die mehr als 1 Bit umfassen.)
□ Allerdings ist vorausgesetzt, dass ein Sortierschlüssel in Segmente
aufgeteilt werden kann, also eine feste Struktur hat.
□ java.lang.Comparable<T> ist allgemeiner und setzt eine beliebige
totale Ordnung voraus (lexikografisch (Stichwort Umlaute),
Gleitpunktzahlen float und double, Graphen, Farben, …)
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-80
20
13.10 Sortieren durch Mischen („merge sort“)
13.10 Sortieren durch Mischen („merge sort“)
Sortieren durch Mischen („merge sort“)
Grundprinzip
1
Rekursion:
Grundidee: Teile-und-Herrsche
ƒ Wir können eine Liste der Länge n=2m, m>0, mit geringem
Aufwand Θ(n/2) in zwei gleich große Teillisten der Länge n/2=2m-1
zerlegen und mit gleichem Aufwand die Ergebnisse
zusammenfügen (Reißverschluss).
ƒ Leere oder einelementige Mengen sind sortiert.
ƒ Dann gibt es statt (n-1) Rekursionen nur noch m = log2n
n
Lösung 1. Teil
Gesamtlösung
Mischen:
5
4
2
10
7
3
takeAndDrop
3
3
2
5
2
5
8
1
4
8
1
7
4
7
10
3
2
5
8
1
4
7
10
3
2
5
8
1
4
7
3
10
2
5
1
8
4
7
3
10
2
5
1
8
4
7
3
5
10
2
1
2
3
4
1
5
4
7
7
8
5
4
10
7
5
4
≤
≤
2
3
2
…
13.10 Sortieren durch Mischen („merge sort“)
Beispiel
10
10
7
3
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-82
13.10 Sortieren durch Mischen („merge sort“)
10
Lösung 2. Teil
Rekursion
≤
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-81
n Basisfälle
protected LinkedList<E> mergesortRec(LinkedList<E> m) {
if (m.size() < 2) return m;
// Zerlege M in 2 ungefähr gleich lange Teillisten:
// hier durch takeAndDrop, das die zweite Hälfte von M
// in eine neue Liste packt und die erste Hälfte in M
// belässt
Zerlegungsphase
LinkedList<E> o = takeAndDrop(m);
// Sortiere die beiden Teillisten und füge sie zusammen:
return merge(mergesortRec(m), mergesortRec(o));
}
8
10
public LinkedList<E> mergesort() { // Äußere Methode
LinkedList<E> m = new LinkedList<E>();
m.addAll(this);
return mergesortRec(m);
}
merge
Mischphase
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-83
© Michael Philippsen
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-84
21
LinkedList<E> merge(LinkedList<E> l1, LinkedList<E> l2) {
// Implementierung der Funktion addAll wie bei
// Vereinigung von 2 sortierten Mengen (Abschn. 13.5)
l1.addAll(l2);
return l1;
}
10
10
3
3
2
5
2
5
Pro Zeile 2k·n/2k = n
Insgesamt 2·log2n Zeilen
Aufwand in O(n·log2n)
8
1
4
8
1
7
4
7
1
21=2
4
2
2
5
8
1
4
7
2
10
3
2
5
8
1
4
7
3
23=8
1
3
10
2
5
1
8
4
7
3
23=8
1
3
10
2
5
1
8
4
7
2
22=4
2
3
5
10
1
21=2
4
0
20=1
8
2
1
2
3
4
1
5
4
7
7
8
8
10
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-86
Aufwandsschätzung (n sei die Länge der Liste)
Wichtige Eigenschaften
ƒ mergesort() ist stabil.
© Michael Philippsen
8
3
13.10 Sortieren durch Mischen („merge sort“)
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-87
20=1
10
13.10 Sortieren durch Mischen („merge sort“)
ƒ Wir benennen die Ebenen von oben kommend mit 0, 1, …
ƒ Gesamtaufwand ist Summe der Aufrufe aller Knoten im
Rekursionsbaum.
ƒ Aufwand eines Knotens auf Ebene k ist jeweils in O(n/2k) für das
Zerlegen und Mischen der sortierten Hälften (Länge ca. n/2k).
ƒ Anzahl der Knoten/Listen auf Ebene k ist 2k, also in O(2k).
ƒ Summe der Aufwände auf Ebene k ist demnach O(2k)·O(n/2k) =
O(n), unabhängig von k.
ƒ Anzahl der Ebenen ist in O(log2n).
ƒ Also Gesamtaufwand im schlechtesten Fall: O(n·log2n),
besser als alle allgemein verwendbaren Verfahren bisher!
0
22=4
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-85
ƒ Rekursive Aufrufe lassen sich als Baum darstellen, dessen Knoten
Aufrufe von mergesortRec repräsentieren.
Länge n/2k
Aufwand im Beispiel
Listen 2k
LinkedList<E> takeAndDrop(LinkedList<E> m) {
// Belässt die zweite Hälfte der Elemente von M,
// entfernt den Rest und liefert ihn zurück
LinkedList<E> o = new LinkedList<E>();
Iterator<E> i = m.iterator();
int n = m.size();
for (int j = 1; j <= n/2; j++) {
o.add(i.next());
i.remove();
Übung: vervollständigen Sie
}
obigen Iterator, damit remove und
return o;
next korrekt zusammenwirken.
}
13.10 Sortieren durch Mischen („merge sort“)
Ebene k
13.10 Sortieren durch Mischen („merge sort“)
ƒ Schnellstes Verfahren auf verketteten Listen wegen der geringsten
Zahl an Vergleichen.
ƒ Sequentieller Zugriff auf Teillisten Æ externes Sortierverfahren.
Daher verbreitetes Sortierverfahren für große Listen, die nicht
mehr in den Hauptspeicher passen:
□
□
□
□
Unterteile Datei in Abschnitte.
Sortiere jeden der Abschnitte für sich im Hauptspeicher.
Speichere sortierten Abschnitt in Hilfsdatei.
Verschmelze die Hilfsdateien mit Reißverschlussverfahren. Wichtig
ist dabei, dass die Hilfsdateien sequentiell von links nach rechts
verarbeitet werden. (Das geht schnell und ohne wahlfreien Zugriff.)
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-88
22
13.10 Sortierung durch Mischen („merge sort“)
Aufwände für SortedSet<E>
boolean isEmpty ()
int size()
O(1)
O(1)
boolean contains(Object o)
boolean add(E elem)
boolean remove(Object o)
O(n)
1 Element: O(n)
n Elemente: O(n·log2n)
O(n)
Für weitere Set-Methoden:
boolean equals(Object o)
boolean addAll(SortedSet<E> c)
boolean retainAll(SortedSet<E> c)
boolean removeAll(SortedSet<E> c)
O(n)
O(n)
O(n)
O(n)
Verbesserung
bei sortierten
Mengen
Algorithmen und Datenstrukturen • Philippsen/Stamminger/Pflaum • WS 2008/09 • Folie 13-89
© Michael Philippsen
23
Herunterladen