Algorithmen und Datenstrukturen (Beispiele in C++) - fbi.h

Werbung
FB Informatik
Prof. Dr. R.Nitsch
Algorithmen und Datenstrukturen (Beispiele in C++)
Reiner Nitsch
 8471
 [email protected]
Such-Algorithmen – Lineare Suche
FB Informatik
Prof. Dr. R.Nitsch
Prinzip der linearen Suche:
Betrachte jedes Element im Suchbereich
Vergleiche jedes Element im Suchbereich mit dem Suchwert
Wenn gefunden (Suchtreffer), gib Index oder Zeiger auf Suchtreffer zurück
Wenn nicht gefunden (Suchfehler), gib Sentinel zurück.
int* find( int* pfirst, int* plast, int val ) {
while( pfirst<plast )
if( *pfirst++ == val )
return pfirst;
// Suchtreffer
return plast; // sentinel für Suchfehler
}
Funktioniert der Algorithmus auch mit std::string-Containern?
ja
Funktioniert der Algorithmus auch mit einem C-Array?
ja
Welche Anforderungen stellt diese Implementierung an den Container?
Der den Suchbereich definierende Datentyp (hier: int* )muss
- mittels ++ zum nächsten Element navigieren können
- mittels * dereferenzieren, d.h. auf das Element lesend zugreifen können.
Welche Anforderungen stellt diese Implementierung an den Elementtyp?
operator== muß für Elementtyp des Arrays (hier: int) definiert sein.
28.06.2012
2
Such-Algorithmen – Binäre Suche
15.06.2012
FB Informatik
Prof. Dr. R.Nitsch
Nachteil der linearen Suche: langsam, weil Suche in kleinen Schritten
Besser: Suche in großen Schritten
Frage: Wie errät man eine Zufalls-Zahl zwischen 0 und 100 am schnellsten?
2. Version des Suchalgorithmus: Binäre Suche
Algorithmischer Kern
Rateversuch: Mittlere Zahl aus (sortiertem) Suchbereichs
wenn gleich: fertig
sonst: links oder rechts weitersuchen
int* binarySearchI(int* pfirst, int* plast, int val ) {
//Binäre Suche (iterativ) im Bereich [pfirst, plast);
// Vorbedingung: sortierte Elemente in Bezug auf operator<
T* pend = plast;
// Position des one-past-the-end Elements merken!
while (pfirst<pend) {
Neue Fragen:
T* pmid = pfirst+(pend-pfirst)/2;
if( val<*pmid ) pend = pmid;
// links weitersuchen Verhalten bei Duplikaten?
else if( *pmid<val ) pfirst = pmid+1; // rechts weitersuchen Welcher Algorithmus ist besser?
else return pmid;
// gefunden
Wie misst man die Effizienz von
}
Algorithmen?
return plast;
// nicht gefunden
Mögliche Benchmarking-Kriterien?
}
Anforderungen an Objekte vom Element-Typ?
28.06.2012
Benötigt nur < für Elementtyp
Achtung: == wird gar nicht benutzt!
3
Such-Algorithmen – Binäre Suche
15.06.2012
FB Informatik
Prof. Dr. R.Nitsch
Nachteil der linearen Suche: langsam, weil Suche in kleinen Schritten
Besser: Suche in großen Schritten
Frage: Wie errät man eine Zufalls-Zahl zwischen 0 und 100 am schnellsten?
2. Version des Suchalgorithmus: Binäre Suche
Algorithmischer Kern
Rateversuch: Mittlere Zahl aus (sortiertem) Suchbereichs
wenn gleich: fertig
sonst: links oder rechts weitersuchen
int* binarySearchI(int* pfirst, int* plast, int val ) {
//Binäre Suche (iterativ) im Bereich [pfirst, plast);
// Vorbedingung: sortierte Elemente in Bezug auf operator<
int* pend = plast;
// Position des one-past-the-end Elements merken!
while (pfirst<plast) {
Neue Fragen:
int* pmid = pfirst+(plast-pfirst)/2;
if( val==*pmid ) return pmid;
Verhalten bei Duplikaten?
// gefunden
else if( *pmid<val ) pfirst = pmid+1; // rechts weitersuchen Welcher Algorithmus ist besser?
else plast = pmid;;
// links weitersuchen Wie misst man die Effizienz von
}
Algorithmen?
return pend;
// nicht gefunden
Mögliche Benchmarking-Kriterien?
}
Anforderungen an Objekte vom Element-Typ?
28.06.2012
Benötigt nur < für Elementtyp
Achtung: == wird gar nicht benutzt!
4
Aufwand von Algorithmen (Komplexität)
FB Informatik
Prof. Dr. R.Nitsch
Kriterien sind u.a.
Speicheraufwand
statisch
 für Programm/Algorithmus
 für Daten
dynamisch, d.h. abhängig von Datenmenge
Speicherkomplexität
 für Aufruf und Initialisierungen
 für Wiederholungen
Zeitkomplexität
Zeitaufwand
statisch
dynamisch
Eine präzise detailreiche Bestimmung der Aufwände wird i.A nicht durchgeführt, weil dies
 mathematisch oft nicht handhabbar ist
 uninteressant ist: für Vergleichszwecke reicht auch ger. Detailierungsgrad
Vereinfachungen (Abstraktionen) bei der Ermittlung des Zeitaufwandes
Der tatsächliche Zeitaufwand ist immer prozessorabhängig.
Um davon unabhängig zu werden, macht man folgende Vereinfachungen:
 Jede Anweisung (Schleifen ausgenommen) benötigt den Zeitaufwand 1
 Bei Wiederholungen/Schleifen sind wiederholten Anweisungen mit der Anzahl der Wiederholungen zu
