O(1) - Erstellen persönlicher Webseiten

Werbung
Fakultät Informatik, Institut für Technische Informatik, Professur Rechnerarchitektur
Effiziente Parallele Algorithmen
Vorlesung 1
Zellescher Weg 12
Nöthnitzer Straße 46
Willers-Bau A 205
Raum 1044
Tel. +49 351 - 463 - 35450
Tel. +49 351 - 463 - 38246
Wolfgang E. Nagel ([email protected])
Effiziente Parallele Algorithmen
!   Lehrveranstaltung jeweils im Wintersemester
!   Zeit und Ort:
–  Dienstag, 5. Doppelstunde (14:50 - 16:20 Uhr), ab 16. Oktober 2012
–  INF / E010
!   Studiengänge:
–  Studiengänge Informatik (Bachelor, Master, Diplom)
–  Bachelor-Studiengang Medieninformatik
–  Studiengang Informationssystemtechnik
–  Master-Studiengang Mathematik und Technomathematik
–  Module: INF-B-510, INF-B-520, INF-B-530, INF-B-540 und INF-VERT5
!   Umfang:
–  2 SWS Vorlesung
–  2 SWS Übung
!   Form des Abschlusses: Prüfung (mündlich)
Wolfgang E. Nagel
Effiziente Parallele Algorithmen: Übung
!   Betreuung durch Prof. Nagel / Michael Wagner
–  Jeweils wöchentlich
!   Zeit und Ort:
–  Freitag, 4. Doppelstunde (13:00 - 14:30 Uhr) – Beginn am 26.10.2012
–  INF / E 010
!   Prüfung nach Abschluss der Vorlesungszeit
Wolfgang E. Nagel
Lehrveranstaltungen der Professur Rechnerarchitektur
Wintersemester
Hochleistungsrechner und ihre Programmierung I
2/2
Prof. Nagel
Einführung in die Technische Informatik
4/2/2
Prof. Nagel
Konzepte der parallelen Programmierung
2/0
Dr. Trenkler
Leistungsanalyse für Rechnersysteme
2/2
Komplexpraktikum Linux-Cluster in Theorie und Praxis
0/0/4
Hochparallele Simulationsrechnungen mit CUDA und OpenCL
2/2
Komplexpraktikum "Paralleles Rechnen"
0/0/4
Hauptseminar Rechnerarchitektur und Programmierung
0/2
Dr. Müller/
Dr. Brunst
Prof. Nagel/
DI Georgi
Prof. Nagel/
DI Juckeland
Prof. Nagel /
Dr. Trenkler
Prof. Nagel
Lecture: Mathematical Biology: Statistical Modelling
2/0
Prof. Deutsch/
Dr. Beyer
Wolfgang E. Nagel
Effiziente Parallele Algorithmen
!   Die Lehrveranstaltungen sind Bestandteil folgender Studiengänge:
–  Bachelor-Studiengang Informatik (Module: INF-B-510, INF-B-520)
–  Master-Studiengang Informatik
(Module: INF-BAS5, INF-VERT5, INF-MA-PR)
–  Diplom-Studiengang Informatik (Module: INF-BAS5, INF-VERT5 )
–  Bachelor-Studiengang Medieninformatik (Module: INF-B-530, INF-B-540)
–  Studiengang Informationssystemtechnik (Module: INF-BAS5, INF-VERT5 )
–  Master-Studiengang Mathematik und Technomathematik
nur die Lehrveranstaltungen
•  Effiziente parallele Algorithmen
•  Hochleistungsrechner und ihre Programmierung I
•  Konzepte der parallelen Programmierung
Wolfgang E. Nagel
Lehrveranstaltungen der Professur Rechnerarchitektur
Hochleistungsrechner und ihre Programmierung I
Vorlesung (V2, Prof. Nagel) und Übung (Ü2, Dr. Trenkler)
im WS 2012/2013:
Mittwoch, 9:20 - 10:50 Uhr - WIL A317
Beginn der Übung:
Montag, 6. DS - INF E008
Themenbereiche:
–  Konzepte der Parallelverarbeitung
–  Parallele und skalierbare Architekturen
•  Architekturkonzepte
•  Leistungsmerkmale
•  Aktuelle Beispiele
- ab 22.10.
–  Software und die Programmierung
•  Parallele Programmiermodelle
•  Programmentwicklung
•  Betriebssystemunterstützung
•  Praktische Erfahrungen
–  anwendungsnahe, interdisziplinär
orientierte Programmierung von
Parallelrechnern
•  Algorithmische Fallbeispiele
•  Nutzung von Werkzeugen
Wolfgang E. Nagel
Lehrveranstaltungen der Professur Rechnerarchitektur
Basismodul : Einführung in die Technische Informatik
Vorlesung (jeweils V4/Ü2/P2), (Modul INF-BAS5)
Diese Vorlesungsreihe wird als Ringvorlesung von den drei Professoren der
Technischen Informatik angeboten.
Zeiten im WS 2012/2013:
Vorlesung:
Donnerstag, 3. und 4. DS - INF E006
Übung:
Freitag, 3. DS - INF E001
Praktika:
Donnerstag, 6. DS - INF E010
Prof. Spallek
- 11.10.2012 bis 08.11.2012 - VLSI-Entwurfssysteme
Prof. Pionteck
- 15.11.2012 bis 13.12.2012 - Eingebettete System
Prof. Nagel
- 20.12.2012 bis 01.02.2013 - Parallelverarbeitung
Wolfgang E. Nagel
Lehrveranstaltungen der Professur Rechnerarchitektur
Leistungsanalyse für Rechnersysteme
Vorlesung (V2, Dr. Müller, Dr. Brunst) und Übung (Ü2, Dr. Brunst)
im WS 2012/2013:
Mittwoch, 13.00 – 14.30 Uhr - INF E001
Übung: Donnerstag, 13.00 – 14.30 Uhr - INF E046
Themenbereiche:
–  Performance-Analyse = Analyse + Computersysteme
–  „Performance Analyst“ = Mathematiker und Informatiker
!   Spezifikation von Performance-Anforderungen
!   Evaluierung von Design-Alternativen
!   Vergleich von zwei und mehreren Systemen
!   Bestimmung eines optimalen Wertes für einen Parameter (System Tuning)
!   Identifikation von Engpässen
!   Charakterisierung der Last auf einem System
!   Bestimmung der Anzahl und der Größe von Komponenten
!   Vorhersage der Performance für zukünftige Lasten
Wolfgang E. Nagel
Lehrveranstaltungen der Professur Rechnerarchitektur
Konzepte der parallelen Programmierung
Vorlesung (V2, Dr. Trenkler )
im WS 2012/2013:
Freitag, 11.10 – 12.40 Uhr - INF E007
Themenbereiche:
!   Parallele Maschinenmodelle
!   Parallele Programmiermodelle
!   Entwurf von paralleler Software
!   MPI (Message Passing Interface)
!
OpenMP
!   CAF (Co-Array Fortran)
!   Gegenüberstellung von MPI, OpenMP und CAF
Wolfgang E. Nagel
Lehrveranstaltungen der Professur Rechnerarchitektur
Linux-Cluster in Theorie und Praxis
Vorlesung (P4, Prof. Nagel/ DI Georgi)
im WS 2012/2013: Montag und Donnerstag, 14.50 – 16.20 Uhr - INF E040
Themenbereiche:
!   Aufbau eines Linux-Clusters unter Anleitung
!   Dazu gehört:
•  Installation von Betriebssystem und Systemsoftware
•  Konfiguration und Administration der einzelnen Softwarekomponenten
•  Leistungsuntersuchung des laufenden Systems
•  Hardware-Aufbau
!   Die praktische Umsetzung der Projektphasen erfolgt jeweils nach einer
theoretischen Einführung in die zu bearbeitende Problematik
Wolfgang E. Nagel
Lehrveranstaltungen der Professur Rechnerarchitektur
Hochparallele Simulationsrechnungen mit CUDA und OpenCL
Vorlesung (V2/Ü2, Prof. Nagel / DI Juckeland)
im WS 2012/2013:
Vorlesung: Montag, 11.10 – 12.40 Uhr - INF E069
Übung: Dienstag, 11.10 – 12.40 Uhr - INF E069
Themenbereiche:
!   Simulationen bzw. Berechnungen mit hochparallelen Prozessoren
!   Untergliederung in:
•  Einführung GPU Computing und CUDA
•  Hard-/Software-Ökosystem
•  Speichermanagement
•  Parallele Algorithmen
•  Performance Tuning
•  OpenCL
•  Fallstudien
Wolfgang E. Nagel
Lehrveranstaltungen der Professur Rechnerarchitektur
Mathematical Biology: Statistical Modelling
Vorlesung (Prof. Deutsch, Dr. A. Beyer)
im WS 2012/2013: Dienstag, 14.50 – 16.20 Uhr - Biotec - CRTD Raum 2
(Beginn 16.10.2012)
Themenbereiche:
!   Statistische Methoden, t-Test, Lineare Regression
!   Methoden des Maschinenlernens, Clusteranalyse, Zeitreihenanalyse
!   jeweils mit Bezug auf reale biologische Daten
!   Einführung in die experimentellen Techniken zur Erhebung der biologischen
Datensätze
Wolfgang E. Nagel
Lehrveranstaltungen der Professur Rechnerarchitektur
Hauptseminar Rechnerarchitektur und Programmierung
!   Vorbesprechung/ Eröffnung: 16.10.2012 - 17.00 Uhr, INF 1005
!   Beginn der Veranstaltung:
Später im Semester, je nach Belegung, evtl. Blockcharakter
!   Themen
–  Speichersysteme
–  Prozessoren
–  Verbindungsstrukturen
–  Programmierung
Wolfgang E. Nagel
Lehrveranstaltungen der Professur Rechnerarchitektur
Sommersemester
!   Hochleistungsrechner und ihre Programmierung II 2/2
Prof. Nagel
!   Rechnerarchitektur II
2/2
Prof. Nagel
!   Struktur und Operationsprinzip von Prozessoren
2/0
Prof. Nagel
DI Juckeland
!   Verbindungseinrichtungen in parallelen Systemen
2/0
Prof. Nagel
DI Georgi
!   Proseminar Rechnerarchitektur
0/2
Prof. Nagel
!   Hauptseminar
Rechnerarchitektur und Programmierung
0/2
Prof. Nagel
!   Mathematische Biologie
2/2
Prof. Deutsch
Seminar: Biology and Mathematics of Brain Tumours
Dr. Beyer
Dr. Klink
Wolfgang E. Nagel
Lehrveranstaltungen der Professur Rechnerarchitektur
Hochleistungsrechner und ihre Programmierung II
Vorlesung (jeweils V2/Ü2)
wieder im SS 2013
Themenbereiche:
!   Konzepte der Parallelverarbeitung
!   Software und die Programmierung
!   Parallele und skalierbare Architekturen
–  Parallele Programmiermodelle
–  Architekturkonzepte
–  Programmentwicklung
–  Leistungsmerkmale
–  Betriebssystemunterstützung
–  Aktuelle Beispiele
–  Praktische Erfahrungen
!   Anwendungsnahe, interdisziplinär
orientierte Programmierung von
Parallelrechnern
–  Algorithmische Fallbeispiele
–  Nutzung von Werkzeugen
Wolfgang E. Nagel
Lehrveranstaltungen der Professur Rechnerarchitektur
Rechnerarchitektur II
Vorlesung (jeweils V2/Ü2),
wieder im SS 2013
Themenbereiche:
!   Definition und Prinzipien
!   Historischer Rückblick
!   Klassifizierungen
!   Beschreibungen
!   Möglichkeiten zur Leistungssteigerung
!   Statische und dynamische Verbindungseinrichtungen
!   Architektonische Modelle der Parallelverarbeitung
!   Leistungsbewertung
!   Zuverlässigkeit
!   Entwicklungstendenzen und Ausblick
Wolfgang E. Nagel
Lehrveranstaltungen der Professur Rechnerarchitektur
Struktur und Operationsprinzip von Prozessoren (DI Juckeland)
Vorlesung (jeweils V2)
wieder im SS 2013
Themenbereiche:
!   Rechnerarchitektur-Definition nach
Giloi
!   Unterscheidungskriterien von
Prozessorarchitekturen
!   Registersätze
!   Datentypen und -zugriff
!   Adressierungsarten
!   Befehlsformate
!   Befehlsgruppen
!   Varianten der Mikroarchitektur
!   Cache- und Speicherorganisation
!   Busprotokoll
!   Familienkonzepte und Entwicklungslinien
!   Untersuchung praxisrelevanter
Prozessorfamilien: MIPS, SPARC, IBM
POWER, PowerPC, IA-32, Intel64,
AMD64, IA-64, IBM Cell (CBEA)
!   Trends
!   Ausnahmeverarbeitung
Wolfgang E. Nagel
Lehrveranstaltungen der Professur Rechnerarchitektur
Biology and Mathematics of Brain Tumours
Seminar, Englisch
Dozenten: Prof. Deutsch und Dr. Beyer, Dr. Klink
wieder im SS 2013
Themenbereiche:
!   Brain Tumourigenesis and Progression
!
Tumour Molecular Signalling
!   Brain Tumour Invasion
!   Brain Tumour Prognosis and Therapies
Wolfgang E. Nagel
Fakultät Informatik, Institut für Technische Informatik, Professur Rechnerarchitektur
Einführung
Zellescher Weg 12
Nöthnitzer Straße 46
Willers-Bau A 205
Raum 1044
Tel. +49 351 - 463 - 35450
Tel. +49 351 - 463 - 38246
Wolfgang E. Nagel ([email protected])
Münzfernsprecher
Wolfgang E. Nagel
Rezept für Tzatziki
Zutaten:
Zubereitung:
– 
1 Salatgurke
– 
Salz
– 
500 g Dickmilch
– 
3 Essl. Olivenöl
– 
1 Essl. Essig
– 
1 Zwiebel
– 
2-3 Knoblauchzehen
– 
Pfeffer
– 
Dill
– 
Gurke schälen, halbieren, die Kerne
mit einem Teelöffel herauskratzen.
Gurke grob raffeln, leicht salzen und
beiseite stellen.
– 
Dickmilch mit Öl und Essig
verrühren, Zwiebel pellen, fein
reiben und dazugeben. Knoblauch
pellen und durch die Presse
ebenfalls dazugeben. Gurkenraspeln
in einem Tuch ausdrücken und mit
der Sauce verrühren. Mit Pfeffer
und Salz abschmecken, mit
gehacktem Dill verrühren.
Wolfgang E. Nagel
Understanding Computer Technology
Wolfgang E. Nagel
Der Algorithmusbegriff
Der Begriff Algorithmus wird in der Informatik verwendet, um eine
Lösungsvorschrift für ein Problem zu beschreiben, die sich für eine
Implementierung als Computerprogramm eignet.
Definition:
Ein Algorithmus ist eine endliche Folge von Instruktionen
(Verarbeitungsschritten), bei der jede Instruktion eine klare Bedeutung hat und
in endlicher Zeit ausgeführt werden kann.
♦
Beispiele für Instruktionen:
– 
x = x + z;
– 
if a > 0 goto Label1;
Wolfgang E. Nagel
Beispiele für Anwendungsgebiete (1)
1. Anwendungsprogramme
!   Lineare Algebra (dicht- oder dünnbesetzte Felder)
Werte
Indizes
1
2
1
7
5
3
!   Verarbeitung von Personen-/Kontendaten
char Vorname[20];
char Nachname[20];
char Postleitzahl[5];
char Ort[20];
Wolfgang E. Nagel
8
7
8
3.1
1.5
1.7
3.0
0.3
2.0
1.7
0.5
6.3
Beispiele für Anwendungsgebiete (2)
2. Compiler
!   Erstellung von Listen für alle verwendeten Symbole
!   Ziel:
a)  schnelles Suchen
b)  kompakte Speicherung
3. Betriebssysteme
!   Verwaltung von Warteschlangen (Queues)
z. B. Zeitscheibenverfahren oder Batch
!   Betriebsmittelvergabe allgemein, z.B. Verwaltung von Speicherbereiche
(Stack)
Wolfgang E. Nagel
Eigenschaften eines Algorithmus
!   Endlichkeit: Der Algorithmus stoppt nach einer endlichen Anzahl von
Schritten (Operationen).
!   Definitheit: Die Regeln sind eindeutig und präzise (deterministisch), die
Reihenfolge der durchzuführenden Operationen ist (ggf. in Abhängigkeit von
den Eingabedaten) eindeutig festgelegt.
!   Anfangszustand: Der Algorithmus beginnt mit einem eindeutig
definierten Anfangszustand, z.B. haben die Eingabedaten einen
wohldefinierten Wert; der Startwert des Algorithmus ist damit festgelegt.
Daten, die im Algorithmus verarbeitet werden, haben definierte Werte.
!   Endzustand: Es ist eindeutig festgelegt, unter welchen Bedingungen der
Algorithmus stoppt; die vom Algorithmus erzeugten Ausgabedaten sind
wohldefiniert.
Wolfgang E. Nagel
Darstellung der Algorithmen
1.  Verbale Umschreibung (Handlungsanweisung)
2.  Evtl. höhere Programmiersprache (inkl.. Pseudocode)
3.  Nassi-Shneiderman-Struktogramme o. ä. graphische Darstellungen
4.  Pre-Post-Conditions
Wolfgang E. Nagel
Pre-Condition
Bedingung, die erfüllt sein muss, damit die spezifizierte Operation korrekt
ausführbar ist.
Ist diese Bedingung nicht erfüllt, ist das Ergebnis der Operation undefiniert.
Dies führt z.B. zu
!   Fehlermeldung,
!   Programmabbruch oder
!   inkorrekten Werten.
Aus diesem Grund muss vor der Ausführung der Operation sichergestellt
werden, dass die Pre-Condition erfüllt ist.
Wolfgang E. Nagel
Post-Condition
Beschreibt das System nach Ausführung der Operation.
Dabei werden nur die Teile beschrieben, die durch die Operation u.U.
manipuliert werden.
Es werden keine Angaben gemacht, wie die Zustandstransformation erreicht
werden muss. Es wird nur beschrieben, was erreicht werden muss.
Wolfgang E. Nagel
Abstrakter atomarer Datentyp color
1.  Wertebereich
{ rot, grün, blau, gelb, orange, violett }
2.  Operationen
mix (c1,c2 : color) : color
pre c1 und c2 sind Primärfarben
post mix ist die Farbe, die aus Mischen von c1 und c2 entsteht
primary (c : color) : boolean
post IF
c ist Primärfarbe
THEN
primary ist true
ELSE
primary ist false
form (c : color; var c1, c2 : color)
pre
c ist keine Primärfarbe
post c1 und c2 sind die beiden Primärfarben aus denen c besteht
assign (var c1 : color; c2 : color)
post c1 hat den Wert c2
Wolfgang E. Nagel
Entwurf von Algorithmen
!   Top-down
generelle Strategie zuerst, Details am Schluss, d.h. schrittweise
Verfeinerung vom (abstrakten) Modell zum (konkreten) Programm
!   Bottom-up
Grundfunktionen zuerst entwerfen, übergeordnete Funktionen hierauf
aufbauen, d.h. zusammensetzen vorhandener Teile zu neuen Bausteinen
Wolfgang E. Nagel
Top-Down-Entwurf
Problem
Eingabe
Ausgabe
Wolfgang E. Nagel
Bottom-Up-Entwurf
String
Vektor
X11-Oberfläche
Wolfgang E. Nagel
Strukturierte Programmierung
!   Hilfsmittel zur Problemlösung
!   Top-Down-Entwurf von Algorithmen und Datenstrukturen
!   Black-box als Konzept:
–  ´Was macht sie?´ ;
–  nicht: ´Wie macht sie es?´
–  Hidden information zwischen Black-boxes
–  Black-boxes bilden unabhängige Module
–  Datenabstraktion als Konzept
Wolfgang E. Nagel
Was ist eine gute Lösung eines Problems?
!   Summe aller Kosten während der Lebensdauer eines Programms minimal
Einflussfaktoren:
–  CPU-Zeit
–  Mensch-Maschine Interaktion
–  Debugging bei der Fehlersuche
–  Wartung
!   Das schnellere Programm ist nicht immer besser
Wolfgang E. Nagel
Schlüsselworte für die Programmierung
!   Modularität durch Top-Down-Entwurf
!   Verfeinerungsprozess
!   Verteilung des Problems bei der Programmkonstruktion,
kleine unabhängige Aufgaben
!   Debugging (Fehlersuche) ist in kleinen Teilen wesentlich einfacher
!   Lesbarkeit des Programms wird erhöht
!   Modifikation einer Datenstruktur einfacher (nur lokale Änderungen)
!   Elimination von redundantem Programm-Code (Unterprogramm-Aufrufe)
Wolfgang E. Nagel
Programmierstil I
1.  Nutzung von Unterprogrammen (vernünftige Partitionierung und
Namensgebung) unter Wahrung der Übersichtlichkeit vorteilhaft für
–  Wartung des Programms
–  Fehlersuche im Programm
2.  Vermeidung von globalen Variablen in Unterprogrammen
–  nur dann ´global´, wenn ´global der Natur nach´
(z.B. globale Konstante nur einmal im Programm vereinbart)
–  Kompromisse in Abhängigkeit von Programmiersprache u.U. notwendig
3.  Nutzung von ´call-by-reference´-Parametern bei Feldern und Strukturen
–  Rückgabevariablen
–  Overhead durch Kopieren wird verhindert
–  Fehlersuche u.U. etwas schwieriger
Wolfgang E. Nagel
Programmierstil II
4.  Nutzung von Funktionen ausschließlich ohne Seiteneffekte
–  Keine Zuweisungen zu ´call-by-reference´-Parametern
–  Kein I/O
–  Keine Zuweisungen zu globalen Variablen
5.  Keine Nutzung von GOTOs
–  Zumindest für diese Vorlesung bindend
6.  Lesbarkeit von Programmen
–  gute, übersichtliche Struktur
–  vernünftige Wahl der Variablennamen
–  gute Programmaufteilung
–  Einrückung
–  Leerzeilen
–  Kommentierung
Wolfgang E. Nagel
Programmierstil III
7.  Compiler Arbeit übernehmen lassen
–  z.B. -Wall bei manchen Compilern einschalten, um Warnungen zu
aktivieren (und Meldungen ernst nehmen)
–  Funktionsprototypen in C verwenden (-Wall gibt ansonsten Warnung
aus)
–  Assertions in C verwenden
#include <assert.h>
assert(x > 0);
–  globale Funktionen, Typen, Variablen, Konstanten in .h-Datei definieren
und diese Datei in andere Dateien importieren (include), die diese
Funktionen etc. benötigen
–  nicht-globale Funktionen etc. so definieren, dass sie nur in dieser Datei
sichtbar sind
static int foo(void) {...}
Wolfgang E. Nagel
Programmierstil IV
8.  Dokumentation
a)  Header für das Hauptprogramm sollte enthalten:
–  Zweck des Programms
–  Autor (Name, Vorname), Datum der Erstellung, wo zu erreichen
–  Kurze Beschreibung der globalen Algorithmen und Datenstrukturen
–  Kurze Beschreibung für die Nutzung des Programms
–  Beschreibung der Schlüsselvariablen
–  Wichtig: Angaben über Annahmen, die das Programm z.B. über
Eingaben macht
a)  Mini-Header für jedes Modul (Prozedur)
b)  Kommentare innerhalb der Module zur Erklärung wichtiger oder
unübersichtlicher Teile des Programms
Wolfgang E. Nagel
Programmierstil V
9.  Debugging
–  Interaktiver Debugger
–  Write-Anweisungen, am Anfang und Ende von Unterprogrammen
–  Write-Anweisungen, am Anfang und Ende von Schleifen
–  Write-Anweisungen in IF-Anweisungen
Wolfgang E. Nagel
Problem
Problem
Eingabe
Verarbeitung
Wolfgang E. Nagel
Ausgabe
Nassi-Shneiderman-Struktogramme
!   dienen der Dokumentation und der Veranschaulichung von Programmen
!   haben als Grundelemente die wesentlichen Kontrollfluss-Strukturen von
Programmiersprachen
Wolfgang E. Nagel
Sequenz
Aktion
1
Aktion
2
Aktion
n
Wolfgang E. Nagel
Einfache Auswahl (IF-Abfrage)
Bedingung
false
true
Aktion 1
Aktion 2
Wolfgang E. Nagel
Mehrfache Auswahl (Case-Abfrage)
Auswahl
Fall
1
2
Aktion
1
n
Aktion
2
Aktion
. . .
Wolfgang E. Nagel
n
in C
if(condition)
in Fortran 90
[name:] IF(condition) THEN
statement;
statement
else
ELSE
statement;
statement
END IF [name]
switch(expression)
{
[name:] SELECT CASE(expression)
CASE selector
constant: statement;
statement
break;
CASE DEFAULT
default: statement;
statement
}
END SELECT [name]
Wolfgang E. Nagel
Iteration (While-Schleife)
WHILE
Bedingung
Aktion
Wolfgang E. Nagel
Iteration (Repeat-Schleife)
Aktion
UNTIL
Bedingung
Wolfgang E. Nagel
Iteration (for-Schleife)
for index:=anfang TO ende DO
Aktion
Wolfgang E. Nagel
in C
in Fortran 90
for (var = start ; var != end ;++ var)
[name:] DO var = start, end[,step]
statement
statement ;
END DO [name]
[name:] DO
IF(.NOT.cond) EXIT
statement
END DO [name]
while (cond)
statement ;
do
oder
[name:] DO WHILE (cond)
statement
END DO [name]
[name:] DO
statement
IF (condition) EXIT
END DO [name]
statement ;
while (condition) ;
Wolfgang E. Nagel
Beispiel
list _ index bin_search ( keytype key )
low = 1
high = n
element = 0
(element == 0 ) && ( low <= high )
i = (low +high ) /2
middle = list [i ].key
key > middle
T
F
key < middle
low = i + 1
TRUE
FALSE
high = i - 1
element = i
return element
Wolfgang E. Nagel
Registermaschine (RAM)
Programm
0: LOAD 0
1: ADD 4
2: STORE 1
3: STOP
Akkumulator
3
Speicher
0: 3
1: 0
Programmzähler
2
...
Befehlssatz:
-  LOAD, STORE immediate/direct/indirect
-  ADD, SUB register
-  JUMP label
-  JUMP>0, JUMP=0 label
Wolfgang E. Nagel
2: 0
...
Aufwand eines RAM-Programmes
!   Jeder ausgeführte Befehl zählt als ein Rechenschritt
!   Speicherplatzbedarf entspricht den zusätzlich zur Eingabe benutzten
Speicherzellen
Wolfgang E. Nagel
Effizienz eines Algorithmus
Mehrere Möglichkeiten für das ´´Messen´´ der Effizienz eines Algorithmus:
!   ´´Programmieren´´ des Algorithmus in einer künstlichen Programmiersprache
und zählen und gewichten der Operationen, die nötig sind, um eine
bestimmte Problemgröße zu lösen
!   Programmieren des Algorithmus auf einem realen Rechner und messen des
Zeitverbrauchs
!   Zählen der Operationen auf einem sehr hohen Niveau, z.B. Anzahl der ´´zu
betrachtenden´´ Elemente beim Suchen in einer Liste, oder Anzahl der zu
vertauschenden Elementpaare beim Sortieren einer Liste
Wolfgang E. Nagel
Definition:
Eine Funktion f : M → N heißt berechenbar, wenn es einen
Algorithmus gibt, der für jeden Eingabewert m ∈ M, für den
f(m) definiert ist, nach endlich vielen Schritten anhält und das Ergebnis f(m)
liefert; in allen Fällen, in denen f(m) nicht definiert ist, bricht der Algorithmus
nicht ab.
♦
Komplexität einer berechenbaren Funktion:
Der zu ihrer Berechnung erforderliche Aufwand an Betriebsmitteln
(Speicherplatz, Rechenzeit, benötigte Geräte, usw.)
Untersuchung des Rechenaufwandes soll unabhängig von speziellen Computern
sein → formales Berechnungsmodell z.B. Turingmaschine
Wolfgang E. Nagel
!   Die Komplexität eines Algorithmus ist der erforderliche
Rechenaufwand bei einer konkreten Realisierung des Algorithmus innerhalb
des Berechnungsmodells.
!   Die Komplexität einer Funktion ist die Komplexität des bestmöglichen
Algorithmus der Menge aller Algorithmen, die die Funktion berechnen.
!   Untersucht wird Zeitverhalten und Speicherplatzbedarf eines
Algorithmus.
!   Laufzeit eines Algorithmus:
Anzahl der Rechenschritte, die bei Durchführung für eine Eingabe
gemacht werden.
Wolfgang E. Nagel
Zusammenhang Algorithmus - Funktion
Sei A ein Algorithmus, der die Funktion f berechnet. Dann ist die Komplexität
von A eine obere Schranke für die Komplexität von f. Umgekehrt ist die
Komplexität von f eine untere Schranke für die Komplexität von A. Fallen
untere und obere Schranke zusammen, so hat man einen optimalen
Algorithmus für das gestellte Problem.
Wolfgang E. Nagel
Abstrakte Maschinen
!   Turingmaschine:
–  Rechenschritt: eine Anwendung der Übergangsfunktion δ
–  Speicherplatzbedarf: Zahl der benötigten Speicherzellen (zusätzlich zur
Eingabe)
!   RAM:
–  Rechenschritt: Ausführung eines Befehls
–  Speicherplatzbedarf: Zahl der benötigten Speicherzellen (zusätzlich zur
Eingabe)
Wolfgang E. Nagel
Definition:
Sei f : N→ R+ eine Funktion.
Dann sind folgende Funktionenmengen definiert:
O(f) = {g: N→ R+ | ∃ n0 ∈ N, ∃ c ∈ R+ mit g(n)≤ c f(n) ∀ n≥n0 }
Ω(f) = {g: N→ R+ | ∃ n0 ∈ N, ∃ c ∈ R+ mit g(n)≥ c f(n) ∀ n≥n0 }
♦
Wolfgang E. Nagel
Bemerkungen:
!   O(f) ist eine Funktionenklasse, nämlich die Menge der Funktionen, die
asymptotisch höchstens so schnell wachsen wie c f.
!   Ω(f) ist eine Funktionenklasse, nämlich die Menge der Funktionen, die
asymptotisch mindestens so schnell wachsen wie c f.
!   g ∈ O(f) heißt nicht, dass g(n) ≤ f(n) für alle n gilt.
Wolfgang E. Nagel
Sei n Problemgröße, A Algorithmus.
Der Zeitbedarf eines Algorithmus lässt sich darstellen als eine Zeitfunktion
TA(n) mit TA: N→ R+
Definition:
Ein Algorithmus hat die Komplexität O(f), wenn TA ∈ O(f) gilt.
♦
Bemerkung:
Die Zeitfunktion TA hängt i.allg. von der Eingabe des Algorithmus ab, z.B. bei
einem Sortieralgorithmus, ob die zu sortierenden Elemente bereits vorsortiert
vorliegen.
Wolfgang E. Nagel
Beispiel
!   TA(n) = n(n-1)/2
–  TA(n) ∈ O(n2), denn:
–  n(n-1)/2 = 1/2 (n2-n) ≤ n2-n (für n ≥ 1) ≤ n2
–  mit n0=1, c=1 erfüllt TA(n) die Definition
!   TA(n) = n2+2n
–  TA(n) ∈ O(n2), denn:
–  n2+2n = n2+n+n ≤ n2+ n2+ n2 = 3n2
–  mit n0=1, c=3 ist die Definition erfüllt
Dennoch ist ein Algorithmus mit Aufwand n(n-1)/2
besser als ein Algorithmus mit n2+2n!
Wolfgang E. Nagel
Bemerkungen
!   Man sagt üblicherweise f(n) ∈ O(log n) statt f ∈ O(log).
!   Typische Zeitschranken: log2 n, n, n log2 n, n2, n3, 2n, nn
!   Es gilt: O(log n) < O(n) < O(n log n) < O(n2) < O(2n) < O(nn)
!   Falls f(n) ∈ O(s(n)), g(n) ∈ O(r(n)), dann gilt:
f(n) + g(n) ∈ O(s(n)+r(n))
!   Falls f(n) ∈ O(s(n)), g(n) ∈ O(r(n)), dann gilt:
f(n) • g(n) ∈ O(s(n) • r(n))
Wolfgang E. Nagel
Bezeichnungen
Festlegung:
f ist konstant, wenn f ∈ O(1)
f wächst logarithmisch, wenn f(n) ∈ O(log n)
f wächst linear, wenn f(n) ∈ O(n)
f wächst quadratisch, wenn f(n) ∈ O(n2)
f wächst polynomiell, wenn f(n) ∈ O(nk) für ein k ∈ N
f wächst exponentiell, wenn f(n) ∈ O(2cn) für ein c ∈ R+
Wolfgang E. Nagel
Laufzeitverhalten von Algorithmen
Algorithmus
A1
Laufzeit (ms)
1000 N
am
schnellsten
für
max.
Problemgröße
(3600 sec)
N>=149
3600
90<=N<=148
1500
A2
200 N log N
A3
10 N²
10<=N<=89
600
A4
30 N³
nie
50
A5
2N
1<=N<=9
22
Wolfgang E. Nagel
Laufzeitverhalten von Algorithmen (Rechner 10x schneller)
Algorithmus
A1
Laufzeit (ms)
1000 N
max.
Problemgröße
(3600 sec)
Faktor
36000
10
A2
200 N log N
13500
9
A3
10 N²
1900
3,166
A4
30 N³
106
2,012
A5
2N
25
Wolfgang E. Nagel
(+3)
1,136
Komplexität
!   Man unterscheidet den Zeitbedarf im schlechtesten Fall (worst case), im
Mittel (average case) und im besten Fall (best case)
!   Bei der Komplexitätsanalyse wird i.Allg. der schlechteste Fall betrachtet
(worst-case Analyse).
Wolfgang E. Nagel
Fakultät Informatik, Institut für Technische Informatik, Professur Rechnerarchitektur
Effiziente Parallele Algorithmen
Datenstrukturen Teil I
Zellescher Weg 12
Nöthnitzer Straße 46
Willers-Bau A 205
Raum 1044
Tel. +49 351 - 463 - 35450
Tel. +49 351 - 463 - 38246
Wolfgang E. Nagel ([email protected])
Datentyp
Definition:
Ein Datentyp ist aus zwei Angaben festgelegt:
1.  eine Menge von Datenobjekten (Werte);
2.  eine Menge von Operationen auf diesen Datenobjekten.
♦
Wolfgang E. Nagel
Datenstruktur
Definition:
Eine Datenstruktur ist ein Datentyp mit den folgenden Eigenschaften:
1.  Sie kann in eine Menge von zusammenhängenden Datenelementen
zerlegt werden, wobei jedes dieser Elemente ein atomarer Datentyp oder
wieder eine eigene Datenstruktur ist.
2.  Sie setzt die Elemente durch eine Menge von Regeln (eine Struktur) in eine
Beziehung.
♦
Wolfgang E. Nagel
Abstraktionsniveau bei Datentypen
Abstrakter Datentyp
–  Datentyp, der nur in der Vorstellung des Programmentwicklers entsteht.
–  Gibt die wichtigsten Eigenschaften ohne Einzelheiten oder
Nebenbedingungen der Realisierung an.
–  Umsetzung in Programmiersprache
Virtueller Datentyp
Datentyp in einer höheren Programmiersprache
↓ Durch Compiler und Betriebssystem
Physikalischer Datentyp
Datentyp, der auf einem physikalischen Prozessor existiert
Wolfgang E. Nagel
Spezifikation eines strukturierten abstrakten Datentyps
Struktur
Elemente
Wertebereich
Operationen
Spezifikation
Repräsentation
Implementation
Wolfgang E. Nagel
Physikalische Speicherung von Daten
1.  Arbeitsspeicher (Hauptspeicher)
–  Speicher mit wahlfreiem Zugriff (random access)
–  Zugriff auf Daten über Adressen
–  Wort- oder Byte-orientierte direkte Adressierung
–  Im Vergleich zu externen Speichern relativ kurze Zugriffszeit
(Größenordnung 30-50 ns-Bereich)
–  Aus Kostengründen und im Vergleich zu externen Speichern relativ
geringe Speicherkapazität (Wenige Gigabytes)
Wolfgang E. Nagel
Physikalische Speicherung von Daten
2. Externer Speicher
–  Magnetbänder
•  Daten werden sequentiell gespeichert
•  Im Vergleich zum Hauptspeicher große Speicherkapazität
(ca. 400 GByte – 1 TByte)
•  Langsamer Zugriff, zum Teil manueller Eingriff notwendig
–  Magnetplatte
•  Daten sind blockweise gespeichert, Blöcke wahlfrei zugreifbar
•  Große Speicherkapazität (bis zu mehreren TBytes)
•  Im Vergleich zum Arbeitsspeicher lange Zugriffszeiten (einige ms)
Wolfgang E. Nagel
Elementare Strukturrelationen
"   Menge (Set)
–  Elemente stehen nur insoweit in Beziehung, als sie zu einer Menge
gehören
"   Lineare Struktur
–  Elemente stehen jeweils zu einem anderen Element in Beziehung (oneto-one Relation)
"   Baumstruktur
–  Elemente stehen zu mehreren anderen Elementen in
Beziehung (one-to-many Relation)
"   Graph- oder Netzwerkstruktur
–  Viele Elemente stehen mit vielen anderen Elementen in Beziehung
(many-to-many Relation)
Wolfgang E. Nagel
Homogene/heterogene Datenstruktur
Definition:
1.  In einer homogenen Datenstruktur haben alle Komponenten den gleichen
Datentyp.
2.  In einer heterogenen Datenstruktur haben die Komponenten
unterschiedliche Datentypen.
♦
Wolfgang E. Nagel
Schlüsselmenge
Definition:
Eine Schlüsselmenge ist eine linear geordnete Menge S = {s1, ..., sn} .
♦
Bemerkung:
–  Schlüssel dienen der eindeutigen Identifikation von Daten und werden
i.A. zusammen mit den Daten abgespeichert oder sind Bestandteil der
Daten.
–  Schlüssel müssen nicht Zahlen sein. Auch Namen können z.B. als
Schlüssel verwendet werden (lexikographische Sortierung).
Beispiele:
–  Kontonummer
–  Nummer eines Versicherungsvertrages
–  Personalnummer, Postleitzahlen
Wolfgang E. Nagel
Datenstruktur Feld
Definition:
Ein Feld ist eine Kette von Elementen.
1. Elemente
Durch Festlegung eines Komponententyps ist der Typ des Elementes
festgelegt. Da alle Elemente den gleichen Typ haben, ist ein Feld eine
homogene Datenstruktur.
2. Struktur
Ein Indextyp, der linear sein muss, legt den Wertebereich der Indexwerte
fest. Die Indexwerte stehen in einer 1-1 Relation zu den
Komponentenwerten, jeder Indexwert identifiziert genau einen
Komponentenwert.
Wolfgang E. Nagel
Datenstruktur Feld
3.  Operationen
retrieve ( arraytype S, elemtype C, int i1 , ... , int in )
gibt der Variablen C den Wert von S, der mit dem Index i1 , ... , in
korrespondiert.
Beispiel in C: C = S[i1]...[in];
update ( arraytype S, elemtype C, int i1 , ..., int in )
speichert den Wert der Variablen C auf die Komponente von S, die mit dem
Index i1 , ..., in korrespondiert.
Beispiel in C:
S[i1]...[in] = C;
Wolfgang E. Nagel
Mögliche Fehlerquellen
retrieve
–  Index ist nicht vom zulässigen Typ.
–  Undefinierte Werte in der Feldkomponente.
–  Komponententyp und Typ von C stimmen nicht überein.
update
–  Index nicht vom zulässigen Typ.
–  Undefinierter Wert in der Zuweisungsvariablen.
–  Komponententyp und Typ von C stimmen nicht überein.
–  Index liegt außerhalb des zugehörigen Typs, eine Zuweisung auf
–  Feldelemente außerhalb der zulässigen Grenzen wird z.B. in
–  FORTRAN im Regelfall nicht überprüft.
Wolfgang E. Nagel
Speicherung von eindimensionalen Feldern
"   Datenelemente werden hintereinander im Speicher abgelegt
"   Speicheradresse von a[i] berechnet sich (in C) durch
(Basisadresse + Offset):
&a[0] + i*sizeof( a[0] )
Beispiel:
int a[6];
wobei ein int eine 32-Bit Zahl (4 Bytes) sein soll
a[0]
a[1]
a[2]
a[3]
a[4]
a[5]
Adresse von a[3], wenn Feld ab Adresse 40 beginnt: 40 + 3*4 = 52
Wolfgang E. Nagel
Speicherung von mehrdimensionalen Feldern
"   Datenelemente werden hintereinander im Speicher abgelegt
"   in C, Java u.a. zeilenweise; in Fortran spaltenweise
"   Speicheradresse von a[i1]...[in] berechnet sich (in C) durch
&a[0] + ( ... ( i1 * N2 + i2 ) * N3 +... ) Nk + ik ) * sizeof( a[0] )
Beispiel (zeilenweise Speicherung):
int a[2][3]; wobei ein int eine 32-Bit Zahl (4 Bytes) sein soll
a[0][0] a[0][1]
a[0][2] a[1][0]
a[1][1] a[1][2]
Adresse von a[1][2], wenn Feld ab Adresse 40 beginnt:40 + (1*3+2)*4 = 60
Wolfgang E. Nagel
Datenstruktur Zeiger
Definition:
Ein Zeiger (Pointer) ist ein Datentyp, dessen Werte die Adressen von
Repräsentationen anderer Datentypen sind.
1. Elemente
Die Elemente sind die Werte aller Speicheradressen einschließlich des
Wertes NULL (undefiniert).
2. Struktur
keine
Wolfgang E. Nagel
Datenstruktur Zeiger
3. Operationen (in C)
Assignment
=
Relational
== und !=
Dynamic
malloc, calloc, realloc, free
(C++: new, delete, new[], delete[])
Dereferenzierung * (sowie -> und [])
Wolfgang E. Nagel
Datenstruktur Record
Definition:
Ein record ist eine Gruppierung von unterschiedlichen Datenelementen zu einer
Einheit.
1. Elemente
Jedes Komponentenelement benötigt die Angabe eines eigenen Datentyps.
Da die Komponenten unterschiedliche Datentypen haben können, ist ein record
eine heterogene Datenstruktur.
2. Struktur
Eine Liste von Namen (identifier) legt in einer direkten Abbildung zu den
Komponenten die Struktur des records fest.
Wolfgang E. Nagel
Operationen
3. Operationen
retrieve (R,V,id)
Gibt der Variablen V den Wert der Komponente von record R, die mit id
spezifiziert ist.
Gleiche Typen von V und der entsprechenden Komponente von record R
werden hier vorausgesetzt.
update (R,V,id)
speichert den Wert der Variablen V auf die Komponente von record R,
die durch id bezeichnet ist.
Bemerkung:
Wenn R1 und R2 records vom gleichen Typ sind, kann in manchen
Programmiersprachen durch R1 = R2 der Wert von R2 vollständig auf R1
zugewiesen werden.
Wolfgang E. Nagel
Padding
Padding entsteht dadurch, dass gewisse atomaren Datentypen an bestimmten
Speicheradressen abgelegt sein müssen (z.B. Wortgrenze, Doppelwortgrenze)
ungenutzter Speicherplatz wird automatisch vom Compiler angelegt
Wolfgang E. Nagel
Beispiele
a) Strukturbaum
MITARBEITER
PERSONALNUMMER
VORNAME
NAME
NACHNAME
ANSCHRIFT
GEBURTSNAME
STRASSE
Nachname Geburtsn.
HAUSNR
PLZ
ORT
Padding
b) Speicherdarstellung
Pers.nr. Vorname
KINDERZAHL
Strasse
Wolfgang E. Nagel
Nr
PLZ
Ort
Ki.
Datenstruktur Menge
"   Bei einer Menge hat ein Element keinen Vorgänger, Nachfolger, Vater, Sohn,
und es gibt auch kein erstes oder letztes Element.
"   Ein Element ist entweder in der Menge enthalten oder nicht enthalten.
"   Die Elemente sind atomar und haben einen ordinalen Datentyp.
"   Die Datenstruktur Set ist nur in wenigen Programmiersprachen direkt in der
Sprache verfügbar (z.B. PASCAL).
Wolfgang E. Nagel
Datenstruktur Menge
Definition:
Ein Set ist eine Sammlung von Elementen, die alle den gleichen Datentyp
haben.
1. Elemente
Die Elemente haben einen Aufzählungstyp und sind atomar.
2. Struktur
Die Elemente sind Mitglieder einer Menge.
3. Operationen
assign (set s1, const set s2)
post
s1 hat die gleichen Elemente wie s2
bool in (const set s1, element e)
post
IF
e Element von s1
THEN
in ist true
ELSE
in ist false
Wolfgang E. Nagel
Operationen auf der Menge II
set intersection (const set s1, const set s2)
post
intersection ist eine Menge, die den Durchschnitt von s1 und
s2 enthält.
set union (const set s1, const set s2)
post
union ist eine Menge, die die Vereinigung von s1 und s2
enthält.
set difference (const set s1, const set s2)
post
difference ist eine Menge, die s1 ohne s2 enthält.
bool equal (const set s1, const set s2)
post
IF
THEN
ELSE
s1 und s2 haben die gleichen Elemente
equal ist true
equal ist false
Wolfgang E. Nagel
Operationen auf der Menge III
bool subset (const set s1, const set s2)
post
IF
THEN
ELSE
jedes Element von s1 auch in s2
subset ist true
subset ist false
mkset (set s, const element elem_vec[], int n)
post
s enthält elem_vec[0],...,elem_vec[n-1] als Elemente.
set create (element ubound, id_type basetype_id)
post
create ist eine leere Menge. Diese kann Elemente aufnehmen,
die nicht größer als ubound sind, und typkompatibel zu Mengen,
die mit der gleichen basetype_id erzeugt wurden.
void delete (set s)
post
s existiert nicht mehr.
Wolfgang E. Nagel
Beispielimplementierung für Menge
typedef enum {false, true} bool;
typedef struct set set;
struct set
{
int type_id;
int ub;
bool *elem;
/* Typenkennung */
/* größtes mögliches Element */
/* Element vorhanden */
/* (ja/nein) */
};
Wolfgang E. Nagel
Datenstruktur Sequenz
Definition:
Eine Sequenz ist eine geordnete endliche Folge eines gegebenen Datentyps,
wobei auf alle Elemente nur sequentiell zugegriffen werden kann.
1. Elemente
Die Elemente sind vom Typ stdelement (standard-element).
2. Struktur
Die Struktur zwischen den Elementen ist linear.
Wolfgang E. Nagel
Operationen auf der Sequenz I
3. Operationen
find_first()
pre
Die Sequenz ist nicht leer.
post
Das erste Element ist das current_element.
find_next ()
pre
Die Sequenz ist nicht leer und current_element ist nicht das
letzte Element.
post
c_next ist das current_element.
Wolfgang E. Nagel
Operationen auf der Sequenz II
append (const stdelement e)
post Die Sequenz enthält e als letztes Element und dieses Element
ist das current_element.
stdelement retrieve()
pre
Die Sequenz ist nicht leer.
post
retrieve ist eine Kopie des current_element.
Wolfgang E. Nagel
Operationen auf der Sequenz III
bool empty()
post
IF
Die Sequenz enthält kein Element
THEN
empty ist true
ELSE
empty ist false
bool last()
post
IF
Das letzte Element ist das current_element
THEN
last ist true
ELSE
last ist false
create()
post
clear()
post
Die Sequenz existiert und ist leer.
Die Sequenz ist leer.
♦
Wolfgang E. Nagel
Veranschaulichung
Sequenz e1,...,en
e1 e2 e3
en
current_element
nach find_first
e1 e2 e3
en
nach find_next
e1 e2 e3
en
nach append(en+1)
e1 e2 e3
en en+1
Wolfgang E. Nagel
Sequentielle Dateien in C
In C sind Sequenzen vom Typ FILE *.
Implementierung der Operationen (FILE *stream):
find_first:
stream = fopen(Dateiname, "rb+");
bzw.
rewind(stream);
create:
stream = fopen(Dateiname, "wb+");
bzw.
stream = tmpfile();
append:
fseek(stream, 0, SEEK_END);
fwrite(&e, sizeof(e), 1, stream);
fflush(stream);
clear:
freopen(Dateiname, "wb+", stream);
Wolfgang E. Nagel
Sequentielle Dateien in C
e = retrieve(); find_next() (nur in Kombination möglich):
fread(&e, sizeof(e), 1, stream);
empty, last:
Keine unmittelbare Entsprechung, aber mit feof(stream) kann man
das Erreichen des Dateiendes abfragen. Diese Funktion liefert allerdings
erst true, nachdem versucht wurde, auf den Nachfolger des letzten
Elementes (durch retrieve) zuzugreifen
Wolfgang E. Nagel
Textdateien
Dateien mit Komponenten vom Typ char
–  haben zusätzliche Zeilenstruktur (‚\n‘)
–  zusätzliche Konvertierungen erlaubt (z.B. Lesen von float-Zahlen)
Wolfgang E. Nagel
Lineare Datenstrukturen
Datenstruktur mit beliebig erweiterbarer Sequenz von Daten
–  Einfügen am Ende der Sequenz
–  Durchlaufen der Sequenz
–  Abspeicherung der Sequenz muss nicht notwendigerweise
aufeinanderfolgend sein
Wolfgang E. Nagel
Lineare Datenstrukturen
head
current_node
...
...
First
node
last
node
c_prior
c_pre
Wolfgang E. Nagel
c_next
Beispiel in C
struct liste
{
int
Daten;
struct liste *next;
} *head, *current_node, *tmp;
head = malloc(sizeof(*head)); current_node
head->Daten = 4711;
head->next = NULL;
current_node = head;
tmp = malloc(sizeof(*tmp));
current_node
tmp->Daten = 31415;
tmp->next = NULL;
current_node->next = tmp;
current_node = tmp;
Wolfgang E. Nagel
head!
4711
-head!
4711
31415
--
Einfach verkettete Liste
Definition:
Eine einfach verkettete Liste ist eine Folge aus keinem oder aus endlich vielen
Elementen eines gegebenen Datentyps, bei der der Nachfolger eines
Elementes durch einen Pointer erreicht werden kann.
1. Elemente
Die Elemente sind Knoten. Jeder Knoten enthält eine Datenkomponente und
einen Knoten-Pointer.
2. Struktur
Die Struktur zwischen den Knoten ist linear.
Wolfgang E. Nagel
Vereinbarung bei Post-Conditions
current_node:
c_pre:
c_prior:
c_next:
l_pre:
zeigt auf den aktuellen Knoten
current_node vor Operation
Vorgänger von c_pre
Nachfolger von c_pre
gesamte Liste vor Operation
current_node
c_prior
c_pre
c_next
Wolfgang E. Nagel
Operationen auf einfach verketteter Liste I
3. Operationen
insert_after (const stdelement e)
pre
Liste darf nicht voll sein.
post
Ein neuer Knoten mit dem Datenelement e ist in der Liste
enthalten, dieser Knoten ist auch der neue current_node.
IF
L_pre war nicht leer
THEN
current_node ist Nachfolger von c_pre und
c_next ist Nachfolger vom current_node.
Wolfgang E. Nagel
Vor insert_after
head
current_node
Wolfgang E. Nagel
Nach insert_after
head
c_pre
c_next
current_node
e
Wolfgang E. Nagel
Operationen auf einfach verketteter Liste II
insert_before (const stdelement e)
pre
Liste darf nicht voll sein.
post
Ein neuer Knoten mit dem Datenelement e ist in der Liste
enthalten, dieser Knoten ist auch der neue current_node.
IF
THEN
L_pre war nicht leer
IF
c_pre war erstes Element in der Liste
THEN
c_pre ist Nachfolger vom current_node
ELSE
current_node ist Nachfolger von c_prior
und c_pre ist Nachfolger vom
current_node.
Wolfgang E. Nagel
Operationen auf einfach verketteter Liste III
delete()
pre
Die Liste ist nicht leer.
post
c_pre ist nicht mehr in der Liste.
IF
THEN
ELSE
c_pre gleich erster Knoten
c_next ist erster Knoten, falls er existiert
Nachfolger von c_prior ist c_next
IF
THEN
ELSE
Liste nicht leer
current_node gleich erster Knoten
current_node gleich leerer Knoten
stdelement retrieve ()
pre
Die Liste ist nicht leer.
post
retrieve ist eine Kopie des Datenelementes von c_pre.
Wolfgang E. Nagel
Operationen auf einfach verketteter Liste IV
update (const stdelement e)
pre
Die Liste ist nicht leer.
post
c_pre enthält e als sein Datenelement.
find_first
pre
Die Liste ist nicht leer.
post
current_node zeigt auf den ersten Knoten
bool find_next ()
pre
Die Liste ist nicht leer.
post
IF
THEN
ELSE
c_pre zeigt nicht auf den letzten Knoten der Liste
current_node gleich c_next; find_next ist true
find_next ist false
Wolfgang E. Nagel
Operationen auf einfach verketteter Liste V
bool find_prior ()
pre
Die Liste ist nicht leer.
post
IF
THEN
ELSE
c_pre zeigt nicht auf den ersten Knoten der Liste
current_node gleich c_prior; find_prior ist true
find_prior ist false
bool locate (const stdelement e)
pre
Die Liste ist nicht leer.
post
IF
THEN
ELSE
e ist in der Liste
locate ist true und current_node zeigt auf den
Knoten, der e als Datenelement enthält
locate ist false und current_node zeigt auf den
letzten Knoten der Liste
Wolfgang E. Nagel
Operationen auf einfach verketteter Liste VI
bool empty()
post
IF
THEN
ELSE
Liste nicht leer
empty ist false
empty ist true
bool full()
post
IF
THEN
ELSE
Liste enthält maximal zulässige Anzahl von
Elementen
full ist true
full ist false
create()
post
Eine leere verkettete Liste existiert.
Wolfgang E. Nagel
Realisierung über Pointer
typedef struct node * listpointer;
struct node
{
stdelement element;
listpointer next;
};
/* oder struct node *next; */
/* globale Variablen: */
static listpointer head, current_node;
/* lokale Variablen: */
listpointer p;
bool found, not_failed;
Wolfgang E. Nagel
create/retrieve/update
Komplexität:
void create ( void )
O(1)
head = NULL
current _ node = NULL
stdelement retrieve ( void )
O(1)
return (current _ node ->element )
void update ( stdelement e )
O(1)
current _ node ->element = e
Wolfgang E. Nagel
insert_after
Komplexität:
void insert_ after ( stdelement e )
p = (listpointer ) malloc (sizeof(struct node ))
assert (p ! = NULL )
O(1)
p ->element = e
head != NULL
T
F
p ->next = current_node ->next
p ->next = NULL
current_ node ->next = p
head = p
current_ node = p
Wolfgang E. Nagel
insert_before
Komplexität:
void insert_ before( stdelement e )
current_ node == head
TRUE
p = (listpointer) malloc (sizeof(struct node ))
assert(p != NULL)
FALSE
p = head
p->next != current_ node
p->element = e
p = p->next
p->next = head
head = p
current_ node = p
current_ node = p
insert_ after(e)
Wolfgang E. Nagel
O(n)
find_first/find_next
Komplexität:
void find_first ( void )
O(1)
current_node = head
bool find _ next ( void )
found = (current_ node ->next != NULL)
O(1)
found
T
F
current_ node = current_ node ->next
return found
Wolfgang E. Nagel
find_prior
Komplexität:
bool find _ prior( void )
found = (current_ node != head )
found
T
F
p = head
p->next != current_ node
p = p->next
current_ node = p
return found
Wolfgang E. Nagel
O(n)
locate/empty
Komplexität:
bool locate ( stdelement e )
not _ failed = true
O(n)
find _ first()
not _ failed && ( retrieve () != e )
not _ failed = find _ next ( )
return not _ failed
bool empty ( void )
O(1)
return (head == NULL)
Wolfgang E. Nagel
delete
Komplexität:
void delete ( void )
current _ node != head
TRUE
FALSE
p = head
O(n)
p ->next != current _ node
p = p ->next
head = head ->next
p ->next = current _ node ->next
free (current _ node )
current _ node = head
Wolfgang E. Nagel
Realisierung mit 3 Feldern
typedef int listpointer;
typedef listpointer listpointer_array[MAXLIST];
/* globale Variablen: */
static stdelement
static listpointer_array
static listpointer
static listpointer
list[MAXLIST];
next, free_list;
head, current_node;
first_free;
/* lokale Variablen: */
listpointer i, p;
bool found, not_failed;
Wolfgang E. Nagel
first_free
4
0
5
current_node
head
3
2
a4
a1
a2
a3
1
-1
list
next
n-1
free_list
-1
-1
3
n-2
Wolfgang E. Nagel
create
Komplexität:
void create( void )
head = -1
current_ node = -1
O(n)
for (i = 0 ; i < MAXLIST ; i ++)
free_ list[i ] = i
next[i ] = -1
first_ free = 0
Wolfgang E. Nagel
insert_after
Komplexität:
void insert_ after( stdelement e )
p = free_ list[first_ free++]
list[p] = e
O(1)
current_ node >= 0
T
F
next[p] = next[current_ node ]
next[p] = -1
next[current_ node ] = p
head = p
current_ node = p
Wolfgang E. Nagel
insert_before
Komplexität:
void insert_ before ( stdelement e )
current_ node != head
TRUE
FALSE
p = head
p = free_ list[first_ free++]
next[p] != current_ node
list[p] = e
p = next[p]
next[p] = head
current_ node = p
head = p
insert_ after (e)
current_ node = p
Wolfgang E. Nagel
O(n)
delete
Komplexität:
void delete ( void )
current_ node != head
T
F
p = head
O(n)
next [p ] != current_ node
head = next [head ]
p = next[p ]
next [p ] = next[current_ node ]
free_ list[--first_ free] = current_ node
current_ node = head
Wolfgang E. Nagel
Realisierung durch ein Feld
Idee: keine Verkettung, sondern Element a[i] steht auf Position i im Feld.
Konsequenz: Einfügen bedeutet verschieben von Elementen ab Einfügeposition.
typedef int list_index;
/* globale Variablen: */
static stdelement list[MAXLIST];
static list_index last_node;
/* Anzahl Knoten in Liste - 1 */
static list_index current_node;
/* lokale Variablen: */
list_index k;
bool found, not_failed;
Wolfgang E. Nagel
create
Komplexität:
void create ( void )
O(1)
last_ node = -1
current_ node = -1
Wolfgang E. Nagel
insert_after
Komplexität:
void insert_after ( stdelement e )
last_node >= 0
T
F
for (k = last_node ; k > current_node ; k--)
list[k+1] = list[k]
list[++current_node] = e
last_node++
Wolfgang E. Nagel
O(n)
insert_before
Komplexität:
void insert_ before ( stdelement e )
current_ node >= 0
T
F
current_ node -insert_ after (e )
Wolfgang E. Nagel
O(n)
delete
Komplexität:
void delete ( void )
for (k = current_ node +1 ; k <= last_ node ; k++)
O(n)
list[k-1 ] = list[k]
last_ node -current_ node = (last_ node >= 0 ) ? 0 : -1
Wolfgang E. Nagel
N-fach verkettete Liste
Definition:
Eine n-fach verkettete Liste ist eine Folge aus keinem oder aus endlich vielen
Elementen eines gegebenen Datentyps, bei denen die Nachfolger eines
Elementes durch Pointer erreicht werden.
1. Elemente
Die Elemente heißen Knoten. Jeder Knoten enthält eine
Datenkomponente und n Knotenpointer.
2. Struktur
Die Struktur dieser Knoten ist linear.
3. Operationen
wie bei einfach verketteter Liste.
Wolfgang E. Nagel
Begründung
Ziel: Listenelement soll nur einmal gespeichert werden
Unter Umständen können jedoch mehrere logische Sortierungen sinnvoll sein:
–  Name
–  Geburtsdatum
–  Kinderanzahl
–  Postleitzahl
Spezialfall:
Doppelt verkettete Liste, in der jedes Listenelement einen Zeiger auf Vorgänger
und Nachfolger hat. Dadurch können einige Operationen schneller ausgeführt
werden als bei einfach verketteten Listen.
Wolfgang E. Nagel
Verpointerung
head
current_node
...
...
first
node
last
node
Wolfgang E. Nagel
Doppelt verkettete Liste
typedef struct node *listpointer;
struct node
{
stdelement element;
listpointer next;
listpointer prior;
};
/* globale Variablen: */
static listpointer head, current_node;
Wolfgang E. Nagel
find_prior
Komplexität:
bool find _ prior ( void )
found = ( current _ node ->prior != NULL )
O(1)
found
T
F
current _ node = current _ node - >prior
return found
Wolfgang E. Nagel
insert_after
Komplexität:
void insert_ after ( stdelement e )
p = (listpointer ) malloc (sizeof (struct node ))
assert(p != NULL )
p ->element = e
O(1)
p ->prior = current _ node
head != NULL
T
F
p ->next = current _ node ->next
p ->next = NULL
c_ pre = current _ node
find _ next ()
T
F
current _ node ->prior = p
head = p
c_ pre ->next = p
current _ node = p
Wolfgang E. Nagel
insert_before
Komplexität:
void insert_ before ( stdelement e )
current_ node != head
T
F
p = (listpointer ) malloc (sizeof (struct node ))
assert(p != NULL)
find _ prior ()
O(1)
p ->element = e
p ->prior = NULL
p ->next = head
head != NULL
T
F
head ->prior = p
insert_ after (e )
head = p
current_ node = p
Wolfgang E. Nagel
delete
Komplexität:
void delete ( void )
current_ node != head
TRUE
FALSE
c_ pre = current_ node
head = head ->next
O(1)
find _ prior ()
free(current_ node )
current_ node ->next = c_ pre->next
!empty()
free(c_ pre)
T
c_ pre = current_ node
find _ first()
find _ next()
T
current_ node ->prior = c_ pre
F
current_ node ->prior = NULL
current_ node = head
Wolfgang E. Nagel
F
Zeitkomplexität der einzelnen Basisoperationen in verschiedenen Listenrealisierungen
Einfach verkettete Liste
Operationen
Dynamische
Pointer
Realisierung
mit 3 Feldern
Realisierung
mit einem Feld
Doppelt
verkettete Liste
insert_after
O(1)
O(1)
O(n)
O(1)
insert_before
O(n)
O(n)
O(n)
O(1)
delete
O(n)
O(n)
O(n)
O(1)
retrieve
O(1)
O(1)
O(1)
O(1)
update
O(1)
O(1)
O(1)
O(1)
find_first
O(1)
O(1)
O(1)
O(1)
find_next
O(1)
O(1)
O(1)
O(1)
find_prior
O(n)
O(n)
O(1)
O(1)
Wolfgang E. Nagel
Zeitkomplexität der einzelnen Basisoperationen in verschiedenen Listenrealisierungen
Einfach verkettete Liste
Operationen
Dynamische
Pointer
Realisierung
mit 3 Feldern
Realisierung
mit einem Feld
Doppelt
verkettete Liste
locate
O(n)
O(n)
O(n)
O(n)
create
O(1)
O(n)
O(1)
O(1)
clear
O(n)
O(n)
O(1)
O(n)
empty
O(1)
O(1)
O(1)
O(1)
full
O(1)
O(1)
O(1)
O(1)
Wolfgang E. Nagel
Zusammenfassung
"   Verkettete Listen erlauben eine flexible Handhabung beim Einfügen und
Löschen von Elementen an vorgegebenen Positionen (kein Shiften von
Elementen).
"   Dem steht ein erhöhter Speicherplatz für die Pointer gegenüber.
"   Verkettete Listen sollten nur dort verwendet werden, wo häufiges Einfügen
oder Löschen wichtig ist.
"   Elemente werden i.A. nicht zusammenhängend abgespeichert.
"   Für Anwendungen, bei denen es auf einen schnellen Zugriff (oder schnelles
Suchen) ankommt, sind ARRAY-Realisierungen für Listen wesentlich
vorteilhafter.
Wolfgang E. Nagel
Stapel
"   andere Namen: Stack, Kellerspeicher, LIFO-Liste
"   Anwendungen: z.B. Implementierung von Programmiersprachen,
Auswertung von arithmetischen Ausdrücken, Übersetzung von
Programmiersprachen, u.w.
"   Anwendung außerhalb des EDV-Bereich: Tablettspender im Kasino
Wolfgang E. Nagel
Stapel
Definition:
Bei einem Stack handelt es sich um eine lineare Datenstruktur mit einem LIFO
Verhalten.
1. Elemente
Die Elemente sind von einem beliebigen Datentyp (stdelement).
2. Struktur
Die Struktur setzt die Elemente so in Beziehung, dass die
Ankunftsreihenfolge zu jeder Zeit erhalten bleibt (z.B. Zeitmarke).
3.  Operationen
create muss vor jeder anderen Operation ausgeführt werden, d.h.
zusätzliche Pre-Condition für die anderen Operationen: der Stack existiert.
Wolfgang E. Nagel
Operationen auf Stapeln I
push (const stdelement e)
pre
Der Stack ist nicht voll.
post
Der Stack enthält e als das zuletzt angekommene Element.
stdelement pop ()
pre
Der Stack ist nicht leer.
post
pop ist das zuletzt angekommene Element. Der Stack enthält
dieses Element nicht mehr.
Wolfgang E. Nagel
Operationen auf Stapeln II
bool empty()
post
IF
THEN
ELSE
Der Stack enthält kein Element
empty ist true
empty ist false
bool full ()
post
IF
THEN
ELSE
Der Stack hat seine maximale Länge erreicht
full ist true
full ist false
create()
post
Der Stack existiert und ist leer.
Wolfgang E. Nagel
Realisierung mit Hilfe einer verketteten Struktur
typedef struct stack_element *stack_pointer;
struct stack_element
{
stdelement
element;
stack_pointer next;
};
/* globale Variablen: */
static stack_pointer top;
Wolfgang E. Nagel
Element
TOP
Element
Element
.
.
.
Element
Element
Element
Wolfgang E. Nagel
create/empty
Komplexität:
void create ( void )
O(1)
top = NULL
bool empty ( void )
O(1)
return (top == NULL )
Wolfgang E. Nagel
push
Komplexität:
void push( stdelement e )
p = (stack_ pointer ) malloc (sizeof (struct stack_ element ))
assert (p != NULL)
O(1)
p ->element = e
p ->next = top
top = p
Wolfgang E. Nagel
P
neues Element
Element
Element
TOP
Element
Element
Wolfgang E. Nagel
pop
Komplexität:
stdelement pop ( void )
e = top->element
p = top
O(1)
top = top-> next
free(p )
return e
Wolfgang E. Nagel
Realisierung mit Hilfe eines Feldes
typedef int stackbereich;
typedef stdelement stack_type[MAX_STACK];
/* globale Variablen: */
static stackbereich top;
static stack_type stack;
belegt
0
frei
top
Wolfgang E. Nagel
MAX_STACK-1
create/empty/full
Komplexität:
void create ( void )
O(1)
top = -1
bool empty( void )
O(1)
return (top == -1 )
bool full ( void )
O(1)
return (top == (MAX _ STACK -1 ))
Wolfgang E. Nagel
push/pop
Komplexität:
void push( stdelement e )
O(1)
stack [++top ] = e
stdelement pop ( void )
O(1)
return (stack[ top - -] )
Wolfgang E. Nagel
Zeitkomplexitäten für die Stack-Implementierungen
Operation
verkettete Liste
Feldimplementation
push
O(1)
O(1)
pop
O(1)
O(1)
empty
O(1)
O(1)
full
O(1)
O(1)
create
O(1)
O(1)
clear
O(n)
O(1)
Wolfgang E. Nagel
Warteschlangen (Queues)
"   Wichtige Datenstruktur z.B. in Betriebssystemkernen
"   FIFO-Prinzip (First-In-First-Out)
"   Einfügen am Ende (tail), Entfernen am Anfang der Schlange (head)
"   Einfügeoperation: put oder enqueue
" Entferneoperation: get oder dequeue
Wolfgang E. Nagel
Element
head (get hier)
Element
Element
...
Element
Element
tail (put hier)
Wolfgang E. Nagel
Queues
Definition:
Eine Queue ist ein Datentyp mit einem FIFO-Verhalten (First-In-First-Out).
1. Elemente
Die Elemente sind von einem beliebigen Datentyp (stdelement).
2. Struktur
Die Struktur setzt die Elemente so in Beziehung, dass ihre
Ankunftsreihenfolge erhalten bleibt.
3. Operationen
create muss vor jeder anderen Operation ausgeführt werden,
d.h. zusätzliche Pre-Condition für die anderen Operationen:
die Queue existiert.
Q_pre bezeichnet die Queue vor Ausführung der Operation.
Wolfgang E. Nagel
Operationen auf Queues I
put (e : stdelement)
pre
Die Queue ist nicht voll.
post
Die Queue enthält e als das zuletzt angekommene Element.
get (var e : stdelement)
pre
Die Queue ist nicht leer.
post
e ist das Element, das am längsten in Q_pre ist.
Das Element e ist nicht mehr in der Queue.
Wolfgang E. Nagel
Operationen auf Queues II
bool empty()
post
IF
THEN
ELSE
bool full ()
post IF
THEN
ELSE
clear()
post
Anzahl der Elemente in der Queue ist Null.
empty ist true
empty ist false
Queue hat maximal zulässige Anzahl von Elementen
erreicht
full ist true
full ist false
Die Queue ist leer.
create()
post
Die Queue existiert und ist leer.
Wolfgang E. Nagel
Realisierung mit Hilfe eines Feldes
#define MAX_QUEUE ...!
!
/* globale Variablen: */!
static stdelement queue[MAX_QUEUE];!
static int head, tail;!
Wolfgang E. Nagel
create/clear
Komplexität:
void create ( void )
O(1)
tail = -1
head = MAX_QUEUE -1
void clear ( void )
O(1)
create()
Wolfgang E. Nagel
empty/full
Komplexität:
bool empty ( void )
O(1)
return (tail == -1)
bool full ( void )
O(1)
return (tail == head)
Wolfgang E. Nagel
put/get
Komplexität:
void put ( stdelement e )
tail = (tail+1)%MAX_QUEUE
O(1)
queue[tail] = e
stdelement get ( void )
int h
h = (head+1)%MAX_QUEUE
h == tail
TRUE
clear()
FALSE
head = h
return (queue[h])
Wolfgang E. Nagel
O(1)
length
Komplexität:
int get ( void )
tail > head
TRUE
len = tail - head
FALSE
len = MAX_QUEUE + tail - head
return (len)
Wolfgang E. Nagel
O(1)
Fakultät Informatik, Institut für Technische Informatik, Professur Rechnerarchitektur
Effiziente Parallele Algorithmen
Datenstrukturen Teil II
Zellescher Weg 12
Nöthnitzer Straße 46
Willers-Bau A 205
Raum 1044
Tel. +49 351 - 463 - 35450
Tel. +49 351 - 463 - 38246
Wolfgang E. Nagel ([email protected])
Begriffe aus der Graphentheorie
!   Ein Graph G‘ = (V‘, E‘) heißt Teilgraph von G = (V, E), wenn V‘ ⊆ V und E‘ ⊆
E gilt.
!   Ein Weg heißt einfacher Weg, wenn jeder Knoten auf dem Weg genau
einmal vorkommt.
!   Ein Zykel (Kreis) ist ein einfacher Weg mit der Ausnahme α(W) = ω(W).
!   Zwei Knoten heißen adjazent, wenn es eine Kante gibt, die sie verbindet.
!   Ein Graph heißt gewichtet (bewertet), wenn jeder Kante ein Wert als
Gewicht zugeordnet ist. (z.B. Transportkosten, Entfernung, etc.)
Wolfgang E. Nagel
Speicherung von Graphen
1.  Adjazenzmatrix
Sei G = (V, E) ein ungerichteter oder gerichteter Graph mit V = {v1, ... , vn} .
Die Adjazenzmatrix eines Graphen ist eine n x n Matrix
A = (aij), i,j = 1...n mit
aij =
1 falls (vi,vj) ∈ E
0 sonst
2.  Adjazenzlisten (Nachbarschaftslisten)
Es wird eine Liste aller Knoten verwendet, deren Elemente auf den Anfang
einer verketteten Liste zeigen, in der alle Knoten stehen, die mit dem
entsprechenden Knoten adjazent sind.
Wolfgang E. Nagel
Speicherkomplexität
! Adjazenzmatrix:
|V|2
! Adjazenzliste:
|V| + |E|
!   Der Platzbedarf für Adjazenzmatrizen ist i.A. wesentlich höher als der für
Adjazenzlisten, jedoch ist die Verwaltung der Adjazenzmatrix effizienter
möglich (Zugriff auf a[i][j]) als die der Adjazenzlisten.
Wolfgang E. Nagel
Datenstruktur Baum
Definition:
Ein Baum ist ein gerichteter Graph G = (V, E) mit folgenden Eigenschaften:
Es existiert genau ein Knoten v ∈ V (Wurzel des Baumes, root) derart, dass für
alle Knoten vi genau ein Weg von v nach vi ∈ V existiert.
♦
Wolfgang E. Nagel
Unterscheidung Baum/Graph
Baum (und Graph)
Graphen, aber keine Bäume
Wolfgang E. Nagel
Bemerkungen
!   Hierarchische (rekursive) Datenstruktur
!   Partielle Ordnung auf den Knoten eines Baumes, da alle Wege von
der Wurzel ausgehen.
–  A heißt Vorgänger von B, wenn A auf einem Weg von der Wurzel
zu B liegt.
B heißt entsprechend Nachfolger von A.
–  A heißt Vater von B, wenn (A, B) ∈ E, B heißt Sohn von A.
–  Haben B und C denselben Vater, so heißen sie Brüder.
–  Knoten, die keinen Nachfolger haben, heißen Blätter.
–  Knoten, die einen oder mehrere Nachfolger haben, heißen
innere Knoten.
!   Ein Knoten S mit allen Nachfolgern wird Teilbaum eines Baumes T
genannt, falls S ein Knoten ungleich der Wurzel von T ist.
Wolfgang E. Nagel
Knotenbezeichnungen
Wurzel (root)
Blatt (leaf)
innerer Knoten
Blatt
Blatt
Wolfgang E. Nagel
Definitionen
Definition:
Jeder Knoten in einem Baum liegt auf einem bestimmten Level.
1.  Das Level der Wurzel ist 0.
2.  Das Level eines Knotens ist gleich dem um 1 inkrementierten Level seines
Vaters.
♦
Definition:
Die Höhe eines Baumes ist gleich dem maximalen Level, welches mit einem
beliebigen Knoten des Baumes assoziiert ist.
♦
Wolfgang E. Nagel
Datenstruktur Binärbaum
Strukturgedanke:
Bäume, bei denen jeder Knoten höchstens zwei Söhne hat, heißen
Binärbäume.
Hat jeder Knoten entweder keinen oder zwei Söhne und die Blätter liegen auf
dem gleichen Level, so heißt der Baum voller Binärbaum.
Ist die Reihenfolge der Söhne durch die Indizes eindeutig festgelegt
(T1 = linker Sohn, linker Teilbaum; T2 = rechter Sohn, rechter Teilbaum),
so handelt es sich um einen geordneten Binärbaum.
Wolfgang E. Nagel
Besuchen aller Knoten
!   Es gibt n! Möglichkeiten, eine Struktur mit n Elementen zu durchlaufen.
!   Für einen Binärbaum gibt es 3!=6 Möglichkeiten, einen solchen Baum
geordnet zu durchlaufen:
–  W L R = Preorder
–  W R L
–  R W L
–  R L W
–  L W R = Inorder
W
–  L R W = Postorder
L
Wolfgang E. Nagel
R
1
Preorder
2
4
3
5
6
3
Inorder
1
5
2
4
6
6
Postorder
2
5
1
3
Wolfgang E. Nagel
4
Arithmetische Ausdrücke in Prefix-Notation
+!
!   (a + b) * c + 7
Prefix-Notation:
7!
*!
+*+abc7
+!
Postfix-Notation: a b + c * 7 +
a!
c!
b!
!   (a + b) * (c + 7)
Prefix-Notation:
*!
*+ab+c7
+!
Postfix-Notation: a b + c 7 + *
a!
+!
b!
c!
Bemerkung: Bei der Infix-Notation sind i.A. Klammern notwendig,
um die Baumstruktur eindeutig wiedergeben zu können.
Wolfgang E. Nagel
7!
Größe, Höhe und durchschnittliche Weglänge
Baum 1
Baum 2
Baum 3
Größe
1
2
7
Höhe
0
1
2
0 +1 1
=
2
2
0 + 1 + 1 + 2 + 2 + 2 + 2 10
=
7
7
durchschn.
Weglänge
€
€
0
€
€
€
Wolfgang E. Nagel
€
€
Datenstruktur Binärbaum
Definition: Binärbaum
1. Elemente
Die Elemente eines Binärbaumes heißen Knoten, und jeder Knoten enthält
eine Datenkomponente e.
2. Struktur
Ein Binärbaum ist entweder leer oder besteht aus einem Knoten (Wurzel)
zusammen mit zwei Binärbäumen. Diese zwei Binärbäume sind disjunkt
voneinander sowie von der Wurzel und werden linker bzw. rechter
Teilbaum der Wurzel genannt.
Ein Binärbaum hat eine hierarchische Struktur. Immer dann, wenn der
Baum nicht leer ist, gibt es eine eindeutige Wurzel.
Jeder Knoten (außer der Wurzel) hat genau einen Vater und keinen, einen
oder zwei Söhne.
Ein Sohn ist entweder ein rechter oder ein linker Sohn eines Knotens.
Wolfgang E. Nagel
Operationen auf dem Binärbaum I
3. Operationen
create
muss vor jeder anderen Operation ausgeführt werden, d.h. zusätzliche
Pre-Condition für die anderen Operationen: der Binärbaum existiert.
Vereinbarungen:
•  current_node bezeichnet den aktuell bearbeiteten Knoten im Baum.
•  c_pre ist der current_node vor der Ausführung der Operation.
•  t_pre ist der Baum vor Ausführung der Operation.
•  Folgende Datentypen werden vereinbart:
-  typedef enum {preorder, inorder, postorder} order;
-  typedef enum {rootv, leftchild, richtchild, parent} relative;
-  struct status {
int size;
int height;
double average_path_length;
};
•  rootv wird verwendet, um den Knoten Wurzel von dem Pointer root
auf die Wurzel zu unterscheiden.
Wolfgang E. Nagel
Operationen auf dem Binärbaum II
traverse (order traverse_order,
void process (stdelement *e_ptr, int level, void *data_ptr),
void *data_ptr)
pre
Der Baum ist nicht leer.
post Jeder Knoten des Baumes wurde genau einmal bearbeitet.
Die Reihenfolge, in der die Knoten bearbeitet werden, ist durch
den Parameter traverse_order festgelegt.
switch (traverse_order)
preorder:
Jeder Knoten wird vor der Verarbeitung seiner
Teilbäume bearbeitet.
inorder:
Jeder Knoten wird nach der Verarbeitung
seines linken Teilbaumes und vor der
Verarbeitung seines rechten Teilbaum bearbeitet.
postorder:
Jeder Knoten wird nach der Verarbeitung seiner
Teilbäume bearbeitet.
Jeder Knoten wird über die Funktion process bearbeitet, die einen Zeiger
auf das Datenelement übergeben bekommt. data_ptr kann auf einen
Datenbereich zeigen, der z.B. in process verwendet wird.
Wolfgang E. Nagel
Operationen auf dem Binärbaum III
bool insert (const stdelement e, relative rel)
pre
Entweder ist rel gleich rootv und der Baum ist leer, oder rel
ist ungleich rootv und der Baum ist nicht leer.
post
Ein neuer Knoten mit dem Datenelement e kann in Abhängigkeit
vom Wert rel im Baum enthalten sein.
Falls der Knoten im Baum enthalten ist, ist er der current_node.
...
Wolfgang E. Nagel
Operationen auf dem Binärbaum IV
bool insert (...) cont.
switch (rel)
rootv:
leftchild:
e ist Element der Wurzel des Baumes und insert ist true.
IF
c_pre hat keinen linken Sohn
THEN c_pre hat einen linken Sohn, dieser Knoten enthält
e als Datenelement, und insert ist true
ELSE insert ist false
rightchild:
IF
c_pre hat keinen rechten Sohn
THEN c_pre hat einen rechten Sohn, dieser Knoten enthält
e als Datenelement, und insert ist true
ELSE insert ist false
parent: insert ist false
Wolfgang E. Nagel
Operationen auf dem Binärbaum V
void delete_sub()
pre
Der Baum ist nicht leer.
post
Der Teilbaum von t_pre, dessen Wurzel c_pre ist, ist aus dem
Baum entfernt. Der neue current_node ist die Wurzel des
Baumes.
stdelement retrieve()
pre
Der Baum ist nicht leer
post
retrieve ist eine Kopie des Datenelements von c_pre.
void update (const stdelement e)
pre
Der Baum ist nicht leer.
post
c_pre enthält e als sein Datenelement.
Wolfgang E. Nagel
Operationen auf dem Binärbaum VI
bool find (relative rel )
pre
Der Baum ist nicht leer.
post Der current_node ist in Abhängigkeit von rel wie folgt definiert
switch (rel)
rootv:
der current_node ist die Wurzel und find ist true.
leftchild:
IF
c_pre hat linken Sohn
THEN
current_node ist der linke Sohn, find ist true
ELSE
find ist false
rightchild:
IF
c_pre hat rechten Sohn
THEN
current_node ist der rechte Sohn, find ist true
ELSE
find ist false
parent:
IF
c_pre hat Vater
THEN
current_node ist der Vater, find ist true
ELSE
find ist false
Wolfgang E. Nagel
Operationen auf dem Binärbaum VII
struct status characteristics()
post
characteristics enthält die Anzahl der Knoten, die Höhe des
Baumes und die durchschnittliche Pfadlänge von der Wurzel
zu einem Blatt
bool empty()
post
IF
THEN
ELSE
Baum nicht leer
empty ist false
empty ist true
create()
post
Ein leerer Binärbaum existiert.
clear()
post
Der Binärbaum ist leer.
Wolfgang E. Nagel
Darstellung von Binärbäumen im Rechner
1. Inhalt des Knotens
2. Pointer auf den linken Sohn
3. Pointer auf den rechten Sohn
4. Ein Pointer root zeigt auf die Wurzel des Baumes
e!
Wolfgang E. Nagel
Binärbaumdarstellung mit Pointern
Wurzel
root
V
Y
X
Z
Wolfgang E. Nagel
Binärbaumdarstellung mit Feldern
Element
Y
1
left
right
-1
-1
2
root
Wurzel
3
6
7
Z
4
-1
-1
5
V
6
1
4
X
7
-1
-1
.
.
.
.
.
.
.
.
.
Wolfgang E. Nagel
Realisierung eines Binärbaumes
typedef enum {preorder, inorder, postorder} order;
typedef enum {rootv, leftchild, rightchild, parent}
relative;
typedef struct node * node_pointer;
struct node {
stdelement element;
node_pointer left, right;
};
struct status {
int size, height
double average_path_length;
};
struct characteristics {
struct status st;
int total_path_length;
};
static node_pointer root, current_node;
Wolfgang E. Nagel
newnode
Komplexität:
static node _ pointer newnode ( void )
p = (node _ pointer ) malloc (sizeof (struct node ))
assert ( p != NULL )
O(1)
p ->left = NULL
p ->right = NULL
return p
Wolfgang E. Nagel
insert - Teil 1
bool insert ( stdelement e, relative rel )
success = true
p = newnode ( )
p->element= e
switch(rel)
rootv
root = p
break
leftchild
current_node -> left == NULL
TRUE
current_node ->left = p
FALSE
success = false
break
Wolfgang E. Nagel
insert - Teil 2
Komplexität:
rightchild
current _ node->right == NULL
TRUE
current_node ->right = p
FALSE
success = false
break
parent
success = false
O(1)
break
success
T
current_ node = p
F
free( p )
return success
Wolfgang E. Nagel
find_parent
static node _ pointer find _ parent ( void )
Komplexität:
Stack erzeugen
push(NULL)
found = false
p = root
(p != NULL) && !found
(p ->left == current_ node ) | | (p ->right == current_ node )
TRUE
FALSE
p ->right != NULL
O(n)
T
found =
F
push(p ->right )
p ->left != NULL
true
TRUE
p = p ->left
FALSE
p = pop ()
Stack löschen
return p
Wolfgang E. Nagel
find_parent (rekursiv)
static node_ pointer find _ parent _ rec ( node_ pointer p)
Komplexität:
node_ pointer found
p = = NULL
T
F
p - > left == current_ node | | p - > right = = current_
node
T
found =
F
found = find _ parent _ rec (p - > left )
found
NULL
= p
found == NULL
T
found = find _ parent _ rec (p - > right)
O(n)
return found
node_ pointer find _ parent ( void )
node_ pointer found
T
found = NULL
F
root = = current_ node
F
found = find _ parent _ rec ( root )
return found
Wolfgang E. Nagel
find - Teil 1
bool find( relative rel )
success = true
switch(rel)
rootv
current _ node = root
break
leftchild
current _ node->left != NULL
T
current_ node = current_ node->left
F
success = false
break
Wolfgang E. Nagel
find - Teil 2
Komplexität:
rightchild
current _ node->right != NULL
T
F
current_ node = current_ node->right
success = false
break
parent
(p = find_ parent()) != NULL
TRUE
current_ node = p
FALSE
success = false
break
return success
Wolfgang E. Nagel
O(n)
treedispose
Komplexität:
static void treedispose( node _ pointer p )
p != NULL
T
F
O(n)
treedispose(p->left )
treedispose(p->right)
free(p)
Wolfgang E. Nagel
delete_sub
Komplexität:
void delete _ sub( void )
(p = find _ parent ()) != NULL
T
F
p ->left == current _ node
TRUE
p ->left = NULL
FALSE
O(n)
p ->right = NULL
clear ()
treedispose (current _ node )
current _ node = root
Wolfgang E. Nagel
retrieve/update
Komplexität:
stdelement retrieve ( void )
O(1)
return (current _ node ->element )
void update ( stdelement e )
O(1)
current _ node - >element = e
Wolfgang E. Nagel
traverse
Komplexität:
void traverse (order ord,
void (*process)( stdelement *e_ ptr, int level , void *data _ ptr ), void *data _ ptr )
ord
preorder
preord(root, 0,
inorder
postorder
process,
inord (root, 0, process,
data _ ptr)
data _ ptr)
postord(root, 0, process,
data _ ptr)
break
break
break
Wolfgang E. Nagel
default
O(n)
preord
Komplexität:
static void preord ( node _ pointer p , int level ,
void ( *process)( stdelement *e _ ptr, int level , void *data _ ptr ), void *data _ ptr )
p != NULL
T
F
O(n)
process( &(p ->element ), level , data _ ptr)
preord ( p ->left , level +1 , process, data _ ptr)
preord ( p ->right , level +1 , process, data _ ptr)
Wolfgang E. Nagel
inord
Komplexität:
static void inord ( node _ pointer p, int level ,
void (*process)( stdelement *e_ ptr, int level , void *data _ ptr ), void *data _ ptr )
p != NULL
T
F
O(n)
inord (p->left , level +1, process, data _ ptr)
process (&(p->element ), level , data _ ptr)
inord (p->right, level +1, process, data _ ptr)
Wolfgang E. Nagel
postord
Komplexität:
static void postord ( node _ pointer p , int level ,
void (*process)( stdelement *e _ ptr, int level , void *data _ ptr ), void *data _ ptr )
p != NULL
T
F
postord (p ->left , level +1 , process, data _ ptr)
postord (p ->right , level +1 , process, data _ ptr)
process(&(p ->element ), level , data _ ptr)
Wolfgang E. Nagel
O(n)
characteristics
Komplexität:
struct status characteristics ( void )
d.st.size = 0
d.st.height = 0
d.total _ path _ length = 0
!empty()
T
F
traverse(inorder , process_ characteristics, &d)
d.st.average _
d.st.average _ path _ length =
(double ) d .total _ path _ length / (double ) d .st.size
return (d .st)
Wolfgang E. Nagel
path _ length
= 0.0
O(n)
create/clear/empty
Komplexität:
void create ( void )
O(1)
root = NULL
current_ node = NULL
void clear ( void )
O(n)
treedispose (root)
create ()
bool empty( void )
O(1)
return (root == NULL)
Wolfgang E. Nagel
Bäume und Rekursion
Ein voller Binärbaum der Höhe H hat K = 2H+1 - 1 Knoten, von
denen B = 2H Blätter und I = 2H - 1 innere Knoten sind.
Aufrufgraphen (speziell bei Rekursion)
Bei n Eingabeelementen
H = k * n (verschiedene Wege stellen verschiedene Alternativen dar)
oder
B = n bzw. K = n
Wolfgang E. Nagel
Strategien zur Wegsuche
Volle Traversierung
→ Rekursion (Größe bestimmt Komplexität)
oder
Wegsuche
einfach;
es wird immer maximal ein Sohn besucht (Liste)
→ Iteration (Höhe bestimmt Komplexität)
komplex;
es müssen im worst case alle Wege untersucht werden
→ Rekursion (Größe bestimmt Komplexität)
Wolfgang E. Nagel
Komplexität
H=k*n
K = n oder B = n
Volle Traversierung
O(2n)
O(n)
Einfache Wegsuche
O(n)
O(log n)
Wolfgang E. Nagel
Rekursion bei Binärbäumen
typedef … returntype;
typedef … statustype;
bool goon_left( statustype s , node_pointer p );
bool goon_right( statustype s , node_pointer p );
statustype begin( node_pointer p );
statustype middle( statustype s, node_pointer p );
returntype end( statustype s , node_pointer p );
statustype from_left( statustype s, returntype r );
statustype from_right( statustype s, returntype r );
// Alle Funktionen haben die Komplexität O(1)
returntype rec( node_pointer p )
{
status = begin( p );
if ( goon_left( status, p ) )
{
result = rec( p->left );
status = from_left( status, result );
}
status = middle( status, p );
if ( goon_right( status, p ) )
{
result = rec( p->right );
status = from_right( status, result );
}
result = end( status, p );
return result;
}
Wolfgang E. Nagel
Datenstruktur binärer Suchbaum
Strukturgedanke:
Ein binärer Suchbaum ist ein Binärbaum mit der Eigenschaft, dass für jeden
Knoten N die folgenden Aussagen wahr sind:
1. 
Falls sich der Knoten L im linken Teilbaum vom Knoten N befindet,
so ist der Wert vom Knoten L kleiner als der Wert vom Knoten N.
2. 
Falls sich der Knoten R im rechten Teilbaum vom Knoten N befindet,
so ist der Wert vom Knoten R größer als der Wert vom Knoten N.
Vorteil: Man findet schneller ein Element, weil man weiß, wo man suchen
muss.
Wolfgang E. Nagel
Datenstruktur binärer Suchbaum
Definition: binärer Suchbaum
1. Elemente
Die Elemente eines binären Suchbaumes heißen Knoten;
sie sind durch einen Schlüssel eindeutig identifiziert.
2. Struktur
Ein binärer Suchbaum ist ein Binärbaum mit den zusätzlichen Eigenschaften:
–  Für alle Knoten im linken Teilbaum eines Knotens N gilt, dass sie
bezüglich des Schlüssels kleiner sind als der Knoten N.
–  Für alle Knoten im rechten Teilbaum eines Knotens N gilt, dass sie
bezüglich des Schlüssels größer sind als der Knoten N.
–  Diese Eigenschaften gelten rekursiv, d.h. jeder Teilbaum ist wiederum
ein binärer Suchbaum.
Wolfgang E. Nagel
Operationen auf dem binären Suchbaum I
3. Operationen
Spezifikation von vier wichtigen Operationen, die restlichen Operationen
wie in der Definition des Binärbaumes.
bool insert (const stdelement e)
post
IF
THEN
ELSE
t_pre enthält e nicht
e ist Element des Baumes, und insert ist true
insert ist false
bool delete_key (const stdelement e)
post
IF
THEN
ELSE
t_pre enthält das Element e
dieses Element ist nicht mehr im Baum enthalten,
und delete_key ist true
delete_key ist false
Wolfgang E. Nagel
Operationen auf dem binären Suchbaum II
update (const stdelement e)
pre
Der Baum ist nicht leer.
post
current_node enthält das Element e, und der Baum ist
weiterhin ein binärer Suchbaum.
bool findkey (const stdelement e)
pre
Der Baum ist nicht leer.
post IF
der Baum enthält einen Knoten mit dem Inhalt e
THEN
dieser Knoten ist der current_node und findkey ist
true
ELSE
der current_node ist der Knoten, an dem ein Knoten
mit dem Inhalt e als Sohn angehängt wäre, wenn e
im Baum enthalten wäre und findkey ist false
Wolfgang E. Nagel
Realisierung eines binären Suchbaumes
Wie beim binären Baum, d.h.:
typedef enum {preorder, inorder, postorder} order;
typedef enum {rootv, leftchild, rightchild, parent}
relative;
typedef struct node *node_pointer;
struct node
{
stdelement element;
node_pointer left, right;
};
/* globale Variablen: */
static node_pointer root, current_node;
Wolfgang E. Nagel
findkey
bool findkey( stdelement e )
success= false
p = root
current_ node = NULL
!success&& (p != NULL)
current_ node = p
p->element .key == e.key
T
F
e.key < p->element .key
success= true
TRUE
p = p->left
FALSE
p = p->right
return success
Wolfgang E. Nagel
insert
bool insert( stdelement e )
findkey(e)
T
F
p = newnode()
p->element = e
success= true
root == NULL
T
F
e.key < current_ node ->element.key
success= false
TRUE
root = p
FALSE
current_ node ->left = p
current_ node ->right = p
current_ node = p
return success
Wolfgang E. Nagel
delete_key (Skizze)
bool delete _ key_ s ( stdelement e )
suche nach dem Knoten , der entfernt werden soll
found
T
einer der Teilbäume
F
dieses Knotens ist leer
TRUE
FALSE
entferne den Knoten durch
Umhängen
des Zeigers
des Vaters auf den
nichtleeren Teilbaum (falls
beide leer , ist der
Teilbaum leer )
finde den am weitesten rechts stehenden
Knoten des linken Teilbaumes (=> righty )
bringe den Inhalt von righty zu dem Knoten ,
der gelöscht
werden soll
entferne den Zeiger von rightys Vater und
setze ihn auf den linken Teilbaum von righty
gib den Speicherplatz für
den Knoten frei
gib den Speicherplatz für righty frei
return found
Wolfgang E. Nagel
delete_key
bool delete _ key( stdelement e )
!empty()
TRUE
success= del (e, &root)
FALSE
success= false
current_ node = root
return success
Wolfgang E. Nagel
del
static bool del ( stdelement e , node _ pointer *addr _ of _ nodeptr )
p = *addr _ of _ nodeptr
p == NULL
T
F
e . key < p - >element . key
T
F
e . key > p - >element . key
T
F
remove = p
success =
success = true
success =
success =
p - >right == NULL
T
del ( e ,
F
p - >left == NULL
&( p - >left )
del ( e ,
*addr _ of _
TRUE
nodeptr =
false
)
&( p - >right ) )
p - >left
*addr _ of
_ nodeptr
= p - >right
free ( remove )
return success
Wolfgang E. Nagel
FALSE
subdel ( &( p - >left ) ,
&remove )
subdel
static void subdel ( node _ pointer *addr _ of_ nodeptr , node _ pointer *addr _ of_ remove )
q = *addr _ of_ nodeptr
q ->right != NULL
TRUE
FALSE
(*addr _ of_ remove )->element = q ->element
subdel (&(q->right), addr _ of_ remove )
*addr _ of_ remove = q
*addr _ of_ nodeptr = q->left
Wolfgang E. Nagel
delete_key (iterativ)
bool delete_ key (stdelement e)
success := true
root == NULL
T
F
p = root
addr_ p = & root
p != NULL & & e. key != p- >element.key
success
T
e.key < p->element.key
addr_ p = & ( p- >left)
addr_ p = & ( p- > right)
p = p- > left
p = p- >right
= false
T
success := false
p == NULL
del ( addr_ p )
current_ node = root
return success
Wolfgang E. Nagel
F
F
del (iterativ)
static void del ( node_ pointer *addr _ p )
p = *addr_ p
remove = p
p- >right == NULL
T
T
F
p- > left == NULL
addr_ p = & ( p- >left)
p = p- >left
*addr_ p
*addr _
p- >right != NULL
addr _ p = & ( p- > right )
p = p- >
p = p- > right
remove- > element = p- > element
= p- >left
right
remove = p
*addr_ p = p- > left
free( remove )
Wolfgang E. Nagel
F
update
void update ( stdelement e )
delete_key ( current _ node - >element )
insert ( e )
Wolfgang E. Nagel
Einfügen in einen binären Suchbaum
!   keine balancierte Verteilung der Knoten wird garantiert
!   bei ungünstiger Reihenfolge der Einfügeoperationen kann es zu linearen
Listen statt balancierten Suchbäumen kommen
Wolfgang E. Nagel
Definitionen
Definition:
Ein Binärbaum heißt minimal genau dann, wenn kein Binärbaum existiert, der
die gleiche Anzahl von Knoten aber eine niedrigere Höhe hat.
♦
Definition:
Ein vollständiger Binärbaum ist ein minimaler Binärbaum, in dem die Knoten
auf dem untersten Level so weit wie möglich links stehen.
♦
Wolfgang E. Nagel
Suchen in vollen binären Suchbäumen
Suchlänge zu einem Knoten: Weglänge von der Wurzel bis zum Knoten
Suche in einem vollen binären Suchbaum:
–  worst case: log(n+1) entspricht O(log n)
–  average case: log(n+1) - 1 entspricht O(log n)
Degenerierter Baum, d.h. auf jedem Level nur ein Knoten:
–  worst case: n entspricht O(n)
–  average case: (n+1)/2 entspricht O(n)
è höhenbalancierte Bäume erzeugen
Bekannte Varianten von höhenbalancierten Bäumen sind:
–  AVL-Bäume
–  2-3-Bäume
–  B-Bäume
Wolfgang E. Nagel
Einfügen in einem höhenbalancierten Binärbaum
A
B
B
A
T1
T3
T1
T2
T2
neuer Knoten
Wolfgang E. Nagel
T3
Datenstruktur B-Baum
Definition:
Jeder Knoten in einem B-Baum der Ordnung d enthält d bis 2d Elemente.
Die Wurzel bildet die einzige Ausnahme, sie kann 1 bis 2d Elemente enthalten.
Die Anzahl der Söhne in einem B-Baum ist entweder 0 oder um eins größer als
die Anzahl der Elemente, die der Knoten enthält.
Die Elemente in einem Knoten sind aufsteigend sortiert.
♦
Wolfgang E. Nagel
B-Bäume der Ordnung d
Jeder Knoten des Baumes kann als (p0, k1, p1, k2, p2,..., kn, pn) aufgefasst
werden, wobei pi ein Zeiger auf den i-ten Sohn des Knotens ist (0≤i ≤2d) und ki
ein Schlüssel (1≤i ≤2d).
Für die Schlüssel innerhalb der Knoten gilt k1<k2<...<kn.
Alle Schlüssel in dem Teilbaum, auf den p0 zeigt, sind kleiner als k1.
Für 1≤i<2d liegen alle Schlüssel k des Teilbaumes, auf den pi zeigt, im Bereich
ki<k<ki+1.
Alle Schlüssel in dem Teilbaum, in den pn zeigt, sind größer als kn.
Der längste Weg in einem B-Baum der Ordnung d ist logd+1 n.
Wolfgang E. Nagel
B-Baum der Ordnung 2
30 38 42
10 20 25
32 34
40 41
Wolfgang E. Nagel
44 50 56
Datenstruktur B-Baum
Definition: B-Baum
1. Elemente
Die Elemente sind von einem beliebigen Datentyp (stdelement) und sind
durch eine Schlüssel eindeutig identifiziert.
2.  Struktur
Ein B-Baum ist ein geordneter Baum. Jeder Knoten, außer der Wurzel, hat
einen eindeutigen Vater.
Jeder Knoten, außer den Blättern, hat einen Sohn mehr als er Elemente
enthält.
Die Elemente in einem Knoten sind nach den Schlüsseln aufsteigend
sortiert.
Wolfgang E. Nagel
Operationen auf dem B-Baum
3.  Operationen
Die Operationen sind denen eines Binärbaumes bzw. eines binären
Suchbaumes sehr ähnlich.
–  empty und characteristics bleiben gleich
–  Für create und clear wird Binärbaum durch B-Baum ersetzt.
–  retrieve:
Das Element, das geholt werden soll, muss aus 2d
Möglichkeiten spezifiziert werden.
–  delete_sub: ist nicht erlaubt.
–  find:
Es wird nicht nur der linke oder rechte Sohn angegeben,
sondern es gibt 2d+1 mögliche Söhne.
–  traverse:
inorder ist nicht erlaubt.
–  Die Operationen für einen binären Suchbaum findkey, update, insert
und delete_key bleiben erhalten.
♦
Wolfgang E. Nagel
Realisierung eines B-Baumes
#define ORDER ...
/* Ordnung des B-Baumes */
#define MAXNODE (2*ORDER)
/* max. Anzahl Elemente */
typedef struct node * node_pointer;
struct node {
node_pointer parent;
short
number_of_elements;
stdelement
elements[MAXNODE];
node_pointer children[MAXNODE+1];
};
/* globale Variablen: */
static node_pointer root, current_node;
Wolfgang E. Nagel
/*0..MAXNODE */
Einfügen in einen B-Baum
!   insert1:
–  Der Knoten enthält weniger als 2d Elemente und es kann ein zusätzliches
Element eingefügt werden.
–  Das Element wird so eingefügt, dass die Ordnung erhalten bleibt.
–  Der Einfüge-Prozess ist beendet.
!   insert2:
–  Der Knoten ist voll und muss aufgeteilt werden.
–  Ein neuer Knoten wird gebildet und erhält die d größten Elemente.
–  Die d kleinsten Elemente bleiben in dem Knoten.
–  Das mittlere Element wird an den Vater gegeben.
–  Der Einfüge-Prozess wird fortgesetzt.
!   insert3:
–  Die Wurzel wird aufgeteilt.
–  Ein neuer Wurzelknoten wird gebildet.
–  Das Element mit dem mittleren Schlüssel wird das einzige Element der
Wurzel.
–  Der Einfüge-Prozess ist beendet.
Wolfgang E. Nagel
insert
bool insert( stdelement e )
success = !findkey (e )
success
T
F
!empty()
TRUE
insert_ into _ node (current_ node , e )
FALSE
bilde Wurzel mit dem Element e
return success
Wolfgang E. Nagel
insert_into_node
static void insert_ into _ node ( Knoten , Element )
füge
neues Element der Ordnung nach ein
Knoten enthält
2d +1 Elemente
T
F
entferne mittleres Element aus dem Knoten
bilde neuen Knoten aus den d größten
Elementen
Knoten == Wurzel
TRUE
bilde neue Wurzel mit dem
mittleren Element
FALSE
insert_ into _ node (Vater , mittleres Element )
Wolfgang E. Nagel
Löschen in einem B-Baum
! target_node bezeichnet den Knoten, in dem das Element target_element
gelöscht werden soll.
!   delete1:
target_node enthält mehr als d Elemente.
target_element wird gelöscht.
Der Prozess ist beendet.
Wolfgang E. Nagel
Löschen in einem B-Baum
!   delete2:
target_node enthält genau d Elemente.
Wenn der rechte oder linke Bruder mehr als d Elemente enthält,
kann ein Element an target_node abgegeben werden. Dadurch
muss der Vater geändert werden.
Wenn der rechte und linke Bruder genau d Elemente enthält, wird
target_node mit einem Bruder zu einem Knoten mit 2d Elementen
verschmolzen. Das Element des Vaters, das diese beiden Knoten
trennte, ist auch in dem Knoten enthalten.
Wenn der Vater mehr als d Elemente enthält, wird der Prozess
beendet; sonst wird der Prozess mit dem Vater als neuem
target_node fortgeführt.
Wolfgang E. Nagel
Löschen in einem B-Baum
!   delete3:
target_node ist die Wurzel.
Solange mindestens ein Element übrigbleibt, ändert sich die
Höhe des Baumes nicht.
Wenn das letzte Element entfernt wird, so wird der einzige Sohn
zur neuen Wurzel.
Wolfgang E. Nagel
delete
void delete ( target _ node , target _ element )
target _ node == Blatt
TRUE
FALSE
suche Element s mit nächstgrößerem
delete _ leaf ( target _ node
Blatt x
ersetze target _ element durch s
, target _ element )
delete _ leaf ( x, s)
Wolfgang E. Nagel
Schlüssel
in
delete_leaf
static void delete _ leaf ( target _ node , target _ element )
lösche
target _ element
Anzahl Elemente < d
T
linker oder rechter Bruder hat > d Elemente
TRUE
F
FALSE
verschmelze target _ node mit einem Bruder
ein Element wird über
Vater an target _ node
abgegeben
den
übernimm
das trennende Element s des
Vaters v in diesen Knoten
delete _ leaf ( v, s)
Wolfgang E. Nagel
Fakultät Informatik, Institut für Technische Informatik, Professur Rechnerarchitektur
Effiziente Parallele Algorithmen
Parallele Maschinenmodelle
Zellescher Weg 12
Nöthnitzer Straße 46
Willers-Bau A 205
Raum 1044
Tel. +49 351 - 463 - 35450
Tel. +49 351 - 463 - 38246
Wolfgang E. Nagel ([email protected])
Laufzeit von Algorithmen
Ermittlung der Laufzeit von Algorithmen
!   Implementierung des Algorithmus in einer konkreten Programmiersprache
auf einen konkreten Rechner
–  konkrete Ergebnisse für die Laufzeit als Funktion der Eingabedaten
–  Übertragbarkeit der Ergebnisse problematisch
!   Nutzung eines idealisierten Modellrechners als Referenzmaschine
–  einfache Handhabung, breite Nutzung
–  höhere Allgemeingültigkeit der Ergebnisse
–  Übertragbarkeit auf reale Maschinen problematisch
Wolfgang E. Nagel
RAM (Random Access Machine)
Sequentielle Registermaschine
!   Modell für konventionelle Einprozessorrechner
!   Wichtiges Hilfsmittel für
–  Berechnung der Laufzeit von Algorithmen
–  Ermittlung der Komplexität von Problemen
!   RAM-Modell hat sich für die theoretische Analyse von sequentiellen
Algorithmen weitgehend durchgesetzt
!   Besteht aus:
–  Prozessor, der ein ausgezeichnetes Register, den Akkumulator, und
einen Befehlszähler besitzt
–  Lokalen Speicher mit abzählbar unendlich vielen Zellen (oder auch
Registern) L[0], L[1], L[2], ...
!   Bildet Basis der PRAM
Wolfgang E. Nagel
PRAM (Parallel Random Access Machine)
Parallele Registermaschine
!   Erfinder sind Fortune und Wyllie 1978
!   Globaler Speicher (abzählbar unendlich viele Speicherzellen G[0],G[1], ...)
!   Abzählbar unendlich viele modifizierte RAMs (Random Access Machines)
–  klassische RAM wurde durch ein Flag RUNNING
erweitert, das anzeigt, ob der Prozessor gerade aktiv ist
Wolfgang E. Nagel
PRAM (Parallel Random Access Machine)
!   synchrone Verarbeitung im Sinne einer synchronisierten SPMD-Verarbeitung
–  Steuerung aller Prozessoren durch gemeinsamen Takt
–  Prozessoren führen zu einem Zeitpunkt die selbe oder auch verschiedene
Rechenoperationen aus
–  Maß für die Kosten ist der PRAM-Schritt, besteht aus:
•  Lesen von Daten aus dem gemeinsamen Speicher
•  Ein Berechnungsschritt
•  Schreiben in den gemeinsamen Speicher
!   Zusätzliche Synchronisations- und Speicherzugriffskosten werden nicht
berücksichtigt
Wolfgang E. Nagel
PRAM (Parallel Random Access Machine)
!
!
G[0]! G[1]! G[2]! G[3]!
ACCU!
PC!
RUNNING!
P0!
…!
L[0]!
L[1]!
L[2]!
…!
…!
!
!
!
ACCU!
PC!
RUNNING!
!
!
!
L[1]!
L[2]!
P1!
…!
RAM 1!
Wolfgang E. Nagel
…!
L[0]!
…!
RAM 0!
!
…!
Modifikationen der PRAM
!   EREW (Exclusive Read Exclusive Write) – PRAM
!   CREW (Concurrent Read Exclusive Write) – PRAM
!   CRCW (Concurrent Read Concurrent Write) – PRAM
!   [ ERCW ( Exclusive Read Concurrent Write) - PRAM ]
Wolfgang E. Nagel
PRAM (Parallel Random Access Machine)
praktische Handhabung
!   Bestimmung der Zeitkomplexität von parallelen Algorithmen
•  Zeitaufwand als Funktion der Problemgröße
!   Bestimmung der asymptotischen Zeitkomplexität parallelen Algorithmen
•  Grenzverhalten: Problemgröße gegen unendlich
•  Keine Beschränkung des Parallelismus, da Prozessoranzahl der
PRAM unbeschränkt
•  Zumeist ist man nur an der „Größenordnung“ einer
Komplexitätsfunktion interessiert
-  O(1), O(log n), O(n), O(n2), O(nk), O(2cn)
Wolfgang E. Nagel
PRAM (Parallel Random Access Machine)
Diskussion
!   Welche asymptotische Zeitkomplexität hat der Algorithmus „Rekursives
Doppeln“?
!   Welche asymptotische Zeitkomplexität hat der Algorithmus „Multiplikation
zweier n * n Matrizen“?
!   Was wäre eine ideale asymptotische Zeitkomplexität?
!   Macht die Entwicklung immer schnellerer Computer die Entwicklung
asymptotisch schnellerer Algorithmen überflüssig?
!   Wie schätzen Sie das Butget-Verhältnis innerhalb der Grand Challenge
Initiative der USA ein bzgl.:
–  Entwicklung neuer Rechentechnik
–  Entwicklung neuer Algorithmen?
Wolfgang E. Nagel
P-PRAM (PRAM mit begrenzter Prozessoranzahl)
!   Abschätzung des Sp (Speedup mit p Prozessoren) hat in der
Parallelverarbeitung eine zentrale Bedeutung
T1
Sp =
Tp
p
T1
Tp
Anzahl der Prozessoren
Zeit(-schritte) mit p = 1 Prozessoren
Zeit(-schritte) mit p > 1 Prozessoren
à Erfordert ein paralleles Maschinenmodell mit begrenzter
Prozessoranzahl (P-PRAM)
!   Basis für Skalierbarkeitsanalysen
!   Entwicklung und Darstellung von Algorithmen auf der Grundlage
der P-PRAM
Wolfgang E. Nagel
PRAM
Bewertung der PRAM
!   sehr einfach, breite Anwendung bei der Bewertung von Algorithmen
!   gute Vergleichbarkeit von theoretischen Ergebnissen
!   liefert Aussagen zur asymptotischen Zeitkomplexität und zur logischen
Struktur von Algorithmen
!   oft ungeeignet für die Vorhersage von Laufzeiten realer Rechner
–  Speicher der PRAM ist uniform
à Speicherhierarchie bei realen Maschinen
!   synchrone Berechnung
à zumeist asynchrone Berechnung in der Praxis
Wolfgang E. Nagel
PRAM
Bewertung der PRAM
!   Anzahl der Prozessoren skaliert mit dem Problem
à begrenzte Prozessoranzahl bei realen Maschinen
à P-PRAM liefert als eine Sonderform der PRAM Aussagen zur Laufzeit
von Algorithmen mit begrenzter Prozessoranzahl
!   jede Berechnung, incl. Kommunikation, dauert einen Zeitschritt
à unterschiedliche Kommunikationszeiten (Nachrichtenlänge,
Entfernung der Prozessoren, Zugriffskonflikte)
à Laufzeit-Unterschiede bei verschiedenen arithmetischen
Operationen
Fazit:
Verwendung von parallelen Maschinenmodellen in Betracht ziehen, die
Kommunikation berücksichtigen
Wolfgang E. Nagel
MP-RAM (Message Passing Random Access Machine)
Kennzeichen einer MP-RAM
!   autonome Verarbeitungseinheiten, die ausschließlich über
Nachrichtenaustausch kommunizieren
!   N Verarbeitungseinheiten (VE), jede besitzt einen lokalen Speicher
!   Verbindungsnetzwerk mit fester Topologie (z.B. 2-d-Gitter)
!   Ergänzung des Instruktionsvorrates jeder VE durch eine (synchrone Send- und
Receive-Operation
à erlaubt nur Datenaustausch zwischen benachbarten Knoten
à erzwingt eine Abschätzung der Kommunikationsschritte
à Berücksichtigung der Topologie der Kommunikationsnetzwerkes
Wolfgang E. Nagel
LogP-Modell
Parameter des Modells heißen L, o, g, P und geben dem
Maschinenmodell seinen Namen
!   Kommunikationsverzögerung (communication latency) L
- obere Schranke für die Übertragungszeit einer Nachricht
(mit geringer Anzahl von übertragenen Werten)
!   zusätzliche Kommunikationskosten (communication overhead) o
- Rechenzeit, die ein Prozessor zum Absetzen oder Empfangen einer
Nachricht benötigt
!   Kommunikationstotzeit (gap) g
- Zeit, die zwischen zwei aufeinander folgenden Sende- oder
Empfangsoperationen liegen muss
!   Anzahl der Prozessoren (number of processors) p
Wolfgang E. Nagel
LogP-Modell
weitere Kennzeichen des LogP - Modell
!   alle Prozessoren über Kommunikationsnetzwerk direkt miteinander
verbunden
!   Kanalbandbreite begrenzt
!   Parameter L, o, g, P sind wählbar
à Modell anpassbar an verschiedene Parallelrechner
à L, o, g werden als Vielfaches des Maschinenzyklus der zu
modellierenden Maschine angegeben
!   jede Sende bzw. Empfangsoperation fordert vom Prozessor o
Maschinenzyklen
à alle Sende bzw. Empfangsoperation erfolgen sequentiell
!   alle arithmetischen Operationen einen Maschinenzyklus
Wolfgang E. Nagel
Parallele Maschinenmodelle
Zusammenfassung
!   PRAM und P-PRAM sehr weit verbreitet
–  Bilden auch Grundlage für Abschätzung von Zeitkomplexitäten innerhalb
der Lehrveranstaltung „Effiziente parallele Algorithmen“
! LogP-Modell ist am besten geeignet, wenn theoretisch ermittelte
Zeitkomplexitäten auf die Laufzeit realer Maschinen übertragen werden
sollen
–  Vertiefung innerhalb der Lehrveranstaltung „Konzepte der parallelen
Programmierung“ (Dr. Trenkler)
Wolfgang E. Nagel
Fakultät Informatik, Institut für Technische Informatik, Professur Rechnerarchitektur
Effiziente parallele Algorithmen
Suchen
Zellescher Weg 12
Nöthnitzer Straße 46
Willers-Bau A 205
Raum 1044
Tel. +49 351 - 463 - 35450
Tel. +49 351 - 463 - 38246
Wolfgang E. Nagel ([email protected])
Algorithmen
!   Suchen von Elementen
!   Sortieren von Elementen
!   Effiziente Speicher- und Zugriffsverfahren
!   Graphenalgorithmen
Ziel: Effiziente Verfahren
Wolfgang E. Nagel
Suchen
Definition:
Eine Schlüsselmenge ist eine linear geordnete endliche Menge S = {s1, ..., sn} .
♦
Bemerkung:
!   Schlüssel dienen der eindeutigen Identifikation von Daten und werden i. A.
zusammen mit den Daten abgespeichert oder sind Bestandteil der Daten.
!   Schlüssel müssen nicht Zahlen sein. Auch Namen können als Schlüssel
verwendet werden (lexikographische Sortierung).
Wolfgang E. Nagel
Bemerkungen
!   Such- und Sortieralgorithmen werden hier nur für Felder betrachtet
!   Indizes haben Wertebereich 1..n
!   Feldelemente haben eine Komponte key, die den Schlüssel enthält, also z.B.
struct
{
int key;
double daten;
} liste[N+1];
!   Vergleich zweier Elemente:
if( liste[i].key < liste[j].key) ...
Wolfgang E. Nagel
Lineares Suchen (Sequential Search)
list_ index seq_ search( keytype key )
i =1
element = 0
(element == 0) && (i <= n)
list[i ].key == key
T
F
element = i
i ++
return element
Wolfgang E. Nagel
Lineares Suchen (Sequential Search)
Vorteile des Algorithmus:
!   Einfach zu programmieren.
!   Schnell für kurze Listen.
!   Liste braucht nicht sortiert zu sein.
Nachteile des Algorithmus:
!   Hoher Zeitbedarf für lange Listen.
!   Im Mittel müssen (n+1)/2 Elemente betrachtet werden.
Wolfgang E. Nagel
Sortierte Liste
Definition:
Eine Liste mit n Elementen heißt (nach Schlüsseln) sortierte Liste, falls gilt:
∀ 1 ≤ i ≤ n-1 : Feld[i].Schlüssel ≤ Feld[i+1].Schlüssel.
♦
Wolfgang E. Nagel
Binäres Suchen (Binary Search)
Voraussetzung: sortierte Listenelemente
Prinzip:
!   Greife zuerst auf das mittlere Element zu
!   Prüfe, ob das gesuchte Element
a. kleiner
b. gleich oder
c. größer
ist als das betrachtete Element.
Im Fall b. ist man fertig.
Im Fall a. bzw. c. wird mit der linken bzw. rechten Teilliste fortgefahren.
Wolfgang E. Nagel
Binäres Suchen (Binary Search)
list _ index bin _ search ( keytype key )
low = 1
high = n
element = 0
(element == 0 ) && (low <= high )
i = ( low +high ) /2
middle = list [i ].key
key > middle
T
F
key < middle
low = i + 1
TRUE
FALSE
high = i - 1
element = i
return element
Wolfgang E. Nagel
Komplexität des binären Suchens
Bei jedem Suchschritt halbiert sich die Anzahl der für die Suche relevanten
Elemente.
⇒ im worst-case ⎡log2 n+1⎤ Suchschritte.
Veranschaulichung durch binären Suchbaum.
Mit k Suchschritten sind 2k -1 Elemente erreichbar.
Nach ⎡ log2 n+1 ⎤ -1 Schritten ist ungefähr die Hälfte aller Elemente erreichbar,
d.h. der Suchaufwand im Mittel ist nicht wesentlich geringer.
Wolfgang E. Nagel
Vor-/Nachteile des binären Suchens
Vorteil des Algorithmus:
!   Komplexität O(log2 n).
Nachteil des Algorithmus:
!   Liste muss sortiert sein, d.h. zum Suchaufwand kommt der Sortieraufwand
hinzu.
Der Algorithmus ist günstig, falls oft gesucht, aber selten eingefügt oder
gelöscht werden muss.
Beim Einfügen sind u.U. spezielle Einfüge-Operationen notwendig, die die
Eigenschaft ‚sortiert‘ erhalten.
Wolfgang E. Nagel
Hash-Verfahren
Binärer Suchbaum in höhenbalancierter Version (z.B. B-Baum) erlaubt effiziente
Zugriffe zu Elementen großer Mengen.
Bei n = 10.000 Elementen wird jedes beliebige Element in log2 n ≈ 14 Schritten
gefunden.
Ziel:
Zugriffsverhalten im average-case günstiger als log n zu machen.
à Hash - Verfahren
Annahme:
Feld a[N], in das die Elemente gespeichert werden sollen.
address calculator, der für einen Schlüssel X einen Feldindex i angibt, wo das
Element gespeichert werden soll.
Wolfgang E. Nagel
Feld
Index
1
2
i
X
adress
calculator
3
R
4
T
n-1
...
X
n
Wolfgang E. Nagel
Definitionen
Definition:
Sei S eine Schlüsselmenge und I eine Adressmenge im weitesten Sinn.
Dann heißt h : S → I Hash - Funktion.
Die Bildmenge h(S) ⊆ I bezeichnet die Menge der Hash-Indizes.
♦
Bemerkung:
Die Schlüsselmenge ist im allgemeinen sehr viel größer als die Adressmenge.
Deshalb wird eine Hash-Funktion surjektiv [∀b∈I:∃a∈S:h(a)=b], aber nicht
injektiv [h(a)=h(b)àa=b] sein.
Wolfgang E. Nagel
Beispiele
Schlüsselmenge: Strings
Adressmenge: 0..25
Schlüssel
!   h1(s) = s[0] - ‚A‘
I
J
ia
ib
!   h2(s):
index = 0;
for(i=0; i < strlen(s); ++i)
index += s[i] - ‘A‘;
index = index % 26;
Wolfgang E. Nagel
I
J
ia
ib
Adressen
Beispiel
h3(s) = (ord(s[0]) + ord(s[1]) + ord(s[2])) % 17
mit ord(x) = toupper(x) - ‚A‘
0:
8: Juni
1:
9: August, Oktober
2:
10: Februar
3: Mai, September
11:
4:
12:
5: Januar
13:
6: Juli
14: November
7:
15: April, Dezember
16: März
Wolfgang E. Nagel
Definition
Definition:
Sei S Schlüsselmenge, h Hash-Funktion.
Ist für s1 ≠ s2 (mit si ∈ S)
h(s1) = h(s2),
so spricht man von einer Kollision.
♦
Bemerkung:
Kollisionen sind abhängig von der Hash-Funktion. Deshalb sind solche HashFunktionen gesucht, die gut streuen! Weiterhin sollte die Hash-Funktion
effizient berechenbar sein.
Wolfgang E. Nagel
Wahrscheinlichkeit von Kollisionen
!   Annahme: ideale Hash-Funktion, d.h. gleichmäßige Verteilung über die HashTabelle
!   Geburtstagproblem: Wie groß ist die Wahrscheinlichkeit, dass mindestens 2
von n Leuten am gleichen Tag Geburtstag haben?
!   Analogie zum Geburtstagsproblem:
–  m = 365 Tage = Größe Hash-Tabelle
–  n Personen = Zahl Elemente
Wolfgang E. Nagel
Wahrscheinlichkeit von Kollisionen
p(i;m) := Wahrscheinlichkeit, dass der i-te Schlüssel auf einen freien Platz
abgebildet wird (i=1,...,n) wie alle Schlüssel vorher
n
n −1
n −1
i
P( NoKo ln, m ) = ∏ p(i; m) = ∏ (1 − )
m
i =0
i =0
Wolfgang E. Nagel
P(Kol n,m)
10
0,11695
20
0,41144
22
23
24
0,4757
0,5073
0,53835
30
40
50
0,70632
0,89123
0,97037
Änderung der Größe der Hash-Tabelle
Situation:
Doppelt so viele Elemente wie bisher sollen in einer Hash-Tabelle gespeichert
werden. Wie soll m mit n wachsen, um P(NoKoln,m) konstant zu halten? D.h.
um wieviel muss die Hash-Tabelle vergrößert werden, um die
Kollisionswahrscheinlichkeit konstant zu halten?
Ohne Beweis:
P(NoKoln,m) bleibt in etwa konstant, wenn m (Größe der Hash-Tabelle)
quadratisch mit n (Anzahl Elemente) wächst.
Wolfgang E. Nagel
Kollisionsbehandlung
!   Situation: zwei Einträge werden durch die Hash-Funktion auf die gleiche
Feldadresse abgebildet, d.h. h(s1) = h(s2)
!   verschiedene Strategien bei Kollisionsbehandlung möglich:
–  Hash in Teillisten
–  offenes Hash-Verfahren
Wolfgang E. Nagel
Hash in Teillisten (Variante 1)
Prinzip:
Die Hash-Tabelle besteht aus n linearen Listen.
h(s) = 0
1
h(s) = 1
. . . . . . .
0
...
n-1
Wolfgang E. Nagel
Hash in Teillisten (Variante 2)
Prinzip:
Die Hash-Tabelle besteht aus n Teillisten der fixen Länge k.
0
1
k-1
h(s) = 0
1
h(s) = 1
. . . . . . .
0
...
...
n-1
Wolfgang E. Nagel
Speicherung:
1.  Zunächst wird mit Hilfe der Hash-Funktion aus dem Schlüssel der HashIndex (1 ≤ h(s) ≤ n) berechnet. Dieser gibt an, in welcher Teilliste der
Datensatz gespeichert wird.
2.  Innerhalb der Teillisten wird sequentiell gespeichert.
Bemerkung:
Das Suchen in der Teilliste bleibt sequentiell.
Die Speicherung in der (sequentiellen) Teilliste kann effizient vorgenommen
werden, wenn zu jeder Teilliste ein Pointer existiert, der jeweils auf den ersten
freien Listenplatz zeigt.
Wolfgang E. Nagel
Realisierung einer Hash-Tabelle in Teillisten
#define N 1000
#define K 10
typedef struct
{
int key;
char name[20];
char ort[20];
} daten;
static daten tabelle[N][K];
static short pointer[N];
Wolfgang E. Nagel
Bemerkung:
!   Das Feld Pointer enthält die Indizes des jeweils ersten freien Platzes in jeder
Teilliste und dient gleichzeitig dazu, festzustellen, ob eine Teilliste voll ist.
!   Die Teillisten können als verkettete Liste implementiert werden. Beim
Speichern eines Datensatzes ist dann Einfügen am Anfang sinnvoll.
Wolfgang E. Nagel
Schrittzahl beim Suchen:
Sei n die Anzahl der Teillisten und m die Anzahl der gespeicherten Records.
Bei idealer Verteilung der Schlüssel entfallen α = m/n Elemente auf jede
Teilliste. α wird Füllfaktor der Hash-Tabelle genannt. Für den Aufwand (Zugriffe
auf Tabelle) gilt:
erfolgreiches Suchen: 1 + α /2
erfolgloses Suchen: 1 + α
Damit dauert das Suchen O( m/n ) Schritte (m ≥ n ).
Ist m < n ⇒ mindestens ein Suchschritt.
Folgerung:
Um schnell suchen zu können, müssen größenordnungsmäßig so viele
Teillisten wie Records vorhanden sein.
Wolfgang E. Nagel
Offenes Hash - Verfahren
Prinzip:
1.  Berechnung einer Speicheradresse aus dem Schlüssel eines Datensatzes
mit Hilfe einer geeigneten Hash-Funktion. Ist der so berechnete
Speicherplatz frei, so wird der Datensatz dort gespeichert.
2.  Ist der errechnete Speicherplatz durch einen anderen Datensatz belegt, so
liegt eine Kollision vor.
Berechnung einer Ersatzadresse und Wiederholung des Speicherversuches.
Ist der berechnete Speicherplatz wiederum belegt, so wird erneut eine
Ersatzadresse berechnet, bis ein freier Platz gefunden wird oder der
verfügbare Speicher ganz durchlaufen wurde.
3.  Das Lesen (Suchen) von Elementen erfolgt analog, bis das gesuchte
Element gefunden ist.
4.  Löschoperationen sind jetzt sehr aufwendig!
Wolfgang E. Nagel
Beispiel offenes Hash-Verfahren
Eine Firma hat zur eindeutigen Identifizierung ihrer Mitarbeiter
Personalnummern im Bereich von 1...9999 vergeben. Sie hat zur Zeit 60
Mitarbeiter und hat zur Speicherung der Personaldaten deshalb nur 100
Speicherplätze für Datensätze bereitgestellt.
Schlüsselmenge:
S = {1...9999}
Hash-Indizes:
I = {1...100}
Hash-Funktion:
h : S → I mit h(s) = s mod 100 + 1
Bei einer Kollision wird der berechnete Hash-Index solange um 1 (modulo 100)
erhöht, bis ein freier bzw. der gesuchte Eintrag gefunden wird.
Wolfgang E. Nagel
Divisions-Hash
Definition: Divisions-Hash
Ein Hash-Verfahren, das die Hash-Indizes aus dem Schlüssel mit Hilfe der
Modulo-Funktion berechnet, heißt Divisions-Hash.
Wird die Ersatzadresse bei jeder Kollision durch Erhöhen der alten Adresse um 1
berechnet, so spricht man von linearem Sondieren (linearem Probing).
Die i-te Ersatzadresse für einen Schlüssel s mit Hash-Index h(s) wird also wie
folgt berechnet:
ei(s) = (h(s) + i) mod n
(e0(s) = h(s) ist der Hash-Index der Hash-Funktion selbst)
♦
Wolfgang E. Nagel
Bemerkung:
Nachteil des Divisions-Hash mit linearem Sondieren ist, dass sich an Stellen mit
häufigen Kollisionen leicht Ketten bilden, so dass sich die Wahrscheinlichkeit für
weitere Kollisionen in diesem Bereich noch erhöht. Das Verfahren streut also
nicht wirklich. Diesen Effekt [h0(s) ≠ h0(t) ^ hi(s) = hi(t) à hi+1(s) = hi+1(t)] nennt
man primäre Häufung (primary clustering).
Ein weiterer Effekt ist die sekundäre Häufung (secondary clustering). Wenn
zwei Schlüssel denselben Hashwert haben h0(s) = h0(t), so ist auch hi(s) = hi(t).
Wolfgang E. Nagel
Quadratisches Sondieren
Definition:
Die Strategie, bei der die Funktion
ei(s) := (h(s) + i2 ) mod n
zur Berechnung der Ersatzadresse gewählt wird, heißt
quadratisches Sondieren.
♦
Wolfgang E. Nagel
Quadratisches Sondieren
(Sei n = 11, d.h. 11 Speicheradressen)
i
0
1
2
3
4
i2
0
1
4
9 16 25 36 49 64 81 100
i2 mod n
0
1
4
9
5
5
3
6
3
7
5
8
9
9
4
10
1
Bemerkungen:
–  Eine primäre Häufung findet nicht mehr statt.
–  Ein Nachteil ist aber offensichtlich, dass ei(s) = en-i(s) gilt, d.h. nicht alle
zur Verfügung stehenden Adressen werden erreicht.
Wolfgang E. Nagel
Satz: (ohne Beweis)
Ist n eine Primzahl, so sind die Zahlen
i2 mod n für 0 ≤ i ≤ n/2
paarweise verschieden.
Hiermit lässt sich also bei geeigneter Wahl der Tabellengröße immerhin
der halbe Speicherplatz überdecken.
♦
Satz: (ohne Beweis)
Ist n ≡ 3 mod 4 (n kongruent 3 modulo 4, d.h. n lässt bei Division durch 4 den
Rest 3) und n Primzahl, so ist die Menge der Reste modulo n von
i2 für i ungerade
- i2 für i gerade (inkl. i = 0)
ein volles Restsystem modulo n, d.h. alle möglichen Reste kommen genau
einmal vor.
♦
Wolfgang E. Nagel
Daher:
Die Funktion
ei(s) := (h(s) + (-1)i+1 i2 ) mod n
mit n ≡ 3 mod 4 und n Primzahl stellt n verschiedene Ersatzadressen bereit.
Wolfgang E. Nagel
Vollständiger Hash
Sei n = 11, d.h. 11 Speicheradresse. n=11 erfüllt die oben genannte
Voraussetzung, wegen (11 div 4) = 2 mit einem Rest von 3.
i
(-1)i+1 i2 mod n
0
1
2
3
4
5
6
7
8
9
10
0
1
7
9
6
3
8
5
2
4
10
Bemerkungen:
–  Ist (-1)i+1 i2 mod n negativ, so muss der Wert um n erhöht werden.
–  Laufen die Adressen nicht von 0 bis n-1, sondern von 1 bis n, so ist die
Ersatzadresse entsprechend um 1 zu erhöhen. Andere Indexbereiche
sollten vorher auf den Adressbereich 0 bis n-1 transformiert werden.
Wolfgang E. Nagel
Definitionen
Definition:
Unter der Schrittzahl S(s) versteht man die Anzahl der zu berechnenden
Hash-Indizes und Ersatzadressen, die nötig sind, um den Datensatz mit
Schlüsseln s zu speichern bzw. wiederzufinden.
♦
Definition:
Der Füllungsgrad einer Hash-Tabelle ist der Quotient
α = k/n,
wobei n die Anzahl der Tabellenplätze (Adressen) und k die Anzahl
der belegten Tabellenplätze ist.
♦
Wolfgang E. Nagel
Bemerkung:
Für ein ideales Hash-Verfahren gilt:
1. Zu Beginn eines Speicherversuches sind alle möglichen SpeicherBelegungen gleich wahrscheinlich.
2. Die Anzahl der Speicherversuche ist unabhängig davon, welche
Adressen bereits belegt sind.
Bei einem idealen Hash-Verfahren ist der Erwartungswert für die mittlere
Schrittzahl S(n,k) bei der Speicherung eines Datensatzes in einer Tabelle mit n
Plätzen, von denen k bereits belegt sind, gegeben durch:
S(n,k) =
n+1
n-k+1
Wolfgang E. Nagel
Eigenschaften des offenen Hash-Verfahrens
!   Zugrundeliegende Datenstruktur: Speicher mit wahlfreiem Zugriff
(z.B. Array, Array of Records, etc.).
!   Schlüssel werden zusammen mit den Daten gespeichert.
Schlüssel sind eindeutig.
!   Es muss erkennbar sein, ob ein Speicherplatz belegt ist oder nicht
(z. B.Schlüsselfeld leer, extra Bit, etc.).
!   Beim Füllen der Tabelle sollten die auftretenden Schlüssel möglichst
gleichmäßig auf die verfügbaren Adressen verteilt werden (keine Häufung).
Wolfgang E. Nagel
Eigenschaften des offenen Hash-Verfahrens
!   Beim Berechnen der Ersatzadressen sollten alle möglichen Adressen (genau
einmal) durchlaufen werden.
!   Bei festem Füllungsgrad ist die mittlere Schrittzahl beim Suchen niedriger als
die beim Speichern.
!   Die Tabellengröße kann nicht dynamisch verändert werden. Beim
vollständigen Hash muss sie zusätzlich eine Primzahl n ≡ 3 mod 4 sein.
Wolfgang E. Nagel
Vergleich Offene Hash-Verfahren - Verfahren mit Teillisten
!   Der Speicherplatzbedarf ist bei Hash in Teillisten im allgemeinen höher, denn
die Hash-Tabelle ist schon voll, wenn eine Teiltabelle voll ist (unter
Umständen ist nur 1/n der Tabelle belegt).
!   Die Zugriffszeit beim Hash in Teillisten ist nur dann günstig, wenn die
Teillisten kurz sind.
Diese entspricht einer Situation mit wenigen Kollisionen beim offenen Hash.
!   Löschen von Elementen ist beim Hash in Teillisten ohne Probleme möglich!
Wolfgang E. Nagel
Bemerkungen
!   Eine injektive Hash-Funktion bedeutet indizierter Array-Zugriff mit O(1)
(Indexmenge muss klein sein)
!   Aufwand der Hash-Funktion muss gering sein
!   Schlechte Hash-Funktion [h(s)=k] bedeutet Suchen in linearer Liste
!   Gute Kenntnis der Daten ist wichtig
!   Hash-Verfahren nur, wenn:
1.  Hash-Funktion nicht injektiv sein kann oder Indexmenge zu gross wird
2.  Sehr oft auf die Daten zugegriffen wird.
3.  Kaum Daten gelöscht werden.
4.  Daten bekannt sind.
Wolfgang E. Nagel
Fakultät Informatik, Institut für Technische Informatik, Professur Rechnerarchitektur
Effiziente parallele Algorithmen
Sortieren
Zellescher Weg 12
Nöthnitzer Straße 46
Willers-Bau A 205
Raum 1044
Tel. +49 351 - 463 - 35450
Tel. +49 351 - 463 - 38246
Wolfgang E. Nagel ([email protected])
Sortierverfahren
Sortierverfahren
internes Sortieren
externes Sortieren
die zu sortierenden
die zu sortierenden
Elemente liegen
Elemente brauchen nicht
vollständig im
vollständig im
Hauptspeicher
Hauptspeicher zu liegen
Wolfgang E. Nagel
Sortierverfahren
Sortierverfahren
Allgemeine Voraussetzungen:
!   Daten seien mit Schlüsseln in einem Feld gespeichert.
!   Für den Zugriff zu den Daten sei wahlfreie Adressierung gegeben
(eindimensionales Feld von Records).
Bemerkung:
Das Sortieren von Daten kann auf zwei Arten geschehen:
1.  Daten (inkl. Schlüssel) werden im Speicher auf die richtige Position gebracht
(vertauscht).
2.  Die Daten bleiben im Speicher an ihrer ursprünglichen Position.
Der Zugriff zu den Daten erfolgt über eine "Sortierpermutation" der Indizes.
Wolfgang E. Nagel
Definitionen
Definition:
Eine Permutation σ über (1..n) heißt eine Sortierpermutation der Liste A, falls
gilt:
∀ i, j 1≤ i < j ≤ n:
A[σ(i)].key ≤ A[σ(j)].key
♦
Definition:
Eine Sortiermethode heißt stabil, wenn die relative Ordnung der Elemente mit
gleichen Schlüsseln durch den Sortierprozess unverändert bleibt
♦
Wolfgang E. Nagel
Beispiel Sortierpermutation
1
2
3
4
5
6
7
8
9
72
38
12
21
73
11
13
23
35
6
3
7
4
8
9
2
1
5
Index
Daten (key)
Sortierpermutation
Das Sortieren mit Hilfe einer Sortierpermutation ist u.U. dann sinnvoll, wenn das
Umspeichern der Datenrecords sehr aufwendig ist oder wenn eine Liste nach
mehreren Schlüsseln sortiert werden soll.
Implementierung z.B. über einen Indexvektor int ix[N] mit ix[i]=i
(i=1,...,N) initialisiert. Nach dem Sortieren steht im allgemeinen in ix[i] ein
Wert ungleich i. Der Zugriff auf die Datenelemente geschieht durch Daten[ix
[i]].
Wolfgang E. Nagel
Beispiel stabiles Sortieren
char Daten [NDATEN] [MAXSTRLEN];
Schlüssel: Daten[i][0], d.h. erster Buchstabe
Datensatz:
"Heinz", "Anton", "Ulli", "Alfred", "Wolfgang"
Stabiles Sortieren:
"Anton", "Alfred", "Heinz", "Ulli", "Wolfgang"
Instabiles Sortieren: "Alfred", "Anton", "Heinz", "Ulli", "Wolfgang"
Stabilität wichtig, wenn man z.B. vorher/nachher nach einem anderen
Kriterium sortiert. Bei den Verfahren, die wir betrachten, sind nur das
Sortieren durch Einfügen und der Bubblesort stabile Verfahren.
Wolfgang E. Nagel
Sortieren durch Auswahl (Selection Sort)
Grundidee:
for(i=1; i<n; ++i)
{
1) Suche kleinstes Element in a[i]..a[n] und weise Index der Variablen min zu
2) Vertausche a[i] und a[min]
}
Wolfgang E. Nagel
Selection Sort
void sel_ sort( void )
for (i = 1 ; i <= (n -1 ); i ++)
min = i
for (j = (i +1 ); j <= n ; j ++)
a [j ].key < a [min ].key
T
F
min = j
help = a [min ]
a [min ] = a [i ]
a [i ] = help
Wolfgang E. Nagel
Zeitkomplexität
Im i-ten Schritt ist das Minimum aus (n-i+1) Elementen zu suchen, wobei i von 1
bis (n-1) läuft. Im ersten Schritt sind es also (n-1) Vergleiche.
n!1
T
av
sel _ sort
(n) = (n !1) + (n ! 2) +…+1 = " i =
i=1
n(n !1)
# O(n 2 )
2
Vertauscht werden maximal n-1 Elemente, da das Vertauschen in der äußeren
Schleife stattfindet.
Wolfgang E. Nagel
Sortieren durch Auswahl (Selection Sort)
Vorteile des Algorithmus:
!   Einfach zu programmieren.
!   Schnell für sehr kleine Werte von n (z.B. n<20).
Nachteile des Algorithmus:
!   Hohe Zeitkomplexität von O(n2).
!   Der Algorithmus erkennt nicht, ob eine Liste vorsortiert ist.
Bei total sortierter Eingabe ist der gleiche Rechenaufwand nötig.
Wolfgang E. Nagel
Selection Sort (Permutationsvektor)
void sel _ sort_ permutation ( void )
for (i = 1 ; i < = n ; i + + )
s[ i ] = i
for (i = 1 ; i < = (n -1 ); i + + )
min = i
for (j = (i + 1 ); j < = n ; j + + )
a [ s[ j ] ] . key < a [ s[ min ] ] . key
T
F
min = j
help = s[ min ]
s[ min ] = s[ i ]
s[ i ] = help
Wolfgang E. Nagel
Sortieren durch Einfügen (Insertion Sort)
Grundidee:
for(i=2; i<=n; ++i)
{
1) x = a[i]
2) füge a[i] am entsprechenden Platz in a[1]...a[i] ein
}
Wolfgang E. Nagel
Insertion Sort
void ins _ sort( void )
for (i = 2 ; i <= n ; i ++)
x = a [i ]
a [0 ] = x
for (j = (i -1 ); a [j ].key > x.key; j --)
a [ j +1 ] = a [ j ]
a [ j +1 ] = x
Wolfgang E. Nagel
Erläuterung
Zur Bestimmung des entsprechenden Einfügeplatzes ist es angebracht,
zwischen Vergleichen und Bewegungen abzuwechseln, d.h. x "nach links
wandern zu lassen", mit dem nächsten Element a[j] zu vergleichen und
entweder x einzufügen oder a[j] nach rechts zu schieben und nach links
fortzufahren.
Es gibt zwei Möglichkeiten, durch die der Prozess des "nach links Wanderns"
beendet werden kann:
–  Ein Element a[j] mit kleinerem Schlüssel als x wird gefunden.
–  Das linke Ende der Zielsequenz ist erreicht.
Für diese Wiederholung mit zwei Abbruchbedingungen wird eine Marke a[0]=x
verwendet. Konsequenz: Erweiterung des Indexbereiches auf 0..n.
Wolfgang E. Nagel
Zeitkomplexität
Die Zahl der Vergleiche von Schlüsseln beim i-ten Durchlauf ist höchstens i und
mindestens 1 und somit, unter der Annahme, dass alle n! Permutationen gleich
wahrscheinlich sind, im Mittel (i+1)/2.
n
2
"
%
i
+1
1
n(n
+
2)
n(n
+
2)
!
2
n
+ 2n ! 2
av
2
T ins
(n)
=
=
!1
=
=
)
O(n
)
$
'
(
_ sort
&
2# 2
4
4
i=2 2
Der beste Fall ist, wenn die Elemente von Anfang an geordnet sind. Der
schlimmste Fall tritt ein, wenn die Elemente zu Beginn in umgekehrter
Reihenfolge angeordnet sind.
Der Algorithmus lässt sich verbessern, indem man eine binäre Suche zum
Auffinden der Einfügeposition verwendet (Binäres Einfügen).
Wolfgang E. Nagel
Sortieren durch Einfügen (Insertion Sort)
Vorteile des Algorithmus:
!   Einfach zu programmieren.
!   Schnell für sehr kleine Werte von n.
!   Vorsortierung wird ausgenutzt.
Nachteile des Algorithmus:
!   Hohe Zeitkomplexität von O(n2).
Wolfgang E. Nagel
Sortieren durch Vertauschen (Bubble Sort, Sinking Sort)
Prinzip:
!   Jeweils zwei aufeinanderfolgende a[i] und a[i+1] ( i = 1, .., (n-1)) werden
miteinander verglichen und vertauscht, wenn a[i] > a[i+1] gilt.
Auf diese Weise wandert im ersten Durchgang zumindest das größte Element
an das Ende der Liste.
!   Es werden weitere Durchgänge für i = 1, ..., k gemacht, wobei k die Position
ist, von der ab im vorigen Durchgang keine Vertauschungen mehr aufgetreten
sind.
Wolfgang E. Nagel
Bubble Sort
void bubble_sort (void )
bound = n
bound > 1
k= 1
j =1
a[j ].key > a[j +1].key
T
F
help = a[j ]
a[j ] = a[j +1]
a[j +1] = help
k= j
j ++
while ( bound > j )
bound = k
Wolfgang E. Nagel
Zeitkomplexität
Im ersten Durchgang werden (n-1) Elementpaare miteinander verglichen und
ggf. miteinander vertauscht.
Allgemein werden im i-ten Durchgang im ungünstigsten Fall (n-1) Elementpaare
verglichen und vertauscht (i=1,...,n-1), d.h. der Aufwand beträgt:
n!1
w
T bubble
_ sort (n) = (n !1) + (n ! 2) +…1 = " i =
i=1
n(n !1)
# O(n 2 )
2
Im günstigsten Fall, d.h. wenn die Liste schon vorsortiert ist, sind (n-1)
Vergleiche erforderlich. Der Aufwand beträgt dann:
T bbubble _ sort (n) ! O(n)
Wolfgang E. Nagel
Sortieren durch Vertauschen (Bubble Sort, Sinking Sort)
Vorteile des Algorithmus:
!   Schnell für sehr kleines n.
!   Vorsortierung wird ausgenutzt.
Nachteile des Algorithmus:
!   Viele Vertauschungen in der innersten Schleife.
!   Hohe Komplexitätsordnung.
Wolfgang E. Nagel
Heapsort
Prinzip:
Der Algorithmus besteht aus 2 Schritten.
1.  Die Elemente werden in einen Heap gebracht.
2.  Die Elemente werden sortiert aus dem Heap entnommen.
Beachte: Das kleinste Element ist das erste Element.
Heap
a[1]
a[2] a[3]
...
sortiert!
a[k] a[k+1]
Wolfgang E. Nagel
a[n]!
Grundidee
Im ersten Schritt werden a[1] und a[n] ausgetauscht. Für a[1],...,a[n-1] wird der
Heap wieder aufgebaut. Ergebnis des ersten Schrittes ist, dass a[1],...,a[n-1]
einen Heap bilden und a[n] eine sortierte Liste.
Im zweiten Schritt werden a[1] und a[n-1] vertauscht, und für a[1],...,a[n-2] wird
der Heap wieder aufgebaut, d.h. a[1],...,a[n-2] erfüllen die Heapbedingung und
a[n-1], a[n] bilden eine sortierte Liste.
usw.
Wolfgang E. Nagel
Realisierung eines Heap
typedef struct
{
keytype key;
datatype data;
} stdelement;
/* Vektor mit n+1 Elementen a[0]...a[n],
von denen a[0] nicht benutzt wird. */
extern stdelement a[];
Wolfgang E. Nagel
heapcreate
void heapcreate ( int k )
insert = k/2 + 1
insert > 1
siftdown (--insert , k)
Wolfgang E. Nagel
siftdown
void siftdown(int pos, int r)
i = pos
j = 2*pos
x = a [i ]
finished = false
(j <= r) && !finished
j<r
T
F
a[j ].key > a[j +1].key
T
F
j ++
x.key <= a[j ].key
TRUE
FALSE
a [i ] = a [j ]
finished = true
i=j
j = 2*i
a [i ] = x
Wolfgang E. Nagel
Heapsort
void heapsort ( void )
heapcreate (n )
k= n
k> 1
temp = a [1 ]
a [ 1 ] = a [ k]
a [ k] = temp
siftdown (1 , --k)
Wolfgang E. Nagel
Zeitkomplexität
Für das Bilden des Heap (heapcreate) wird für n/2 Elemente die Funktion
siftdown ausgeführt. Maximal werden log(n+1) Elemente verglichen.
Für Schritt 1 gilt:
n
w
T heap
(n)
=
log(n +1)
_ create
2
Für das Sortieren der Elemente wird siftdown für n-1 Elemente ausgeführt, d.h.
für Schritt 2 gilt:
T (n) = (n !1)log(n +1)
Für Heapsort insgesamt gilt dann:
T
w
heap _ sort
3
(n) = n log(n +1) ! O(n log n)
2
Wolfgang E. Nagel
Quicksort: divide-and-conquer-Prinzip (‘teile‘ und ‘herrsche‘)
Vorgehensweise:
1.  Die zu sortierende Liste wird in zwei Teile aufgeteilt (untere Teilliste, obere
Teilliste).
2.  Die Aufteilung geschieht so, dass in der unteren Teilliste nur Elemente ≤
einem Referenz-Element, in der oberen Teilliste nur Elemente ≥ diesem
Referenz-Element vorkommen (ggf. müssen dabei Elemente vertauscht
werden).
3.  Die so entstandenen Teillisten werden nach dem gleichen Prinzip
„sortiert" (rekursive Vorgehensweise). Das Verfahren endet, wenn die
Teillisten weniger als zwei Elemente enthalten.
≤X
untere Teilliste
X
Referenz-Element
Wolfgang E. Nagel
≥X
obere Teilliste
Quicksort Teil 1
void quicksort ( int low , int high )
i = low
j = high
x = a [ (low + high )/2 ]
a [i ].key < x.key
i ++
a [j ].key > x.key
j --
Wolfgang E. Nagel
Quicksort Teil 2
i <= j
T
F
help = a [i ]
a [ i + +] = a [ j ]
a [j --] = help
while ( i <= j )
j > low
T
F
quicksort (low , j )
i < high
T
F
quicksort (i , high )
Wolfgang E. Nagel
Korrektheit des Algorithmus
Nach Ende der Partitionierung in zwei Teillisten (Ende der while-do-Schleife) gilt:
–  i zeigt auf ein Element >= x.key
–  j zeigt auf ein Element <= x.key
–  i > j
–  Alle Elemente links von j sind <= x.key, weil i > j
–  Alle Elemente rechts von i sind >= x.key, weil i > j
Also ist (low,j) eine Teilliste mit Elementen <= x.key und (i,high) eine Teilliste mit
Elementen >= x.key. Zwei Fälle sind möglich:
–  j+1=i: ok, da vollständige Zerlegung in zwei Teillisten.
–  j+2=i: In diesem Fall gilt unmittelbar vor Ende der while-do-Schleife i==j
und a[i]==a[j]==x. Dann folgt i++ und j--, und damit endet die Schleife.
Also: (low,j) x (i,high), d.h. x steht schon an der korrekten Position, (low,j)
und (i,high) sind noch zu sortieren.
Wolfgang E. Nagel
Zeitkomplexität
Aufwand im günstigsten Fall (Teillisten werden stets halbiert):
Stufe 1
X
1
n
In jeder Teilliste der Stufe 1 werden n/2 Elemente betrachtet und ggf.
vertauscht. ⇒ Insgesamt in Stufe 1: n/2 + n/2 = n
Stufe 2
X
1
X
n/2
n
In jeder Teilliste werden n/4 Elemente betrachtet, also insgesamt 4*(n/4)=n.
Allg. gilt: In jeder Stufe werden n Elemente betrachtet. Abbruch bei Teillisten der
Länge n<2 ⇒ 'log n( Stufen.
Also:
T bquicksort (n) ! O(n log n)
Wolfgang E. Nagel
Zeitkomplexität
Aufwand im worst-case:
!   In jeder Stufe wird als Referenz-Element X das größte oder kleinste Element
der Teilliste ausgewählt.
!   Dann gilt:
Die Länge der längsten Teilliste ist (n-1) bei Stufe 1, (n-2) bei Stufe 2, etc.
Allg.: (n-i) bei Stufe i.
!   In diesem Fall existieren (n-1) Stufen.
In jeder Stufe werden n Elemente betrachtet.
!   Also:
w
T quicksort
(n) ! O(n 2 )
Aufwand im Mittel:
!   Genaue Analyse ist sehr aufwendig.
!   Resultat:
av
T quicksort
(n) ! O(n log n)
Wolfgang E. Nagel
Verbesserungsmöglichkeiten:
1. End-Rekursion vermeiden.
Die End-Abfrage erfolgt nicht nach dem letzten (rekursiven) Aufruf, sondern
vor dem Aufruf.
2. Kleine Teillisten (mit n < 20) sollten mit Insertion-Sort oder Bubble_Sort
sortiert werden.
Vorteil: Dies verhindert die sehr häufigen Rekursionen am Ende.
3. Medium-of-three-Methode zum Auswählen des Referenz-Elementes:
Prinzip:
Es werden drei Elemente als Referenz-Elemente, z.B. vom
Listenanfang, vom Listenende und aus der Mitte gewählt. Das
Element mit dem mittleren Schlüssel wird als Referenz-Element
gewählt.
Wolfgang E. Nagel
Quicksort Teil 1
void quicksort( int low , int high )
Stack erzeugen
stack_ empty = false
low < high
TRUE
FALSE
i = low
j = high
Stack leer
x = a [ (low +high )/2 ]
a [i ].key < x.key
i ++
a [j ].key > x.key
T
Wolfgang E. Nagel
F
Quicksort Teil 2
j -i <= j
T
F
help = a [i ]
a [ i + +] = a [ j ]
pop (& low , &high
a [j --] = help
stack_ empty = true
while ( i <= j )
(j -low ) > (high -i )
T
)
F
push (low , j )
push (i , high )
low = i
high = j
while ( !stack_ empty )
Stack löschen
Wolfgang E. Nagel
Quicksort
Vorteile des Algorithmus:
!   Niedrige Komplexitätsordnung im average-case.
!   Kleine Konstanten in der Zeitfunktion.
Nachteile des Algorithmus:
!   Zusätzlicher Zeit- und Platzbedarf für den Stack.
!   Vorsortierung wird nicht ausgenutzt.
!   Schlechte Zeitkomplexität im worst-case-Fall.
Wolfgang E. Nagel
Mergesort (Sortieren durch Mischen)
!   Bisherige Sortierverfahren hatten als Voraussetzung, dass alle Daten in
einem Speicher mit direktem Zugriff (d.h. Zugriff über a[i]) vorhanden waren.
Diese Verfahren sind deshalb nicht anwendbar bei sehr großen
Datenbeständen z.B. auf Hintergrundspeichern mit sequentiellem Zugriff (wie
Bändern).
!   Sortieren durch Mischen basiert auf der Grundidee, dass man zwei
Sequenzen von sortierten Daten zu einer Sequenz zusammenführt.
!   Hier für den Fall n=2k
Wolfgang E. Nagel
Mergesort (Sortieren durch Mischen)
Grundidee:
1.  Zerlege die Sequenz c in zwei Hälften a und b.
2.  Mische a und b durch Kombination einzelner Elemente zu geordneten
Paaren.
3.  Bezeichne die gemischte Sequenz mit c, und wiederhole Schritt 1. und 2.,
wobei dieses Mal die geordneten Paare zu geordneten Quadrupeln
zusammenzufassen sind.
4.  Wiederhole die voranstehenden Schritte durch Mischen der Quadrupel zu
Octrupel, und fahre damit so lange fort, indem jedes mal die Längen der
gemischten Sequenzen verdoppelt werden, bis die ganze Sequenz geordnet
ist.
Wolfgang E. Nagel
A a1 a2
. . .
an
C a1 a2 b1 b2
1 2 3 4
B b1 b2
. . .
bn
Wolfgang E. Nagel
. . .
mj
Mergesort mit Feldern
void mergesort _ array( void )
n1 = n /2
k = (int ) log2 (n )
for (i = 1 ; i <= k; i ++)
distribute _ array(n1 )
length = ipow2 (i -1 )
/* Länge
der Teillisten */
mmax = n /ipow2 (i )
/* Anzahl der Mischvorgänge
*/
for (m = 1 ; m <= mmax ; m ++)
anfx = (m -1 )*length + 1
/* Anfangsindex */
endx = m *length
/* Endindex */
anfxc = (m -1 )*ipow2 (i ) + 1
/* Anfangsindex für
merge _ array(anfx , endx , anfx , endx , anfxc )
Wolfgang E. Nagel
Array c */
Mergesort Teil 1
void mergesort _ file ( void )
n1 = n /2
k = (int ) log2 (n )
for (i = 1 ; i <= k; i ++ )
distribute _ file (n1 )
assert( (c = fopen (DATEI _ C, " w" )) != NULL )
assert( (a = fopen (DATEI _ A , " r" )) != NULL )
assert( (b = fopen (DATEI _ B , " r" )) != NULL )
Wolfgang E. Nagel
Mergesort Teil 2
a _ element = getstdelem (a )
b _ element = getstdelem (b )
length = ipow2 (i -1 )
/* Länge
der Teillisten */
mmax = n /ipow2 (i )
/* Anzahl der Mischvorgänge
*/
for (m = 1 ; m <= mmax ; m ++ )
anfx = (m -1 )*length + 1
/* Anfangsindex */
endx = m *length
/* Endindex */
anfxc = (m -1 )*ipow2 (i ) + 1
/* Anfangsindex für
Array c */
merge _ file (anfx , endx , anfx , endx , & a _ element , & b _ element )
fclose (a ), fclose (b ), fclose (c)
Wolfgang E. Nagel
Verteilen des Feldes C auf A und B
static void distribute _ array( int n1 )
i=1
for (j = 1 ; j <= n1 ; j ++)
a[j ] = c[i ++]
for (j = 1 ; j <= n1 ; j ++)
b[j ] = c[i ++]
Wolfgang E. Nagel
Verteilen der Datei c auf a und b
static void distribute _ file ( int n1 )
assert( (c = fopen (DATEI _ C, " r" )) != NULL )
assert( (a = fopen (DATEI _ A , " w" )) != NULL )
assert( (b = fopen (DATEI _ B , " w" )) != NULL )
c_ element = getstdelem (c)
for (j = 1 ; j <= n1 ; j ++ )
copy (a , c, & c_ element )
for (j = 1 ; j <= n1 ; j ++ )
copy (b , c, & c_ element )
fclose (a ), fclose (b ), fclose (c)
Wolfgang E. Nagel
Kopieren eines Elementes von f1 nach f2
static void copy( FILE *out , FILE *in , stdelement *elem _ptr )
putstdelem (*elem _ ptr, out )
*elem _ptr = getstdelem (in )
Wolfgang E. Nagel
Mischen von zwei sortierten Feldern
void merge _ array ( int i , int m1 , int j , int m2 , int k )
(i < = m1 ) & & (j <= m2 )
a [i ].key <= b [ j ].key
TRUE
c[k++ ] = a [i + +]
FALSE
c[k++ ] = b [j + +]
i < = m1
c[k++ ] = a [i + +]
j < = m2
c[k++ ] = b [j + +]
Wolfgang E. Nagel
Mischen von zwei sortierten Dateien
void merge _ file _ to _ eof ( void )
assert( (f = fopen (DATEI _ A , " r" )) ! = NULL )
assert( (g = fopen (DATEI _ B , " r" )) ! = NULL )
assert( (h = fopen (DATEI _ C, " w" )) ! = NULL )
f_ element = getstdelem (f )
g _ element = getstdelem (g )
endfg = feof (f) | | feof (g )
!endfg
f_ element . key <= g _ element . key
TRUE
FALSE
copy (h , f, & f _ element )
copy (h , g , & g _ element )
endfg = feof (f )
endfg = feof (g )
!feof (g )
copy (h , g , & g _ element )
!feof (f)
copy (h , f, & f _ element )
Wolfgang E. Nagel
Mischen einer Anzahl von Elementen
void merge _ file ( int i , int m1 , int j , int m2 , stdelement *a _ ptr, stdelement *b _ ptr )
(i <= m1 ) & & (j <= m2 )
a _ ptr->key <= b _ ptr->key
TRUE
FALSE
copy (c, a , a _ ptr)
copy (c, b , b _ ptr)
i ++
j ++
i <= m1
copy (c, a , a _ ptr)
i ++
j <= m2
copy (c, b , b _ ptr)
j ++
Wolfgang E. Nagel
Zeitkomplexität
!   Die Dateien A, B und C werden k = (log n)-mal verteilt. Das Verteilen und
Mischen fordert O(n) Operationen. Daraus folgt:
w
T av
(n)
=
T
mergesort
mergesort (n) ! O(n log n)
Wolfgang E. Nagel
Vorteile des Algorithmus:
!   Niedrige Komplexitätsordnung (auch im worst-case).
!   Der Mergesort-Algorithmus sortiert (auch) extern.
Nachteile des Algorithmus:
!   Wird das Verfahren als internes Sortierverfahren verwendet, so ist je nach
Implementierung zusätzlicher Speicherplatz erforderlich.
!   Programmierung für beliebiges n ist aufwendig.
!   Die Konstanten bezüglich der O-Notation sind u.U. sehr groß.
Wolfgang E. Nagel
Bitonic Sort
Definition:
Eine Folge a1,a2,..,an heisst bitonisch, wenn der erste Teil der Folge
aufsteigend und der zweite absteigend sortiert ist, oder wenn man die
Folgenglieder so verschieben kann, dass diese Bedingung gilt.
♦
Beispiele bitonischer Folgen:
11, 20, 43, 72, 85, 43, 29, 27, 16
81, 96, 98, 15, 14, 11, 10, 44, 56
Wolfgang E. Nagel
Bitonic Sort – Idee des Algorithmus
!   Fundamentale Ideen: Rekursion und Divide-and-Conquer
!   Ist a1,..,an bereits bitonisch, so werden die beiden Teilfolgen zu einer
sortierten Folge gemischt.
!   Andernfalls geht man rekursiv vor und versucht den gewünschten Zustand
herzustellen, indem die linke Hälfte aufsteigend und die rechte Hälfte
absteigend sortiert wird.
Wolfgang E. Nagel
Bitonic Sort – Idee des Algorithmus
Mischen einer bitonischen Folge zu einer sortierten Folge:
!   Gegeben: a1,a2,..,a2n
!   1. Schritt: Jedes Element aj,1≤j≤n, der ersten Hälfte wird mit
korrespondierendem Element aj+n der zweiten Hälfte verglichen; beide werden
vertauscht, wenn sie in der falschen Reihenfolge stehen, falls also aj>aj+1 ist.
Alle Elemente der ersten Hälfte der Folge sind nun kleiner als die Elemente
der zweiten Hälfte, und beide Hälften sind bitonisch.
!   2. Schritt: Anwendung des Verfahrens rekursiv getrennt auf die beiden Hälften
der Folge
Offenbar ist die Folge sortiert, wenn die Rekursion abbricht.
Wolfgang E. Nagel
Bitonic Sort – Parallele Realisierung I
!   Netzwerk von Prozessoren mit log n Spalten
!   Funktionsverhalten der Prozessoren:
X
Y
≤
X‘
Y‘
X‘:=min(X,Y)
Y‘:=max(X,Y), X,Y,X‘,Y‘
Z
Wolfgang E. Nagel
Bitonic Sort – Zeitkomplexität
!   Aufwand sequentiell (Bitonische Folge ! Sortierte Folge):
av
w
T bitonic
(n) = T bitonic
(n) ! O(n log n)
Wolfgang E. Nagel
Bitonic Sort – Parallele Realisierung I
!   Netzwerk von Prozessoren mit log n Spalten
!   Funktionsverhalten der Prozessoren:
X
Y
≤
X‘
X
Y‘
Y
X‘:=min(X,Y)
Y‘:=max(X,Y), X,Y,X‘,Y‘
≥
X‘
Y‘
X‘:=max(X,Y)
Z
Y‘:=min(X,Y), X,Y,X‘,Y‘
Wolfgang E. Nagel
Z
Bitonic Sort – Zeitkomplexität
!   Aufwand sequentiell (Beliebige Folge ! Sortierte Folge):
av
w
T bitonic
(n) = T bitonic
(n) ! O(n log n)
Wolfgang E. Nagel
Zeitkomplexitäten der verschiedenen Sortierverfahren
Sortierverfahren
worst-case
average-case
Selection Sort
N2
N2
Insertion Sort
N2
N2
Bubble Sort
N2
N2
Quicksort
N2
N log2 N
Heapsort
N log2 N
N log2 N
Mergesort
N log2 N
N log2 N
Bitonic Sort
N log22 N
N log22 N
Algorithmus von Cole
N log2 N
N log2 N
Wolfgang E. Nagel
Center for Information Services and High Performance Computing (ZIH)
Effiziente parallele Algorithmen
Paralleler Mergesort Algorithmus
Zellescher Weg 12
Willers-Bau A205
Tel. +49 351 - 463 35450
Nöthnitzer Str. 46
Raum 1044
Tel. +49 351 - 463 38246
Wolfgang E. Nagel([email protected])
Agenda
1
Einführung
2
Prinzip
3
Ausführliche Beschreibung
Wolfgang E. Nagel
Agenda
1
Einführung
2
Prinzip
3
Ausführliche Beschreibung
Wolfgang E. Nagel
Paralleles Sortieren
Bitonisches Sortieren: O(log2 n) mit n Komparatoren
Erster paralleler Sortieralgorithmus in O(log n) mit O(n) Prozessoren:
AKS-Sortiernetzwerk (Ajtai,Komlós, Szemerédi 1983)
Proportionalitätskonstante so hoch, dass nur schneller für n > 10100
1986 präsentierte Cole parallelen Sortieralgorithmus in O(log n)-Klasse
und mit kleinerer Konstante
Wolfgang E. Nagel
Agenda
1
Einführung
2
Prinzip
3
Ausführliche Beschreibung
Wolfgang E. Nagel
Prinzip
Vollständiger Binärbaum, n = 2k zu sortierende Elemente befinden sich
an den Blättern
An jedem Knoten werden die Elemente des linken und rechten Teilbaums
gemischt
Mischen beginnt an Blättern und setzt sich bis zur Wurzel fort
Bevor das Mischen in einem Knoten abgeschlossen ist, werden Samples
der Elemente, die bisher empfangen wurden, den Baum hoch geschickt
Dies ermöglicht das gleichzeitige Mischen an verschiedenen Stufen des
Baumes
Wolfgang E. Nagel
Beobachtungen von Cole
Cole’s log n merging procedure:
“The Problem is to merge two sorted arrays of n items. We proceed in
log n stages. In the ith stage, for each array, we take a sorted sample of
2i 1 items, comprising every n/2i 1 th items in the array. We compute
the merge of these samples.”
Wolfgang E. Nagel
Beobachtungen von Cole
Beobachtungen von Cole:
Merging in constant time:
Given the results of the merge from the (i
ith stage can be done in O(1).
1)st stage, the merge in the
Level by level approach ! O(log2 n)
Die zweite Beobachtung von Cole erklärt, wie eine langsamere
Merging-Prozedur zu einem schnelleren Sortieralgorithmus führen kann:
The merges at the di↵erent levels can be pipelined:
This is possible since merged samples made at level l of the tree may be
used to provide samples of the appropriate size for merging at the next
level above l without losing the O(1) time merging property.
Wolfgang E. Nagel
Beobachtungen von Cole
x
u
v
level l
1
level l
w
Level by level:
Sobald Knoten v (und w ) eine sortierte Sequenz hat, die alle Elemente des
Unterbaums von v (w ) enthält, beginnt das Mergen am Level l. Der
Merge-Knoten u braucht log k Stufen, wobei k die Anzahl der Elemente der
sortierten Sequenzen in Knoten v und w bezeichnet. Jede dieser Stufen kann
in konstanter Zeit ausgeführt werden, aufgrund des systematischen Samplings
der sortierten Folgen in v und w . Ist Knoten u fertig mit dem Mergen,
beginnt Knoten x damit.
Wolfgang E. Nagel
Beobachtungen von Cole
x
u
v
level l
1
level l
w
Pipelined:
Hier beginnen die Knoten früher mit dem Mergen. Sobald Knoten u vier
Elemente enthält, sendet er Samples zu seinem Vorfahr-Knoten x. Knoten x
tut das gleiche, sobald er vier Elemente enthält, beginnt er mit der
Versendung der Samples an seinen Vorfahr.
Cole hat gezeigt, dass diese pipelineartige Vorgehensweise beim Mischen so
implementiert werden kann, dass jede Merge-Stufe in O(1) ausgeführt
werden kann.
Wolfgang E. Nagel
Agenda
1
Einführung
2
Prinzip
3
Ausführliche Beschreibung
Wolfgang E. Nagel
Aufgabe jedes Knotens u
Jeder Knoten u produziert einen sortierte Liste L(u) und speichert ein
Feld Up(u), welches eine sortierte Untermenge von L(u) ist
Zu Beginn gilt für alle Blätter y : Up(y ) = L(y )
Am Ende des Algorithmus gilt für die Wurzel t des Baumes:
Up(t) = L(t)
Während der Ausführung des Algorithmus wird Up(u) üblicherweise aus
einem Sample der Elemente aus L(u) bestehen
Definition
Ein Knoten u wird fertig genannt, wenn |Up(u)| = |L(u)|, andernfalls ist u
ein aktiver Knoten.
Zu Beginn des Algorithmus sind nur die Blätter fertig
Wolfgang E. Nagel
Aufgabe jedes Knotens u
Eine Stufe im parallelen Mergesort-Algorithmus von Cole:
Phase 1: Generierung der Samples
Phase 2: Mergen in O(1)
Schritt 1: Mergen
Berechnung der Kreuzränge
Teilschritt 1
Teilschritt 2
Generierung von NewUp(u)
Schritt 2: Bereitstellung der Ränge
fromsendernode
fromothernode
Wolfgang E. Nagel
Aufgabe jedes Knotens u
In jeder Stufe des Algorithmus wird ein neues Feld NewUp(u) in jedem
aktiven Knoten auf folgende Weise in zwei Phasen erzeugt:
(P1) Samples von den Feldern Up(v ) und Up(w ) werden generiert und in
SampleUp(v ) bzw. SampleUp(w ) gespeichert.
(P2) NewUp(u) entsteht aus Mischen von SampleUp(v ) und SampleUp(w )
mit Hilfe von Up(u).
Für jeden Knoten gilt, dass das NewUp(u)-Feld generiert in Stufe i das
Up(u)-Feld zu Beginn von Stufe i + 1 ist. An fertigen Knoten wird Phase 2
nicht ausgeführt, da diese bereits die sortierte Liste L(u) erzeugt haben.
Wolfgang E. Nagel
Aufgabe jedes Knotens u
Die zwei Phasen der Berechnung von NewUp(u):
NewUp(u)
MergeWithHelp
Up(u)
SampleUp(v )
SampleUp(w )
MakeSamples
Up(v )
Up(w )
Wolfgang E. Nagel
Phase 1: Generierung der Samples
Berechnung der Samples SampleUp(u) wird parallel von allen Knoten auf
folgende Weise durchgeführt:
1
2
Wenn u ein aktiver Knoten ist, dann ist SampleUp(u) das sortierte Feld,
welches aus jedem 4. Element von Up(u) besteht (beginnend beim 1.
Element). Ist |Up(u)| < 4, ist SampleUp(u) leer.
Wenn u das 1. Mal fertig ist, dann wird SampleUp(u) wie für aktive
Knoten gebildet. Ist u das 2. Mal fertig, besteht SampleUp(u) aus jedem
2. Element von Up(u). Und ist u das 3. Mal fertig, gilt
SampleUp(u) = Up(u).
SampleUp(u) ist immer sortiert!
Wolfgang E. Nagel
Phase 1: Generierung der Samples
Der Algorithmus hat 3 log n Stufen
Zu Beginn sind nur die Blätter fertig
In der 3. Stufe als fertiger Knoten hat ein Knoten v alle Elemente aus
L(v ) benutzt, um SampleUp(v ) in Phase 1 zu generieren
Der Vater-Knoten u hat SampleUp(v ) und SampleUp(w ) zu Up(u) in
Phase 2 gemischt
Knoten u hat alle Elemente in L(u) empfangen und die Kind-Knoten v
und w sind fertig
In der nächsten Stufe ist u das erste mal fertig
Das Level mit fertigen Knoten bewegt sich jede 3. Stufe ein Level nach
oben
Nach 3 log n Stufen wird die Wurzel t fertig und der Algorithmus
terminiert
Wolfgang E. Nagel
Phase 2: Mergen in O(1)
Der komplizierteste Teil im Algorithmus von Cole ist das Mergen von
SampleUp(v ) und SampleUp(w ) zu NewUp(u) in O(1). Das Mergen basiert
auf der Bereitstellung von Rängen.
Definition
Der Rang von e in einer sortierten Folge S ist rng (e, S) := |{x 2 S|x  e}|.
Als Rang zwischen zwei sortierten Folgen A und B wird die Funktion
RngA,B : A ! N bezeichnet mit RngA,B (e) = rng (e, B) 8e 2 A.
Beispiel:
A = (2, 4, 6, 8)
B = (1, 3, 5, 7)
rng (1, A) = 0
rng (5, A) = 2
RngA,B = (1, 2, 3, 4)
Wolfgang E. Nagel
Phase 2: Mergen in O(1)
Der Rang gibt an, wo ein Element e in eine sortierte Folge S eingefügt
werden muss, wenn die Sortierung erhalten bleiben soll. Dabei ist es wichtig
zu wissen, ob e in S ist, oder nicht.
Beispiel:
S = (1, 2, 5, 8, 12)
e = 2 ) Rng (e, S) = 2
e = 9 ) Rng (e, S) = 4
insert an Position 2 oder 3 = Rng (e, S) + 1
insert an Position 5 = Rng (e, S) + 1
Rng (e, S) + 1 ist die korrekte Position, wo e in S eingefügt werden müsste.
Wolfgang E. Nagel
Phase 2: Mergen in O(1)
Annahme A1
Zu Beginn jeder Stufe sind für einen aktiven Knoten u mit Kind-Knoten v
und w die Ränge Rng (Up(u), SampleUp(v )) und Rng (Up(u), SampleUp(w ))
bekannt.
Die Generierung von NewUp(u) kann in zwei Schritte geteilt werden. Zum
einen in das Mergen der Samples der Kind-Knoten und zum anderen
Sicherstellung von obiger Annahme A1 .
Wolfgang E. Nagel
Phase 2, Schritt 1: Mergen
NewUp(u) soll aus Mergen von SampleUp(v ) und SampleUp(w )
Gesucht: Die Position von jedem Element aus SampleUp(v ) und
SampleUp(w ) in NewUp(u)
Mergen dann einfach durch Einfügen jedes Elementes an die richtige
Position
Betrachtung eines beliebigen Elementes e in SampleUp(v ).
Gesucht ist die Position von e in NewUp(u), also rng (e, NewUp(u)), der
Rang von e in NewUp(u)
NewUp(u) wurde aus Mergen von SampleUp(v ) und SampleUp(w )
generiert, es gilt also:
rng (e, NewUp(u)) = rng (e, SampleUp(v )) + rng (e, SampleUp(w ))
Wolfgang E. Nagel
Phase 2, Schritt 1: Mergen
e
r1 + r2
d
NewUp(u)
f
Up(u)
r
r2 = 2
t
e
r1 2
SampleUp(v )
SampleUp(w )
Die Benutzung der Ränge für die Merging-Prozedur in O(1). Kennt man die
Position von e in SampleUp(v ), rng (e, SampleUp(v )) = r1 , und seine
Position in SampleUp(w ), rng (e, SampleUp(w ) = r2 , dann ist die Position
von e in NewUp(u), rng (e, NewUp(u)), einfach r1 + r2 .
Wolfgang E. Nagel
Berechnung der Kreuzränge
Betrachtung der Situation, wenn e aus SampleUp(v ) ist
Zunächst (parallele) Berechnung des Ranges in Up(u) für jedes Element
e 2 Sample(Up(v )) (Beschreibung auf nächster Folie)
Der Rang von e in Up(u) liefert die Elemente d und f in Up(u), welche
e in Up(u) einschließen, also d  e  f
Annahme A1 liefert die Ränge r := rng (d, SampleUp(w )) und
t := rng (f , SampleUp(w )), welche die richtige Position beschreiben,
wenn d und f in SampleUp(w ) eingefügt werden sollen
Die richtige Position von e in SampleUp(w ) hängt von r und t ab (da
e 2 [d, f ])
Es kann gezeigt werden, dass r und t immer maximal einen Bereich von
4 Positionen beschreibt, und die richtige Position, rng (e, SampleUp(w )),
kann in konstanter Zeit bestimmt werden (ausführlicher siehe Teilschritt
2)
Wolfgang E. Nagel
Teilschritt 1: Berechnung von Rng (SampleUp(v ), Up(u))
Die Berechnung von Rng (SampleUp(v ), Up(u)) erfolgt mittels der
Berechnung von Rng (Up(u), SampleUp(v )).
1
2
...
b
c
i1
i2
r
r +1
...
Up(u)
1
2
...
s
...
1
s
...
SampleUp(v )
Betrachtung eines beliebigen Elementes i1 2 Up(u)
Der Bereich [i1 , i2 i ist das Intervall beginnend bei i1 und endend bei i2 ,
wobei i2 nicht mehr enthalten ist
Gesucht: Anzahl der Elemente in SampleUp(v ), welche sich in
I (i1 ) := [i1 , i2 i befinden
Wolfgang E. Nagel
Teilschritt 1: Berechnung von Rng (SampleUp(v ), Up(u))
1
2
...
b
c
i1
i2
r
r +1
...
Up(u)
1
2
...
s
...
1
s
...
SampleUp(v )
Die Elemente in I (i1 ) haben den Rang b in Up(u), wobei b die Position
von i1 in Up(u) ist (Zähler für Feldpositionen beginnt bei 1)
Ist I (i1 ) gefunden, kann ein Prozessor assoziiert mit dem Element i1 , den
Rang b der Elemente in der Menge bestimmen
Gleichzeitig kann ein Prozessor assoziiert mit i2 den Rang c von I (i2 )
ermitteln.
Wolfgang E. Nagel
Teilschritt 1: Berechnung von Rng (SampleUp(v ), Up(u))
Berechnung von I (i1 )
Gesucht sind alle Elemente ↵ 2 SampleUp(v ) mit rng (↵, Up(u)) = b
Diese Elemente müssen die Bedingung
(↵
i1 ) ^ (↵ < i2 )
erfüllen.
Sei item(j) des Element, welches an Position j in SampleUp(v ) steht
Annahme A1 liefert rng (i1 , SampleUp(v )), also r , und
rng (i2 , SampleUp(v )), also s, was zu
(item(r )
i1 ) ^ (item(s)  i2 )
führt
Demzufolge: Wenn Elemente in SampleUp(v ) zwischen Position r und
s (r < < s) exisitieren, muss rng ( , Up(u)) = b gelten
Wolfgang E. Nagel
Teilschritt 1: Berechnung von Rng (SampleUp(v ), Up(u))
Die Elemente r und s benötigen eine gesonderte Behandlung:
item(r ) = i1 ) rng (item(r ), Up(u)) = b
item(r ) < i1 ) rng (item(r ), Up(u)) < b
item(s) = i2 ) rng (item(s), Up(u)) = c
item(s) < i2 ) rng (item(s), Up(u)) < c ) rng (item(s), Up(u)) = b
Zusammenfassend:
I (i1 ) wird durch Vergleichen von item(r ) mit i1 und item(s) mit i2 gefunden.
Ein mit i1 assoziierter Prozessor kann diese einfachen Berechnungen
durchführen und den Rang b von allen Elementen in I (i1 ) in konstanter Zeit
bestimmen. Das ist möglich, da gezeigt werden kann, dass I (y ) für eine
beliebiges y 2 Up(u) maximal drei Elemente enthält.
Wolfgang E. Nagel
Teilschritt 2
Berechnung von Rng (SampleUp(v ), SampleUp(w )) mittels
Rng (SampleUp(v ), Up(u)):
...
d
f
...
Up(u)
rng (e, Up(u))
r
e
t
t+1
...
SampleUp(w )
...
]
< e
[
> e
rng (e, Up(u)), berechnet in Teilschritt 1, liefert die umschließenden
Elemente d und f in Up(u)
Annahme A1 liefert die Ränge r und t
Gesucht: Exakte Position von e beim Einfügen in SampleUp(w )
Frage: Welche Elemente aus SampleUp(w ) müssen mit e verglichen
werden.
Wolfgang E. Nagel
Teilschritt 2
Das liefern die folgenden Beobachtungen:
Alle Elemente in SampleUp(w ) links von Position r (r eingeschlossen)
sind kleiner als e.
Alle Elemente in SampleUp(w ) rechts von Position t sind größer als e.
) Man kann zeigen, dass e mit maximal drei Elementen aus SampleUp(w )
verglichen werden muss.
Ist Teilschritt 2 für jedes Element e aus SampleUp(v ) und SampleUp(w )
erledigt (parallel), kennt jedes Element seine Position in NewUp(u) und kann
an die richtige Position geschrieben werden.
Wolfgang E. Nagel
Phase 2, Schritt 2: Bereitstellung der Ränge
Annahme A1 besagt, dass die Ränge Rng (Up(u), SampleUp(v )) und
Rng (Up(u), SampleUp(w )) zu Beginn jeder Stufe bekannt sind
Gültigkeit der Annahme durch Berechnung von
Rng (NewUp(u), NewSampleUp(v )) und
Rng (NewUp(u), NewSampleUp(w )) am Ende jeder Stufe
Berechnung von Rng (NewUp(u), NewSampleUp(v ))
(Rng (NewUp(u), NewSampleUp(w )) analog)
Betrachtung eines Elementes e in NewUp(u)
Berechnung von rng (e, NewSampleUp(v )) kann in zwei Fälle unterteilt
werden:
e aus SampleUp(v ) (fromsendernode)
e nicht aus SampleUp(v ) (fromothernode)
Wolfgang E. Nagel
Berechnung des Rangs in NewSampleUp
(fromsendernode)
Bekannt: Ränge Rng (Up(u), SampleUp(v )) und
Rng (Up(u), SampleUp(w ))
NewUp(u) beinhaltet alle Elemente aus SampleUp(v ) und SampleUp(w )
Folglich kann der Rang rng (↵, NewUp(u)) für alle Elemente ↵ aus
NewUp(u) einfach aus der Summe der Ränge rng (↵, SampleUp(v )) und
rng (↵, SampleUp(w )) berechnet werden
Dies erfolgt parallel für jeden Knoten.
Wolfgang E. Nagel
Berechnung des Rangs in NewSampleUp
(fromsendernode)
SampleUp(v ) entsteht aus jedem vierten Element von Up(v )
Finden der Position eines Elementes in Up(v ) einfach, wenn die Position
in SampleUp(v ) bekannt
Hat man Rng (Up(v ), NewUp(v )) berechnet, kann man
Rng (SampleUp(v ), NewUp(v )) einfach berechnen mittels durchgehen
durch Up(v )
Für jedes Element in SampleUp(v ) hat man rng ( , NewUp(v )).
Gleichermaßen, wie für SampleUp(v ), entsteht NewSampleUp(v ) aus
jedem vierten Element von NewUp(v )
Informell ist daher rng ( , NewSampleUp(v )) rng ( , NewUp(v )) geteilt
durch vier.
Wolfgang E. Nagel
Berechnung des Rangs in NewSampleUp
(fromsendernode)
An diesem Punkt kennt man Rng (SampleUp(v ), NewSampleUp(v )) und das
Element e aus NewUp(u) kommt aus SampleUp(v ). Um
rng (e, NewSampleUp(v )) zu finden, verbleibt das Element e in SampleUp(v )
zu lokalisieren. Dies kann einfach realisiert werden, wenn man für jedes
Element in NewUp(u) die Sender-Adresse (Position) von e in SampleUp(v )
speichert.
Wolfgang E. Nagel
Berechnung des Rangs in NewSampleUp (fromothernode)
Abbildung stellt Fall dar, wenn e aus SampleUp(w ) ist. Gesucht ist die
korrekte Position von e, wenn es in NewSampleUp(v ) eingefügt werden soll.
...
d
f
e
...
NewUp(u)
r
von SampleUp(w )
von SampleUp(v )
t
...
NewSampleUp(v )
...
]
<e
[
>e
Wolfgang E. Nagel
Berechnung des Rangs in NewSampleUp (fromothernode)
Die folgende Annahme wird helfen:
Annahme A2
In jeder Stufe, zu Beginn von Schritt 2 (Bereitstellung der Ränge), sind für
jedes Element e in NewUp(u), welches aus SampleUp(w ) stammt, die
umschließenden Elemente d und f aus SampleUp(w ) bekannt. (und natürlich
andersrum für Elemente aus SampleUp(v)).
Das Element d ist das erste Element in NewUp(u) “vom anderen
Knoten” (SampleUp(v )) links von e
Identisch ist f das erste Element auf der rechten Seite
Die Ränge r und t von d und f in NewSampleUp(v ) sind bekannt (im
Fall fromsendernode berechnet)
Wolfgang E. Nagel
Berechnung des Rangs in NewSampleUp (fromothernode)
Gleiche Situation, wie Teilschritt 2
Der Rang rng (e, NewSampleUp(v )) wird durch Vergleichen von e mit
maximal drei Elementen aus NewSampleUp(v ) ermittelt. (d und f
haben verschiedene Bedeutungen in Abbildung und Annahme A2 )
Bekannt: e nicht in SampleUp(w )
Der Rang rng (e, SampleUp(w )) liefert exakt die zwei Elemente von der
anderen Menge, die e einschließen
Für ein Element e aus SampleUp(w ) liefert die komplett symmetrische
Berechnung rng (e, SampleUp(v )), und daher implizit die zwei Elemente d
und f von SampleUp(v ), welche e einschließen. Dies sind die gesuchten
Elemente aus der Abbildung.
Daher ist die Gültigkeit von Annahme A2 gesichert, wenn am Ende von
Schritt 1, der Generierung von NewUp(u), die Positionen (Ränge) von d und
f in NewUp(u) gespeichert werden.
Wolfgang E. Nagel
Zusammenfassung
Algorithmus von Cole: O(log n) mit O(n) Prozessoren
Bitonisches Sortieren: O(log2 n) mit n Prozessoren
Algorithmus von Cole nur schneller für n
268 ⇡ 3 · 1020
Folglich wird in der Praxis das einfachere und schnellere bitonische
Sortieren Einsatz finden.
ABER: Sortieren in O(log n) mit O(n) Prozessoren ist möglich!
Wolfgang E. Nagel
Fakultät Informatik, Institut für Technische Informatik, Professur Rechnerarchitektur
Effiziente parallele Algorithmen
Graphenalgorithmen
Zellescher Weg 12
Nöthnitzer Straße 46
Willers-Bau A 205
Raum 1044
Tel. +49 351 - 463 - 35450
Tel. +49 351 - 463 - 38246
Wolfgang E. Nagel ([email protected])
Graphenalgorithmen
!   Besuchen aller Knoten
!   Kürzeste Wege zwischen zwei Knoten
!   Minimale Spannbäume
Wolfgang E. Nagel
Besuchen aller Knoten
!   Wichtiges Anwendungsskelett für viele Graphalgorithmen: Wende einer
Funktion auf jeden Knoten des Graphen an
!   Tiefensuche und Breitensuche hier für gerichtete und ungerichtete Graphen
A
B
C
E
D
Wolfgang E. Nagel
Depth-First-Search (DFS)
1.  Markiere alle Knoten als nicht besucht.
2.  Wähle beliebigen Knoten v als Startknoten.
3.  Dieser Knoten wird als besucht markiert.
4.  Jeder Knoten, der adjazent zu v und noch nicht als besucht markiert ist, wird
als nächster Knoten durch rekursiven Aufruf von ‘Depth-first-search‘
besucht.
5.  Wenn alle Knoten, die von v erreichbar sind, besucht worden sind, ist die
Suche für v beendet.
6.  Wenn noch Knoten existieren, die als nicht besucht markiert sind, wird einer
dieser Knoten ausgewählt und man fährt mit Schritt 3 des Algorithmus fort.
Wolfgang E. Nagel
Bemerkungen zu DFS
!   Die Rekursion kann mit einem Stack simuliert werden.
!   Preorder-Durchlaufstrategie
!   DFS kann als Basis für viele andere Graphenalgorithmen dienen
!   für gerichtete oder ungerichtete Graphen
Wolfgang E. Nagel
Breadth-First-Search (BFS)
1.  Markiere alle Knoten als nicht besucht.
2.  Wähle beliebigen Knoten v als Startknoten.
3.  Dieser Knoten wird als besucht markiert.
4.  Jeder Knoten, der adjazent zu v und noch nicht als besucht markiert ist, wird
markiert und in einer Schlange gespeichert.
5.  Entferne Knoten v aus der Schlange und fahre bei 4. fort, bis die Schlange
leer ist.
6.  Wenn noch Knoten existieren, die als nicht besucht markiert sind, wird einer
dieser Knoten ausgewählt und man fährt mit Schritt 3 des Algorithmus fort.
Wolfgang E. Nagel
Datenstrukturen
L[v]
: Adjazenzliste für Knoten v
mark[v]
: kann ‚visited‘ oder ‚unvisited‘ sein; zu Beginn ‚unvisited‘
Initialisierung für den DFS- und BFS-Algorithmus:
for(v = 1; v <= |V|; ++v)
mark[v] = unvisited;
for(v = 1; v <= |V|; ++v)
if(mark[v] == unvisited)
dfs(v);
bzw.
bfs(v);
Wolfgang E. Nagel
DFS
void depth _first_search( vertex v )
mark[v] = visited
for each vertex w in L[v]
mark[w] == unvisited
T
F
depth _first_search(w)
Wolfgang E. Nagel
BFS
void breadth _first_search( vertex v )
mark[v] = visited
put (v)
!empty ()
v = get ()
for each vertex w in L [v]
mark[w] == unvisited
T
F
mark[w] = visited
put (w)
Wolfgang E. Nagel
Beispiel
1
3
2
4
8
5
6
7
9
Besuchsreihenfolge DFS: 1 2 4 8 9 5 3 6 7
Besuchsreihenfolge BFS: 1 2 3 4 5 6 7 8 9
Wolfgang E. Nagel
Zeitkomplexität
!   Speicherung des Graphen mit Adjazenzlisten:
–  DFS, BFS: O( |V| + |E| )
!   Speicherung des Graphen mit Adjazenzmatrix:
–  DFS, BFS: O( |V|2 )
Wolfgang E. Nagel
Kürzeste-Wege-Probleme
!   Praktische Bedeutung für Transport- und Kommunikationsnetzwerke
1.  Bestimmung des kürzesten Weges von einem festen Knoten (Quelle) zu
allen anderen Knoten in einem Graphen
(single-source shortest-path problem)
2.  Bestimmung des kürzesten Weges zwischen allen Knotenpaaren in einem
Graphen
(all-pairs shortest-path problem)
3.  Bestimme den kürzesten Weg zwischen zwei gegebenen Knoten.
Wolfgang E. Nagel
Anwendungsbeispiel
Eine Spedition fährt jeden Tag im Dreieck Dresden-Leipzig-Chemnitz und
möchte die Kosten minimieren. Die Kantenkosten im Graphen sind z.B.
Kilometer oder benötigte Zeit.
Leipzig
90
60
100
25
xxxxx
Chemnitz
60
75
Dresden
1)  Kostenminimaler Weg von Chemnitz zu allen anderen Orten?
2)  Kostenminimaler Weg zwischen allen Orten?
3)  Kostenminimaler Weg zwischen Chemnitz und Leipzig?
Wolfgang E. Nagel
Kürzeste Wege von einer Quelle
Gegeben:
Gerichteter oder ungerichteter Graph G = (V,E).
Jeder Kante von vi nach vj ist eine positive Bewertung C(i,j) zugeordnet
(Kantenkosten).
Bemerkung:
Ist die Bewertung negativ, so addiere den Betrag der betragsmäßig größten
negativen Bewertung zu allen Bewertungen ≠ 0 und ∞ .
Es ergibt sich ein Folgeproblem, bei dem alle Kantenbewertungen positiv sind.
D.h. wir gehen ab jetzt von einer positiven Kantenbewertung aus.
Keine Kante zwischen vi und vj : C(i,j) = ∞ ,
Diagonalelemente: C(i,j) = 0
Wolfgang E. Nagel
Algorithmus von Dijkstra
Eine Menge S enthält die Knoten, deren kürzeste Entfernungen von der
vorgegebenen Quelle bereits bekannt sind.
Vor.: C[u,v] ≥ 0 ∀ u, v ∈V .
1. Zu Beginn: S = {Quelle}
2. Der Algorithmus beginnt an der Quelle und berechnet die direkten Wege zu
allen erreichbaren Nachbarknoten (analog BFS).
Der Knoten mit der kürzesten Entfernung wird zu S hinzugenommen.
3. Die Berechnung wird an diesem Knoten fortgesetzt und die Wege von
diesem Knoten zu allen Knoten, die nicht in S sind, berechnet.
Dieser Schritt wird so lange wiederholt, bis alle Knoten in S enthalten sind.
Wolfgang E. Nagel
Bemerkungen
!   Das Feldelement d[v] enthält die jeweils aktuelle kürzeste Entfernung des
Knotens v von der Quelle.
!   Wenn man zusätzlich zu der kürzesten Entfernung auch den kürzesten Weg
zu jedem Knoten haben möchte (d.h. welche Zwischenknoten), kann man ein
Feld p verwenden, so daß p[v] den direkten Vorgänger von v auf dem
kürzesten Weg enthält. P[v] wird mit der Quelle initialisiert. Am Ende des
Algorithmus kann der kürzeste Weg zu jedem Knoten mit Hilfe des Feldes p
bestimmt werden.
Wolfgang E. Nagel
Algorithmus von Dijkstra
void dijkstra1 ( void )
S [ 1 ] = true
/ * Knoten 1 ist in der Menge S enthalten
*/
for (i = 2 ; i < = N ; i + + )
S [ i ] = false
/ * keine weiteren
Elemente
in S * /
d [i ] = c[1 ][i ]
p [i ] = 1
for (i = 1 ; i < = N -1 ; i + + )
wähle
einen Knoten w in V -S , so daß
S [ w ] = true
/ * füge
d [ w ] ein Minimum
ist
w zu S hinzu * /
for (jeden Knoten v in V -S )
d [w]+ c[w][v] < d [v]
T
F
d [v] = d [w]+ c[w][v]
p [v] = w
Wolfgang E. Nagel
Beispiel zum Algorithmus von Dijkstra (z.B. Flugplan)
1
10
100
30
5
2
50
10
60
3
Iteration
Initial.
1
2
3
4
4
20
S
{1}
w
-
d[2]
10
d[3]
∞
d[4]
30
{1,2}
{1,2,4}
{1,2,4,3}
{1,2,4,3,5}
2
4
3
5
10
10
10
10
60
50
50
50
30
30
30
30
Wolfgang E. Nagel
d[5]
100
100
90
60
60
Bemerkungen
!   Wenn man den kürzesten Weg von der Quelle zu jedem Knoten
rekonstruieren möchte, so gibt nach Ablauf des Algorithmus p[v] den direkten
Vorgänger von v auf dem kürzesten Weg an.
!   Beispiel:
Iteration
p[2] p[3] p[4] p[5]
Initial.
1
1
1
1
1
1
2
1
1
2
1
4
1
4
3
1
4
1
3
4
1
4
1
3
!   Zur Bestimmung des kürzesten Weges von 1 nach 5 bestimmt den Vorgänger
von 5 = 3, den Vorgänger von 3 = 4, den Vorgänger von 4 = 1, d.h. der
kürzeste Weg ist 1-4-3-5 mit Länge 60.
Wolfgang E. Nagel
Zeitkomplexität des Dijkstra-Algorithmus
! Adjazenzmatrix:
innere for-Schleife braucht O(|V|) Zeit
Also insgesamt:
Tavdijkstra (|V|) = Twdijkstra (|V|) = Tbdijkstra (|V|) ∈ O(|V|2)
! Adjazenzliste:
Falls |E| << |V|2 ist, so kann man die Zeitkomplexität auf
O(max{|V|, |E|}) reduzieren.
Wolfgang E. Nagel
Kürzeste Wege zwischen allen Knotenpaaren
Gegeben: G = (V,E) und für jede Kante (v,w) eine nicht-negative
Bewertung C(v,w).
Aufgabenstellung:
Bestimme für alle geordneten Paare (v,w) den kürzesten Weg von v nach w.
Lösungsmöglichkeit:
1. Wende den Dijkstra-Algorithmus für alle Knoten v als Quelle an
⇒ Zeitkomplexität O(n3).
2. Verwende den Floyd-Algorithmus.
Wolfgang E. Nagel
Grundidee zum Algorithmus von Floyd
Im ersten Schritt vergleicht man alle Wege, die von einem beliebigen Knoten i zu
einem anderen Knoten j führen, mit dem Umweg über Knoten 1. Ist der Umweg
kürzer, so wird der alte Weg durch den Umweg ersetzt.
Im zweiten Schritt werden alle Umwege über den Knoten 2 gebildet.
Im k-ten Schritt werden alle Umwege über den Knoten k gebildet, usw.
Zum Schluß enthält A[i][j] den kürzesten Weg von i nach j.
k
Ak-1 [i][k]
i
Ak-1 [k][j]
Ak-1 [i][j]
Wolfgang E. Nagel
j
Algorithmus von Floyd
1.  Der Floyd-Algorithmus nutzt eine n x n Matrix, um die Längen der kürzesten
Wege zu berechnen.
2.  Setze zu Beginn A0[i,j] = C[i,j].
Diagonalelemente = 0
∀i≠j
3.  Es werden n Iterationen über die Matrix A gemacht.
In der k-ten Iteration wird die folgende Formel zur Berechnung der neuen A
[i,j] benutzt:
Ak[i,j] = min (Ak-1[i,j], Ak-1 [i,k] + Ak-1[k,j])
4.  Zum Schluß enthält An[i][j] den kürzesten Weg von i nach j.
Wolfgang E. Nagel
Floyd-Algorithmus
void floyd ( double A [N+ 1 ][ N+ 1 ] , double C[ N+ 1 ] [N+ 1 ] )
int i , j , k
for (i = 1 ; i < = N; i + + )
for (j = 1 ; j < = N; j + + )
A [ i ] [ j ] = C[ i ] [ j ]
P [i ][ j ] = -1
for (i = 1 ; i < = N; i + + )
A [ i ] [i ] = 0.0
for (k = 1 ; k < = N; k+ + )
for (i = 1 ; i < = N; i + + )
for (j = 1 ; j < = N; j + + )
A [ i ][ k] + A [ k][ j ] < A [i ][ j ]
T
F
A [ i ][ j ] = A [ i ][ k] + A [k][ j ]
P [ i ][ j ] = k
Wolfgang E. Nagel
Zeitkomplexität des Floyd-Algorithmus
Tavfloyd (|V|) = Twfloyd ( |V| ) = Tbfloyd ( |V| ) ∈ O(n3)
Bemerkung:
Der Algorithmus hat eine sehr einfache Struktur. Deshalb wird ein Compiler
hier effizienten Code erzeugen.
Wolfgang E. Nagel
Transitiver Abschluss
!   In speziellen Fällen ist man lediglich daran interessiert, ob ein Weg (einer
beliebigen Länge) von Knoten i zu Knoten j existiert.
!   Der Floyd-Algorithmus kann so modifiziert werden, dass er dieses Problem
löst: Warshall-Algorithmus.
!   Angenommen, die Kostenmatrix C sei gerade die Adjazenzmatrix des
Graphen G, d.h. C[i][j] = 1, falls eine Kante von Knoten i nach Knoten j
existiert und 0 sonst.
!   Wir wollen die Matrix A so berechnen, dass A[i][j] = 1 genau dann gilt, wenn
ein nicht-trivialer Weg von i nach j existiert, d.h. der Weg hat eine Länge ≥ 1.
!   A wird häufig der transitive Abschluss der Adjazenzmatrix genannt.
!   Berechnungsformel: Ak[i][j] = Ak-1[i][j] OR (Ak-1[i][k] AND Ak-1[k][j])
Wolfgang E. Nagel
Warshall-Algorithmus
void warshall ( bool A [ N+ 1 ] [ N+ 1 ] , bool C[ N+ 1 ] [ N+ 1 ] )
int i , j , k
for (i = 1 ; i < = N; i + + )
for (j = 1 ; j < = N; j + + )
A [ i ][ j ] = C[ i ] [ j ]
for (k = 1 ; k < = N; k+ + )
for (i = 1 ; i < = N; i + + )
for (j = 1 ; j < = N; j + + )
A [ i ] [ j ] = = false
T
F
A [ i ] [ j ] = A [ i ] [ k] & & A [ k] [ j ]
Wolfgang E. Nagel
Zeitkomplexität des Warshall-Algorithmus
Tavwarshall ( |V| ) = Twwarshall ( |V| ) = Tbwarshall ( |V| ) ∈ O( |V|3 )
Wolfgang E. Nagel
Minimale Spannbäume
Definition:
Ein Spannbaum ist ein Teilgraph eines ungerichteten Graphen G, welcher ein
Baum ist und alle Knoten von G enthält.
Ein minimaler Spannbaum eines gewichteten ungerichteten Graphen G ist ein
Spannbaum mit minimalen Gewicht.
♦
!   Das Gewicht in einem Subgraphen eines gewichteten Graphen ist die Summe
der Gewichte der Kanten des Subgraphen.
Wolfgang E. Nagel
Minimale Spannbäume
Algorithmus von Prim
!   Algorithmus von Prim zum Finden eines minimalen Spannbaums ist ein
Greedy-Algorithmus
!   Algorithmus beginnt mit der Wahl eines beliebigen Startknotens
!   Dann wird der minimale Spannbaum aufgebaut, durch Auswahl eines neuen
Knotens und Kante, die garantiert im minimalen Spannbaum sind
!   Der Algorithmus bricht ab, wenn alle Knoten ausgewählt wurden
Wolfgang E. Nagel
Algorithmus von Prim
void prim (V,E,w,r)
VT={r};
d[r]=0;
for all v (V-VT)
edge (r,v) exists
T
F
1d[v]=w(r,v);
d[v]=∞
while find a vertex u such that d[u]=min{d(v)|v (V-VT)};
1VT=VT
{u};
for all v (V-VT)
1d[v]=min{d[v],w(u,v)};
Wolfgang E. Nagel
Zeitkomplexität
!   Die while-Schleife wird |V|-1 Mal ausgeführt
!   Die Berechnungen von min{d[v]|v (V-VT)} und der for-Schleife führen O(|V|)
Schritte aus
!   Damit ergibt sich die Zeitkomplexität Tprim(|V|) ∈ O(|V|2)
Wolfgang E. Nagel
Parallele Formulierung vom Algorithmus von Prim
!   Algorithmus von Prim ist iterativ
!   In jeder Iteration wird ein neuer Knoten zum minimalen Spannbaum
hinzugefügt
!   Da sich der Wert d[v] für einen Knoten v immer, wenn ein neuer Knoten u zu
VT hinzugefügt, ändern kann, ist es ausgeschlossen mehr als einen Knoten
für das Hinzufügen in den minimalen Spannbaum auszuwählen
!   Daher können die Iterationen in der while-Schleife nicht parallel ausgeführt
werden
Wolfgang E. Nagel
Parallele Formulierung vom Algorithmus von Prim
Jede Iteration kann auf folgende Weise parallelisiert werden:
!   Sei p Anzahl Prozesse, n Anzahl Knoten
!   Partitionierung des Feldes d und der Menge V, so dass jede Untermenge n/p
aufeinanderfolgende Knoten beinhaltet und die Arbeit assoziiert mit jeder
Untermenge wird von unterschiedlichen Prozessen ausgeführt
n
p
d[1…n]
Prozesse
…
…
0 1
i
p-1
!   Sei Vi die Untermenge der Knoten assoziiert mit Prozess pi für i=1,..,p-1
!   Jeder Prozess pi speichert den Teil des Feldes d, der zu Vi gehört
!   Jeder Prozess pi berechnet di[u]=min{di[v]|v∈(V-VT)∩Vi} während jeder
Iteration der while-Schleife
!   Das globale Minimum über alle di[u] wird dann in Prozess p0 gespeichert
Wolfgang E. Nagel
Parallele Formulierung vom Algorithmus von Prim
!   Prozess p0 verteilt Knoten u, welcher in VT eingefügt wurde, an alle Prozesse
!   Der Prozess pi, der für Knoten u verantwortlich ist, markiert u zugehörig zur
Menge VT
!   Schließlich kann jeder Prozess die Werte in d[v] für seine lokalen Knoten
aktualisieren
!   Wird ein neuer Knoten u zu VT hinzugefügt, müssen die Werte d[v] für v∈(VVT) aktualisiert werden
!   Der Prozess, der für v verantwortlich ist, muss das Gewicht der Kante (u,v)
kennen, daher muss jeder Prozess die Spalten der gewichteten
Adjazenzmatrix speichern, welche zur Menge Vi, assoziiert mit ihm, gehören
A
Prozesse
…
0 1
…
i
Wolfgang E. Nagel
n
p-1
Zeitkomplexität des parallelisierten Algorithmus von Prim
!   Der Aufwand eines jeden Prozesses, um die Werte von d[v] zu minimieren
und aktualisieren während jeder Iteration ist O(n/p)
!   Damit ist der parallele Algorithmus von Prim in O(n) mit n Prozessen
Wolfgang E. Nagel
Zusammenfassung der Algorithmen zur Graphen-Theorie
!   Die Einschränkung auf gerichtete Graphen ist nicht substantiell.
Entsprechende Lösungen können auch für ungerichtete Graphen angegeben
werden.
!   Teile dieser Algorithmen finden sich als ‘Skelett‘ in vielen interessanten
Lösungen zu Problemen aus dem wirklichen Leben:
–  Netzplantechnik
–  Compiler (Vektorisierung)
–  Problem des Handlungsreisenden
–  Produktionssteuerung, etc.
!   Darüber hinaus gibt es eine große Anzahl weiterer Probleme und
interessanter Algorithmen.
Wolfgang E. Nagel
Weitere Graph-Algorithmen
!   Zusammenhangskomponenten (Erreichbarkeit)
!   Flüsse auf Netzwerken (maximale, minimale)
!   Transportprobleme
–  Euler-Kreis: Kreis, in dem jede Kante einmal durchlaufen wird (einfaches
Problem)
–  Hamilton-Kreis: Kreis, in dem jeder Knoten einmal durchlaufen wird (sehr
schwieriges Problem)
–  Traveling Salesman: Gibt es eine Rundreise, die jeden Knoten einmal
besucht und deren Länge < L ist? (sehr schwieriges Problem)
!   Färbungsprobleme
Wolfgang E. Nagel
Herunterladen