gewichten, die meist von der Anzahl der Eingabedaten, n , bestimmt ist.
28.06.2012
5
Aufwand von Algorithmen - Abstraktionen
FB Informatik
Prof. Dr. R.Nitsch
Wenn die Algorithmuslaufzeit T(n) nicht für alle Eingaben derselben Länge n gleich
ist, sind folgende Grenzfälle interessant:
der beste Fall (best case) Tbest
der schlimmste Fall (worst case) Tworst
das Durchschnittsverhalten (average case) Tavg
O-Notation (auch Landau-Notation) der Laufzeitfunktion
ist ein mathematisches Verfahren zur Einordnung der Komplexität von Funktionen für
großes n.
benennt lediglich aus einer Klasse gleich schnell wachsender Funktionen den einfachsten
Repräsentanten als obere Schranke (Supremum). Meist reicht diese Abschätzung der
Größenordnung, weil Algorithmen sich schon hier unterscheiden.
gibt ein Maß für die Anzahl der Elementarschritte in Abhängigkeit von der Größe der
Eingangsvariablen an.
28.06.2012
6
Such-Algorithmen – Binäre Suche
FB Informatik
Prof. Dr. R.Nitsch
Lineare Suche
1
1
1
int* find( int* pfirst, int* plast, int val ) {
while( pfirst<plast )
if( *pfirst++ == val ) return pfirst;
return plast; // sentinel
}
n: Anzahl Datenelemente
R(n): Anzahl Wdhlgn.
Laufzeit?
1
1
1
Suchtreffer: T(n)=2·R(n)
=2·(n/2)·2=n
T(n)  O(n)
Suchfehler: T(n)=2·R(n)+1
=2·n+1
T(n)  O(n)
Binäre Suche
1
1
1
1
1
1
int* binarySearchI(int* pfirst, int* plast, int val ) {
//Binäre Suche (iterativ) im Bereich [pfirst, plast);
// Vorbedingung: sortierte Elemente
Laufzeit?
{
int* pend = plast;
1
while (pfirst<plast) {
1
int* pmid = pfirst+(plast-pfirst)/2;
1
if( val==*pmid ) return pmid;
1
else if( *pmid<val ) pfirst = pmid+1;
1
else plast = pmid;
}
return pend; }
1
28.06.2012
T(n)=R(n)·3+2
R(n) = ?
7
Komplexität der binären Suche
FB Informatik
Prof. Dr. R.Nitsch
Aufwandsabschätzung für binäre Suche
• Jede Iteration benötigt die Zeit 4
• Jede Iteration halbiert den Suchbereich
Nach dem 1. Halbieren enthält Suchbereich noch n/2 = n/21 Elemente
Nach dem 2. Halbieren enthält Suchbereich noch n/4 = n/22 Elemente
…
Nach dem R-ten Halbieren enthält Suchbereich noch n/2R Elemente
• Worst case: Suche endet, wenn Suchbereich nur noch 1 Element enthält!
n
 Zeitkomplexität T(n) = R(n)·3+2= log2(n) · 3 + 2
= 1  R = log2(n) Wiederholungen
2R
d.h. die Zeitkomplexität wächst logarithmisch mit n
Dies bringt man abkürzend durch die "Big-O"-Notation zum Ausdruck: Zeitkomplexität = O( log n )
Ergebnis: In sortierten Reihen kann wesentlich schneller gesucht werden!
Beispiel: Laufzeitvergleich
lineare ↔ binäre Suche
(gemessene Werte:
Intel X86 Prozessor, 1,66 GHz,
Debug-Konfiguration)
28.06.2012
n
O(n)
O(log n)
1000
106
109
2,6 us
2,6 ms
2,6 s
0,1 us
0,2 us
0,3 us
8
Beispiele zur Schätzung der Zeitkomplexität
Anweisung
Zeitkomplexität T(n)
Big-O
FB Informatik
Prof. Dr. R.Nitsch
Laufzeit
Typische Algorithmen
x=x+1;
1
O(1)
konstant
Elementzugriff in phy.Seq.
for (int i=1; i<=n; i++)
x=x+i;
n*1
O(n)
linear
Elementzugriff in log. Seq.
Suche in unsort. Seq.
n*n*1
O(n2)
quadratisch
primitive Sortierverfahren
for ( int i=1; i<=n; ++i )
for( int j=1; j<=n; ++j )
x=x+i+j;
for ( int i=1; i<=n; ++i )
for( int j=1; j<=n; ++j )
x=x+i+j;
int k=0;
while( k<=n ) {
cout << 2*k+1 << endl;
k++;
}
n*n*1
1
Welche Zeitkomplexität hat die lineare Suche?
n2+2n+3
(n+1)*2
Nur der größte Beitrag wird berücksichtigt,
wenn lediglich die Größenordnung interessiert.
Genauer: n2+2n+3 wächst höchstens so schnell wie
c·n2, c>0 ab einem gewissen n0>0
28.06.2012
O(n2)
O(nk)
polynomiell
z.B. Zahlenschlossprojekt: O(n3)
O(2n)
exponentiell
Backtracking-Algorithmen,
rekursive Variante der
Fibonacci-Folge
9
Weitere Beispiele für O-Notation der Algorithmuslaufzeit
FB Informatik
Prof. Dr. R.Nitsch
Notation
Bedeutung
Anschauliche Erklärung
T(n) є O(1)
ist konstant
überschreitet einen konstanten Wert nicht Nachschlagen des x-ten Elementes in einem
(unabhängig vom Wert des Arguments).
Feld.
T(n) є O(log n)
wächst
logarithmisch
wächst ca. um einen konstanten Betrag,
wenn sich das Argument verzehnfacht.
Binäre Suche im sortierten Feld mit n
Einträgen
T(n) є O(√n)
wächst wie die
Wurzelfunktion
wächst ungefähr auf das Doppelte, wenn
sich das Argument vervierfacht
naiver Primzahltest mittels Teilen durch
jede Zahl ≤n
T(n) є O(n)
wächst linear
wächst ungefähr auf das Doppelte, wenn
sich das Argument verdoppelt.
Suche im unsortierten Feld mit Einträgen
(Bsp. Lineare Suche)
T(n) є O(nlog n)
hat super-lineares
Wachstum
T(n) є O(n2)
wächst
quadratisch
wächst ungefähr auf das Vierfache, wenn Einfache Algorithmen zum Sortieren von
sich das Argument verdoppelt
Zahlen Selectionsort
T(n) є O(nk)
wächst
polynomiell
wächst ungefähr auf das Doppelte, wenn
sich das Argument um eins erhöht
Zahlenschloßprojekt T(n)=O(n3)
T(n) є O(n!)
wächst faktoriell
wächst ungefähr um das n-fache, wenn
sich das Argument um eins erhöht.
Problem des Handlungsreisenden
28.06.2012
Beispiele für Laufzeiten
Fortgeschrittenere Algorithmen zum
Sortieren von Zahlen Mergesort, Heapsort
10
Warum ist die Zeitkomplexität eines Algorithmus so wichtig?
FB Informatik
Prof. Dr. R.Nitsch
Häufig ist die erste Idee, die HW zu beschleunigen.
Aber: der Geschwindigkeitsvorteil ist dabei auf konstanten Faktor beschränkt
Bessere HW ist zudem teuer, bzw. stößt an technische Grenzen
Ein schneller Algorithmus auf einer langsamen Maschine wird immer schneller
sein als ein langsamer Algorithmus auf einer schnellen Maschine!
Supercomputers are for people too rich and
too stupid to design efficient algorithms
(Steven Skiena)
28.06.2012
11
Sortieren
FB Informatik
Prof. Dr. R.Nitsch
Für ein Feld von n Objekten gibt es n! Permutationen
Sortieren ist ein Vorgang, der durch (möglichst wenige) paarweise Vergleiche von
Objekten eine dieser Permutationen herausfiltert, in der die Objekte einer
bestimmten Ordnungsrelation (z.B. größer, kleiner, …) genügen.
Stabile Sortierung
Beispiel:
7
2
2
3
5
5
3
7
unsortiert sortiert
Kommt ein Schlüsselwert
mehrfach vor (Duplikate) ist
die Sortierung nicht eindeutig:
1
7
1
3
3
3
Ein Sortierverfahren, bei dem die
Reihenfolge von Schlüsselduplikaten
nach dem Sortieren unverändert ist,
bezeichnet man als
"Stabiles Sortierverfahren"
3
1
3
7
3
7
sortiert
unsortiert
stabil sortiert
Schlüsselwerte
28.06.2012
12
Sortierverfahren - Klassifizierungskriterien
Zeitverbrauch
 Anzahl Schlüsselvergleiche (Cmin, Cmax, Cavg)
 Anzahl Vertauschungen (Mmin, Mmax, Mavg)
 Sensibilität bezüglich Eingabeverteilung
Speicherplatzverbrauch (Programme u. Daten)
FB Informatik
Prof. Dr. R.Nitsch

Zeitkomplexität

Speicherkomplexität
 Speicherplatzbedarf am geringsten für Sortieren am Ort ("in place" oder "in situ")
Natürliche Sortierverfahren arbeiten mit vorsortierten Daten schneller als mit unsortierten.
Bei adaptiven Sortierverfahren ist der Kontrollfluss, das heißt die Abfolge der Befehle
abhängig von den Daten. Nicht-adaptive Verfahren können gut in Hardware implementiert
werden.
Stabile Sortierverfahren, ändern die Reihenfolge von Duplikaten beim Sortieren nicht.
Wie schnell kann man sortieren?
Voraussetzung für jedes Sortieren: Auf den zu sortierenden Objekten muss eine Ordnung für
die Schlüssel definiert sein.
Untere Komplexitätsschranke für Sortierverfahren:
Satz: Jedes vergleichsbasierte Sortierverfahren für N Elemente benötigt im
Mittel und im schlechtesten Fall eine Laufzeit von wenigstens
T(N)O(N·log N) Vergleiche
28.06.2012
13
Sortierverfahren - Klassifizierung von Sortiertechniken
Sortieren durch
L1
FB Informatik
Prof. Dr. R.Nitsch
Min. Element von L2
1) Auswählen
2) Einfügen
sortiert
sortiert
Elementare Sortierverfahren
3) Austauschen
Ki>K
4) Zerlegen
K
Kj>K
5) Mischen
10 40 60 … … …
bereits sortiert
20 30 50 … … …
L1
10 20 30 40 50 60 … …
L
L2
"Reißverschlußverfahren"
6) …noch viel mehr
28.06.2012
14
Sortieren durch Austauschen (exchange sort, bubble sort)
Austauschen
lokal
first
Idee:
Beginnend am Anfang der unsortierten
Teilreihe werden jeweils Elementpaare
gebildet.
Die Elemente eines Paares werden
verglichen und dann getauscht, wenn das
größere Element näher am Anfang der
Reihe liegt.
Nach N-1 Schritten ist der 1. Sortierlauf
beendet und das größte Element zum Ende
der Reihe wie eine Blase "aufgestiegen" (
Bubble Sort). Es bildet dort das 1. Element
der teilsortierten Reihe.
Der Vorgang wird mit der um ein Element
kleineren unsortierten Teilreihe wiederholt.
Nach N-1 solchen Sortierläufen ist der
Sortiervorgang abgeschlossen.
last
420
97
97
97
97
97
420
420
420
420
420
420
420
301
301
301
301
301
420
35
35
35
35
35
420
…
…
…
…
…
teilsortierte Reihe
first
last
unsortierte Reihe
97
97
97
97
420
420
301
301
301
301
420
35
35
35
35
420
420
420
420
420
…
…
…
…
Ist der Sortiervorgang stabil?
28.06.2012
FB Informatik
Prof. Dr. R.Nitsch
ja
15
Implementierung von Bubble-Sort
FB Informatik
Prof. Dr. R.Nitsch
/* BubbleSort - Sorts a subsequence [pfirst,plast)
@param pfirst pointer to first element of subsequence
@param plast pointer to one-past-the-end element of the subsequence */
// version 1 (with pointer)
void bubbleSort( int* pfirst, int* plast ) {
for ( int* plastu=plast-1; plastu>pfirst; --plastu )
plastu verweist auf Ende der unsortierten Reihe
for ( int* pp=pfirst ; pp<plastu ; ++pp )
if ( *(pp+1)<*pp )
pp verweist auf erstes Element eines Pärchens
std::swap( *pp, *(pp+1) );
Wenn Nachfolger kleiner
}
Plätze wechseln
// Version 2 (with pointer)
void bubbleSort( int* pfirst, int* plast ) {
bool swapped;
Version 2 nutzt die Eigenschaft aus, dass nach
for( int* plastu=plast-1; plastu>pfirst; --plastu )
einer Iteration, in der keine Vertauschungen
{ swapped = false;
stattfanden auch in den restlichen Iterationen
for( int *pp=pfirst ; pp<plastu ; ++pp )
keine Vertauschungen mehr stattfinden.
if( *(pp+1) < *pp ) {
swap( *pp, *(pp+1) );
swapped= true;
}
if( !swapped ) return;
}
28.06.2012
16
Aufwandsabschätzung für Bubble-Sort
Zeitkomplexität
Anz.Vgl   N  1  (N  2)  ...  1
immer gleich!
FB Informatik
Prof. Dr. R.Nitsch
first
N 1
 1  2  ...  (N  1)   k
k 1

(N  1)  N N
N


2
2
2
2
 O(N2)
N2 N
Anzahl Vertauschungen 

 O(N2)
4
4
97
97
97
420
420
301
301
301
301
420
35
35
35
35
420
420
420
420
420
…
…
…
…
teilsortierte Reihe
97
97
97
420
420
301
301
301
301
420
35
35
35
35
420
420
420
420
420
in-place
…
…
last
stabil
Vertauschungen: O(N2) im Mittel und im worst case; keine im best case.
Vergleiche (Ver.2): O(N2) im Mittel und im worst case; O(N) im best case.
Aufwand (Anzahl Vergleiche) ist unabhängig von Eingabeverteilung.
 Wird in der Praxis kaum eingesetzt.
…
…
Zusammenfassung
28.06.2012
first
unsortierte Reihe
97
Im Mittel halb so viele wie Vergleiche.





last
97
17
Sortieren durch Auswahl (selection sort, MinSort, ExchangeSort)
Idee:
Auswahl des kleinsten Elementes im
unsortierten Teil (L2) der Reihe
Austausch mit dem ersten Element der
unsortierten Teilreihe
Die teilsortierte Reihe (L1) ist danach
um 1 Element gewachsen. Die
unsortierte Reihe enthält 1 Element
weniger.
Nach N solchen Durchläufen ist die
Reihe sortiert.
first
last
420
35
35
35
35
420
420
97
97
97
97
97
420
301
301
301
301
301
420
420
35
420
420
420
420
…
…
…
…
…
teilsortierte Reihe
unsortierte Reihe
L1
Min. Element von L2
Auswählen
first
Ist der Sortiervorgang stabil?
FB Informatik
Prof. Dr. R.Nitsch
firstu
min
last
 nein!
Beispiel: s. Abb. ( Key 420 )
28.06.2012
18
Sortieren durch Auswahl - Komplexität und Eigenschaften
// Algorithmus in Pseudocode
prozedur selectionSort( a, first, last )
[ a[first], a[last] ) : Bereich sortierbarer Elemente
Variable: firstu:=first Position d. 1. Elem. der unsort. Reihe
wiederhole
Variable: min:= firstu
für jede Position pos von firstu+1 bis last-1 wiederhole
falls a[pos]<a[min] dann
min = pos
ende falls
ende für
Vertausche a[min] und a[firstu]
firstu = firstu+1
solange firstu<last
ende prozedur
first
35
35
35
420
420
97
97
97
97
97
420
301
301
301
301
301
420
420
35
420
420
420
420
…
…
…
…
…
teilsortierte Reihe

k 1




unsortierte Reihe
Min. Element von L2
L1
a
firstu
min
last
Zusammenfassung
Anzahl Vertauschungen unabhängig von
Eingabeverteilung: N-1
Anzahl Vgl. unabhängig von Eingabeverteilung
In-situ-Verfahren
O(N2) Vergleiche
besser als Bubble-Sort!
nicht stabil
(N  1)  N N
N


 O(N2)
2
2
2
N 1
Max. Anzahl der Vertauschungen:
28.06.2012
(bei invers sortierter Reihe)

2
35
first
N 1
 1  2  ...  (N  1)   k
420
last
Anzahl der Vergleiche (immer gleich!):
 N  1  (N  2)  ...  1
FB Informatik
Prof. Dr. R.Nitsch
19
Sortieren durch Einfügen (insertion sort)
FB Informatik
Prof. Dr. R.Nitsch
Idee: Vorgehen wie beim Sortieren eines
first
420
97
97
35
35
Kartenspiels.
97
420
420
97
97
1. Starte mit oberster Karte den sortierten
Stapel
420
420
420
420
301
2. Nimm die jeweils nächste Karte vom
35
35
35
420
420
unsort. Stapel
301
301
301
301
420
3. Füge sie an der richtigen Stelle im
last
…
…
…
…
…
sortierten Stapel ein
 In einem Array müssen die größeren
Elemente um 1 Indexposition weiter
teilsortierte Reihe
unsortierte Reihe
rücken, um dem kleineren Einfügeelement
prozedur insertionSort( a, first, last )
Platz zu machen.
[ a[first], a[last] ) : Bereich sortierbarer Elemente
 Mit jedem Einfügeschritt wird der
pos: mögliche Einfügeposition
sortierte Stapel um 1 ein Element größer.
posC: Position des nächsten Einfügekandidaten
 Nach N-1 solchen Einfügeschritten ist der
valC: Wert des nächsten Einfügekandidaten
Sortiervorgang abgeschlossen.
für jedes Element von posC=first+1 bis last-1 wiederhole
6 54321
valC
a
first
28.06.2012
sortiert
pos
posC
last
pos = posC
valC := a[posC]
solange first<pos und valC<a[pos-1] wiederhole
a[pos] = a[pos-1]
pos = pos-1
> → stabil
ende solange // Einfügepos. gefunden >= → instabil
a[pos] = valC // Einfügen
ende für
ende prozedur
20
Sortieren durch Einfügen - Komplexität und Eigenschaften
Worst Case bei invers sortierter Reihe
first
Max. Anzahl der Vergleiche:
N 1
 1  2  ...  (N  1)   k
k 1
(N  1)  N N 2 N



2
2
2
 O(N2)
last
420
97
97
35
35
97
420
420
97
97
420
420
420
420
301
35
35
35
420
420
301
301
301
301
420
…
…
…
…
…
teilsortierte Reihe
Max. Anzahl der Verschiebungen:
FB Informatik
Prof. Dr. R.Nitsch
unsortierte Reihe
1  2  ...  (N  1)  N (N  1) / 2
Best Case bei sortierter Reihe
Min. Anzahl der Vergleiche:
1  1  ...  1  N  1
Min. Anzahl der Verschiebungen:
 O(N)
0
 Vorteilhaft bei fast sortierten Reihen:  O(N)
In-place-Verfahren, stabil
28.06.2012
21
Sortieren durch Mischen (Mergesort; John von Neumann 1945)
FB Informatik
Prof. Dr. R.Nitsch
Gegeben folgendes Feld der Größe 10.
3
8
9 11 18 1
7 10 22 32
Die beiden "Hälften" sind hier bereits vorsortiert!
Wir können das Feld sortieren, indem wir jeweils von der ersten oder der zweiten
Hälfte ein Element wegnehmen, je nachdem, welches "dran" ist, . . . und die
weggenommenen Elemente in ein anderes Feld kopieren.
1
3
7
8
3
8
9 11 18
1
7 10 22 32
9 10 11 18 22 32
Verschmelzen (Merge)
Vorsortierte Teilreihen
Falls die beiden Hälften nicht schon sortiert sind, dann müssen sie eben vorher
sortiert werden.
Wie? Durch Verschmelzen der jeweiligen Hälften (also Viertel). Und wenn die auch
nicht schon sortiert sind?
Dann werden eben wiederum die jeweiligen Hälften (also Achtel) verschmolzen, usw.
bis man bei Feldern der Größe Eins angelangt ist und die sind ja stets sortiert.
Mergesort beruht auf dem "Teile und herrsche" Prinzip
28.06.2012
22
Mergesort - top-down approach (rekursiv)
FB Informatik
Prof. Dr. R.Nitsch
Aufruf: mergesort(a,0,7)
1
8
5
1
8
8
11
5
1 10
2
6
5
2
4 7
8
5
3
0
11 7
14
8
1
5
5
0 11 7
12 13
1 10
3
10
4
3
1 10
5
7
16
7 11
10
1
15
11
9
8
unsortiert, 1. Aufrufebene
1 10 0 11 7
17
2
8 10
0
7 11
algorithm mergeSort (F) -> FS
Eingabe: eine zu sortierende Folge F
Ausgabe: eine sortierte Folge FS
if F einelementig then return F
else
teile F in der Mitte in F1 und F2;
// 1. Aufruf
F1:= mergeSort(F1);
// 2. Aufruf
F2:= mergeSort (F2);
F:= merge(F1,F2);
fi
return F;
merge implementiert die Mischfunktion!
18
0
28.06.2012
1
5
7
8 10 11
1
sortiert, 1. Aufrufebene
23
Implementierung in C++
22.6.2012
FB Informatik
Prof. Dr. R.Nitsch
Aufruf: mergesort(a,0,7)
f[]
0
1
8
5
first
2
3
4
5
6
7
1 10 0 11 7
?
mid
last
algorithm mergeSort (F) -> FS
Eingabe: eine zu sortierende Folge F
Ausgabe: eine sortierte Folge FS
if F einelementig then
return F
else
teile F in der Mitte in F1 und F2;
F1:= mergeSort(F1);
F2:= mergeSort (F2);
F:= merge(F1,F2);
fi
return F;
28.06.2012
unsortiert, 1. Aufrufebene
void mergeSort(T f[],int first, int last ) {
// Eingabe: eine zu sortierende Folge f
// Ausgabe: eine sortierte Folge fs
// Sortierbereich [first,last); last-1 ist letztes Element.
if (last-first <= 1) // einelementig oder leer
return;
else
int mid = (first+last)/2; // teile
mergeSort(f, first, mid);
mergeSort(f, mid, last);
merge(f, first, mid, last);
}
return;
}
24
Algorithmus merge in Pseudocode
procedure merge (F1,F2) -> F
Eingabe: zwei vorsortierte Folgen F1, F2
Ausgabe: eine sortierte Folge F
F:= leere Folge;
while F1 oder F2 nicht leer do
Entferne das kleinere der Anfangselemente
aus F1 bzw. F2
Füge dieses Element an F an
od;
Füge die verbliebene nichtleere Folge F1
oder F2 an F an;
return F;
28.06.2012
FB Informatik
Prof. Dr. R.Nitsch
void merge( T f[], int first, int mid, int last ) {
/* Eingabe: 2 Folgen F1:=F[first...mid), F2:=F[mid...last) Ausgabe: eine sortierte Folge F
*/
vector<T> tmp(last‐first); // temporary field tmp
int i1 = first; // next element in F1
int i2 = mid ; // next element in F2
while ( i1<mid && i2<last) { if ( f[i1]<f[i2] ) tmp.push_back( f[i1++] );
else
tmp.push_back( f[i2++] );
}
while (i1<mid) tmp.push_back( f[i1++] );
while (i2<last) tmp.push_back( f[i2++] );
// copy back the temporary field tmp
for ( int j=0; j<tmp.size(); j++ )
f[first+j] = tmp[j];
return;
}
25
Mergesort - Komplexität und Eigenschaften
void mergeSort( int f[], int first, int last ) { if (last‐first<=1) return;
int mid = (first+last)/2;
// sort the first and the second half
T(N/2)
mergeSort(f, first, mid);
T(N/2)
mergeSort(f, mid, last);
merge(f, first, mid, last);
3N
}
FB Informatik
Prof. Dr. R.Nitsch
gesucht: T(N) = ?
Merge: je Element
1Vergleich
1 Kopieren in Hilfsarray
1 zurück kopieren
= 3·N
Annahme: N ist 2er-Potenz
T(20) = 0
= 0
einsetzen
T(21) = 2·T(20)+3·21 =
2· 0
T(22)
2·(3·21)
=
2·T(21)+3·22
=
+ 3·21 = 3·21
+
3·22 =
3·2·22
T(23) = 2·T(22) + 3·23 = 2·(2·3·22) + 3·23 = 3·3·23
T(24) = 2·T(23) + 3·24 = 2·(3·3·23) + 3·24 = 3·4·24
T(2k) = 3·2k·k
2k=N  k=log2N
T(N) = 3·N·log2 N
28.06.2012
T(N) = 2·T(N/2) + 3·N
Eigenschaften Mergesort
 Zeitkomplexität:
 Sensibel bzgl. Eingabevertlg:
 In-place-Verfahren:
 Zus. Speicherbedarf:
 stabil:
O(NlogN)
nein
nein
N
ja
Lösung
26
Quicksort (Nach C.A.R. Hoare, 1960)
FB Informatik
Prof. Dr. R.Nitsch
ist ein Teile-und-herrsche-Verfahren zum Sortieren
beruht auf dem Zerlegen (Partitionieren) eines Feldes in 2 Teile, die es dann
unabhängig voneinander sortiert
sortiert in-place aber ist nicht stabil
Grundidee
In einem Feld wird (zunächst) willkürlich ein Trennelement (TE) ausgesucht
Alle anderen Elemente werden neu angeordnet: Größere Elemente rechts und kleinere
bzw. gleich große Elemente links vom Trennelement.
Danach steht das Trennelement an der richtigen Position.
Zerlegen des Feldes (partition)
Trennelement (willkürlich immer rechts aussen)
Feld
linkes Teilfeld
TE
TE
rechtes Teilfeld
vorher
nachher
Die gleiche Zerlegung wird nacheinander rekursiv auf das linke und rechte Teilfeld
angewendet, d.h. es wird wieder je 1 TE an die richtige Stelle gebracht.
Dies wird solange wiederholt, bis es nichts mehr zum Zerlegen gibt: 0 oder 1 Element
im Teilfeld!
Zum Schluss ist das Feld vollständig sortiert.
28.06.2012
27
quicksort - Implementierung
FB Informatik
Prof. Dr. R.Nitsch
Trennelement (willkürlich immer rechts aussen)
Feld
linkes Teilfeld
Prozedur quicksort (pfirst, plast)
pfirst,r: definieren Sortierbereich [pfirst, plast)
ptrenn: endgültige Position des Trennzeichens
falls plast<=pfirst dann tue nichts
sonst
ptrenn := partition( pfirst, plast )
quicksort ( pfirst, ptrenn )
quicksort ( ptrenn+1, plast )
ende falls
ende prozedur
28.06.2012
vorher
nachher
rechtes Teilfeld
TE
pfirst
TE
plast
pt
template < typename T > void quicksort( T* pfirst, T* plast ) {
if( plast<=pfirst ) return; // base case
T* pt = partition( pfirst, plast );
quicksort( pfirst , pt
);
quicksort( pt+1 , plast );
return;
}
28
partition bei der Arbeit
vor 1. SL
pl pfirst
linkes Teilfeld
pfirst
TE
pl
pt
6
3
1
5
FB Informatik
Prof. Dr. R.Nitsch
pr
7
4
6
3
pl
1
5
pr
nachher
rechtes Teilfeld
vor 2.SL
plast
T* partition( T* pfirst, T* plast )
{
T* pl = pfirst‐1;
T* pt = plast‐1;
T* pr = plast‐1;
while (true) {
while( *(++pl)<*pt );
while( *pt<*(‐‐pr) && pr!=pfirst ); if( pl>=pr ) break;
std::swap(*pl,*pr);
}
std::swap( *pl, *pt );
return pl;
}
28.06.2012
nach 1. SL
vorher
pr pt plast
TE
4
pl
Trennelement (willkürlich immer rechts aussen)
Feld
7
1
4
6
3
pl
nach 2. SL
vor 3. SL
nach 3. SL
1
1
1
7
5
pr
4
4
4
6
3
pl
pr
3
6
pl
pr
3
6
pr
≤ pl
7
5
7
5
7
5
Tausche pl und pt
Ende partition
SL: Suchlauf
1
4
3
5
7
Endposition!
6
Quicksort bei der Arbeit
4
8
2
7
3
2
5
nach
2
4
3
2
5
8
6
7
pt
2
4
3
2
8
6
7
2
4
3
2
6
7
8
pt
4
3
2
6
8
6
8
(1) terminiert nicht ohne (3)
3
4
Ebene 3
3
3
4
4
Ebene 4
28.06.2012
endgültige
Position
6
pt
Ebene 2
gewähltes
Trennelem.
vor
partition
Ebene 1
T* partition( T* pfirst, T* plast )
{
T* pl = pfirst‐1;
T* pt = *(plast‐1);
T* pr = plast‐1;
while (true) {
while( *(++pl)<*pt ); //(1)
while( *pt<*(‐‐pr) ) //(2)
if( pr==pfirst ) break; //(3)
if( pl>=pr ) break; //(4)
std::swap(*pl,*pr);
}
std::swap( *pl,*pt );
return pl;
}
Ebene 0
void quicksort( T* pfirst, T* plast ) {
if(plast<=pfirst) return; //leere Folge
T* pt = partition( pfirst, plast );
quicksort( pfirst , pt
);
quicksort( pt+1 , plast );
return;
}
FB Informatik
Prof. Dr. R.Nitsch
2
3
3
pt
pt
terminiert
mit (4)
30
Kleine Teilfolgen
FB Informatik
Prof. Dr. R.Nitsch
Quicksort ruft sich bei vielen kleinen Teilfolgen selbst auf. Wegen des AufrufOverheads ist das nicht sehr effizient.
Enthält eine Teilfolge weniger als M Elemente, ist es besser, die restliche Sortierarbeit durch ein Standardsortierverfahren wie InsertionSort erledigen zu lassen.
Die optimale Schwelle M hängt von der Implementierung ab. In der Praxis verwendet
wird meist 8≤M ≤32.
void quicksortM( T* pfirst, T* plast ) {
if(plast‐pfirst<16) { // Kleine Teilfolge insertionSort(pfirst,plast);
return;
}
T*pt = partition( pfirst, plast );
quicksort( pfirst , pt );
quicksort( pt+1, plast );
return;
}
Kombinierter Quicksort (Hier: M=16)
Laufzeit ca. 40% kürzer
Alternative Implementierung des
Kombinierten Quicksort
28.06.2012
void quicksortI( T* pfirst, T* plast ) {
if(plast‐pfirst<16) { return; // Interrupt für kleine Teilfolgen }
T* pt = partition( pfirst, plast );
quicksort( pfirst , pt );
quicksort( pt+1, plast );
return;
}
void hybridsort( T* pfirst, T* plast )
{
quicksortI(pfirst,plast);
insertionSort(pfirst,plast);
}
31
Eigenschaften von Quicksort
FB Informatik
Prof. Dr. R.Nitsch
Quicksort ist ein Teile-und-Herrsche-Verfahren.
 Verarbeitet Daten "in place"
 Ist nicht stabil
 benötigt im ungünstigsten Fall (=bereits sortiert) ca. N2/2 Vergleiche  O(N2)
 Idealfall: Trennelement ist stets der Median des Teilfelds
 Halbierung der Teilfelder mit jeder Rekursion)  O(N·logN)
 Zuviel Overhead für kleine Teilfelder
Es gibt viele Varianten
28.06.2012
33
Quickie zum Thema "Graphen"
FB Informatik
Prof. Dr. R.Nitsch
(gerichteter) Graph:
besteht aus Knoten und gerichteten Kanten
o Knoten: einfaches Objekt, das einen Namen und andere zugeordnete
Informationen enthalten kann.
o Kante: Stellt eine Verbindung zwischen zwei Knoten her
zeigt eine gerichtete Kante von A nach B so istA direkter Vorgänger
von B und B direkter Nachfolger von A
Ausgangsgrad eines Knotens A:
o(A) := Anzahl seiner direkten Nachfolger
Eingangsgrad eines Knotens A
i(A) := Anzahl seiner direkten Vorgänger
o in G (s. Abb.) ist Knoten 0 direkter Nachfolger von Knoten 3; Knoten 2 hat
den Ausgangsgrad 1 und den Eingangsgrad 2. Knoten 4 ist isoliert.
Pfad:
Ist eine endliche Folgen von Kanten
o In G ist p:=30  1  2 ein Pfad mit Länge=3, 3 ist Anfangsknoten und 2 ist
Endknoten
p heißt Zyklus wenn Anfangsknoten = Endknoten
o G enthält den Zyklus 0 1 2 0
28.06.2012
Anwendungen für Graphen:
o Routing in Computernetzen,
o Navigation im Straßenverkehr,
o …
34
Quickie zum Thema "Bäume"
FB Informatik
Prof. Dr. R.Nitsch
Wurzelbaum: ist ein gerichteter Graph mit folgenden
Einschränkungen:
er hat genau 1 Knoten mit dem Eingangsgrad 0
(Wurzelknoten)
alle anderen Knoten haben den Eingangsgrad 1, und
er enthält keine Zyklen.
Wurzel
Innerer Knoten
Blatt
Wurzel-Baum
Blatt: Knoten ohne Nachfolger. Alle anderen werden innere Knoten genannt
Tiefe d (Ebene) eines Knotens A: d(A):=Länge des Pfades WurzelA
Tiefe des Baumes: d(T):= maximale Tiefe seiner Knoten
Ebene 0
Wurzel
d=0
Ebene 1
d=1
Ebene 2
d=2
Ebene 3
d=3
Wurzel-Baum
28.06.2012
Anwendungen für Bäume:
o Familienstammbäume,
o Organigramme
o Verzeichnisbäume
o Satzanalyse mit Parse-Baum
o Verarbeitung von Computersprachen
35
Quickie zum Thema binäre Bäume
FB Informatik
Prof. Dr. R.Nitsch
Binärer Baum: ist ein Wurzelbaum, bei dem jeder Knoten
maximal 2 Nachfolger hat.
Vollständiger Binärer Baum: ist ein Binärer Baum, bei dem
Wurzel
Binärer Baum
jede Ebene bis auf die unterste vollständig besetzt ist
auf der untersten Ebene höchstens Blätter ganz rechts fehlen.
ein vollständiger Baum mit N Knoten hat die Tiefe d(T)=int(log2(N))
N=7 Knoten
N=11 Knoten
Wurzel
N=12 Knoten
d=0
d=1
d=2
d=3
Anwendungen für binäre Bäume:
o Prioritätswarteschlangen,
o Heapsort,
o Symboltabellen,
o …
Vollständige Binäre Bäume
28.06.2012
36
Datenstruktur Heap
FB Informatik
Prof. Dr. R.Nitsch
Geordnete Bäume, Schlüsselbäume: die Knoten
dieser Bäume sind Objekte, die Schlüsselwerte
enthalten, für die eine Ordnungsrelation
definiert ist (z.B. < oder ≤)
29
10
39
6
27
92 44
43
2
31
33
11
Binärer Schlüsselbaum
Heap-geordneter binärer Schlüsselbaum: der
Schlüssel in jedem Knoten ist kleiner oder
gleich dem Schlüssel seines Vorgängerknotens.
Wurzel enthält den größten Schlüsselwert
Ein Heap ist die Arraydarstellung eines
vollständigen Heap-geordneten binären
Schlüsselbaums (siehe nächste Seite).
28.06.2012
92
27
44
33
29
11
43
31 39
6
2
10
Vollständiger Heap-geordneter
binärer Schlüsselbaum
37
Arraydarstellung eines vollständigen binären Schlüsselbaums
Die Knoten werden Zeile für Zeile von oben
beginnend durchnummeriert und ergeben so die
Indices des Arrays a[1,N].
Achtung: das erste Element bekommt den Index 1.
Vollständige Binärbäume lassen sich ganz einfach in
ein Array abbilden (siehe Abb. rechts). Es
bekommen
die Wurzel den Index 1
Knoten auf der 2. Ebene die Indices 2 und 3
…
Knoten auf Ebene d die Indices 2d-1 bis 2d-1
29
1
39
2
6
27
4
8
31
92 44
9
5
10
2
6
33
11
11
12=N
10
3
43
7
a 29 39 10 6 31 2 43 27 92 44 33 11
1 2 3 4 5 6 7 8 9 10 11 12
Arraydarstellung eines
vollständigen binären Schlüsselbaums
92
1
für Algorithmen ist es wichtig, zu jedem Knoten k
den direkten Vorgänger bzw. Nachfolger zu kennen:
Index des direkten Vorgängers: parent(k) = k div 2
Index des linken Nachfolgers: left(k) = k*2
Index des rechten Nachfolgers: right(k) = k*2+1
FB Informatik
Prof. Dr. R.Nitsch
44
2
39
27
8
4
33
6
31
9
10
5
29
2
11
12
11
6
43
3
10
7
a 92 44 43 39 33 11 10 27 6 31 29 2
1 2 3 4 5 6 7 8 9 10 11 12
Heap
28.06.2012
38
Heapsort – Phase 1
FB Informatik
Prof. Dr. R.Nitsch
ist das komplexeste Verfahren
arbeitet in zwei Schritten:
Daten in eine bestimmte Struktur bringen (Phase 1)
Die vorstrukturierten Daten sortieren (Phase 2)
Phase 1: Daten in einen Heap umwandeln
Man fängt in der vorletzten Zeile an (Feldlänge/2) und
"versickert" falsch platzierte Knoten:
Vergleiche den ausgesuchten Knoten mit den beiden
darunterliegenden (falls die existieren).
Ist er der Größte, dann passiert nichts weiter.
sonst vertausche ihn mit dem größten darunterliegenden
und versickere ihn von dieser Stelle aus weiter.
Gehe zum nächstkleineren Index und fange von vorne an.
Beispiel folgt!
28.06.2012
29
1
39
6
27
8
4
2
31
92 44
9
10
5
33
11
11
12
2
6
10
3
43
7
a 29 39 10 6 31 2 43 27 92 44 33 11
1 2 3 4 5 6 7 8 9 10 11 12
39
29
1
Beispiel zu Phase 1: Unsortiertes Array in Heap umwandeln
FB Informatik
Prof. Dr. R.Nitsch
39
Feldlänge N=12
27
4
8
Berechne N/2 => Erster Index ist 6. "Versickere" die 2: 11 ist
größer als 2, also vertausche die beiden.
Nächster Index ist 5 "Versickere" die 31: 44 ist die größte
Zahl, also vertausche die beiden.
Nächster Index ist 4 "Versickere" die 6: 92 ist die größte
Zahl, also vertausche die beiden.
Nächster Index ist 3 "Versickere" die 10: 43 ist die größte
Zahl, also vertausche die beiden.
Nächster Index ist 2 "Versickere" die 39: 92 ist die größte
Zahl, also vertausche die beiden. An dieser Position ist die 39
größer als die beiden Unterknoten. Kein weiterer Tausch.
2
6
5
92 44
9
2
6
31
10
33
11
11
12
29
1
39
2
92
27
4
8
44
6
31
9
10
5
33
2
11
12
11
6
29
1
92
2
39
27
4
8
44
6
31
9
10
5
33
2
11
12
11
6
92
1
44
Letzter Index ist 1 "Versickere" die 29: Vertauschung mit 92,
dann mit 44, dann mit 33.
27
8
28.06.2012
2
39
4
33
6
31
9
10
5
29
2
11
12
11
6
10
3
43
7
10
3
43
7
43
3
10
7
43
3
10
Heap a 92 44 43 39 33 11 10 27 6 31 29 2
1 2 3 4 5 6 7 8 9 10 11 12
7
Heapsort – Phase 2 mit Beispiel
FB Informatik
Prof. Dr. R.Nitsch
Phase 2: Daten sortieren
In diesem Schritt geht man davon aus, dass das Feld (oder der
äquivalente Baum) Heapstruktur besitzt.
Algorithmus: Wiederhole bis Baumgröße = 1
 Vertausche im Array das erste mit dem letzten Element.
 Verkürze das Array um ein Element. (Das letzte Element im
Array ist bereits das größte).
 Versickere das neue erste Element im verkleinerten Baum
Beispiel zu Phase 2
2
1
44
2
39
27
4
8
1.
33
5
6
31
9
10
29
92
11
12
11
6
44
1
2.
2
2
39
27
8
4
33
6
31
9
10
5
28.06.2012
29
92
11
12
11
6
43
3
3.
10
2
27
4
8
33
6
31
9
10
5
29
92
11
12
11
6
44
1
7
2
4. 27
2
8
4
33
6
31
9
10
5
2
39
27
4
8
33
5
6
31
9
10
29
2
11
12
43
3
29
92
11
12
11
6
43
3
10
2
7
8
4
1.
2
33
6
31
9
10
5
44
92
11
12
2
4. 27
2
8
4
31
6
43
9
10
11
6
39
1
33 3.
7
10
7
2.
43
1
27
10
11
6
43
3
Heap am Ende von Phase 1
2.
39
10
44
39
39
2
7
43
3
44
1
92
1
5
1.
44
92
11
12
11
6
29
3
10
7
29
3
10
7
41
Heapsort - Implementierung
FB Informatik
Prof. Dr. R.Nitsch
Pseudocode
prozedur heapsort( a[], l, r )
N:=r-l Feldlänge
pr:=a+l Adresse des 1. Objekts
Phase 1: Heap erzeugen
für k:=N/2 bis k=1 wiederhole
versickere Knoten k
ende wiederhole
Phase 2: Sortieren
solange Feldlänge>1 wiederhole
vertausche erstes mit letztem Feldelement
Reduziere Feldlänge um 1
versickere 1. Element.
ende wiederhole
ende prozedur
28.06.2012
C++ Quellcode
void heapsort( T a[], int l, int r ) {
int N=r‐l;
T* pr = a+l; // pointer to root
// Phase 1: Heap erzeugen
for( int k=N/2; k>=1; ‐‐k ) versickern(pr,k,N);
// Phase 2: Sortieren
while (N>1) {
swap( pr[0], pr[N‐1] );
‐‐N; versickern(pr,1,N);
} }
42
Heapsort - Implementierung
Pseudocode
prozedur versickern( a[], k, N )
a: Adresse des 1.Objekts eines Arrays mit N Objekten
k: Position eines ggf. zu versickernden Parent-Knotens
solange ein linker Nachfolger existiert wiederhole
merke Position des linken Nachfolgers
falls auch ein rechter Nachfolger existiert
und falls dieser größer als sein linker Bruder ist
merke dessen Position
Falls der Nachfolger an der gemerkten Position
nicht größer als dessen Vorgänger ist
fertig
sonst
vertausche die beiden
Mache den Nachfolger zum nächsten Vorgänger
ende falls
ende wiederhole
ende prozedur
28.06.2012
FB Informatik
Prof. Dr. R.Nitsch
C++ Quellcode
// Versickern des Knotens k von N Knoten
void versickern( T a[], int k, int N )
{
int& parent = k; int left=2*k;
while ( left<=N ) {
int greater = left; if ( left<N && a[(left)‐1]<a[(left+1)‐1] ) ++greater;
if ( !(a[(parent)‐1]<a[(greater)‐1]) ) break;
else {
swap(a[(parent)‐1],a[(greater)‐1]);
parent=greater;
left = 2*parent;
} // end if
} // end while
return;
} // end versickern
43
Empirischer Laufzeitvergleich der Sortierverfahren
FB Informatik
Prof. Dr. R.Nitsch
Sortieren eines Arrays von 10000 Objekten (4 Byte/Objekt)
Sortiermethode
median_qsort (M=32)
hybridsort (M=32)
quicksort
std::sort
std::stable_sort
heapsort
mergesort
28.06.2012
Laufzeit/ms
0,49
0,53
0,64
0,66
0,67
0,71
1,87
rel. Performance
100%
108%
131%
135%
137%
145%
382%
44
Absteigend sortieren
FB Informatik
Prof. Dr. R.Nitsch
Alle Sortieralgorithmen brauchen eine Ordnungsrelation. Diese ist im Standardfall
(aufsteigende Sortierung) der '<'-Operator (s. Beispiel)
Standardfall: Aufsteigende Sortierung
//Sortieren im Bereich [first,last)
void bubbleSort( int* pfirst, int* plast ) {
for ( int* plastu=plast-1; plastu>pfirst; --plastu )
for ( int* pp=pfirst ; pp<plastu ; ++pp )
if ( *(pp+1)<*pp )
std::swap( *pp, *(pp+1) );
}
Nachteil dieser Implementierungen:
Sie sind nur eingeschränkt
wiederverwendbar (nur aufsteigende
oder nur absteigende Sortierung)
Absteigende Sortierung Was ist zu ändern?
//Sortieren im Bereich [first,last)
void bubbleSort( int* pfirst, int* plast ) {
for ( int* plastu=plast-1; plastu>pfirst; --plastu )
for ( int* pp=pfirst ; pp<plastu ; ++pp )
if ( *(pp+1)<*pp )
std::swap( *pp, *(pp+1) );
}
28.06.2012
45
Vermeidung der Kopplung zwischen Algorithmus und Ordnungsrelation
FB Informatik
Prof. Dr. R.Nitsch
Universell einsetzbare Sortieralgorithmen indirekt indem sie die Entscheidung über die
Sortierreihenfolge an eine bestimmte Funktion delegieren. Diese testet, ob die beiden
Operanden in der gewünschten Ordnungsrelation zueinander stehen und gibt das Ergebnis als
Wahrheitswert zurück.
Beispiel:
bool compare( int a1, int a2 ) {
return (a1>a2);
}
//Sortieren im Bereich [first,last)
void bubbleSort( int* pfirst, int* plast ) {
for ( int* plastu=plast-1; plastu>pfirst; --plastu )
for ( int* pp=pfirst ; pp<plastu ; ++pp )
if ( compare(*pp,*(pp+1))==false )
std::swap( *pp, *(pp+1) );
}
Vorteil: Sortieralgorithmus und Ordnungsrelation sind entkoppelt.
Nachteil: Der Sortieralgorithmus kann (im selben Programm) nicht nach verschiedenen
Ordnungsrelationen sortieren.
28.06.2012
46
Sortieralgorithmus für unterschiedliche Ordnungsrelationen
FB Informatik
Prof. Dr. R.Nitsch
Verbesserung: Die Vergleichsfunktion wird als Funktionszeiger oder Funktionsobjekt
übergeben.
Beispiel mit Funktionszeiger:
//Sortieren im Bereich [first,last)
void bubbleSort( int* pfirst, int* plast, bool (*cmp)(int,int) ) {
for ( int* plastu=plast-1; plastu>pfirst; --plastu )
for ( int* pp=pfirst ; pp<plastu ; ++pp )
if ( cmp(*pp,*(pp+1))==false )
std::swap( *pp, *(pp+1) );
}
Hinweis: Funktionen zum Vergleichen zweier Operanden bezeichnet man auch als binäre
Predicate-Funktion.
Aufgabe: Implementieren sie eine binäre Predicatefunktion, die auf der Menge der Ganzzahlen
vomTyp int die Ordnungsrelation "ist betragsmäßig kleiner als" auswertet. Sortieren Sie das
Feld a für nach aufsteigenden Beträgen.
int
a[10] = { ‐5,‐4,‐3,‐2,‐1,1,2,3,4,5 };
for( int i=0; i<MAX_I; ++i ) { cout << a[i] << endl; }
28.06.2012
47
Generischer Sortieralgorithmus
FB Informatik
Prof. Dr. R.Nitsch
Ein generischer Algorithmus
erfüllt seine Aufgabe unabhängig vom Typ der Eingabeobjekte.
wird in C++ durch Templates realisiert
Aufgabe: Wandeln Sie bubblesort in ein Template um. Elementtyp und Typ des
Predicate (Funktionszeiger oder Funktionsobjekt) sollen austauschbar sein. Danach
wenden Sie es an.
//Sortieren im Bereich [first,last)
void bubbleSort( int* pfirst, int* plast, bool (*cmp)(int,int) ) {
for ( int* plastu=plast‐1; plastu>pfirst; ‐‐plastu ) for ( int* pp=pfirst ; pp<plastu ; ++pp )
if ( cmp(*pp,*(pp+1))==false )
std::swap( *pp, *(pp+1) );
}
int
a[10] = { ‐5,‐4,‐3,‐2,‐1,1,2,3,4,5 };
Aufruf mit Funktionszeiger
Aufruf mit Funktionsobjekt
for( int i=0; i<MAX_I; ++i ) { cout << a[i] << endl; }
28.06.2012
48
Sortieralgorithmen der STL
FB Informatik
Prof. Dr. R.Nitsch
STL stellt 4 generische Sortieralgorithmen zur Verfügung: sort, stable_sort,
partial_sort und heap_sort. Alle sind als Funktions-Template implementiert.
Vorbedingung:
physikalisch sequentielle Container (z.B. C-Array, string, vector, … )
Verwendete Ordnungsrelation:
operator< für den Elementtyp (default) oder benutzerdefiniert in Form eines PredicateObjekts oder einer Predicate-Funktion
Eigenschaft
Zeitkomplexität
sort
partial_sort
stable_sort
O(N log N) bis O(N2) O(N log N) O(N log N) bis O(N (log N)2)
sort_heap
O(N log N)
in-situ
ja
ja
ja
ja
Stabilität
nein
nein
ja
nein
Hybrid-Sort
(mit Quicksort)
heap-SortPrinzip
merge-SortPrinzip
• stable_sort ist etwa 40% langsamer als sort
28.06.2012
49
Anwendung von std::sort
FB Informatik
Prof. Dr. R.Nitsch
a) Mit operator< aufsteigend sortieren
#include <algorithm>
int a[10] = {5,‐4,+3,‐2,+1,‐1,2,‐3,4,‐5 };
std::sort( a, a+10 ); for( int i=0; i<MAX_I; ++i ) { cout << a[i] << endl; }
b) Sortieren mit binärem Predicate
#include <algorithm>
int a[10] = {5,‐4,+3,‐2,+1,‐1,2,‐3,4,‐5 };
std::sort( a, a+10, less_abs ); Aufruf mit Funktionszeiger
std::sort( a, a+10, Less_abs ); Aufruf mit Funktionsobjekt
for( int i=0; i<MAX_I; ++i ) { cout << a[i] << endl; }
Die Aufrufsyntax für sort und stable_sort ist identisch
28.06.2012
50
Anwendung von std::sort_heap
FB Informatik
Prof. Dr. R.Nitsch
Die beiden Phasen von Heapsort sind in der STL getrennt implementiert.
std::make_heap(first,last) stellt die Heapstruktur her.
std::sort_heap(first,last) setzt einen Heap voraus und übernimmt die Sortieraufgabe.
Beide Funktionen, hintereinander angewendet, erzeugen eine aufsteigende Sortierung
Die überladenen Varianten der beiden Funktionstemplates akzeptieren ein binäres
Predicate als 3. Parameter.
a) Mit operator< aufsteigend sortieren
#include <algorithm>
int a[10] = {5,‐4,+3,‐2,+1,‐1,2,‐3,4,‐5 };
std::make_heap( a, a+10 ); sort_heap( a, a+10 ); b) Sortieren mit binärem Predicate
#include <algorithm>
int a[10] = {5,‐4,+3,‐2,+1,‐1,2,‐3,4,‐5 };
std::make_heap( a,a+10,less_abs ); std::sort_heap( a,a+10,less_abs ); Aufruf mit Funktionszeiger
std::make_heap( a,a+10,Less_abs() ); std::sort_heap( a,a+10,Less_abs ); Aufruf mit Funktionsobjekt
28.06.2012
51
Null-terminierte Zeichenketten (C-Strings) lexikographisch sortieren
FB Informatik
Prof. Dr. R.Nitsch
Die folgende Anwendung erzeugt eine Liste von 10 Wörtern in einem statischen Container
std::array und gibt diese aus.
Sortieren Sie diese Liste lexikographisch korrekt (unabhängig von Groß-Klein-Schreibung)
#include <array>
#include <algorithm>
#include <iostream>
using namespace std;
const int MAX_W=10, MAX_L=32;
int main() {
array< array<char,MAX_L>, MAX_W > words = { "Zahlen", "EUROPA", "Euro","Ente", "Entenbraten", "entern", "Derby", "derb", "Rettung", "Rettungsschirm" };
std::sort( words.data(), words.data()+MAX_W, Lexikographic_less() );
for( int i=0; i<MAX_W; ++i )
cout << words[i].data() << endl;
}
28.06.2012
52
Implementierung der lexikographischen Ordnungsrelation
FB Informatik
Prof. Dr. R.Nitsch
class Lexikographic_less {
public:
bool operator() ( array<char,MAX_L> op1, array<char,MAX_L> op2 )
{
int i=0; // Aktuelle Zeichenposition
while( op1[i]!='\0' && op2[i]!='\0' ) { // Solange kein Wortende erreicht ist wiederhole
char c1 = tolower( op1[i] ); // lies Zeichen c1=op1[i] und c2=op2[i] von aktueller Pos.
char c2 = tolower( op2[i] ); // und wandle Groß‐ in Kleinbuchstaben (Header <cctype>)
if(c1<c2) // Falls Buchstabe c1 vor c2 im Alphabet steht
return true;
// steht op1 vor op2
else if (c1>c2) // sonst falls Buchstabe c1 nach c2 im Alphabet steht
return false; // steht op2 vor op1
else
// sonst noch keine Entscheidung möglich (c1==c2)
++i;
// betrachte nächsten Buchstaben
}
if( op1[i]!='\0' ) // Falls Zeichenkette op1 länger als op2 ist return false;
// steht sie hinter op2
else
// sonst
return true;
// steht sie vor op2
}
};
28.06.2012
53
Herunterladen