Einführung in die Informatik

Werbung
Günther Stiege
Einführung in die Informatik
November 2012
i
Vorwort
Sie wartet im Park bei den Tannen.
Er hat noch viel zu tun:
Den Back gepätscht.
Pettern gemetscht.
An der Platine den Finger gequetscht.
Igemehlt.
Einen Apfel geschält.
Die Datei abgedätet.
Er hat sich arg verspätet.
Nun
geht sie verärgert von dannen.
chg escalera 1999
Das vorliegende Buch ist aus einem Skript zu den Vorlesungen „Programmierung“ und
„Datenstrukturen“ entstanden, die ich im Wintersemester 1998/99 und im Sommersemester 1999 an der Universität Oldenburg gehalten habe. Diese beiden Vorlesungen bildeten einen einführenden, in erster Linie für Anfänger im Dimplomstudiengang Informatik bestimmten Kurs. Den Diplomstudiengang Informatik gibt es nicht mehr. Eine anspruchsvolle einführende Darstellung der Informatik ist jedoch auch für den universitären
Bachelor-Studiengang in Informatik notwendig. Sie wird auch in naturwissenschaftlichen
und technischen universitären Studiengängen gebraucht. Für diesen Leserkreis ist das
Buch gedacht. Ich habe den Stoff in die Teile „Grundlagen“, „Algorithmen“, „Einfache Datenstrukturen“, „Allgemeine Graphen“ und „Parallelität“ eingeteilt. Das soll kurz erläutert
werden.
In den Grundlagen wird mit einführenden Beispielen im 1. Kapitel ein Einstieg in den Stoff
gefunden. Ich habe es für zweckmäßig gehalten, Knuth [Knut1997] sowie einer Vorlesung
meines Kollegen R. Vollmar zu folgen und mit dem guten, alten euklidischen Algorithmus zu beginnen. Die dazugehörenden Effizienzbetrachtungen sind zugegebenermaßen für
Anfänger in den ersten Vorlesungstunden nicht ganz einfach. Hier wie auch im Rest des
Buches habe ich jedoch Wert darauf gelegt, mathematischen Schwierigkeiten, insbesondere
auch Beweisen, nicht auszuweichen. Die leider in der Informatikausbildung zunehmend zu
beobachtende Tendenz, Schulung in formalem Denken zugunsten praktischer Fertigkeiten
und Kenntnisse zu reduzieren, halte ich für fatal.
Das ausführliche 2. Kapitel erläutert anhand der Sprache C die Grundlagen der Pro-
ii
grammierung. Auch hierzu einige Anmerkungen. Es ist üblich, in der universitären Informatikausbildung außer dem Informatik-Grundkurs einen getrennten Programmierkurs
vorzusehen. Meistens wird dort eine „moderne“ Sprache, objektorientiert oder funktional,
zu Grunde gelegt. Ich bin bewußt davon abgewichen, da ich eine explizite und intensive
Beschäftigung mit Konzepten wie Zeigern und Unterprogrammen in der Informatikausbildung für sehr wichtig und prägend halte.
Die Kapitel 3 „Darstellung von Daten durch Bitmuster“ und 4 „Rechensysteme“ vermitteln
so viel über „reale“ Rechner, wie für eine Einführung nötig ist.
Der Kern des Kurses sind die Teile II, III und IV. In Teil II werden in zwei Kapiteln
Algorithmen allgemein untersucht. Es wird der naive Algorithmus-Begriff behandelt und
es werden Markov-Methoden als Beispiel für eine Formalisierung des Algorithmus-Begriffs
vorgestellt. Die Churchsche These wird erwähnt und die Unlösbarkeit des Halteproblems
gezeigt. Es wird die Zeiteffizienz von Algorithmen betrachtet und dazu die O-Notation
eingeführt. Zum Schluß wird das Problem P = N P erläutert. In zwei ergänzenden Abschnitten wird in Form eines kurzen Überblicks auf Turingmaschinen und Formale Sprachen und auf die näherungsweise Lösung schwieriger Probleme eingegangen.
Teil III ist einfachen Datenstrukturen, nämlich Listen, Suchbäumen, Hashing und Sortieren gewidmet. Die entsprechenden Datenstrukturen und Algorithmen werden vorgestellt
und untersucht.
Teil IV des Buches handelt von allgemeinen Graphen. Das sind Graphen, in denen ungerichtete Kanten und gerichtete Bögen beliebig gemischt auftreten. Diese vereinheitlichende
Sicht ist neu und war mir zu der Zeit, in der der Kurs durchgeführt wurde, noch nicht
bekannt. Aus diesem Grund ist Teil IV des Buches ganz anders, insbesondere auch umfangreicher, als die graphentheoretischen Kapitel des ursprünglichen Skripts. Den Kern
des vierten Teils bilden die Kapitel „Grundlagen“, „Darstellungen“, „Wege und Zusammenhang“, „Tiefensuche und Breitensuche“ sowie „Die Biblockzerlegung“.
In der Informatik und anderen Anwendungsgebieten der Graphentheorie sind Netzwerke,
also Graphen, deren Knoten und/oder Linien zusätzliche Attribute aufweisen, von besonderer Bedeutung. Als wichtiges Beispiel werden „Kürzeste Wegen“ in allgemeinen Graphen
untersucht.
Parallelität ist ein sehr wichiges Gebiet der Informatik. In Einführungen und Grundkursen
bleibt oft keine Zeit, es zu behandeln. Einen ersten Einblick geben die zwei Kapitel von
Teil V. Das Kapitel „Parallelität in Rechensystemen und Netzen“ beschreibt, wo und
in welcher Form parallele Abläufe auftreten. Das Kapitel „Programmieren II: Parallele
Programme“ behandelt am Beispiel von Leichtgewichtsprozessen in C einige einführende
Fragen der Parallelität.
Ich habe mich bemüht, die nach meiner Meinung wesentlichen Dinge eines jeden angesprochenen Gebietes hinreichend ausführlich darzustellen. Eine Reihe von Dingen, die ich auch
für wichtig halte, die man bei Zeitmangel aber überspringen kann, habe ich explizit mit
iii
einem * als Ergänzung gekennzeichnet. Es sind einzelne Abschnitte, in der Graphentheorie
auch ganze Kapitel. Einige Ergänzungen haben nur Überblickscharakter. In Ergänzungen
wird durchaus auf andere Ergänzungen zurückgegriffen, an anderer Stelle nicht.
Zu den meisten Kapiteln gibt es Übungen. Zur Mehrzahl der Übungen sind im Anhang
Lösungen oder Lösungshinweise angegeben. Die Übungen reichen für einen ergänzenden
Übungskurs nicht aus, denn reine Lernaufgaben zum Trainieren des Stoffes habe ich nicht
aufgenommen. Statt dessen habe ich für die Übungen Erweiterungen und Ergänzungen
ausgewählt. An mehr als einer Stelle wird der Leser auch aufgefordert, einfache Aussagen
des Stoffteils selber zu beweisen.
In den Anhängen zum Buch befinden sich eine Zusammenfassung mathematischer Hilfsmittel allgemeiner Art, eine kurze Darstellung der benötigten Begriffe und Ergebnisse aus
der Mengenlehre, ein Anhang über wichtige Grundlagen der Wahrscheinlichkeitstheorie
und einiges mehr..
Für die Vorlesung, das Skript und jetzt das Buch habe ich eine Vielzahl von Lehrbüchern
und andere schriftliche Quellen benutzt. Sie sind als Literaturzitate im Text der einzelnen Kapitel, speziell in den abschließenden Abschnitten „Literatur“ angegeben. Kurs und
Skript wurden in besonderem Maße haben von den Büchern von Aho/Ullman [AhoU1995],
Cormen/Leiserson/Rivest [CormLR1990], Knuth ([Knut1997], [Knut1998], [Knut1998a])
sowie Kowalk [Kowa1996] beeinflußt.
Eine Reihe von Verzeichnissen ergänzt das Buch. Besonderen Wert habe ich auf ein ausführliches Stichwortverzeichnis gelegt. Insgesamt ergibt sich ein ziemlich dickes Buch. Es
würde mich freuen, wenn es als ganzens oder in Teilen zu intensivem Lesen anregte. Besonders erfeut wäre ich, wenn es Neulingen in der Informatik etwas von der Faszination
und der Schönheit dieser Wissenschaft vermittelte.
Das Buch ist in LATEX geschrieben und nach amerikanischem Brauch wird ein “chapter” in
“sections” und diese in “subsections” unterteilt. Nur widerstrebend habe ich mich durchgerungen, deshalb bei der Kapitelgliederung von „Abschnitten“ (statt Unterkapitel) und
„Unterabschnitten“ zu sprechen.
Vielen Leuten gilt es zu danken:
Meine Mitarbeiter Björn Briel, Olaf Maibaum und Ingo Stierand haben mir bei der Erstellung des Skripts nicht nur durch intensives Korrekturlesen, sondern auch durch viele
fachliche Diskussionen und Anregungen immer wieder geholfen. Auch Anregungen von
Studierenden sind in die endgültige Fassung eingflossen. Etliche Ungenauigkeiten und eine Vielzahl von Schreibfehlern konnten dadurch ausgemerzt werden. Auch Herr Michael
Uelschen und Frau Susanne Steiner haben durch Korrekturlesen zur Verbesserung beigetragen. Meinem Kollegen Hermann Luttermann danke ich für die Durchsicht des Kapitels
über Parallelität in Rechensystemen und Netzen.
Der nach der Vorlesung und der ersten Skriptversion entstandene umfangreiche Teil über
allgemeine Graphen, wäre ohne die mehrjährige intensive Zuasmmenarbeit mit Ingo Stie-
iv
rand und Sergej Alekseev nicht möglich gewesen. Leider sind die interessanten und wichtigen Ergebnisse zur Ablaufanalyse aus der Dissertation Alekseev [Alek2006] wegen Platzmangels nicht mehr in das Buch eingeflossen.
Natürlich braucht es für die Enstehung eines Buches wie dieses eine vorherige Reifezeit
und sowie eine anregende Arbeitsumgebung. Beides habe ich im Kreis der Kollegen und
meiner Mitarbeiter an meinen Wirkungsstätten, der TU Braunschweig, der Universität
Hildesheim und der Universität Oldenburg gehabt.
Ein besonderer Dank gilt meinem Sohn Harold, der mit viel Zähigkeit den für ihn schwierigen Text Korrektur gelesen hat. Und natürlich meiner Frau, ohne deren Unterstützung
ich das Buch gar nicht hätte schreiben können.
Hannover, im November 2012
G. Stiege
Inhaltsverzeichnis
i
Vorwort
I
Grundlagen
1
1 Einführende Beispiele
1.1 Der euklidische Algorithmus . . . . . . . . . . . . . . .
1.1.1 Entwicklung und Untersuchung des Algorithmus
1.1.2 Ein Programm für den euklidischen Algorithmus
1.1.3 Effizienz des euklidischen Algorithmus . . . . .
1.2 Sortieren durch Einfügen . . . . . . . . . . . . . . . . .
1.2.1 Allgemeines zum Sortieren . . . . . . . . . . . .
1.2.2 Insertion Sort: Algorithmus und Programm . . .
1.2.3 Effizienz von Sortieren durch Einfügen . . . . .
1.3 Ein Kochrezept . . . . . . . . . . . . . . . . . . . . . .
Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . .
Literatur . . . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
3
3
6
10
18
18
18
19
25
27
28
2 Programmieren I: Sequentielle Programme
2.1 Schichtenaufbau eines Rechensystems . . . .
2.2 Dateien und Datenbanken . . . . . . . . . .
2.3 Grundbegriffe . . . . . . . . . . . . . . . . .
2.4 Werte, Variable, Zeiger, Namen . . . . . . .
2.5 Wertebereiche, Operationen, Ausdrücke . . .
2.5.1 Ganze Zahlen . . . . . . . . . . . . .
2.5.2 Rationale Zahlen . . . . . . . . . . .
2.5.3 Zeichen . . . . . . . . . . . . . . . .
2.5.4 Wahrheitswerte . . . . . . . . . . . .
2.5.5 Adressen . . . . . . . . . . . . . . . .
2.5.6 Aufzählungstypen . . . . . . . . . . .
2.6 Reihungen, Zeichenreihen, Sätze . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
29
29
33
34
37
45
45
48
52
54
57
59
61
v
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
vi
INHALTSVERZEICHNIS
2.7
2.8
2.6.1 Reihungen . . . . . .
2.6.2 Zeigerarithmetik in C
2.6.3 Zeichenreihen . . . .
2.6.4 Sätze . . . . . . . . .
Programmsteuerung . . . .
Unterprogramme . . . . . .
Literatur . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3 Darstellung von Daten durch Bitmuster
3.1 Bits und Bitmuster . . . . . . . . . . . . .
3.2 Darstellung natürlicher Zahlen . . . . . . .
3.3 Darstellung ganzer Zahlen . . . . . . . . .
3.4 Darstellung rationaler Zahlen . . . . . . .
3.5 Hexadezimaldarstellung von Bitmustern .
3.6 Darstellung von Zeichenreihen . . . . . . .
3.7 Eigentliche Bitmuster und Wahrheitswerte
3.8 Bitmuster in C . . . . . . . . . . . . . . .
Literatur . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
61
65
67
68
78
83
91
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
93
94
96
101
105
109
112
117
118
119
4 Rechensysteme
4.1 Grobschema der konventionellen Maschine . . .
4.1.1 Prozessor . . . . . . . . . . . . . . . . .
4.1.2 Hauptspeicher . . . . . . . . . . . . . . .
4.1.3 Ein-/Ausgabeschnittstellen . . . . . . . .
4.1.4 Übertragungsmedium . . . . . . . . . . .
4.1.5 Periphere Geräte . . . . . . . . . . . . .
4.2 Prozessor und Maschinenbefehle . . . . . . . . .
4.2.1 Register des Prozessors . . . . . . . . . .
4.2.2 Maschinenbefehle und Unterbrechungen .
4.2.3 Befehlsklassen . . . . . . . . . . . . . . .
4.2.4 Adressierungsarten . . . . . . . . . . . .
4.3 Grundsoftware . . . . . . . . . . . . . . . . . . .
4.3.1 Betriebssystem . . . . . . . . . . . . . .
4.3.2 Weitere Programme der Grundsoftware .
4.4 Virtuelle Adressierung . . . . . . . . . . . . . .
Literatur . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
121
121
121
122
126
127
127
129
129
133
135
137
138
139
144
145
147
II
Algorithmen
5 Algorithmen I: Naiv und formalisiert
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
149
151
INHALTSVERZEICHNIS
5.1
5.2
Der naive Algorithmus-Begriff . . . . . . . . . . .
5.1.1 Eigenschaften . . . . . . . . . . . . . . . .
5.1.2 Schrittweise Verfeinerung . . . . . . . . . .
5.1.3 Entwurfstechniken für Algorithmen . . . .
5.1.4 Algorithmen in applikativer Darstellung .
Der formalisierte Algorithmusbegriff . . . . . . . .
5.2.1 Markov-Algorithmen . . . . . . . . . . . .
5.2.2 Churchsche These . . . . . . . . . . . . . .
5.2.3 Algorithmisch unlösbare Probleme . . . .
5.2.4 Turing-Maschinen und formale Sprachen*
Aufgaben . . . . . . . . . . . . . . . . . . . . . .
Literatur . . . . . . . . . . . . . . . . . . . . . . .
vii
.
.
.
.
.
.
.
.
.
.
.
.
6 Algorithmen II: Effizienz und Komplexität
6.1 Laufzeitanalyse von Algorithmen und Programmen
6.1.1 Vorbemerkungen . . . . . . . . . . . . . . .
6.1.2 Beispiel: Mischsortieren . . . . . . . . . . . .
6.1.3 Boden und Decke . . . . . . . . . . . . . . .
6.1.4 Modulo . . . . . . . . . . . . . . . . . . . .
6.1.5 Abschätzungen und Größenordnungen . . .
6.1.6 Rekurrenzen und erzeugende Funktionen* .
6.2 Die Komplexität von Problemen . . . . . . . . . . .
6.2.1 Überblick . . . . . . . . . . . . . . . . . . .
6.2.2 Komplexität von Algorithmen . . . . . . . .
6.2.3 Beispiel: Hamiltonkreise . . . . . . . . . . .
6.2.4 Die Problemklassen P, N P und weitere . .
6.3 Näherungslösungen schwieriger Probleme* . . . . .
Aufgaben . . . . . . . . . . . . . . . . . . . . . . .
Literatur . . . . . . . . . . . . . . . . . . . . . . . .
III
Einfache Datenstrukturen
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
152
152
155
159
162
166
166
169
170
172
175
175
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
177
. 177
. 177
. 178
. 189
. 193
. 195
. 199
. 199
. 199
. 200
. 201
. 210
. 216
. 223
. 223
225
7 Allgemeines zu Datenstrukturen
227
7.1 Sätze und Vetretersätze . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
7.2 Schlüssel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228
8 Listen
231
8.1 Terminologie und Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . 231
8.1.1 Definition und Beispiele . . . . . . . . . . . . . . . . . . . . . . . . 231
8.1.2 Operationen mit Listen . . . . . . . . . . . . . . . . . . . . . . . . . 232
viii
INHALTSVERZEICHNIS
8.2
8.3
8.4
Binärsuche . . . . . . . . . . . . . . . . . . . . .
Realisierung von Listen . . . . . . . . . . . . . .
8.3.1 Verkettete Listen . . . . . . . . . . . . .
8.3.2 Realisierung von Listen durch Reihungen
Keller, Schlangen, Halden . . . . . . . . . . . .
8.4.1 Keller . . . . . . . . . . . . . . . . . . .
8.4.2 Keller: Beispiele und Anwendungen . . .
8.4.3 Schlangen . . . . . . . . . . . . . . . . .
8.4.4 Halden und Prioritätswarteschlangen . .
Aufgaben . . . . . . . . . . . . . . . . . . . . .
Literatur . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
9 Suchbäume
9.1 Binäre Suchbäume . . . . . . . . . . . . . . . . . . . . . . . .
9.1.1 Allgemeines zum Suchen . . . . . . . . . . . . . . . . .
9.1.2 Binärbäume . . . . . . . . . . . . . . . . . . . . . . . .
9.1.3 Binäre Suchbäume und Operationen auf ihnen . . . . .
9.2 Rot-Schwarz-Bäume . . . . . . . . . . . . . . . . . . . . . . .
9.2.1 Definition und Eigenschaften von Rot-Schwarz-Bäumen
9.2.2 Operationen auf Rot-Schwarz-Bäumen . . . . . . . . .
9.3 Zufällige binäre Suchbäume . . . . . . . . . . . . . . . . . . .
9.4 B-Bäume und externe Datenspeicherung . . . . . . . . . . . .
9.4.1 Mehrweg-Suchbäume . . . . . . . . . . . . . . . . . . .
9.4.2 Speicherung bei Dateien und Datenbanken . . . . . . .
9.4.3 B-Bäume . . . . . . . . . . . . . . . . . . . . . . . . .
9.5 Digitale Bäume . . . . . . . . . . . . . . . . . . . . . . . . . .
Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Literatur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10 Suchen mit Schlüsseltransformation
10.1 Direkte Speicherung . . . . . . . . . . . . . . . .
10.2 Grundlagen des Hashings . . . . . . . . . . . . . .
10.2.1 Hashingalgorithmen . . . . . . . . . . . . .
10.2.2 Kollisionsauflösung durch Verkettung . . .
10.2.3 Kollisionsauflösung durch offenes Hashing
10.2.4 Universelles Hashing . . . . . . . . . . . .
10.2.5 Dynamisches Hashing . . . . . . . . . . . .
Aufgaben . . . . . . . . . . . . . . . . . . . . . .
Literatur . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
235
239
239
243
245
245
247
251
252
255
255
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
257
. 257
. 257
. 258
. 266
. 277
. 277
. 280
. 293
. 296
. 296
. 298
. 299
. 303
. 303
. 305
.
.
.
.
.
.
.
.
.
307
. 308
. 308
. 309
. 311
. 312
. 316
. 316
. 317
. 317
INHALTSVERZEICHNIS
ix
11 Sortieren
11.1 Allgemeines zum Sortieren . . . . . . . . . . .
11.2 Quicksort . . . . . . . . . . . . . . . . . . . .
11.2.1 Algorithmus und Programm . . . . . .
11.2.2 Komplexität von Quicksort . . . . . . .
11.2.3 Randomisiertes Quicksort . . . . . . .
11.2.4 Qicksort und Mischsortieren . . . . . .
11.3 Halden, Heapsort und Prioritätswartesclangen
11.3.1 Halden (als Datenstruktur) . . . . . .
11.3.2 Sortieren mit Halden (Heapsort) . . . .
11.3.3 Prioritätswarteschlangen (nach Knuth)
11.4 Mindestkomplexität beim Sortieren . . . . . .
11.5 Lineares Sortieren . . . . . . . . . . . . . . . .
Aufgaben . . . . . . . . . . . . . . . . . . . .
Literatur . . . . . . . . . . . . . . . . . . . . .
IV
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Allgemeine Graphen
319
. 319
. 320
. 320
. 326
. 328
. 329
. 329
. 329
. 332
. 333
. 337
. 339
. 342
. 342
345
12 Grundlagen allgemeiner Graphen
12.1 Definitionen und Beispiele . . . . . . . . . . . .
12.2 Orientierungsklassen und Untergraphen . . . . .
12.3 Bipartite Graphen . . . . . . . . . . . . . . . .
12.4 Der Grad eines Knotens . . . . . . . . . . . . .
12.5 Anmerkung: Ersetzung von Kanten durch Bögen
12.6 Gleichheit und Isomorphie von Graphen* . . . .
Aufgaben . . . . . . . . . . . . . . . . . . . . .
Literatur . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
347
347
351
353
355
356
357
359
360
13 Darstellungen von Graphen
13.1 Graphische Darstellung . . . . . . . . . . .
13.2 Darstellung durch Matrizen . . . . . . . .
13.3 Darstellung durch Listen . . . . . . . . . .
13.4 Externe Darstellungen und Basiswerkzeuge
Aufgaben . . . . . . . . . . . . . . . . . .
Literatur . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
361
361
362
365
369
371
371
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
14 Wege und Zusammenhang
373
14.1 Wege: Definitionen und elementare Eigenschaften . . . . . . . . . . . . . . 373
14.2 Erreichbarkeit und Zusammenhang . . . . . . . . . . . . . . . . . . . . . . 378
14.3 Brücken und Schnittpunkte . . . . . . . . . . . . . . . . . . . . . . . . . . 381
x
INHALTSVERZEICHNIS
14.4 Kreisfreiheit und Bäume . . . . . . . . . . . . . .
14.4.1 Kreisfreiheit und Zusammenhang . . . . .
14.4.2 a-Bäume . . . . . . . . . . . . . . . . . . .
14.4.3 f-Bäume . . . . . . . . . . . . . . . . . . .
14.5 Partielle Ordnung und Schichtennumerierung . . .
14.6 Klassifizierung von Zusammenhangskomponenten
14.7 Abgeleitete Graphen . . . . . . . . . . . . . . . .
14.8 Eulersche und hamiltonsche Wege . . . . . . . . .
14.8.1 Eulerwege . . . . . . . . . . . . . . . . . .
14.8.1.1 a-Eulerkreise . . . . . . . . . . .
14.8.1.2 f-Eulerkreise . . . . . . . . . . .
14.8.2 Hamiltonwege . . . . . . . . . . . . . . . .
14.9 Datenstrukturen für Wege und Kreiszerlegung . .
14.9.1 Wege als verkettete Listen . . . . . . . . .
14.9.2 Kreiszerlegung* . . . . . . . . . . . . . . .
Aufgaben . . . . . . . . . . . . . . . . . . . . . .
Literatur . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
15 Tiefensuche und Breitensuche
15.1 Tiefensuche . . . . . . . . . . . . . . . . . . . . . . .
15.2 Tiefensuchbäume . . . . . . . . . . . . . . . . . . . .
15.3 Bestimmung schwacher Zusammenhangskomponenten
15.4 Bestimmung starker Zusammenhangskomponenten . .
15.5 Breitensuche . . . . . . . . . . . . . . . . . . . . . . .
Aufgaben . . . . . . . . . . . . . . . . . . . . . . . .
Literatur . . . . . . . . . . . . . . . . . . . . . . . . .
16 Die
16.1
16.2
16.3
16.4
16.5
16.6
16.7
Biblockzerlegung
Klassen geschlossener a-Wege und Kantenzerlegungen
Die Biblockzerlegung allgemeiner Graphen . . . . . .
Eigenschaften der Komponenten der Biblockzerlegung
Klassifikation von Linien und Knoten . . . . . . . . .
Der Biblockgraph . . . . . . . . . . . . . . . . . . . .
Algorithmen zur Bestimmung der Biblockzerlegung .
Digraphen und vollständige Orientierungen . . . . . .
Aufgaben . . . . . . . . . . . . . . . . . . . . . . . .
Literatur . . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
382
382
384
386
387
392
394
399
399
399
400
404
405
405
406
408
411
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
413
. 413
. 419
. 423
. 423
. 430
. 433
. 434
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
437
437
439
443
448
449
451
458
461
462
17 Perioden*
465
17.1 a-Periode und f-Periode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 465
17.2 Periodizitätsklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 468
INHALTSVERZEICHNIS
xi
17.3 Ein Algorithmus zur Bestimmung der Periode . . . . . . . . . . . . . . . . 470
Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 473
Literatur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 475
18 Ergänzungen zur Graphentheorie*
18.1 Mengertheorie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
18.1.1 Trennende Mengen und disjunkte Wege . . . . . . . . . . . . .
18.1.2 Die Mengersätze . . . . . . . . . . . . . . . . . . . . . . . . .
18.1.3 Erweiterungen zu den Mengersätzen . . . . . . . . . . . . . . .
18.1.4 Die Struktur von Mengertrennmengen . . . . . . . . . . . . .
Literatur zu Abschnitt „Mengertheorie“ . . . . . . . . . . . . .
18.2 Korrespondenzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
18.2.1 Überdeckungen und Unabhängigkeit . . . . . . . . . . . . . .
18.2.2 Korrespondenzen (Matchings) . . . . . . . . . . . . . . . . . .
18.2.3 Maximale Korrespondenzen und alternierende Wege . . . . . .
18.2.4 Ein Algorithmus zum Finden maximaler Korrepondenzen . . .
18.2.5 Korrektheit und Effizienz des Algorithmus . . . . . . . . . . .
18.2.6 Korrespondenzen in bipartiten Graphen . . . . . . . . . . . .
Literatur zu Abschnitt „Korrespondenzen“ . . . . . . . . . . .
18.3 Höhere Zusammenhangszerlegungen . . . . . . . . . . . . . . . . . . .
18.3.1 k-a-Zusammenhang . . . . . . . . . . . . . . . . . . . . . . . .
18.3.2 k-a-Linienzusammenhang . . . . . . . . . . . . . . . . . . . . .
18.3.3 Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Aufgaben zu Abschnitt „Höhere Zusammenhangszerlegungen“
Literatur zu Abschnitt „Höhere Zusammenhangszerlegungen“ .
18.4 Partitionen und starke Transitivität . . . . . . . . . . . . . . . . . . .
18.4.1 Partitionen in allgemeinen Graphen . . . . . . . . . . . . . . .
18.4.2 Starke Transitivität . . . . . . . . . . . . . . . . . . . . . . . .
Aufgaben zu Abschnitt “Partitionen und starke Transitivität“
Literatur zu Abschnitt “Partitionen und starke Transitivität“ .
18.5 Knotenfärbungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Aufgaben zu Abschnitt „Färbungen“ . . . . . . . . . . . . . .
Literatur zu Abschnitt „Färbungen“ . . . . . . . . . . . . . . .
18.6 Planarität . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Literatur zu Abschnitt „Planarität“ . . . . . . . . . . . . . . .
19 Kürzeste Wege in Netzwerken
19.1 Gewichtete Wege . . . . . . . . . . . . . . . . . . . . . . .
19.2 Kürzeste Wege mit nichtnegativen Gewichten . . . . . . .
19.3 Kürzeste Wege mit beliebigen Gewichten . . . . . . . . . .
19.4 Kürzeste Wege unter Berücksichtigung der Graphstruktur .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
477
. 477
. 477
. 478
. 480
. 481
. 482
. 482
. 482
. 483
. 484
. 487
. 492
. 494
. 495
. 495
. 496
. 499
. 500
. 500
. 500
. 501
. 501
. 504
. 505
. 505
. 505
. 508
. 508
. 509
. 510
.
.
.
.
511
. 511
. 513
. 516
. 518
xii
INHALTSVERZEICHNIS
19.4.1 Kürzeste
19.4.2 Kürzeste
Aufgaben . . .
Literatur . . . .
a-Entfernungen
f-Entfernungen
. . . . . . . . .
. . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
20 Flüsse in Netzwerken*
20.1 Definition und Zielsetzung . . . . . . . .
20.2 Verbessernde Wege und Schnitte . . . . .
20.3 Algorithmus nach Ford und Fulkerson . .
20.4 Der Algoritmus von Edmonds und Karp
20.4.1 Beschreibung des Algorithmus . .
20.4.2 Korrektheit und Komplexität des
Karp . . . . . . . . . . . . . . . .
20.4.3 Vereinfachung des Netzes . . . . .
20.5 Nicht ganzzahlige Flüsse . . . . . . . . .
20.6 Existenz eines maximalen Flusses . . . .
Aufgaben . . . . . . . . . . . . . . . . .
Literatur . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
Algorithmus von
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
Edmonds und
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
.
.
.
.
518
520
523
523
525
. 525
. 527
. 531
. 532
. 532
.
.
.
.
.
.
534
542
543
543
544
544
21 Weitere Ergänzungen zu Netzwerken*
21.1 Minimale erzeugende Bäume . . . . . . . . . . . . . . . . . . . . . . . . .
Literatur zu Abschnitt „Minimale erzeugende Bäume“ . . . . . . .
21.2 Gewichtsoptimale Korrespondenzen . . . . . . . . . . . . . . . . . . . . .
Literatur zu Abschnitt „Gewichtsoptimale Korrespondenzen“ . . .
21.3 Minimale Rundwege . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
21.3.1 Das Problem des chinesischen Briefträgers . . . . . . . . . . . . .
21.3.2 Das Problem des Handlungsreisenden . . . . . . . . . . . . . . . .
Aufgaben zu Abschnitt “Minimale Rundwege“ . . . . . . . . . . .
Literatur zu Abschnitt „Minimale Rundwege“ . . . . . . . . . . .
21.4 Endliche Markovketten . . . . . . . . . . . . . . . . . . . . . . . . . . . .
21.4.1 Endliche Markovketten mit stationären Übergangswahrscheinlichkeiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
21.4.2 Markovgraphen (Beispiel) . . . . . . . . . . . . . . . . . . . . . .
21.4.3 Zustandsklassifizierung . . . . . . . . . . . . . . . . . . . . . . . .
Literatur zu Abschnitt „Endliche Markovketten“ . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
547
547
549
549
551
552
552
555
555
556
556
.
.
.
.
556
557
559
562
V
563
Parallelität
22 Parallelität in Rechensystemen und Netzen
565
22.1 Parallelität auf der Hardware-Ebene . . . . . . . . . . . . . . . . . . . . . . 565
INHALTSVERZEICHNIS
22.2 Parallelität auf der Betriebssystem-Ebene
22.3 Vernetzte Rechner . . . . . . . . . . . .
22.4 Hochleistungsrechner . . . . . . . . . . .
Literatur . . . . . . . . . . . . . . . . . .
xiii
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
23 Programmieren II: Parallele Programme
23.1 Programmierbeispiele für Leichtgewichtsprozesse .
23.1.1 Beispiel: Paralleles Zählen . . . . . . . . .
23.1.2 Beispiel: Matrixmultiplikation . . . . . . .
23.1.3 Verallgemeinerter Speedup . . . . . . . . .
23.2 Erzeuger/Verbraucher . . . . . . . . . . . . . . .
23.2.1 Lösung mit Leichtgewichtsprozessen . . . .
23.2.1.1 Programme . . . . . . . . . . . .
23.2.1.2 Semaphore, Mutexe und Signale .
23.2.2 Lösung durch Simulation . . . . . . . . . .
23.2.3 Bediensysteme* . . . . . . . . . . . . . . .
23.3 Beispiel: Zeigerspringen . . . . . . . . . . . . . . .
Aufgaben . . . . . . . . . . . . . . . . . . . . . .
Literatur . . . . . . . . . . . . . . . . . . . . . . .
VI
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
581
. 581
. 582
. 585
. 589
. 590
. 592
. 592
. 595
. 597
. 600
. 604
. 607
. 608
Anhänge
A Mengenlehre
A.1 Mengen . . . . . . . . . . . . . . . . . .
A.2 Geordnete Paare und Relationen . . . .
A.3 Abbildungen und Familien . . . . . . . .
A.4 Allgemeine Vereinigungen, Durchschnitte
A.5 Relationen über einer Menge . . . . . . .
A.6 Mächtigkeit einer Menge . . . . . . . . .
567
576
577
579
609
. . . . . . . .
. . . . . . . .
. . . . . . . .
und Produkte
. . . . . . . .
. . . . . . . .
B Wahrscheinlichkeitstheorie
B.1 Allgemeines zu Wahrscheinlichkeitsräumen
B.2 Diskrete Wahrscheinlichkeitsräume: . . . .
B.3 Stetige Wahrscheinlichkeitsräume: . . . . .
B.4 Stochastische Prozesse . . . . . . . . . . .
B.5 Zufallszahlen . . . . . . . . . . . . . . . .
Literatur . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
611
. 611
. 613
. 614
. 615
. 616
. 616
.
.
.
.
.
.
619
. 620
. 622
. 624
. 626
. 626
. 627
xiv
INHALTSVERZEICHNIS
C Hilfsmittel aus der Analysis und Zahlentheorie
629
C.1 Exponentialfunktion und Logarithmusfunktion . . . . . . . . . . . . . . . . 629
C.2 Ungleichung von Cauchy-Schwarz-Bunjakowski . . . . . . . . . . . . . . . . 631
C.3 Zahlentheoretische Hilfssätze . . . . . . . . . . . . . . . . . . . . . . . . . . 632
D Ergänzungen zu Erzeuger/Verbraucher
635
D.1 Erzeuger/Verbraucher in Realzeit . . . . . . . . . . . . . . . . . . . . . . . 635
D.2 Erzeuger/Verbraucher in der Simulation . . . . . . . . . . . . . . . . . . . 648
E Das
E.1
E.2
E.3
E.4
E.5
System GHS zur Bearbeitung von Graphen
Allgemeine Beschreibung von GHS . . . . . . . . . . . . . .
Beispiel: Einlesen eines Graphen . . . . . . . . . . . . . . . .
Beispiel: Schwache und starke Zusammenhangskomponenten
Beispiel: Biblockzerlegung . . . . . . . . . . . . . . . . . . .
Beispiel: Finden von Hamiltonkreisen . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
659
659
659
660
667
667
F Verzeichnis der Algorithmen
677
G Namensliste
683
L Lösungen ausgewählter Aufgaben
L.1 Kapitel 5: „Algorithmen I“ . . . . . . . . . . . . . . . .
L.2 Kapitel 6: „Algorithmen II“ . . . . . . . . . . . . . . .
L.3 Kapitel 9: „Suchbäume“ . . . . . . . . . . . . . . . . . .
L.4 Kapitel 10: „Hashing“ . . . . . . . . . . . . . . . . . . .
L.5 Kapitel 11: „Sortieren“ . . . . . . . . . . . . . . . . . .
L.6 Kapitel 12: „Grundlagen allgemeiner Graphen“ . . . . .
L.7 Kapitel 13: „Darstellung allgemeiner Graphen“ . . . . .
L.8 Kapitel 14: „Wege und Zusammenhang“ . . . . . . . . .
L.9 Kapitel 15: „Tiefen- und Breitensuche“ . . . . . . . . .
L.10 Kapitel 16: „Die Biblockzerlegung“ . . . . . . . . . . .
L.11 Kapitel 17: „Ergänzung: Perioden“ . . . . . . . . . . . .
L.12 Kapitel 18: “Ergänzungen zur Graphentheorie“ . . . . .
L.13 Kapitel 19: “Kürzeste Wege in Netzwerken“ . . . . . . .
L.14 Kapitel 23 : „Programmieren II: Parallele Programme“
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
687
687
688
692
693
693
694
696
697
701
703
705
707
708
708
Abbildungsverzeichnis
709
Tabellenverzeichnis
715
Literaturverzeichnis
723
INHALTSVERZEICHNIS
Stichwortverzeichnis
xv
743
xvi
INHALTSVERZEICHNIS
Teil I
Grundlagen
1
Kapitel 1
Einführende Beispiele
1.1
Der euklidische Algorithmus
Der Algorithmus ist nach Euklid 1 benannt, obwohl er mit großer Wahrscheinlichkeit schon
Eudoxos 2 bekannt war.
1.1.1
Entwicklung und Untersuchung des Algorithmus
Gesucht ist der größte gemeinsame Teiler zweier positiver natürlicher Zahlen m und n.
Beispiel: m = 98 und n = 21
98 = 4 · 21 + 14
21 = 1 · 14 + 7
14 = 2 · 7 + 0
ggt(98, 21) = 7
Der in diesem Beispiel benutzte Algorithmus ist in Tabelle 1.1 angegeben. Er ist auf eine
Art formuliert, die man „natürliche Sprache mit Regeln“ nennen kann. Der Algorithmus
hat eine Kurzbezeichnung (E) und einen Namen (Euklid). Danach folgt in Kursivschrift
eine Kurzbeschreibung. Den Rest der Beschreibung bilden die einzelnen Schritte (E1, E2,
E3). Auf jeden Schritt folgt der in der Numerierung nächste, wenn nicht explizit (wie z. B.
Euklid, ∗um 300 v.Chr. Ausbildung vermutlich an der Platonischen Akademie in Athen, wirkte unter
Ptolemaios I am Museion in Alexandria. Hat in den „Elementen“ das mathematische Wissen seiner Zeit
umgeformt und in eine axiomatisch-deduktive Form gebracht. Die Elemente beeinflußten die Entwicklung
der Mathematik entscheidend, teilweise bis in die Gegenwart. Sie waren im christlichen Kulturkreis zeitweise das nach der Bibel meistgelesene Buch. Der euklidische Algorithmus – in seiner subtraktiven Form
– steht im 7. Buch, Propositionen 1 und 2. Von Euklid stammen außerdem Werke zur Optik („Katoptrik“)
und zu den Kegelschnitten („Konika“) sowie Arbeiten zur Planimetrie, Astronomie und zur Musiktheorie.
2
Eudoxos von Knidos, ∗408 v.Chr. in Knidos, †355 (?) in Athen. Vielseitiger griechischer Mathematiker,
Naturforscher und Philosoph. Proportionen- und Ähnlichkeitslehre sowie Lehre von den Kegelschnitten,
die in die Elemente und Konika Euklids eingegangen sind. Astronomische und geographische Arbeiten.
1
3
4
KAPITEL 1. EINFÜHRENDE BEISPIELE
'
Algorithmus E (Euklid)
Es ist der größte gemeinsame Teiler zweier positiver natürlicher Zahlen m und n zu
berechnen.
$
E1 [Restbildung] Es wird m durch n geteilt; der Rest sei r.
(Es gilt 0 ≤ r < n)
E2 [Ist Rest Null?] Wenn r = 0, endet der Algorithmus; n ist das Ergebnis.
&
E3 [Vertauschen] Setze m :← n und n :← r und gehe zu Schritt E1 zurück.
Tabelle 1.1: Euklidischer Algorithmus
%
bei Schritt E3) etwas anderes angegeben ist. In Abhängigkeit von einer Prüfung (einem
Test) können in einem Algorithmusschritt auch unterschiedliche Fortsetzungen bestimmt
werden. In Schritt E2 z. B. wird der Algorithmus beendet, wenn der Rest Null wird.
Andernfalls wird mit Schritt E3 fortgefahren.
Beispiel 1.1 Als Beispiel zum euklidischen Algorithmus soll der größte gemeinsame Teiler von 119 und 544 berechnet werden
Anfangswerte
m :← 119
n ← 544
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
119 = 0 · 544 + 119
r :← 119
r 6= 0
m :← 544 n :← 119
544 = 4 · 119 + 68
r :← 68
r 6= 0
m :← 119 n :← 68
119 = 1 · 68 + 51
r :← 51
r 6= 0
m :← 68 n :← 51
68 = 1 · 51 + 17
r :← 17
r 6= 0
m :← 51 n :← 17
51 = 3 · 17 + 0
r :← 0
r = 0; n = 17
(E1)
(E2)
(E3)
(E1)
(E2)
(E3)
(E1)
(E2)
(E3)
(E1)
(E2)
(E3)
(E1)
(E2)
1.1. DER EUKLIDISCHE ALGORITHMUS
ggt(119, 544) = 17.
5
23
Bei jedem Algorithmus muß man nachweisen, daß er auch wirklich das leistet, was er laut
Beschreibung leisten soll. Es soll gezeigt werden, daß bei korrekten Eingabedaten, d. h.
positiven natürlichen Zahlen
1. der Algorithmus hält und
2. das richtige Ergebnis berechnet.
Der euklidische Algorithmus hält. Für die Eingabewerte m und n ergibt sich der
folgenden Ablauf des euklidischen Algorithmus.

m1 = q1 · n1 + r1



Dabei gilt:

m2 = q2 · n2 + r2



..
 m1 = m und n1 = n
.
0 ≤ ri < ni (Rest ist kleiner als Divisor!)
mi = qi · ni + ri


 mi+1 = ni
mi+1 = qi+1 · ni+1 + ri+1 


 ni+1 = ri

..
.
Also ri = ni+1 > ri+1 für i = 1, 2, . . . . D. h. die ri bilden eine streng abnehmende Folge
natürlicher Zahlen. Jede streng abnehmende Folge natürlicher Zahlen ist endlich, hat also
eine letztes Element. Das letzte Element der Folge ri muß Null sein, weil der euklidische
Algorithmus vorher nicht hält.
Es gibt demnach ein erstes k mit rk = 0 und mk = qk · nk .
2
Der euklidische Algorithmus berechnet ggt(n, m).
mi = qi · ni + ri ⇒ jeder gemeinsame Teiler von ni und ri teilt mi
⇒ ggt(ni , ri ) ist gemeinsamer Teiler von mi und ni
⇒ ggt(ni , ri ) ≤ ggt(mi , ni )
ri = mi − qi · ni ⇒ jeder gemeinsame Teiler von mi und ni teilt ri
⇒ ggt(mi , ni ) ist gemeinsamer Teiler von ni und ri
⇒ ggt(mi , ni ) ≤ ggt(ni , ri )
Das heißt ggt(mi , ni ) = ggt(ni , ri ) = ggt(mi−1 , ni−1 ). Es ist also ggt(m, n) = ggt(mk , nk ).
2
Anmerkung 1.1 Man kann im Algorithmus von Tabelle 1.1 die ganzzahlige Division
(Schritt E1) durch eine fortgesetzte Subtraktion des Divisors vom Dividenden ersetzen,
bis das Ergebnis zum ersten Mal kleiner wird als der Divisor. Man spricht dann von der
subtraktiven Form des euklidischen Algorithmus.
2
3
Ende eines Beweises, eines Beispiels oder einer Anmerkung
6
KAPITEL 1. EINFÜHRENDE BEISPIELE
Anmerkung 1.2 Ist m = q · n + r mit q ≥ 2, so gilt m − (q − 1) · n = 1 · n + r. Nach der
ersten Division sind die Abläufe für (m, n) und (m − (q − 1) · n, n) identisch. Sie liefern
den gleichen größten gemeinsamen Teiler und benötigen die gleiche Anzahl von Schritten.
2
1.1.2
Ein Programm für den euklidischen Algorithmus
In der Informatik sind Programmierfertigkeiten unerläßlich und der Zusammenhang zwischen Algorithmen und Programmen von zentraler Bedeutung. Daher soll schon in diesem
Unterabschnitt ein Programm für den euklidischen Algorithmus vorgestellt werden. Es ist
als eine erste Einführung in die Programmiersprache C gedacht.
Doch zunächst die folgende Überlegung: Im Algorithmus von Tabelle 1.1 sind ganz allgemein m und n als Anfangswerte angegeben. In einem Programm muß jedoch konkretisiert
werden, wo diese Werte herkommen. Dafür gibt es verschiedene Möglichkeiten:
• Im Programm als feste Werte angegeben.
• Von einem anderen Stück des gleichen Programms.
• Von einem anderen, getrennt ablaufenden Programm.
• Aus einer Datei.
• Von einem menschlichen Benutzer am Dialoggerät.
Ebenso gibt es verschieden Möglichkeiten für die Entscheidung, was mit dem berechneten ggt(m, n) geschehen soll. Für das zu entwickelnde Programm soll festgelegt werden,
daß ein menschlicher Benutzer am Dialoggerät die Werte m und n über die Tastatur eingibt und den berechneten größten gemeinsamen Teiler auf dem Bildschirm als Ausgabe
erhält. Tabelle 1.2 zeigt, wie das durch eine einfache Erweiterung des ursprünglichen euklidischen Algorithmus erreicht werden kann. Ein graphische Darstellung des erweiterten
Algorithmus als Flußdiagramm ist auf Seite 156 zu finden.
Es soll nun ein C-Programm angegeben und untersucht werden, das den euklidischen Algorithmus mit Ein- und Ausgabe realisiert. Tabelle 1.3 zeigt das Programm. Die Zeichen
zwischen den Zeichenfolgen /* und */ sind Kommentare und dienen nur der besseren Lesbarkeit. Sie haben auf das Programm keinen Einfluß. Die erste wirksame Programmzeile
ist
#include<stdio.h>
Mit der Steueranweisung include wird der Inhalt einer Datei, in diesem Fall der Datei stdio.h, dem Programmtext hinzugefügt. Die Ergänzungen aus stdio.h werden gebraucht, weil im folgenden Anweisungen zur Ein- und Ausgabe benutzt werden.
Das eigentliche Programm beginnt mit der Anweisung main und ist in geschweifte Klammern eingeschlossen. Mit der Anweisung
1.1. DER EUKLIDISCHE ALGORITHMUS
'
Algorithmus EEA (Euklid mit Ein- und Ausgabe)
Es ist der größte gemeinsame Teiler zweier positiver natürlicher Zahlen m und
n zu berechnen.
7
$
EEA1 [Eingabe Anfangswerte] Lies die Anfangswerte von m und n.
(Eingabe und Wertzuweisung)
EEA2 [Restbildung] Es wird m durch n geteilt; der Rest sei r.
(Es gilt 0 ≤ r ≤ n)
EEA3 [Ist Rest Null?] Wenn r = 0, gib den Wert von n als größten gemeinsamen
Teiler aus; der Algorithmus endet.
&
EEA4 [Vertauschen] Setze m :← n und n :← r und gehe zu Schritt EEA2 zurück.
%
Tabelle 1.2: Euklidischer Algorithmus mit Ein- und Ausgabe
int
m, n, rest;
werden die Variablen m, n und rest definiert. Diese Variablen können ganzzahlige Werte
annehmen. Das Ende einer Anweisung wird durch ein Semikolon angegeben.
Es folgen die ausführbaren Anweisungen des Programms. Mit
printf("Bitte m eingeben: ");
wird der in Anführungsstriche angegebene Text auf dem Bildschirm ausgegeben. Die Anweisung
scanf("%d",&m);
bewirkt das Einlesen des ersten Wertes von der Tastatur. In Anführungsstrichen wird das
Format der Eingabe spezifiziert, %d bedeutet, daß die eingelesene Zeichenfolge als ganze
Zahl zu interpretieren ist. Mit dem zweiten Parameter – &m – wird festgelegt, daß die
Variable m diese Zahl als Wert erhält. In Abschnitt 2.8 wird erläutert, warum hier nicht
die Variable m direkt, sondern die Adresse &m dieser Variablen angegeben werden muß.
Es folgt eine if-Anweisung. Die Bedingung in runden Klammern wird geprüft und, falls
sie zutrifft, also in diesem Fall m einen nicht positiven Wert hat, werden die in geschweiften Klammern stehenden nachfolgenden Anweisungen ausgeführt. Sie bewirken, daß eine
Fehlermeldung auf dem Bildschirm erscheint und dann – mit exit(0) – das Programm
beendet wird. Die nächsten sieben Zeilen des Programms bewirken auf die gleiche Weise
die Eingabe und Prüfung des zweiten Wertes (n).
Im Rest des Programms ist der eigentliche euklidische Algorithmus enthalten. Mit der
Anweisung
8
KAPITEL 1. EINFÜHRENDE BEISPIELE
rest = m % n;
wird der Wert von m durch den Wert von n ganzzahlig geteilt und der Variablen rest
der Rest als Wert zugewiesen. Es folgt eine while-Anweisung. Die Bedingung in runden
Klammern – Rest ungleich Null – wird geprüft und solange sie wahr ist, werden die in
geschweiften Klammern stehenden nachfolgenden Anweisungen ausgeführt: m erhält den
gleichen Wert wie n, n erhält den aktuellen Rest als Wert und es wird ein neuer aktueller
Rest gebildet und in rest festgehalten. Diese Anweisungen bilden die while-Schleife. Sie
wird erst verlassen, wenn der Rest Null ist, also der Algorithmus endet.
Zum Schluß wird mit der Anweisung
printf("ggt(m,n) = %d\n",n)
der gefundene größte gemeinsame Teiler ausgegeben. Im Format, das auch hier wieder
zwischen den Anführungsstrichen steht, wird mit %d angegeben, daß an dieser Stelle ein
ganzahliger Wert ausgegeben werden soll. Der Rest des Formats wird so ausgegeben,
wie er im Programm steht. Mit ,n wird spezifiziert, daß der Wert der Variablen n die
auszugebende ganze Zahl ist.
Für weitere Einzelheiten des Programmierens in C wird auf Kapitel 2 „Programmieren I:
Sequentielle Programme“ verwiesen.
1.1. DER EUKLIDISCHE ALGORITHMUS
9
'
/***************************************************************/
/*
Programm EUKLID
*/
/*
*/
/*
Liest zwei natuerliche Zahlen ein, berechnet den
*/
/*
groessten gemeinsamen Teiler und gibt diesen aus.
*/
/***************************************************************/
#include <stdio.h>
$
main()
{
int
/*
/*
}
&
m, n, rest;
Eingabe und Prüfung der Ausgangswerte
printf("Bitte m eingeben: ");
scanf("%d", &m);
if (m <= 0)
{
printf(" m ist nicht positiv\n");
exit(0);
};
printf("Bitte n eingeben: ");
scanf("%d", &n);
if (n <= 0)
{
printf(" n ist nicht positiv\n");
exit(0);
};
Berechnung ggt und Ausgabe
rest = m % n;
/* 1. Divisionsrest berechnen
while (rest != 0)
{
m = n;
/* Vertauschen und
n = rest;
rest = m % n;
/* Rest bilden
};
printf("ggt(m,n) = %d\n", n);
/* Ausgabe Ergebnis
*/
*/
*/
*/
*/
*/
Tabelle 1.3: Programm EUKLID (Berechnung des größten gemeinsamen Teilers)
%
10
KAPITEL 1. EINFÜHRENDE BEISPIELE
1.1.3
Effizienz des euklidischen Algorithmus
Hier soll Effizienz gleichgesetzt werden mit Ausführungszeit. Andere Beurteilungskriterien
von Algorithmen wie Speicherbedarf, Verständlichkeit u. a. sollen nicht berücksichtigt
werden. Die Ausführungszeit wird nicht in Sekunden oder anderen Zeiteinheiten gemessen,
sondern durch die Anzahl Divisonsoperationen in Abhängigkeit von m und n. Es sei
d(m, n) := Anzahl Divisionsoperationen bei der Berechnung von
ggt(m, n) mittels Algorithmus E
d(m, n) gibt auch die Anahl Schleifendurchläufe des Algorithmus an und wir fassen einen
Schleifendurchlauf als einen Berechnungsschritt konstanten und von m und n unabhängigen Aufwands auf.
Für das folgende nehmen wir m, n ∈ N+ an.
BEC-Analyse (Best Case)
Welches ist der bestmögliche Fall? Für alle m, n gilt
d(m, n) = 1 ⇔ m = a · n mit a ∈ N
d. h.
1. Für alle m und alle n gilt 1 ≤ d(m, n).
2. Für jedes m und alle n, die Teiler von m sind, gilt d(m, n) = 1.
(1.1)
WOC-Analyse (Worst Case)
Welches ist der schlechtestmögliche Fall?
Es seien m und n positive natürliche Zahlen. Ist m < n, so führt der erste Schleifendurchlauf zur Vertauschung von m und n. Es soll daher m ≥ n angenommen werden und der
euklidische Algorithmus benötige k Divisionen, d. h. d(m, n) = k.
Mit m1 = m und n1 = n ergeben sich die k Schritte, die zur Bestimmung von ggt(m, n) (m ≥
n) nötig sind, folgendermaßen:
m1
m2
= q1 · n1 + r1
= q2 · n2 + r2
..
.
mi
= qi · ni + ri
mi+1 = qi+1 · ni+1 + ri+1
..
.
mk
= qk · nk + rk












ni > ri



mi+1 = ni
dabei gilt
ni+1 = ri







rk = 0






für i = 1, . . . , k − 1
für i = 1, . . . , k − 1
1.1. DER EUKLIDISCHE ALGORITHMUS
11
Zu gegebenem k wollen wir Zahlen mi(k) und ni(k) so bestimmen, daß für alle (m, n) mit
d(m, n) = k gilt mi(k) ≤ m und ni(k) ≤ n, also
(mi(k), ni(k)) := min{(m, n) | d(m, n) = k} für k = 1, 2, 3, . . . .
Man mache sich klar, daß die hier benutzte komponentenweise Ordnung der Paare natürlicher Zahlen eine partielle Ordnung ist (siehe Seite 616) und demzufolge das gewünschte
Minimum möglicherweise gar nicht existiert! Wenn es jedoch existiert, ist es eindeutig
(warum?). Wenn die Zahlen mi(k) und ni(k) existieren, muß nach Anmerkung 1.2 außerdem gelten mi(k) = 1 · ni(k) + ri(k) mit ri(k) < ni(k).
Im folgenden wird gezeigt, daß die gewünschten Zahlen mi(k) und ni(k) existieren und
mit einer Rekursionsformel berechnet werden können. Dazu werden die Fälle k = 1 und
k ≥ 2 unterschieden.
Fall k = 1: Offenbar ist 1 = 1 · 1 + 0, also mi(1) = ni(1) = 1.
Fall k ≥ 2:
Satz 1.1 Für k = 2, 3, . . . existieren mi(k) und ni(k) und sind gegeben durch
1.
2.
mi(2) = 3 und ni(2) = 2 sowie
mi(k + 1) = mi(k) + ni(k) und ni(k + 1) = ni(k) + ri(k) .
(1.2)
(1.3)
Beweis: Durch vollständige Induktion über k.
Induktionsanfang: Es muß gelten r1 > r2 = 0 und n1 > r1 ≥ 1. Die kleinstmöglichen
Werte sind r1 = 1 und n1 = 2, und es ergibt sich mi(2) = 3 und ni(2) = 2.
Induktionsschritt (Schluß von k auf k + 1): Unter der Annahme, daß für κ = 2, 3, . . . , k
mi(κ) und ni(κ) existieren und mi(κ) > ni(κ) gilt, existieren auch mi(k +1) und ni(k +1)
und es gilt
mi(k + 1) = mi(k) + ni(k) und ni(k + 1) = ni(k) + ri(k)
(1.4)
In der Tat: Es sei d(m, n) = k + 1 und m = q · n + r. Dann ist d(n, r) = k, also n ≥ mi(k)
und r ≥ ni(k). D. h. m ≥ 1 · n + r ≥ mi(k) + ni(k) und n ≥ mi(k) = ni(k) + ri(k).
Außerdem ist d(mi(k + 1), ni(k + 1)) = d(mi(k) + ni(k), ni(k) + ri(k)) = d(mi(k) +
ni(k), mi(k)) = 1 + d(mi(k), ni(k)) = 1 + k.
2
Zusammenhang mit Fibonacci-Zahlen:
Die Fibonacci-Zahlen4 sind definiert durch
F0 := 0, F1 := 1 und Fk := Fk−1 + Fk−2 für 2 ≤ k .
(1.5)
Fibonacci, Leonardo (Leonardo von Pisa, Leonardo Pisano) ∗um 1170 in Pisa, †1240 ebd. Italienischer
Mathematiker. Erster bedeutender Mathematiker des (mittelalterlichen) Abendlandes. Lernte auf Sizilien
sowie auf Reisen nach Afrika, Byzanz und Syrien die indischen und arabischen Rechenmethoden kennen,
die er weiterentwickelte und in dem umfangreichen Rechenbuch „Liber abbaci“ (1202) beschrieb. Kleinere
Schriften behandeln geometrische und zahlentheoretische Fragen. Die Fibonacci-Zahlen werden von ihm
im Liber abbaci bei einer Aufgabe zur Vermehrung von Hasen eingeführt.
4
12
KAPITEL 1. EINFÜHRENDE BEISPIELE
Die Fibonacci-Zahlen sind ein Beispiel für eine rekursiv definierte Zahlenfolge: Es gibt
Anfangswerte und eine Rekursionsvorschrift (Rekursionsformel), mit der ein Glied der
Folge aus den Werten der vorangehenden Folgenglieder berechnet werden kann. Rekursive Definitionen beruhen auf dem gleichen Grundprinzip wie Beweise durch vollständige
Induktion (siehe Beweis zu Satz 1.1) und werden daher manchmal auch induktive Definitionen genannt.
Zur Berechnung der Werte einer rekursiv definierten Zahlenfolge und auch für theoretische
Untersuchungen, ist es natürlich sehr wünschenswert, die Folgenglieder auch durch einen
geschlossenen Ausdruck darstellen zu können. Für die Fibonacci-Zahlen ist das möglich,
und in Gleichung 1.6 ist eine geschlossene Darstellung angegeben. Die Ungleichung 1.7
hängt eng damit zusammen und liefert eine wichtige Abschätzung.
√
1
+
5
1
b i ) mit Φ :=
b := 1 − Φ
und Φ
(1.6)
Fi = √ · (Φi − Φ
2
5
√
i Φ
5
1
+
Fi − √ < 1 mit Φ =
≈ 1.618
2
5
(1.7)
Zum Beweis und zu weiteren Einzelheiten über Fibonancci-Zahlen siehe Knuth [Knut1997].
Tabelle 1.4 zeigt die exakten und die Näherungswerte der Fibonacci-Zahlen bis 30.
Nun ist aber ni(2) = 2, ni(3) = ni(2) + ri(2) = 3 und ni(k) = ni(k − 1) + ri(k − 1) =
ni(k − 1) + ni(k − 2) für 4 ≤ k. Daraus und aus mi(k) = ni(k + 1) sowie ri(k) = ni(k − 1)
folgt für k = 2, 3, . . .
#

ri(k) = Fk 
ni(k) = Fk+1
(1.8)

mi(k) = Fk+2
"
!
Was bedeutet dieser Zusammenhang für die WOC-Analyse des euklidischen Algorithmus?
Zur Vereinfachung soll für das folgende m ≥ n ≥ 2 angenommen werden. Zu jedem m
existiert ein eindeutig bestimmtes k mit mi(k) ≤ m < mi(k+1) und es gilt d(m, n) < k+1,
also d(m, n) ≤ k.
Aus mi(k) ≤ m folgt nach Gleichung 1.8 Fk+2 ≤ mi(k) und nach Ungleichung 1.7
Φk+2
√ + α ≤ m mit |α| < 1 .
5
das heißt
Φk+2 <
√
5 · (m + 1) .
1.1. DER EUKLIDISCHE ALGORITHMUS
F0
F1
F2
F3
F4
F5
F6
F7
F8
F9
F10
F11
F12
F13
F14
F15
F16
F17
F18
F19
F20
F21
F22
F23
F24
F25
F26
F27
F28
F29
F30
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
0
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
17711
28657
46368
75025
121393
196418
317811
514229
832040
13
≈
≈
≈
≈
≈
≈
≈
≈
≈
≈
≈
≈
≈
≈
≈
≈
≈
≈
≈
≈
≈
≈
≈
≈
≈
≈
≈
≈
≈
≈
≈
0.447
0.724
1.171
1.894
3.065
4.960
8.025
12.985
21.010
33.994
55.004
88.998
144.001
232.999
377.001
610.000
987.000
1597.000
2584.000
4181.000
6765.001
10946.002
17711.004
28657.006
46368.012
75025.016
121393.031
196418.047
317811.094
514229.156
832040.250
Tabelle 1.4: Fibonacci-Zahlen und Näherungswerte bis 30
Also5
d(m, n) ≤ k <
ln
√
5 · (m + 1)
− 2.
ln Φ
(1.9)
Die Anzahl Schritte, die der euklidische Algorithmus zur Berechnung des größten gemein5
Zu Bezeichnungen und Eigenschaften von Logarithmen siehe Abschnitt C.1, Seite 629. im Anhang.
14
KAPITEL 1. EINFÜHRENDE BEISPIELE
samen Teilers von m und n (n < m) braucht, ist stets durch eine Größe, die nicht schneller
wächst als der Logarithmus von m, beschränkt. Mit der in Unterabschnitt 6.1.5, Seite 195,
einzuführenden Notation läßt sich das als d(m, n) = O(ln m)) schreiben.
Wenn n langsamer wächst als m – stets unter der Annahme n < m – ist die Anzahl
Schritte O(ln n), denn analog zu 1.9 läßt sich zeigen
√
ln 5 · (n + 1)
0
d(m, n) ≤ k <
− 1.
(1.10)
ln Φ
AVC-Analyse (Average Case)
Es soll untersucht werden, wie das Verhalten des Euklidischen Algorithmus im Mittel ist.
Um die Fragestellung zu erläutern und zu präzisieren, werde die Tabelle 1.5 betrachtet.
Die Zeilen geben die Werte für m = 1, 2, . . . 15 und die Spalten die Werte für n (n < m)
an. Die Tabellenelemente zeigen die Schrittanzahl zur Berechnung von ggt(m, n). Man
sieht, daß d(13, 8) = 5 und d(m, n) < 5 in allen anderen Fällen.
Was kann man als den Mittelwert der Werte in der Tabelle 1.5 ansehen? Es ist üblich, die
Summe der Werte geteilt durch die Anzahl Werte als Mittelwert zu nehmen
AV C(15) =
15 P
m
P
d(m, n)
m=1 n=1
120
≈ 2.496774 .
Um zu allgemeinen Aussagen über den Mittelwert zu kommen, müßte man das Verhalten von AV C(m) in Abhängigkeit von m untersuchen. Das soll hier nicht geschehen.
Statt dessen sollen für Werte m = 1, 2, . . . , 100 die Größen BEC(m), W OC(m) und
AV C(m) numerisch berechnet werden. Das Ergebnis ist in Abbildung 1.1 zu sehen. Wie
schon hergeleitet, ist BEC(m) = 1 und W OC(m) eine Sprungfunktion mit logarithmischem Wachstum. Die Kurve für AV C(m) läßt logarithmisches Wachstum vermuten6 .
Wie in der vergrößerten Darstellung von Abbildung 1.2 zu erkennen, wächst AV C(m)
nicht monoton, sondern oszilliert. Die Oszillationen werden allerdings rasch geglättet, wie
die Funktionswerte für den Bereich m = 101, . . . , 200 zeigen.
6
Für einen etwas anders definierten Mittelwert, nämlich
Tm =
1
· [d(m, 1) + d(m, 2) + · · · + d(m, m)] ,
m
ist logarithmisches Wachstum bekannt [Knut1998]. Tm ist der Mittelwert einer Zeile in der Matrix.
1.1. DER EUKLIDISCHE ALGORITHMUS
1
2
3
4
5
6
7
8
15
9
10
11
12
1
1
2
1
1
3
1
2
1
4
1
1
2
1
5
1
2
3
2
1
6
1
1
1
2
2
1
7
1
2
2
3
3
2
1
8
1
1
3
1
4
2
2
1
9
1
2
1
2
3
2
3
2
1
10
1
1
2
2
1
3
3
2
2
1
11
1
2
3
3
2
3
4
4
3
2
1
12
1
1
1
1
3
1
4
2
2
2
2
1
13
1
2
2
2
4
2
3
14
1
1
3
2
3
2
1
15
1
2
1
3
1
2
2
13
14
5
3
3
3
2
1
3
4
3
4
2
2
1
3
3
2
4
2
3
2
Tabelle 1.5: Schrittanzahl beim euklidischen Algorithmus
15
1
16
k
10
9
8
7
6
5
4
3
2
1
0
KAPITEL 1. EINFÜHRENDE BEISPIELE
..
........
..........
...
....
..
...
...
...
...
...
...
...
...
...
...
...
...
...
....
..
...
...
....
..
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
....
..
...
...
....
..
...
...
...
...
...
...
...
...
...
...
............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
......
k = W OC(m)
+++++++++++
++++++++++++++++++++++++++++++++++
+++++++++++++++++++++
+++++++++++++
++++++++
k = AV C(m)
+++++
+++
·· · · ··
· · ·· · ·
·· ·· · ··· · ·
··
··· · ·· ·· ·· ·· ·· ··
·· ··· · ·· ·· ·· ··
··· ··· ·· ··· · ··· ··· ····
·· ··· ···
++
·· · · · ·
k = BEC(m)
···
·
··
·
·
··
+
+
1
10
20
30
40
050
060
070
080
Abbildung 1.1: BEC, WOC und AVC in Abhängigkeit von m
090
m
100
1.1. DER EUKLIDISCHE ALGORITHMUS
17
AV C(m)
5
4
..
.......
..........
...
...
..
....
..
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
....
..
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
....
..
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
....
..
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
..
............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
..
·· ··· · ·· ··
3
2
1
0
··
··
··
·
·
··
··
··
· ·· · · ·· ···
·· · ·· · ··· · ·
···
1
10
101 110
·
··
·· · ·
·
·· · ·
20
120
···
·
··· ·· ·· · · ··· ··
·
· · · ··
30
130
· ·· ··
· ··
·· ·
····
40
140
· ··· ·· · ··· · ·
·· ·· ·· · · ··· · ·· ·
· · · ··
050
150
·· ·
· · · ··
· ·· ·
060
160
· · ·· ·· ·
·
070
170
Abbildung 1.2: AVC in Abhängigkeit von m
· ·· ·· ·· ·· ·· ·· ·· ·
·· · · ··
· · ·· ·· · ·
080
180
··· ·· ··
··· · · · ·· ·
090
190
·· ·
m
100
200
18
KAPITEL 1. EINFÜHRENDE BEISPIELE
1.2
Sortieren durch Einfügen
1.2.1
Allgemeines zum Sortieren
Sortieren: Die Elemente einer endlichen, nichtleeren Menge, der Sortiermenge, sollen der Reihe nach angeordnet werden!
Sortieren bedeutet nicht: Nach Sorten einteilen.
Die Elemente der Sortiermenge besitzen einen Sortierwert. Sie sind nach diesem Sortierwert anzuordnen, zu sortieren. Der Wertebereich, aus dem die Sortierwerte sind, bildet
das Sortierkriterium, auch Sortierwertemenge genannt.
Damit man sortieren kann, muß auf der Sortierwertemenge eine lineare Ordnung (siehe
Anhang A „Mengenlehre“, Abschnitt A.5) definiert sein. Man kann nach dieser Ordnung
aufsteigend oder absteigend sortieren. Der Sortierwert eines Elementes der Sortiermenge
muß nicht eindeutig sein, d. h. unterschiedliche Elemente der zu sortierenden Menge
dürfen den gleichen Sortierwert haben.
Die wichtigsten Beispiele für Sortierwertemengen sind:
• Ganze Zahlen
• Reelle Zahlen
• Wörter mit lexikographischer Ordnung (siehe Seite 114)
Unter einem Sortierverfahren versteht man einen Algorithmus oder ein Programm, mit
dem eine Menge sortiert werden kann.
1.2.2
Insertion Sort: Algorithmus und Programm
Es gibt eine große Anzahl von Sortieralgorithmen. Ein naheliegendes Verfahren ist „Sortieren durch Einfügen“. Es ist mit dem Einordnen der Karten eines Spielers beim Kartenspiel
zu vergleichen. Abbildung 1.3 zeigt ein Beispiel für die Arbeitsweise des Verfahrens. Die
zu sortierende Menge ist {47, 10, 50, 48, 35, 3} und es wird angenommen, daß sie anfangs
im Speicher in der Reihenfolge 47, 10, 50, 48, 35, 3 steht (1. Zeile, linke Spalte in Abbildung 1.3). Es wird ein sortiertes Anfangsstück der Länge 1 gebildet (1. Zeile, rechte
Spalte, kursiv geschriebenes Anfangsstück). In den weiteren Schritten wird jeweils das
Element, das unmittelbar hinter dem sortierten Anfangsstück steht, so weit nach links
in das Anfangsstück heineingeschoben, bis es den Platz erreicht hat, in den es eingefügt
werden muß. Die größeren Werte des Anfangsstücks werden nach rechts verschoben und
das sortierte Anfangsstück so um eine Stelle verlängert. Der Algorithmus endet, wenn das
Anfangsstück die ganze Sortiermenge umfaßt.
In Tabelle 1.6 ist der Algorithmus IS für das Sortieren durch Einfügen angegeben. Er ist in
1.2. SORTIEREN DURCH EINFÜGEN
47 10 50 48 35
19
3
47 10 50 48 35
3
3
10 47 50 48 35
3
48 35
3
10 47 50 48 35
3
35
3
10 47 48 50 35
3
3
10 35 47 48 50
3
10
50 48 35
47
50
10 47
48
10 47 50
35
10 47 48 50
3
10 35 47 48 50
3
10 35 47 48 50
Abbildung 1.3: Beispiel für Sortieren durch Einfügen
anderer Form als der euklidische Algorithmus (Tabelle 1.1) formuliert, und zwar in einem
an C anglehnten Pseudocode. Die in Zeile 1 aufgeführte for-Schleife wird für die Werte
n = 2, 3, . . . , m ausgeführt. Sie besteht aus den nachfolgenden, in geschweifte Klammern
eingeschlossenen Anweisungen.
Der Algorithmus ist als vollständiges C-Programm INSERTSORT in den Tabellen 1.7 und
1.8 zu sehen. Die zu sortierenden Werte werden in die Reihung sortierfeld eingelesen,
darin durch Einfügen sortiert und zum Schluß ausgegeben. Eine graphische Darstellung
des Programms als Struktogramm ist auf Seite 158 zu finden.
1.2.3
Effizienz von Sortieren durch Einfügen
Was soll den Aufwand des Sortierens durch Einfügen messen? Im Algorithmus IS (Tabelle
1.6) sind eine äußere Schleife (for-Schleife) und eine innere Schleife (while-Schleife) zu
erkennen. Die äußere Schleife wird immer m − 1 Mal durchlaufen. Das gilt auch für den
Fall m = 1, in dem sie gar nicht durchlaufen wird! Der Aufwand für einen Durchlauf
20
KAPITEL 1. EINFÜHRENDE BEISPIELE
'
Algorithmus IS (InsertionSort)
Die Werte in den Plätzen P [1], P [2], . . . , P [m] werden durch Einfügen sortiert. Am
Ende stehen die Werte in den Plätzen in aufsteigender Reihenfolge.
&
1 for (n = 2; n ≤ m; n = n + 1)
2
{
3
j = n − 1;
4
aktuellerwert = P [n];
5
while (j ≥ 1 ∧ P [j] > aktuellerwert)
6
{
7
P [j + 1] = P [j];
8
j = j − 1;
9
};
10
P [j + 1] = aktuellerwert;
11
};
%
Tabelle 1.6: Algorithmus Sortieren durch Einfügen
der äußeren Schleife besteht aus einem konstanten Teil und dem Aufwand für die innere
Schleife.
Die innere Schleife führt bei jedem Durchlauf der äußeren Schleife zu mindestens einem
und höchsten n − 1 Tests (Zeile 5). Die Zahl der Vertauschungen (Zeile 7) und der Subtraktionen (Zeile 8) ist um 1 geringer als die Zahl der Tests.
Es ist demnach sinnvoll, den Aufwand des Algorithmus IS durch die Anzahl T (m) der
Tests für die innere Schleifen bei m zu sortierenden Werten zu messen.
BEC-Analyse: Für jeden Durchlauf der äußeren Schleife wird der Test der inneren
Schleife immer nur einmal ausgeführt. Das ist genau dann der Fall, wenn die P [1], P [2], . . . ,
P [m] von Anfang an aufsteigend sortiert sind.
(1.11)
T (m) = m − 1
WOC-Analyse: Im schlechtesten Fall wird der Test der inneren Schleife immer n − 1
Mal ausgeführt. Das ist genau dann der Fall, wenn jedes neue Element, das in das sortierte
Anfangsstück eingefügt werden soll, ganz an den Anfang geschoben werden muß, also
kleiner als alle Werte des sortierten Anfansgstücks ist. Das ist also genau dann der Fall,
wenn die P [1], P [2], . . . , P [m] (2 ≤ m) von Anfang an absteigend sortiert sind.
T (m) =
m
X
n=2
(n − 1) =
m−1
X
n=1
n
$
1.2. SORTIEREN DURCH EINFÜGEN
T (m) =
21
m · (m − 1)
2
(1.12)
AVC-Analyse: Es wird nur eine Plausibilitätsbetrachtung durchgeführt: Bei „zufälliger“ Sortierreihenfolge der P [1], . . . , P [m] am Anfang muß jeder Wert mit der Hälfte der
vor ihm stehenden und schon sortierten (dem sortierten Anfangsstück) verglichen werden.
Dann ergibt sich
m
m−1
X
n−1 X n
T (m) =
=
2
2
n=2
n=1
T (m) =
m · (m − 1)
4
(1.13)
Auch der Durchschnittswert der Sortierzeit wächst quadratisch mit der Anzahl der Sortierelemente.
Beispiel 1.2 Abbildung 1.4 zeigt einen Vergleich der Größen BEC(m), W OC(m) und
AV C(m) für das Programm INSERTSORT bei 100 zu sortierenden Elementen.
2
22
KAPITEL 1. EINFÜHRENDE BEISPIELE
'
/***************************************************************/
/*
Programm INSERTSORT
*/
/*
*/
/*
Liest bis zu 10000 natuerliche Zahlen ein und gibt sie */
/*
in aufsteigend sortierter Reihenfolge aus.
*/
/*
Die erste negative ganze Zahl beendet die Eingabe
*/
/*
Zum Sortieren wird "Sortieren durch Einfuegen"
*/
/*
benutzt.
*/
/***************************************************************/
#include <stdio.h>
main()
{
int
int
sortierfeld[10001];
m, n, j, k;
/*
Setzen Anfangswerte
for (m=0; m< 10000; m=m+1)
{
sortierfeld[m] = 0;
};
*/
/*
/*
Eingabe Sortierwerte
printf("Bitte Sortierwert eingeben: "); */
scanf("%d", &k);
sortierfeld[0] = k;
if (sortierfeld[0] < 0)
{
printf("Keine Sortierung\n");
exit(0);
};
m = 0;
while (sortierfeld[m] >= 0 && m < 10000)
{
m = m+1;
printf("Bitte Sortierwert eingeben: ");
scanf("%d", &k);
sortierfeld[m]=k;
};
*/
/*
&
$
*/
Tabelle 1.7: Programm INSERTSORT (Sortieren durch Einfügen) – Teil 1
%
1.2. SORTIEREN DURCH EINFÜGEN
'
/*
Sortieren durch Einfuegen
m = m-1;
for (n=1; n<=m; n=n+1)
{
j = n-1;
k = sortierfeld[n];
while (j >= 0 && sortierfeld[j] > k)
{
sortierfeld[j+1] = sortierfeld[j];
j = j-1;
};
sortierfeld[j+1] = k;
};
Ausgabe
for (n=0; n<=m; n=n+1)
{
printf("%4d\n", sortierfeld[n]);
};
/*
23
*/
$
*/
&
}
Tabelle 1.8: Programm INSERTSORT (Sortieren durch Einfügen) – Teil 2
%
24
T (m)
5000
4800
4600
4400
4200
4000
3800
3600
3400
3200
3000
2800
2600
2400
2200
2000
1800
1600
1400
1200
1000
800
600
400
200
0
KAPITEL 1. EINFÜHRENDE BEISPIELE
T (m) = W OC(m)
...
.......
.........
....
...
...
...
...
...
...
...
...
...
...
...
....
..
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
....
..
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
....
..
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
....
..
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
....
..
...
...
...
...
...
...
...
...
.
..............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
T (m) = AV C(m)
+
+
+
+
··
+
·
+
·
+
··
+
·
+
·
+
··
+
·
+
··
+
·
+
·
+
··
+
·
·
++
··
·
+
·
++
··
·
+
·
··
++
·
+
··
++
··
+
·
·
++
··
·
+
·
+
·
··
++
·
+
·
·
++
···
·
++
·
+
··
+
···
++
·
+
·
+
··
+++
····
+
·
T (m) = BEC(m)
·
+
·
+++ · · · · · · ·
+
+
+
···
++
+
+
+
· · · · · · m
+
·
+
·
+
·
·
+
·
+
·
+
··
+
··
+
···
····
++++
+
1
10
20
30
40
050
060
070
080
090
100
Abbildung 1.4: BEC, WOC, AVC für Sortieren durch Einfügen
1.3. EIN KOCHREZEPT
1.3
25
Ein Kochrezept
Die bisher behandelten Beispiele betrafen klare sequentielle Algorithmen ohne parallele
Abläufe und ohne zeitliche Nebenbedingungen. Das folgende Beispiel eines Kochrezepts
zeigt, daß diese Bedingungen nicht immer gegeben sind. Das Rezept lautet:
Zutaten: 3 l kalte MilĚ, 1 PŁĘĚen Puddingpulver, zwei gut gehŁufte EğlŽĎel ZuĘer, 1 Ei.
4
Zubereitung: Man nehme 6 EğlŽĎel von der MilĚ und verquirle damit daŊ Puddingpulver, den ZuĘer
und daŊ Eigelb deŊ EiŊ. Man bringe die MilĚ zum KoĚen, gebe dann daŊ verquirlte Puddingpulver
hinzu und laĄe alleŊ unter R§hren kurz aufkoĚen. DaŊ zu Ćeifem SĚnee gesĚlagene Eiweiğ wird
sofort naĚ dem KoĚen unter die Speise gehoben und in die GlŁser gef§llt.7
In Tabelle 1.9 ist das Rezept als Algorithmus nach dem Muster des euklidischen Algorithmus auf Seite 4 formuliert. Dabei fallen verschiedene Dinge auf. Zunächst einmal wird
deutlich, daß in der ursprünglichen Rezeptbeschreibung ein hohes Maß an implizitem
Wissen steckt. Ein Roboter könnte Schritt CP7 des Algorithmus so verstehen, daß umzurühren ist, aber möglicherweise nicht wissen, daß die Kochplatte einzuschalten ist. In
diesem Fall ist der Algorithmus zu verfeinern und die einzelnen Schritte sind in detailliertere Anweisungen aufzuteilen. Auf diese schrittweise Verfeinerung wird in Kapitel 5,
Unterabschnitt 5.1.2, näher eingegangen.
Weiter ist in dem Algorithmus nicht zu erkennen, daß Zeitbedingungen einzuhalten sind.
So muß z. B. Schritt CP6 unmittelbar auf Schritt CP5 folgen, da sonst die Milch wieder
kalt wird.
Schließlich ist es möglich, bestimmte Schritte gleichzeitig auszuführen. Z. B. kann man
die Milch zum Kochen aufsetzen, wenn man die Verquirl-Milch abgenommen hat, und,
während sie aufkocht, das Ei trennen. Diese mögliche Gleichzeitigkeit – in der Informatik
spricht man von Parallelität – wird durchaus beim Kochen ausgenutzt.
Abbildung 1.5 zeigt, wie der Algorithmus CP „Cremepudding“ zu ergänzen ist, wenn
Zeitbedingungen und Parallelitätseigenschaften berücksichtigt werden sollen. In der Abbildung sind die Schritte CP1 - CP9 angegeben und durch Pfeile verbunden. Die Bedeutung
der Pfeile ist: Die Aktion am Pfeilanfang muß beendet sein, bevor die Aktion am Pfeilende
begonnen werden darf. Diese Relation zwischen Aktionen ist transitiv, z. B. darf CP8 nicht
begonnen werden, ehe CP3 beendet ist. Parallel dürfen die Aktionen ausgeführt werden,
von denen keine ein (direkter oder indirekter) Vorgänger der anderen ist, z. B. Milch
aufkochen (CP5), Verquirlen (CP3) und Eischnee schlagen (CP4).
Da Frakturschrift nicht mehr als allgemein bekannt vorausgesetzt werden kann, hier die Transskription:
Zutaten: 3 l kalte Milch, 1 Päckchen Puddingpulver, zwei gut gehäufte Eßlöffel Zucker, 1 Ei.
7
4
Zubereitung: Man nehme 6 Eßlöffel von der Milch und verquirle damit das Puddingpulver, den Zucker
und das Eigelb des Eis. Man bringe die Milch zum Kochen, gebe dann das verquirlte Puddingpulver hinzu
und lasse alles unter Rühren kurz aufkochen. Das zu steifem Schnee geschlagene Eiweiß wird sofort nach
dem Kochen unter die Speise gehoben und in die Gläser gefüllt.
26
'
KAPITEL 1. EINFÜHRENDE BEISPIELE
Algorithmus CP (Cremepudding)
Aus 34 l kalter Milch, einem Päckchen Puddingpulver, 2 gut gehäuften Eßlöffeln
Zucker und einem Ei soll ein Cremepudding für 4 Personen zubereitet werden.
$
CP1 [Verquirl-Milch] Es sind 6 Eßlöffel von der Milch abzunehmen.
CP2 [Ei trennen] Ei in Eigelb und Eiweiß trennen.
CP3 [Verquirlen] Die 6 Eßlöffel Milch (CP1), das Puddingpulver, den Zucker und
das Eigelb (CP2) verquirlen.
CP4 [Eischnee] Das Eiweiß (CP2) zu steifem Schnee schlagen.
CP5 [Milch aufkochen] Die restliche Milch auf den Herd stellen und dort stehen
lassen, bis sie kocht.
CP6 [Puddingpulver zur Milch] Das verquirlte Puddingpulver (CP3) in Milch (CP5)
geben.
CP7 [Mischung aufkochen] Die mit dem Puddingpulver vermischte Milch (CP6)
solange umrühren, bis sie gerade aufkocht.
CP8 [Eiweiß hinzufügen] Das geschlagene Eiweiß (CP4) sofort unter die Speise
(CP7) heben.
CP9 [Abfüllen] Den fertigen Cremepudding (CP8) in Gläser geben. Der Algorithums
&endet.
Tabelle 1.9: Algorithmus Cremepudding
%
Eine Darstellung wie die von Abbildung 1.5 heißt Präzedenzgraph (precedence graph). Es
ist ein gerichteter Graph (Digraph). In diesem Digraphen darf es keine Kreise geben, da
es sonst Aktionen gäbe, die beendet sein müssen, bevor sie begonnen werden dürfen.
Daß einige Aktionen unmittelbar aufeinanderfolgen müssen, ist im Präzedenzgraphen der
Abbildung 1.5 durch die Angabe R! gekennzeichnet, die an einigen Pfeilen steht. R! steht
für „Realzeitbedingung“. Realzeitbedingungen dieser und anderer Art spielen in der Informatik und ihren Anwendungen eine wichtige Rolle, werden aber in diesem Buch nicht
weiter behandelt.
1.3. EIN KOCHREZEPT
27
Algorithmus PCP [Paralleler Cremepudding]
Aus 34 l kalter Milch, einem Päckchen Puddingpulver, 2 gut gehäuften Eßlöffeln Zucker
und einem Ei soll ein Cremepudding für 4 Personen zubereitet werden.
PCP1 [CP mit Reihenfolgeangaben] Man führe die in Algorithmus CP angegebenen
Schritte nach dem folgenden Zeitdiagramm aus:
CP2 [Ei trennen]
CP1 [Verquirl-Milch]
... ..............
..
...............
.......
...
..............
.......
...
...............
.......
...............
.......
...
.
.
.
.
.
.
.
.
.
...............
.
...
...............
.......
...
...............
........
..............
.......
...
...............
.......
...
...............
........
.
.
.
.
.
.
...
...............
.......
...............
.......
....... ..
..............
............... ...
. ........
........
.................. ......................
...
........... ..
CP3 [Verquirlen]
CP5 [Milch aufkochen]
...
............
...
..............
...............
...
...............
...
..............
.
.
.
.
.
.
.
.
.
.
.
.
.
...
....
...............
...
...............
...
..............
...............
...
...............
.
.
.
.
.
.
...
.
.
.
.
.
.
.
.
...
...............
..........
...............
.......
.
...............
... ..................................
..
R!
CP6 [Puddingpulver zur Milch]
...
...
...
...
...
...
...
...
...
.........
.......
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
..
..........
.......
...
CP4 [Eischnee]
......
......
.....
......
.....
.
.
.
.
.....
.....
......
......
.....
.
.
.
.
.
......
......
......
......
.....
.
.
.
.
.....
.....
......
......
.....
.
.
.
.
.
.
......
.....
..........
.....
..........
......
..........
.....
.
.
..........
.
.
..........
.....
..........
.....
..........
......
..........
.....
..........
......
.
.
..........
.
.
.
..........
.....
..........
......
..........
......
..........
.....
.......... ..
.............
.
...............
.
.
.
.
.
.
.
............ .......
R!
CP7 [Mischung aufkochen]
R!
CP8 [Eiweiß hinzufügen]
...
..
...
...
...
...
...
...
...
.
..........
.......
...
CP9 [Abfüllen]
Abbildung 1.5: Algorithmus Paralleler Cremepudding
Aufgaben
Aufgabe 1.1 Erweitern und modifizieren Sie das Programm EUKLID (Tabelle 1.3, Seite 9)
so, daß es am Schluß außer ggt(m, n) auch d(m, n) ausgibt und zusätzlich für alle gül-
28
KAPITEL 1. EINFÜHRENDE BEISPIELE
tigen Eingaben d(m, n) = d(n, m) gilt. d(m, n) ist die Anzahl Divisionsoperationen des
Programmlaufs.
Literatur
Literatur zum Thema „Sortieren“ ist in Abschnitt 11.5 angegeben.
Zum euklidischen Algorithmus siehe Cormen/Leiserson/Rivest [CormLR1990], Abschnitt
33.2; Knuth [Knut1997], Abschnitt 1.1, und Knuth [Knut1998], Abschnitte 4.5.2 und 4.5.3
(mit interessanten historischen Anmerkungen); Weiss [Weis1995], Abschnitt 2.4 .
Der euklidische Algorithmus ist der Einstieg in das reizvolle und schwierige Gebiet der
Algorithmen für ganzzahlige Probleme. Einführende Betrachtungen zu Primzahlen und
zur Bestimmung ganzzahliger Nullstellen (diophantische Gleichungen) findet man bei Kowalk ([Kowa1996], S. 549-587). Ausführlicher wird das Gebiet in Cormen/Leiserson/Rivest
[CormLR1990], Kapitel 33, behandelt.
Kapitel 2
Programmieren I: Sequentielle
Programme
2.1
Schichtenaufbau eines Rechensystems
Unter einem Rechensystem soll die Gesamtheit des Rechners mit seinen verschiedenen
elektronischen, elektrischen und mechanischen Komponenten und Geräten (die Hardware)
und seinen Programmen und Daten (die Software) verstanden werden. Moderne Rechensysteme sind sehr komplex. Übersicht und Ordnung läßt sich durch eine Einteilung in
Schichten erreichen.
Dazu ist es zweckmäßig (siehe auch [Tane1990]), den Begriff einer Hierarchie virtueller
Maschinen einzuführen und zu benutzen. Eine Maschine führt Anweisungen aus einer
wohldefinierten, die Maschine charakterisierenden Sprache aus. Eine korrekte Folge von
Anweisungen der Sprache heißt Programm für die Maschine, die Ausführung dieser Anweisungen auf der Maschine ist ein Programmablauf. Eine Maschine heißt virtuell, wenn
sie durch ein Programm, das auf einer anderen Maschine abläuft, realisiert wird.
Virtuelle Maschinen können auf zwei Arten realisiert werden: Durch Interpretation oder
durch Übersetzung. Übersetzung wird auch Compilierung genannt.
Seien M, M’ und M” Maschinen und L, L’ und L” die zugehörigen Sprachen. Man spricht
von Interpretation, wenn Programme in der Sprache L durch ein auf M’ ablaufendes und
in L’ formuliertes Programm (Interpreter) ausgeführt (interpretiert) werden (Abbildung
2.1).
Man spricht von Übersetzung, wenn Programme der Sprache L von einem auf M” laufenden und in L” formulierten Programm (Übersetzer, Compiler) in ein Programm der
Sprache L’ umgewandelt (übersetzt) werden. Dieses Programm wird (eventuell zeitlich
verzögert) auf M’ ausgeführt (Abbildung 2.2).
Ein zu übersetzendes (interpretierendes) Programm wird Quellprogramm (source program) genannt. Übersetzungen und Interpretationen müssen genau das bewirken, was
29
30
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
Maschine M
Sprache L
-
Maschine M’
Sprache L’
Abbildung 2.1: Interpretation
Maschine M
Sprache L
Maschine M”
Sprache L”
Maschine M’
Sprache L’
Abbildung 2.2: Übersetzung
in dem Quellprogramm beabsichtigt war, sie müssen „semantisch treu“ sein.
Das Konzept der virtuellen Maschine soll jetzt auf Schichten von Rechensystemen angewendet werden. Tabelle 2.1 zeigt den Aufbau eines Rechensystems als Hierarchie von
Maschinen und Sprachen. Die Basisschicht, auf der alle anderen aufbauen, ist die Hardwaremaschine, auch reale Maschine genannt. Auf ihr werden Mikroprogramme ausgeführt,
die Maschinenprogramme der zweiten Schicht, auch konventionellen Maschine genannt,
interpretieren. Die konventionelle Maschine ist virtuell. Sie ist das, was in der Systemprogrammierung als „Hardware“ angesehen und in den Handbüchern über „Maschinenbefehle“
beschrieben wird. Es ist möglich und auch getan worden, durch Mikroprogrammierung
einer Hardwaremaschine unterschiedliche konventionelle Maschinen zu realisieren.
Auf einer konventionellen Maschine werden Programme einer Betriebssystem-Maschine
Betriebsart
Interpretation
Interpretation
Interpretation
Übersetzung
Übersetzung
.. ...............
Interpretation
2.1. SCHICHTENAUFBAU EINES RECHENSYSTEMS
Maschine
Sprache
läuft auf
Hardwaremaschine (Register,
Mikroprogrammsprache
———
Werk(e) für Rechenoperationen, Busse, Speicher, Ein/Ausgabeschnittstellen, periphere Geräte usw.)
Konventionelle Maschine
Maschinenprogrammsprache
Hardwaremaschine
Betriebssystem-Maschine
Eingeschränkte MaschinenKonventionelle Maschine
programmsprache mit Betriebsbefehlen
Assembler-Maschinen
Assemblersprachen
Hardwaremaschine (Mikroassembler),
Konventionelle
Maschine, Betriebssystemmaschine
Programmiersprachenhöhere Programmiersprachen: Betriebssystem-Maschine
Maschinen
COBOL, FORTRAN, C, Modula, Pascal, PL/1 usw.
.. . . . . . . . . . . . . . . . . . . . . . . . . . . . .. .. . . . . . . . . . . . . . . . . . . . . . . . . . . . .. .. . . . . . . . . . . . . . . . . . . . . . . . . . . . ..
Lisp, Prolog, APL, Basic u. a. ProgrammierprachenMaschinen
Benutzerorientierte Maschinen Kommandosprachen, Parame- Programmiersprachentersprachen, graphische Spra- Maschinen
chen
(Fenstertechnik), Spezialsprachen z. B. Datenbanksprachen
oder Sprachen für Expertensysteme
Interpretation
(Übersetzung)
Tabelle 2.1: Sprach- und Maschinenschichten von Rechensystemen
31
32
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
interpretiert. Diese Programme bestehen aus Maschinenbefehlen der konventionellen Maschine und speziellen Anweisungen an das Betriebssystem, den Betriebsbefehlen. Auf einer konventionellen Maschine können mehrere, unterschiedliche Betriebsystem-Maschinen
realisiert sein. Zum Betriebssystem siehe auch Unterabschnitt 4.3.1.
Programme von Assembler-Maschinen sind in einer dem Menschen angepaßten Art formuliert und werden in eine maschinenangepaßte Form übersetzt. Die Anweisungen von
Assembler-Programmen entsprechen im wesentlichen eins-zu-eins den Anweisungen der
Maschinenprogramme, in die sie übersetzt werden. Assemblerprogramme werden kaum
noch geschrieben; jedoch ist es für Informatiker wichtig, ein Verständnis für maschinennahe Programmierung zu haben. Deshalb wird in Beispiel 4.1, Seite 130, eine einfache konventionelle Maschine vorgestellt, mit der Assemblerprogrammierung geübt werden kann.
Für Programmieraufgaben aller Art werden heutzutage fast ausschließlich höhere Programmiersprachen benutzt. Davon werden die meisten übersetzt, wie z. B. die in Tabelle
2.1 genannten. Auch C++ ist ein Beispiel für eine Sprache, die übersetzt wird. Einige
höhere Programmiersprachen werden interpretiert, Beispiele sind in Tabelle 2.1 genannt.
Gelegentlich gibt es für eine Programmiersprache sowohl Interpreter als auch Übersetzer.
Für Anwendungsprobleme, um deren Lösung es letztlich beim Einsatz von Rechensystemen geht, werden überwiegend benutzerorientierte Maschinen eingesetzt.
Ein interessanter Sonderfall ist Java, an dem auch die Übersetzung höherer Programmiersprachen allgemein etwas genauer untersucht werden soll. In den meisten Fällen wird
ein Programm einer höheren Programmiersprache nicht direkt, sondern in zwei Schritten
übersetzt. Siehe hierzu Abbildung 2.3. Der erste Schritt übersetzt das Quellprogramm in
einen Zwischencode, der von dem Rechensystem, auf dem das übersetzte Programm letztlich ablaufen soll (der Plattform), unabhängig ist. Im zweiten Schritt wird das Zwischencode-Programm in die endgültige plattformabhängige, ablauffähige Form, das Binärprogramm übersetzt. Der Vorteil dieser Zweistufigkeit liegt darin, daß der Teil des Compilers,
der aus Quellcode Zwischencode erzeugt, häufig Front End genannt, nur einmal gebaut
werden muß und dann für jede Plattform nur der Teil, der Zwischencode in Binärcode
übersetzt (Back End), hinzugefügt werden muß. Soll andererseits ein Compiler für eine
neue Programmiersprache gebaut werden, genügt es einen Front End zu bauen und mit
Back Ends, die schon existieren, an die einzelnen Plattformen anzupassen. Im allgemeinen
geschehen die beiden Teilübersetzungen in einem Gesamtübersetzungslauf unmittelbar
hintereinander und werden nach außen nicht sichtbar.
Bei Java hat man nun dieses Vorgehen etwas modifiziert. Es wird zunächst durch Übersetzung ein Zwischencode erzeugt, der bei Java Bytecode heißt. Dieser wird nicht noch
einmal übersetzt, sondern von einem Programm, daß man virtuelle Java-Maschine (Java
Virtual Machine, JVM) nennt, direkt interpretiert. Solche Java-Interpreter gibt es inzwischen auf den meisten Plattformen. Die Interpretation entspricht dem Ablauf eines
Binärprogramms bei konventioneller Übersetzung. Sie kann dementsprechend zu unter-
2.2. DATEIEN UND DATENBANKEN
33
..............................
..............................
..............................
.............
.............
.............
........
........
........
........
........
........
......
......
......
......
......
......
......
......
......
.....
.....
.....
.
.
.
.
.....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
....
....
..
....
..
.
.
.
....
.
.
.
.
.
.
.
.
...
...
...
..
..
..
.
.
.
.
.
.
...
.
.
.
.
...
...
..
..
...
.
...
.
.
.
.
.
.
...
...
...
.
.
.
..
.
.
.
.
.
...
...
...
.
.
.
.
.
.
....
.
...
...
...
.
.
.
...
...
....
...
..
..
.
...
..
..
.
...
.
.
.
..
.
.
..
.
...
.
.
.
....................................................................................................................
.......................................................................................................................
...
..
...
.
...... ....
...... ....
.
.
..
...
.
.
...
...
.
..
...
.
..
.
.
.
.
...
...
.
.
...
.
..
.
.
.
.
...
...
...
...
...
...
...
...
...
...
..
...
..
...
...
..
..
...
...
..
...
...
...
...
...
.
...
.
...
.
.
.
.
.
.
...
...
....
..
..
..
....
....
.....
....
...
...
.....
.....
.....
.....
.....
.....
......
......
......
......
......
......
......
.......
.......
......
......
......
........
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.........
.
.
.
.
.
.
.
.
.
.
.
.
............
.
............
.
.......................................
................................
...............................
Übersetzung
Quellprogramm
Programm
in
Zwischencode
Übersetzung
Binärprogramm
............................................
............................................
.......
.......
.........
.........
......
......
.......
.......
......
.....
......
.....
.
.
.
.
.
.
.
.
....
....
...
....
....
....
....
...
...
...
...
.
.
.
...
...
.
.
.
.
.
.
...
...
.
...
.
.
.
.
...
...
.
..
.
...
.
.
.
...
.
.
.
.
..
.
.
..
..
.....
....
..
..
..
.
..
..
.
.. .
..
..
.....
.........................................................................................................................
......................................................................................................................
.. ..
..
...
...
...
...
..
...
...
.
...
..
...
...
.
...
...
..
...
...
...
...
...
...
...
..
..
...
...
...
...
...
.
.
...
.
.
...
..
....
...
....
....
....
...
....
.....
....
....
......
......
.....
.....
......
.......
......
......
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.........
.
.
.
..........
.
..........................................
.......................................
Übersetzung
Quellprogramm
(Java)
virtuelle
JavaMaschine
Interpretierung
JavaBytecodeMaschine
Abbildung 2.3: Zwischencode und Bytecode
schiedlichen späteren Zeitpunkten und an verschiedenen Orten auf allen Rechensystemen
erfolgen, die einen Java-Interpreter aufweisen. Aus diesem Grund eignet sich Java in
besonderem Maße für den Einsatz in Netzen. Ein Nachteil ist der Effizienzverlust durch
die Interpretation des Bytecodes.
Im folgenden wird in einem kurzen Abschnitt erläutert, was Dateien und Datenbanken
sind. Im Rest des Kapitels werden Aufbau und Benutzung höherer Programmiersprachen
beispielhaft an der Programmiersprache C untersucht. Programmkonstrukte und Datentypen, die es in C nicht gibt, werden nur teilweise erläutert.
2.2
Dateien und Datenbanken
Dateien (file) sind benannte Sammlungen von Daten, die über Programmläufe hinweg
existieren. Man spricht von permanenter Speicherung (permanent storage). Es wird auch
die Bezeichnung nicht flüchtig (persistent) benutzt. Daten, die nur während eines Programmlaufs existieren, heißen temporär (flüchtig, temporary). Dateien werden auf Medien
gespeichert, die dauerhafte Lagerung zulassen (siehe Unterabschnitt 4.1.5, Seite 127). Sie
werden in Verzeichnissen (directory), die oft hierarchisch aufgebaut sind, verwaltet. In
dem Verzeichniseintrag einer Datei wird der Dateiname und weitere Daten wie Lage auf
dem Träger, Zugriffsberechtigungen, Datum der letzten Änderung vermerkt. Oft ist es
üblich, durch Endungen im Dateinamen etwas über Nutzung und Struktur der Datei aus-
34
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
zusagen. Zum Beispiel bezeichnet program.c im Allgemeinen eine C-Quelldatei, die aus
abdruckbaren Zeichen besteht.
Vor dem Aufkommen allgemeiner Vernetzung und der explosionsartigen Zunahme von
Rechneranwendungen im privaten Bereich (Musik, Bilder, Texte u.ä.) wurden in erster Linie Programme in Quell-oder Objektformat und kommerziell-administrative Daten gespeichert. Programmdateien waren Bibliotheken in speziellen Formaten. Dateien für kommerzielladministrative Daten bestanden aus Sätzen (record): Kundensätze, Artikelsätze, Personalsätze, Patientensätze usw. Der Datenverwaltung des Bertriebssystems war der Aufbau
aus Sätzen und deren Typ bekannt und allgemeine Zugriffsmethoden (access method) erlaubten das Suchen, Lesen und Speichern der Sätze. Das hat sich in der Zeit danach rasch
sehr deutlich geändert. Zum einen reicht ein einfacher Aufbau aus Sätzen für die Vielzahl
unterschiedlicher neuer Anwendungen nicht aus. Für Transport und Speicherung benutzt
man statt dessen einfache Folgen von Bits oder Zeichen. Alles was an Struktur darüber
hinaus gebraucht wird, ist nur für die entsprechenden Anwendungssysteme sichtbar. Zum
anderen sind die Sätze der kommerziell-administrativen Datenverarbeitung geblieben. Mit
je einer Datei für jede Klasse von Sätzen konnten jedoch die vielfältigen Verflechtungen
und Beziehungen nur umständlich und schwierig bearbeitet werden. Deshalb hat man
schon recht früh damit begonnen, Datenbestände aufzubauen, die Sätze unterschiedlicher
Klassen und die Beziehungen zwischen diesen abbilden. Solche Datenbestände werden Datenbanken (data base) genannt. Das Programmsystem, das Datenbanken verwaltet, heißt
Datenbanksystem (data base system).
2.3
Grundbegriffe
Allgemeines: Die Programmiersprache C wurde 1972 bei den AT&T Bell Laboratories
(„Bell Labs“) von Ritchie 1 entwickelt. Es gibt eine Vielzahl von Lehrbüchern über C. Das
Standardwerk ist Kernighan/Ritchie [KernR1988]. Die folgenden Ausführungen stützen
sich an mehreren Stellen auch auf Lowes/Paulik [LoweP1995].
C-Programme: C-Programme und ihre Wirkung kann man sich im Grundsatz so vorstellen, daß es eine (virtuelle) C-Maschine gibt, auf der C-Programme direkt ablaufen
(siehe Abbildung 2.4). Die wesentlichen Teile eine C-Programms wurden auf Seite 6 besprochen und sollen anhand des kleinen Programms in Tabelle 2.2 noch einmal wiederholt
werden.
• Kommentar.
Kommentare sind zwischen /* und */ eingeschlossen.
Ritchie, Dennis M. Amerikanischer Informatiker. Entwickelte zusammen mit Thompson (siehe Seite
142) das Betriebssystem Unix. Entwarf für die Entwicklung von Unix die Programmiersprache C, für die
er den ersten Compiler baute.
1
2.3. GRUNDBEGRIFFE
35
....................................
........
......
......
.....
.....
....
...
...
.
.
...
...
.
...
..
.
...
...
....
..
...
..
...
...........................................................................................................
...
...
....
...
...
...
..
.
...
.
.
.
...
.
...
...
....
...
.....
...
......
.....
.
.
.
.
.
.......
.............. ...................
........
C-Programm
'
&
C-Maschine
Abbildung 2.4: Grundschema für C-Programme
/****************************************/
/*
*/
/* Programm ZEICHENZAHL
*/
/*
*/
/* Zaehlt die eingegebenen Zeichen
*/
/* und gibt die Anzahl aus
*/
/*
*/
/****************************************/
#include <stdio.h>
main()
{
int
zahl;
zahl = 0;
while (getchar() != ’\n’)
{
zahl = zahl + 1;
};
printf("%d\n", zahl);
}
$
%
Tabelle 2.2: Programm ZEICHENZAHL
• Makro.
Makros dienen zur Erweiterung des Programms durch Textersetzung oder auch zur
Erweiterung des Programms durch Einfügen des Inhalts zusätzlicher Dateien, wie
z. B. im Programm ZEICHENZAHL die Zeile #include <stdio.h>.
• Funktionsdeklaration (auch Funktionsdefinition genannt)2 .
2
Streng genommen, sind Definitionen und Deklarationen in C zu unterscheiden, Deklarationen legen
36
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
Funktionen sind (in C) Unterprogramme (siehe Abschnitt 2.8). Sie müssen deklariert
werden, bevor sie benutzt werden können. Im Programm ZEICHENZAHL wird z. B
die Funktion getchar benutzt, mit der einelne Zeichen von der Tastatur eingelesen
werden. Die Deklaration von getchar steht nicht explizit im Programm, sondern
wird mit der Datei stdio.h hinzugefügt.
• Variablendeklaration (auch Variablendefinition genannt).2
Auch Variablen (siehe Abschnitt 2.5) müssen deklariert werden, ehe sie benutzt
werden. Im Program ZEICHENZAHL wird mit int zahl eine Variable für ganzzahlige Werte deklariert.
• Anweisungen an die C-Maschine.
Dies sind die ausführbaren Anweisungen, also die, die von der C-Maschine bearbeitet
werden. Im Programm ZEICHENZAHL ist die erste ausführbare Anweisung zahl =
0. Mit ihr wird der Variablen zahl der Wert 0 zugewiesen. Es ist zweckmäßig, sich
vorzustellen, daß die C-Maschine vor der Bearbeitung einer ausführbaren Anweisung
in einem bestimmten Zustand ist und durch die Bearbeitung in einen neuen (im
allgemeinen verschiedenen) Zustand überführt wird.
Vom Programmieren zum Programmablauf:
Programmieren,
edieren
.........................
..........
.......
.......
.....
.....
....
...
...
.
.
...
...
.
...
...
....
...
....
.. .
.
.. .
..........................
................................
.. ..
.. .......
...
..
.
.
...
.
.
.
...
.
...
...
...
...
....
...
......
.....
.
.
.......
.
.
.
.............. .................
......
Quellprogramm
in C
C-Compiler,
Binder
u. a.
Abbildung 2.5 zeigt die einzelnen
..............................
.........
......
......
.....
.....
....
...
.
...
.
...
...
.
...
....
...
..
....
.. .
.
.. .
...........................
................................
.. ..
.. ......
...
..
.
.
...
.
.
.
...
.
...
...
...
...
....
....
......
....
.
.
.......
.
.
.
.............. .................
......
Binärprogramm
Hardware
und Betriebssystem
Abbildung 2.5: Vom Programmieren zum Programmablauf
Schritte von der Programmierung über die Übersetzung bis zum Ablauf eines C-Programms.
Sie gelten ganz ähnlich auch für Programme in den meisten anderen höheren Programmiersprachen. Ein (C-)Quellprogramm ist ein Text (Zeichenreihe). Er wird von einem
Menschen (manchmal auch von einem Programm, einem Programmgenerator) erzeugt
und in einer Quelldatei abgelegt. Durch die Übersetzung mit einem (C-)Compiler und die
Bearbeitung mit einem Binder (vergl. Seite 144) wird aus dem Quellprogramm ein ablauffähiges Binärprogramm. Ein Binärprogramm ist ein Bitmuster. Es besteht aus Maschineneinen Typ fest, während Definitionen auch Speicherplatz reservieren [KernR1988]. In diesem Buch soll
diese Unterscheidung jedoch nicht getroffen werden.
2.4. WERTE, VARIABLE, ZEIGER, NAMEN
37
befehlen (der konventionelle Maschine) und Betriebsbefehlen (siehe auch Unterabschnitt
4.3.1).
Syntax für (C-)Programme: Unter der Syntax einer Programmiersprache versteht
man einen Satz von Regeln, denen der Aufbau des Programmtextes genügen muß, damit
der Übersetzer (Compiler) es als korrekt erkennt und ein Binärprogramm erzeugt. Fragen
der Syntax sind im Compilerbau sehr wichtig. Wir wollen uns im folgenden mit Fragen
der Syntax nicht weiter befassen.
Semantik für (C-)Programme: Unter der Semantik (von C) versteht man die Regeln, die die Zustandsänderungen der (C-)Maschine steuern, wenn die Anweisungen eines (C-)Programms ausgeführt werden. „Semantik beschreibt die Wirkung der Anweisungen.“ Eine wichtige Frage in der Informatik ist: Welche semantischen Eigenschaften eines
Programms kann man automatisch (d. h. mit Hilfe eines anderen Programms) aus dem
Programmtext ableiten? Leider kann man die wichtigsten, z. B. ob ein Programm bei
korrekten Eingabewerten immer hält (Halteproblem, siehe Abschnitt 5.2) nicht erkennen.
Mit der Semantik von C wollen wir uns im folgenden ausführlich befassen, jedoch formlos
(d. h. nicht formal).
2.4
Werte, Variable, Zeiger, Namen
Eine Übersicht über Datentypen: In der Datenverarbeitung werden Werte (value)
gleicher Art, d. h. Daten gleicher Art, zu einem Datentyp zusammengefaßt. Man spricht
von Werten (Daten) des gleichen Typs. Es folgt eine Übersicht über verschiedene Arten
von Datentypen.
• Datentypen, die direkt in der konventionellen Maschine, in der „Hardware“, gegeben
sind.
Werden in Kapitel 3 „Darstellung von Daten durch Bitmuster“ behandelt.
• Datentypen, die direkt in der Programmiersprache gegeben sind.
Werden in diesem Abschnitt behandelt. Es sind
◦ Elementare Datentypen
∗
∗
∗
∗
Ganze Zahlen
Rationale Zahlen
Zeichen und Zeichenreihen
Wahrheitswerte
◦ Zeiger
38
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
◦ Zusammengesetzte Datentypen
∗ Reihungen (array)
∗ Sätze (record)
• Datentypen, die nicht direkt in der Programmiersprache gegeben sind
◦ Datentypen im Programm
Diese Datentypen werden oft Datenstrukturen genannt. Sie werden in den Teilen III „Einfache Datenstrukturen“ sowie IV „Allgemeine Graphen“ behandelt.
∗ Listen
∗ Bäume
∗ Graphen
◦ Datentypen für Dateien und Datenbanken
Hierauf wird in diesem Buch nur kurz eingegangen (siehe Abschnitt 9.4, Seite
296).
Im folgenden werden Datentypen besprochen, die direkt in der Programmiersprache C
gegeben sind. Auf andere Programmiersprachen wird nur am Rande eingegangen. Bei
diesen Datentypen beziehen sich sowohl Zeiger als auch zusammengesetzte Datentypen
auf schon bekannte Datentypen. Zeiger und zusammengesetzte Datentypen sind abgeleitete
Datentypen.
Die Werte der einzelnen Datentypen haben in einem Programmlauf unterschiedliche Ursprünge:
• Sie werden im Programm direkt angegeben (Konstanten).
• Sie werden während des Programmlaufs von außen eingegeben (Eingabegeräte, Dateien, Netz, Interprozeßkommunikation, Betriebssystemaufruf, Bibliotheksaufruf, Signalaufnahme usw.).
• Sie werden während des Programmlaufs erzeugt („berechnet“), im allgemeinen aus
anderen, schon vorhandenen Werten.
Einige während des Programmlaufs berechnete Werte werden (auf unterschiedliche Weise)
ausgegeben3 .
Beispiel 2.1 (Programm DYNREIHUNG) Zur Erläuterung der Betrachtungen über
Werte, Variable, Zeiger und Namen soll als Beispiel das Programm DYNREIHUNG vorgestellt werden. Es ist in den Tabellen 2.3 und 2.4 wiedergegeben. Das Programm liest
Für einfache und häufig benutzte Formen der Eingabe und Ausgabe von Werten gibt es in C Standardfunktionen in der Standardbibliothek. Diese enthält noch weitere nützliche Klassen von Funktionen,
zum Beispiel mathematische Funktionen. Eine vollständige Beschreibung der Standardbibliothek ist in
Lowes/Paulik [LoweP1995] zu finden.
3
2.4. WERTE, VARIABLE, ZEIGER, NAMEN
39
eine Zahlenfolge n, a1 , a2 , . . . an ein. Dabei ist n die Anzahl der anschließend einzulesenden
ganzen Zahlen ai . Von diesen werden Mittelwert, Varianz und Streuung berechnet
E =
V =
σ
=
1
n
·
1
n
·
√
n
P
ai
Mittelwert
(E − ai )2
Varianz
i=1
n
P
i=1
V
Streuung
Das Programm ermittelt auch die Folgenglieder, für die der Abstand zum Mittelwert
maximal ist. Alle berechneten Werte werden ausgegeben.
Die Zahlen a1 , a2 , . . . , an werden vom Programm eingelesen und in aufeinanderfolgenden
Plätzen, in einer Reihung, gespeichert. Diese Plätze stehen nicht von Anfang an zur Verfügung, sondern werden während des Programmlaufes mit der Funktion malloc dynamisch
angefordert und dem Programm zugewiesen. Die Adresse des ersten dieser Plätze wird
in der Variablen zeiger gespeichert. Platz i der Reihung wird dann mit zeiger+(i-1)
angesprochen. Mit der Anweisung free kann dynamisch zugewiesener Speicher wieder
freigegeben werden. Weitere Einzelheiten zu Reihungen sind in Unterabschnitt 2.6.1 zu
finden.
2
Variablen: Bei der Eingabe von Werten und bei der Berechnung neuer Werte während
eines Programmlaufes muß es möglich sein, diese Werte „aufzubewahren“. Das geschieht
mit Hilfe von Variablen.
Eine Variable ist ein Speicherplatz (ein Behälter), der während des Programmlaufs Werte eines bestimmten Datentyps aufnehmen kann.
Eine Variable hat einen Wert und eine Adresse und kann einen Namen haben. Siehe
Abbildung 2.6.
40
'
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
/**********************************************************/
/*
*/
/* Programm DYNREIHUNG
*/
/*
*/
/* Liest die Anzahl einzugebender
*/
/* int-Werte ein.
*/
/*
*/
/* Legt dynamisch eine Reihung
*/
/* entsprechnder Groesse ein.
*/
/*
*/
/* Liest die Werte in die Reihung ein.
*/
/*
*/
/* Berechnet Mittelwert, Varianz und
*/
/* Streuung.
*/
/*
*/
/* Gibt alle Werte mit maximalem
*/
/* Abstand zum Mittelwert aus.
*/
/*
*/
/**********************************************************/
#include <stdio.h>
#include <malloc.h>
#include <math.h>
main()
{
int
zahl, i;
int
*zeiger;
int
summe = 0;
float
mittel, streuung, varianz, max, r;
float
fsumme = 0.;
&
scanf("%d", &zahl);
if (zahl <= 0)
{printf("Anzahl ungueltig\n"); exit(0);};
zeiger = (int *) malloc(zahl * sizeof(int));
if (zeiger == NULL)
{printf("Nicht genuegend Speicher vorhanden\n"); exit(0);};
for (i = 0; i < zahl; i=i+1)
{
scanf("%d", zeiger+i);
summe = summe + *(zeiger+i);
};
mittel = ((float)summe)/zahl;
printf("Mittelwert = %f \n", mittel);
$
%
2.4. WERTE, VARIABLE, ZEIGER, NAMEN
41
Wert einer Variablen: Der Wert wird auch Inhalt genannt. Er kann während eines
Programmlaufs für verschiedene Dinge benutzt werden (Berechnungen, Tests, Ausgaben,
Weiterleitung an andere Programmteile u. a.), ohne daß er dabei geändert wird. Man
sagt, die Variable wird gelesen. Durch Berechnungen, Eingaben u. a. kann der Wert einer
Variablen auch
'
}
&
max = 0.;
for (i = 0; i < zahl; i=i+1)
{
r = *(zeiger+i) - mittel;
if (r < 0.) r = -r;
fsumme = fsumme + r*r;
if (r > max)
max = r;
};
varianz = fsumme/zahl;
streuung = sqrt(varianz);
printf("Varianz = %f \n", varianz);
printf("Streuung = %f \n", streuung);
for (i = 0; i < zahl; i=i+1)
{
r = *(zeiger+i) - mittel;
if (r < 0.) r = -r;
if (r == max)
{
printf("Zahl[%d] = %d |Zahl[%d] - Mittelwert| = %f\n",
i+1, *(zeiger+i), i+1, max);
};
};
$
%
Tabelle 2.4: Programm DYNREIHUNG (Dynamischer Aufbau einer Reihung) – Teil 2
geändert werden. Man sagt, die Variable wird geschrieben. Die wichtigste Form der Wertänderung einer Variablen ist die Zuweisung (Wertzuweisung, assignment). Diese wird in
C in der Form
<variable> = <Ausdruck für den zuzuweisenden Wert>4
Es soll hier nicht allgemein festgelegt werden, was ein solcher Ausdruck ist. Oft ist es ein arithmetischer
Ausdruck, der eine Zahl liefert.
4
42
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
.....
....
... .......
..
...
........
..
........ ..
...
.
.....
....
... .......
.
....
.
...
....... ..
.
........
...
......
...
...
......
...
16708
...
..
... .......
.....
....
..........................
.......
...
.....
.
..
.....
..............................
.....
......
....... ..
....
...
..
....
..
....
..
...
....
..
...
...
..
113
........
..
........ ..
...
.
113
..............................
.....
..
.....
..
......
.............................
....
.
.
.
.
.
.
..........
...
..
...
....
..
...
...
...
...
...
...
...
..
507
..
..
....... ..
..
.......
einwohner
einwohner_zgr
A. Variable einwohner hat den
int-Wert 16708.
B. Variable einwohner_zgr hat
als Wert die Adresse der intVariablen einwohner.
Abbildung 2.6: Werte, Variable, Zeiger, Namen
geschrieben. Das Gleichheitszeichen heißt Zuweisungsoperator. Die Schreibweise ist in vielen anderen Programmiersprachen analog, nur wird oft ein anderes Zeichen (oder eine
Kombination von Zeichen) als Zuweisungsoperator benutzt, z. B. := oder :←.
Wertänderungen durch Zuweisungen sind in (sauber strukturierten) Programmen (für
Menschen) gut erkennbar. Wertänderungen auf anderem Wege sind schwerer zu erkennen
und sollten nach Möglichkeit vermieden werden. Wenn das nicht möglich ist, wie z. B. in
C bei der Eingabe von Werten mittels der Funktion scanf, sollte der Programmtext auf
die Wertänderung durch einen Kommentar oder auf andere Weise besonders hinweisen.
Alle Werte, die eine Variable annimmt, sind vom gleichen Datentyp. Zu einem Zeitpunkt
hat sie nur einen Wert. Dieser Wert kann unbestimmt sein. Im Beispielprogramm
DYNREIHUNG sind z. B. zahl, streuung und fsumme Variable. zahl kann Werte vom
Typ int annehmen, streuung und fsumme sind Variable für Werte vom Typ float.
Alle drei Variablen werden durch eine Variablendeklaration (Variablendefinition) am Anfang des Programmtextes bereitgestellt. Zu Beginn des Programmlaufes haben zahl und
streuung einen unbestimmten Wert. Die Variable zahl erhält während des Programmlaufs bei dem Eingabebefehl scanf einen definierten Wert. Bei der Variablen streuung
passiert das erst, wenn ihr die Quadratwurzel der Varianz zugewiesen wird.
Der Variablen fsumme ist bei der Deklaration ein Anfangswert (Vorbesetzungswert, Initialisierungswert), nämlich 0, zugewiesen worden und diesen Wert hat die Variable beim
Beginn des Programmlaufes. Hat eine Variable (in C) einmal einen bestimmten Wert,
dann wird ihr Wert während des Programmlaufs nicht mehr unbestimmt (siehe jedoch
Abschnitt 2.8).
2.4. WERTE, VARIABLE, ZEIGER, NAMEN
43
Adresse und Name einer Variablen: Jede Variable, die durch eine Deklaration eingeführt wird, erhält bei der Deklaration einen Namen. Keine andere Variable kann den gleichen Namen haben (siehe jedoch Abschnitt 2.8). In einigen Programmiersprachen (nicht
C) kann eine Variable mehrere Namen haben. Variable, die nicht über eine Deklaration
eingeführt werden (s. u.), haben keinen Namen.
Jede Variable hat eine eindeutig bestimmte Adresse, eine Art „Hausnummer“. In Abbildung 2.6 wird das durch Ellipsen angedeutet. Die Adresse der Variablen einwohner ist
113. Dieser Wert ist jedoch nur symbolisch zu sehen und nicht als wirkliche Zahl aufzufassen.
Adressen können in einigen Programmiersprachen im Programm benutzt werden. Man
spricht dann allgemein von Zeigern (pointer) oder Referenzen. Adressen weisen in den
Programmiersprachen, in denen sie benutzt werden können, recht unterschiedliche Eigenschaften auf. Das folgende gilt speziell für C.
Rechnerintern sind Adressen in C-Programmen natürliche Zahlen, die jedoch nicht nur
vom Programm, sondern auch von der Rechnerarchitektur abhängen. Sie haben für den
Programmierer keine explizite Bedeutung. Nur für Spezialisten und für Testzwecke macht
es Sinn, Adressen auszugeben. Es macht jedoch Sinn und ist oft nützlich, Adressen als
Verweise auf Variablen und damit auch auf die Werte, die diese enthalten, zu benutzen.
Das ist in C möglich und gängige Programmierpraxis.
In C gilt: Alle Werte einer Variablen gehören zum gleichen Datentyp
(Ausnahme: union). Der Typ einer Variable bestimmt sich durch den
Datentyp ihrer Werte. Der Typ einer Adresse bestimmt sich durch den
Typ der Variablen, die zu der Adresse gehört.
Ein Zeiger (pointer) ist (in C) eine Variable, deren Werte Adressen eines
bestimmten Typs sind (Ausnahme void).
In Abbildung 2.6 ist in Teil B ein Zeiger zu sehen. Sein Name ist einwohner_zgr, seine
Adresse 507. Der Zeiger enthält als aktuellen Wert die Adresse der Variablen einwohner,
nämlich 113.
Generierung von Variablen: Es kommt in der Programmierung oft vor, daß in einem
Programm Speicherplätze eines bestimmten Typs gebraucht werden, ihre Anzahl aber erst
während des Ablaufs des Programms festgestellt wird. Einige Programmiersprachen, darunter C, kennen Anweisungen, mit denen zur Ablaufzeit Speicherplätze für Variablen eines
Datentyps bereitgestellt werden können. Man spricht auch von dynamischer Erzeugung
von Variablen. Solche Variablen haben eine Adresse, aber keinen Namen.
Ein Beispiel in C ist in Tabelle 2.3 zu sehen. In der Programmzeile
44
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
zeiger = (int *) malloc(zahl * sizeof(int));
wird Speicherplatz mit der Funktion malloc angefordert. Die Größe des angeforderten
Speicherbereichs wird durch zahl * sizeof(int) spezifiziert und die Anfangsadresse als
Adresse vom Datentyp int der Zeigervariablen zeiger zugewiesen.
Konstanten: Wie oben erwähnt, werden Werte von Datentypen in einem Programm
oftmals explizit als Konstanten aufgeführt, z. B. bei Anfangswerten oder Wertzuweisungen
im Programmlauf. Auch als Bestandteile von Ausdrücken treten sie oft auf. So kann zum
Beispiel die Berechnung eines Kreisinhalts in Abhängigkeit vom Radius innerhalb eines
Programms mehrfach durch die Anweisung
inhalt = (radius*radius)*3.1416
erfolgen. Die konstanten Datenwerte müssen auch zur Ablaufzeit des Programms, bei
compilierten Programmen also auch nach der Übersetzung, zur Verfügung stehen. Das
kann so geschehen, als wäre die Konstante eine Variable, deren Wert sich nicht ändert,
ein Literal. Konstanten können während des Programmlaufs aber durchaus auch anders
zur Verfügung stehen, z. B. durch Maschinenbefehle erzeugt werden. Mit der 1 in der
Zuweisung n = n + 1 ist das z. B. häufig der Fall.
In einigen Programmiersprachen können Konstanten auf die gleiche Art wie Variablen
deklariert werden. Wenn das der Fall ist, haben so eingeführte Konstanten analog zu Variablen einen (konstanten) Wert, eine Adresse und einen Namen. Der Name ist eindeutig
(siehe jedoch Abschnitt 2.8). Die Adresse ist die (eindeutige) Hausnummer eines Speicherplatzes und kann von Zeigern als Wert angenommen werden. In C gibt es hierfür das
Sprachelement const und mit der Anweisung
const float pi=3.1416;
kann die Konstante pi deklariert werden. Mit &pi steht im Programm dann auch die
Adresse des Speicherplatzes, in dem der Wert 3.1416 steht, zur Verfügung. Zu konstanten
Adressen siehe Unterabschnitt 2.5.5, speziell Tabelle 2.14.
In C werden Konstanten oft mit der Textersetzung #define angegeben. So würde z. B.
die Zeile
#define PI
3.1416
im Kopfteil des Programms bewirken, daß bei der Übersetzung der Text PI überall im
Programm durch den Text 3.1416 ersetzt wird. Entsprechend würde mit der Angabe
#define TELOL
0441
der Text TELOL durch den Text 0441 ersetzt. Diese Form der Konstantenangabe ist nützlich. Man beachte jedoch, daß sie nicht einer Konstantendeklaration mittels const entspricht.
2.5. WERTEBEREICHE, OPERATIONEN, AUSDRÜCKE
2.5
2.5.1
45
Wertebereiche, Operationen, Ausdrücke
Ganze Zahlen
Datentypen für ganze Zahlen: Es gibt 4 Standardtypen mit Vorzeichen und 4 Standardtypen ohne Vorzeichen sowie zwei allgemeine Typen (siehe Tabelle 2.5). Es ist instal'
&
•
•
•
•
signed
signed
signed
signed
•
•
char
int
char
short int
int
long int
•
•
•
•
unsigned
unsigned
unsigned
unsigned
char
short int
int
long int
$
%
Tabelle 2.5: Standardtypen ganzer Zahlen
lationsabhängig, ob char = signed char oder char = unsigned char. Ebenso ist die
Festlegung von int installationsabhängig, jedoch muß stets gelten signed short int ⊆
int ⊆ signed long int. Es gibt Mindestwertebereiche. Reale Compiler dürfen größere
Wertebereiche aufweisen. Tabelle 2.6 zeigt im Vergleich die Mindestwertbereiche und die
Typ
signed char
unsigned char
signed short int
unsigned short int
signed int
unsigned int
signed long int
unsigned long int
Minimum
Maximum
größtes
donar
kleinstes
donar
−127
−128
127
127
0
0
255
255
−32767
−32768
32767
32767
0
0
65535
65535
−32767
−32768
32767
32767
0
0
65535
65535
−2147483647 −2147483648 2147483647 2147483647
0
0 4294967295 4294967295
donar: Rechner Sparc IPX mit GNU-C-Compiler (gcc, v2.7).
Tabelle 2.6: Mindestwertebereiche ganzer Zahlen
des Rechners „donar“, einer Sparc IPX mit GNU-C-Compiler (gcc, v2.7). Wo kommen die
Werte für donar her? Es gibt installationsspezifische Parameter in der Datei limits.h.
Tabelle 2.7 zeigt einen Auszug aus dieser Datei.
46
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
'
&
CHAR_BIT
CHAR_MAX
CHAR_MIN
UCHAR_MAX
SCHAR_MAX
SCHAR_MIN
INT_MAX
INT_MIN
LONG_MAX
LONG_MIN
SHRT_MAX
SHRT_MIN
UINT_MAX
ULONG_MAX
USHRT_MAX
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
8
127
-128
255
127
-128
2147483647
-2147483648
2147483647
-2147483648
32767
-32768
4294967295
4294967295
65535
$
%
Tabelle 2.7: Ganze Zahlen bei donar (Auszug aus limits.h)
Auch Zeichen sind in C ganze Zahlen, jedoch:
Es wird empfohlen, in C Zeichen, d. h. Werte vom Typ char, nicht als ganze
Zahlen aufzufassen, sondern sie als eigenen, nichtnumerischen Datentyp zu
behandeln.
Einzelheiten dazu sind in Unterabschnitt „Zeichen“, Seite 52, zu finden.
Ganzzahlige Konstanten und ganzzahlige Ein-/Ausgabedaten: Konstanten werden als ganzzahlige Dezimalzahlen mit oder ohne Vorzeichen geschrieben. Zusätze können
den Typ näher festlegen. Beispiele:
-327519, 327519, +327519, 0, -1213456798l, 3111222333UL
Führende Nullen dürfen, außer für den Wert 0, nicht geschrieben werden.
Eine führende Null legt Oktalschreibweise statt Dezimalschreibweise fest.
Bei der Eingabefunktion scanf und der Ausgabefunktion printf werden die Formatierungsanweisungen
%d, %i und %u
benutzt. Für Ergänzungen (Längenangaben, Typpräzisierungen a. a.) sei auf Kernighan/Ritchie
[KernR1988] und Lowes/Paulik [LoweP1995] verwiesen.
C läßt irritierende Merkwürdigkeiten zu, wie das Beispiel 2.2 zeigt.
2.5. WERTEBEREICHE, OPERATIONEN, AUSDRÜCKE
47
Beispiel 2.2
unsigned long int L;
L = -1;
printf("L (int) = %d\n", L);
printf("L (unsigned) = %u\n", L);
L = -1ul;
printf("L (int) = %d\n", L);
printf("L (unsigned) = %u\n", L);
L
L
L
L
(int) = -1
(unsigned) = 4294967295
(int) = -1
(unsigned) = 4294967295
2
Anmerkung 2.1
#
In C können ganzzahlige Werte auch in oktaler oder hexadezimaler Schreibweise angegeben werden. Es wird empfohlen, das nicht zu tun. Die oktale
Schreibweise sollte überhaupt nicht und die hexadezimale Schreibweise ausschließlich für die Spezifizierung von Steuerzeichen und Bitmustern benutzt
werden.
"
!
Steuerzeichen werden in Unterabschnitt 2.5.3 besprochen, zu Bitmustern in C siehe Abschnitt 3.8 im Kapitel 3 „Darstellungen von Daten durch Bitmuster“. C kennt ganzzahlige
Werte auch als Aufzählungswerte (vom Typ enum). Siehe hierzu Unterabschnitt 2.5.6, Seite
59.
2
Ganzzahlige Operationen:
• Ganze Zahlen (signed).
Addition (+), Subtraktion (−) und Multiplikation (∗) sowie Vorzeichenwechsel sind
ganzzahlig. Verhalten und Ergebnis bei Bereichsüberschreitungen sind unbestimmt.
Ganzzahlige Division (/) und Rest (%) genügen der Bedingung m = (m/n)·n+m%n
mit 0 ≤ |m%n| < |n|.
Treten negative Operanden auf, darf der Rest negativ oder positiv sein und der
Quotient ist nicht eindeutig bestimmt. Beispiel:
48
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
◦ −8 = (−1) · 5 + (−3)
◦ −8 = (−2) · 5 + (+2)
Bei donar gilt zusätzlich |(m/n) · n| ≤ |m| und damit sind ganzzahliger Quotient
und Rest eindeutig bestimmt und der Betrag des Quotienten nicht vom Vorzeichen
abhängig. Zu ganzahliger Division und Restbildung siehe auch Unterabschnitt 6.1.4,
Seite 193.
Das Verhalten bei Division durch 0 ist in C nicht festgelegt. Es führt bei vielen
Rechnern (z. B. donar) zu Programmabbruch.
Vergleichsoperationen sind <, <=, >, >=, == und !=. Sie sind zwischen allen Werten
des zulässigen Bereichs definiert und liefern wahr oder falsch (siehe Unterabschnitt
2.5.4).
• Natürliche Zahlen (unsigned).
Die Operationen +, −, ∗ sowie die Vorzeichenwechsel werden grundsätzlich in modulob-Arithmetik ausgeführt (b ist die Größe des Zahlenbereiches). Es gibt daher keine Bereichsüberschreitungen. Zur Modulorechnung siehe Unterabschnitt 6.1.4, Seite
193. Division mit Divisor ungleich 0 und Rest sind eindeutig definiert. Das Verhalten
bei Division durch 0 ist nicht festgelegt, führt in der Regel jedoch zu Programmabbruch.
Die Vergleichsoperationen gelten wie für ganzzahlige Werte.
Anmerkung 2.2 In C gibt es für die Potenzbildung weder bei ganzen noch bei rationalen Zahlen einen elementaren Operator. Statt dessen kann die Funktion pow (siehe
[KernR1988] oder [LoweP1995]) benutzt werden.
2
Ganzzahlige Ausdrücke: Ganzzahlige Ausdrücke werden auf die übliche Art gebildet.
Zu Vorrangregeln siehe die Literatur, [KernR1988] und [LoweP1995]. Ebenso zu eventuell
notwendigen expliziten Typumwandlungen.
Gemischte Ausdrücke, also Ausdrücke mit Operationen zwischen Werten verschiedener
Datentypen, werden nach den folgenden Regeln ausgewertet
1. Kurze Operanden (short) werden stets in int bzw. unsigned int umgewandelt
(integral promotion).
2. Jeder Ausdruck hat den höchstwertigen Typ seiner Operanden (siehe Tabelle 2.8).
2.5.2
Rationale Zahlen
Datentypen für rationale Zahlen: Zum Arbeiten mit Zahlen, die keine ganzen Zahlen sind, gibt es in höheren Programmiersprachen eigene Datentypen. Sie heißen manchmal
2.5. WERTEBEREICHE, OPERATIONEN, AUSDRÜCKE
5
4
3
2
1
long und int gleich
long double
double
float
unsigned int / unsigned long
int / long
long
7
6
5
4
3
2
1
und int ungleich
long double
double
float
unsigned long
long
unsigned int
int
Tabelle 2.8: Prioritäten der Datentypen
• float
•
double
49
• long double
Tabelle 2.9: Standardtypen rationaler Zahlen
real. In C gibt es drei Standard-Datentypen, sie sind in Tabelle 2.9 angegeben. Die Wertemengen dieser Datentypen sind durch Bereichsgrenzen und Genauigkeiten charakterisiert.
Auf die rechnerinterne Darstellung als Gleitpunktzahl (daher kommt die Bezeichnung
float) wird in Abschnitt 3.4 eingegangen. C schreibt vor, daß sowohl für Bereichsgrenzen
als auch für Genauigkeiten stets gelten muß
float ⊆ double ⊆ long double
Eine Übersicht über Bereichsgrenzen nach der Norm und auf dem Rechner donar ist in
Tabelle 2.10. zu sehen. Diese und andere installationsspezifischen Werte sind in der Datei
float.h (Tabelle 2.11) zu finden.
Rationalzahlige Konstanten und rationalzahlige Ein-/Ausgabedaten: Konstanten für rationale Zahlen können (in C) wahlweise mit oder ohne Exponentenangabe geschrieben werden.
Ohne Exponentenangabe z. B.
0.
+ .37
3.1416
− 99.99
Der Dezimalpunkt muß angegeben werden. Mit Exponentenangabe z. B.
3.0e − 5
− 4711e + 23
Der Buchstabe e muß angegeben werden.
4711.e2
50
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
Typ
float
double
long double
Typ
float
double
long double
Größter Wert
Norm
donar
1E + 37 3.402823466E + 38F
1E + 37 1.7976931348623157E + 308
1E + 37 1.189731495357231765085759326628007016E + 4932L
Kleinster (positiver) Wert
Norm
donar
1E − 37 1.175494351E − 38F
1E − 37 2.2250738585072014E − 308
1E − 37 3.362103143112093506262677817321752603E − 4932L
Typ
float
double
long double
Norm
1E − 5
1E − 9
1E − 9
Genauigkeit
donar
1.192092896E − 07F
2.2204460492503131E − 16
1.925929944387235853055977942584927319E − 34L
Typ
Anzahl gültiger Dezimalstellen
donar
Norm
float
double
long double
6
9
9
6
15
33
Tabelle 2.10: Mindestwertebereiche und Genauigkeiten rationaler Zahlen
Voreinstellung für die Genauigkeit ist double. Will man als Genauigkeit float oder long
double haben, so ist das durch nachgestelltes f bzw. L explizit anzugeben. Bei der Eingabefunktion scanf und der Ausgabefunktion printf werden die Formatierungsanweisungen
%f und %e
benutzt. Für Ergänzungen (Längenangaben, Typpräzisierungen u. a.) sei wieder auf Kernighan/
Ritchie [KernR1988] und Lowes/Paulik [LoweP1995] verwiesen.
Operationen und Ausdrücke mit rationalen Zahlen: Zwischen rationalen Zahlen
sind Addition, Subtraktion, Multiplikation und Division zulässig. Das Verhalten bei Überschreiten oder Unterschreiten des zulässigen Wertebeiches (Überlauf, overflow, Unterlauf,
2.5. WERTEBEREICHE, OPERATIONEN, AUSDRÜCKE
'
51
$
FLT_RADIX
FLT_ROUNDS
FLT_DIG
FLT_EPSILON
FLT_MANT_DIG
FLT_MAX
FLT_MAX_EXP
FLT_MIN
FLT_MIN_EXP
=
=
=
=
=
=
=
=
=
2
1
6
1.192093e-07
24
3.402823e+38
128
1.175494e-38
-125
DBL_DIG
DBL_EPSILON
DBL_MANT_DIG
DBL_MAX
DBL_MAX_EXP
DBL_MIN
DBL_MIN_EXP
=
=
=
=
=
=
=
15
2.220446e-16
53
1.797693e+308
1024
2.225074e-308
-1021
=
=
=
=
=
=
=
33
1.925929944387235853055977942584927319e-34
113
1.189731495357231765085759326628007016e+4932
16384
3.362103143112093506262677817321752603e-4932
-16381
%
LDBL_DIG
LDBL_EPSILON
LDBL_MANT_DIG
LDBL_MAX
LDBL_MAX_EXP
LDBL_MIN
LDBL_MIN_EXP
&
Tabelle 2.11: Gleitpunktzahlen auf donar (Auszug aus float.h)
underflow) legt die Norm nicht fest und ist installationsabhängig. Das gleiche gilt für
Division durch Null, die jedoch meistens zu Fehlerabbruch führt.
Das exakte Ergebnis einer Operation zwischen rationalen Zahlen kann auch dann zu einem
nicht darstellbaren Wert führen, wenn es nicht außerhalb der Grenzen des darstellbaren
Bereiches liegt. In einem solchen Fall muß gerundet werden. Die Art der Rundung ist
installationsabhängig und ist (in C) in der Datei float.h vermerkt und über FLT_ROUNDS
abfragbar. Siehe hierzu Lowes/Paulik [LoweP1995], Abschnitt 8.6 . Tabelle 2.11 zeigt, daß
auf donar nach Modus 1 gerundet wird, also zur nächsten darstellbaren Zahl.
Vergleichsoperationen sind <, <=, >, >=, == und !=. Sie sind zwischen allen darstellbaren
rationalen Zahlen definiert und liefern wahr oder falsch (siehe Unterabschnitt 2.5.4).
Arithmetische Ausdrücke mit rationalen Zahlen werden auf die übliche Art gebildet. Zu
52
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
Vorrangregeln siehe die Literatur, [KernR1988] und [LoweP1995]. Ebenso zu eventuell
notwendigen expliziten Typumwandlungen. Da in der Mathematik ganze Zahlen auch
rationale Zahlen sind, treten auch in C ganzzahlige Werte in Ausdrücken mit rationalen
Zahlen häufig auf. Dabei erfolgt eine implizite Typkonversion der ganzzahligen Werte in
rationale Werte (Tabelle 2.8).
Gelegentlich ist eine explizite Typumwandlung sinnvoll oder nötig. Dazu wird vor den
zu konvertierenden Wert der Datentyp, in den umgewandelt werden soll, in Klammern
geschrieben. Beispiel:
float x;
x = 3/2;
x = (float)3/2;
x = 3/(float)2;
x = (float)(3/2);
In der ersten und in der letzten Zuweisung erhält x wegen der ganzzahligen Division den
Wert 1.0; in den mittleren Zuweisungen den Wert 1.5 .
Weitere (von C unabhängige) Einzelheiten zu Gleitpunktzahlen sind in Abschnitt 3.4 zu
finden.
2.5.3
Zeichen
Datentypen für Zeichen: Es gibt die Datentypen
• signed char
• unsigned char
•
char
Tabelle 2.12: Standardtypen für Zeichen
Zeichen sind in C ganze Zahlen (vergleiche Seite 46). Sie sollten jedoch stets als eigener,
nichtnumerischer Datentyp behandelt werden!
Installationsabhängig ist char = signed char oder char = unsigned char.
Zu den Mindestbereichen siehe Tabelle 2.6. Die Werte für ein spezielles System stehen in
limits.h. Ebenso, ob char negative Werte annehmen kann. Die Werte für donar sind in
Tabelle 2.7 aufgeführt.
Zeichenkonstanten und Ein-/Ausgabedaten von Zeichen: Zeichenkonstanten werden als einzelne Zeichen, eingeschlossen in Apostrophe, geschrieben, z. B. ’K’, ’7’ oder
’-’. Welche Zeichen es gibt und welchen internen Zahlenwert sie haben, hängt vom jeweiligen Rechensystem ab und ist meistens für den Programmierer nicht von Bedeutung.
2.5. WERTEBEREICHE, OPERATIONEN, AUSDRÜCKE
53
In der Regel umfaßt der Datentyp char die abdruckbaren Zeichen (Groß- und Kleinbuchstaben, Ziffern, Satz- und Sonderzeichen) eines der beiden gängigen Codes ASCII (siehe
Tabelle 3.4, Seite 115) oder EBCDIC (siehe Tabelle 3.5, Seite 116).
Für einige Zeichen, die nicht darstellbare Steuerzeichen sind (z. B. „Zeilenvorschub“) oder
die (wie z. B. „Apostroph“) in der Syntax schon anderweitig belegt sind, sieht C besondere
Schreibweisen, die mit einem Fluchtsymbol beginnen („Escape-Sequenzen“), vor. Diese
Zeichen sind in Tabelle 2.13 aufgeführt.
\a
\b
\f
\n
\r
\t
\v
\’
\”
\?
\\
Piepen (alert)
Versetzen um eine Position nach links (backspace)
Seitenvorschub (formfeed)
Zeilenvorschub oder Zeilenende (linefeed bzw. new line)
Positionierung auf Zeilenanfang (carriage return)
Horizontaler Tabulator (horizontal tab)
Vertikaler Tabulator (vertical tab)
Apostroph (nicht Begrenzung einer Zeichenkonstante)
Anführungszeichen (nicht Begrenzung einer Konstante für eine Zeichenreihe)
Fragezeichen (nicht Bestandteil eines Trigraphen5 )
Backslash (nicht Einleitung einer Escape-Sequenz)
5
Trigraphen sind Zeichenfolgen, die mit 2 Fragezeichen beginnen und die auf Rechnern mit reduziertem
Zeichensatz den vollen für C benötigten Zeichensatz ermöglichen. Siehe auch Lowes/Paulik [LoweP1995],
Abschnitt 1.7.
Tabelle 2.13: Tabelle der Escape-Sequenzen in C
Wenn man den numerischen Wert eines Zeichens (für eine gegebene Rechenanlage!) kennt,
kann das Zeichen auch in hexadezimaler Schreibweise angegeben werden. Das ist gelegentlich für Steuerzeichen, die nicht standardmäßig vorgesehen sind, notwendig. Zum Beispiel
gehört das Fluchtsymbol (escape) nicht zu den Standardsteuerzeichen von Tabelle 2.13.
Es hat auf donar den numerischen Wert 27 und kann im Programm in der hexadezimalen
Schreibeweise ’\x1B’ angegeben werden. Zu Hexadezimaldarstellungen allgemein siehe
auch Abschnitt 3.5. Es gibt auch eine oktale Schreibweise. Mit Ausnahme des Wertes
Null (manchmal auch Binärnull genannt), der üblicherweise \0 geschrieben wird, sollte
die oktale Schreibweise nicht benutzt werden.
Der Standard erlaubt auch Zeichenkonstanten mit mehr als einem Zeichen. Diese sind
im allgemeinen jedoch nicht sinnvoll. Für künftige Codeerweiterungen, die möglicherweise
mehr als 256 Zeichen brauchen, ist der Datentyp wchar_t vorgesehen. Angaben zu diesem
Typ stehen in der Datei stddef.h.
Bei der Eingabefunktion scanf und der Ausgabefunktion printf wird die Formatierungsanweisung %c benutzt. Soll in printf ein %-Zeichen als Konstante ausgegeben werden, so
54
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
ist %% zu schreiben.
Operationen mit Zeichen:
Arithmetische Operationen mit Zeichen sind möglich und beziehen sich auf die numerischen Werte der Zeichen. Diese Operationen sollten und können vermieden werden.
Vergleichsoperationen sind <, <=, >, >=, == und !=. Sie sind zwischen allen Zeichendarstellungen definiert und liefern wahr oder falsch (siehe Unterabschnitt 2.5.4).
Die Vergleichsoperationen beziehen sich auf die numerischen Werte der Zeichen! Das
macht bei == und != keine Schwierigkeiten, wohl aber bei den anderen Vergleichsoperationen. Es gibt nämlich keine akzeptierte „natürliche Ordnung“ der darstellbaren Zeichen. Zwar ist z. B. stets a < b oder 9 > 5, aber ob alle Großbuchstaben kleiner sind
als alle Kleinbuchstaben, also z. B. Z < m gilt oder nicht, wird in den einzelnen Codes
unterschiedlich festgelegt. Ähnliches gilt für die Frage, ob Ziffern kleiner oder größer sind
als Buchstaben, für die Anordnung der Satz- und Sonderzeichen, für die Einordnung der
Umlaute und ß usw. Kommt es bei einer Anwendung auf eine bestimmte Ordnung der
Zeichen, d. h. auf eine bestimmte Sortierreihenfolge, an, so sollte man nicht die im Rechensystem gegebene Ordnung zugrundelegen, sondern über zusätzliche Datenstrukturen
und Funktionen die gewünschte Ordnung explizit festlegen.
2.5.4
Wahrheitswerte
Allgemeines: Wir gehen aus von einer zweielementigen Menge
B = {T, F } .
Die Elemente werden oft auch mit 0 und 1 oder mit 0 und L bezeichnet. Diese Menge
kann man aus unterschiedlicher Sicht betrachten:
• Algebraische Sicht. Mit den Verknüpfungen
x
F
F
T
T
y x+y
F
F
T
T
F
T
T
F
x
F
F
T
T
y x·y
F F
T
F
F F
T
T
bildet B einen Körper, mit den Verknüpfungen
x
F
F
T
T
y xty
F
F
T
T
F
T
T
T
x
F
F
T
T
y xuy
F
F
T
F
F
F
T
T
2.5. WERTEBEREICHE, OPERATIONEN, AUSDRÜCKE
55
einen komplementären, distributiven Verband, eine boolesche Algebra. In beiden
Fällen werden die Elemente meist mit 0 und 1 bezeichnet.
• Logische Sicht. T unf F werden als Wahrheitswerte „wahr“ und „falsch“ betrachtet,
und es werden Aussagenregeln untersucht.
Die Begründer der modernen algebraischen und logischen Sicht der Strukturen über B =
{T, F } sind Boole 6 und De Morgan 7 . Sie schufen die Grundlagen der Schaltalgebra und
damit die theoretischen Voraussetzungen für die moderne Computertechnik.
Datentyp für Wahrheitswerte: Wir wollen uns mit der logischen Sicht befassen und
Wahrheitswerte als Datentyp in C untersuchen. Wahrheitswerte dienen in Programmiersprachen der Ablaufsteuerung von Programmen (siehe Abschnitt 2.7). In C gibt es im
Gegensatz zu vielen anderen Programmiersprachen keinen eigenen Datentyp für Wahrheitswerte. „Wahr“ und „falsch“ werden durch ganze Zahlen dargestellt, und zwar gilt
Wert = 0
Wert 6= 0
'
bedeutet
bedeutet
„falsch“
„wahr“.
Einer sauberen Programmstrukturierung sind die folgenden Festlegungen sehr
dienlich
#define
#define
typedef
TRUE
FALSE
int
$
1
0
BOOLEAN;
Es wird dringend empfohlen, den Datentyp BOOLEAN durchgehend zu benutzen
and
&Konstanten für Wahrheitswerte als TRUE bzw. FALSE zu schreiben.
%
Operationen mit Wahrheitswerten: Werden auch boolesche Operationen oder logische Verknüpfungen genannt. Es seien x und y (Variable für abstrakte) Wahrheitswerte.
In C seien die Variablen a, b und c als BOOLEAN definiert. Im folgenden werden wichtige
Operationen mit Wahrheitswerten betrachtet und ihre Realisierung in C angegeben.
Einstellige Operationen.
Es gibt 22 = 4 Operationen, d. h. Abbildungen B → B.
x f(x)
F T
T T
f (x) = T (Konstante T ).
In C: c = TRUE;
Boole, George, ∗1815 Lincoln (England), †1864 Ballintemple bei Cork (Irland). Englischer Mathematiker und Logiker. Professor für Mathematik in Cork.
7
De Morgan, Augustus, ∗1806 Madura (Indien), †1871 London. Englischer Mathematiker und Logiker.
Professor für Mathematik am Londoner University College.
6
56
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
x f(x)
F F
T F
f (x) = F (Konstante F ).
In C: c = FALSE;
x f(x)
F F
T T
f (x) = x (Identität).
In C: c = a;
x f(x)
F T
T F
f (x) = x (Negation).
In C: c = !a;
Zweistellige Operationen.
Es gibt 24 = 16 Operationen, d. h. Abbildungen B × B → B. Dei beiden wichtigsten sind
x y f(x,y)
F F
F
F T
T
T F
T
T T
T
f (x, y) = x ∨ y (ODER, Disjunktion).
In C: c = a || b;
x y f(x,y)
F F
F
F T
F
T F
F
T T
T
f (x, y) = x ∧ y (UND, Konjunktion).
In C: c = a && b;
Als (nichttriviale) boolesche Operationen zwischen Wahrheitswerten bietet C nur !, ||
und &&. Es gilt jedoch: Alle n-stelligen (n ≥ 2) Verknüpfungen über B lassen sich auf
diese drei Operationen zurückführen (ohne Beweis). Zum Beispiel
x y f(x,y)
F F
T
F T
T
T F
F
T T
T
f (x, y) = x ⇒ y (WENN-DANN, Implikation).
In C: c = (!a) || (a && b);
2.5. WERTEBEREICHE, OPERATIONEN, AUSDRÜCKE
57
Prädikate: Unter einem n-stelligen Prädikat versteht man eine Abbildung, die jedem
n-Tupel eines Datenbereichs einen Wahrheitswert zuordnet. In C heißen Prädikate relationale (oder logische) Operationen. Bis auf ! sind sie in C alle zweistellig.
• Für Zahlen: ==, !=, <, <=, >, >=.
• Für Zeichen: ==, !=, <, <=, >, >=.
Siehe jedoch Seite 54.
• Für Adressen: ==, !=.
• Für Wahrheitswerte: Die oben behandelten Operationen !, || und &&. Hinzu kommen die Vergleiche == und !=. Achtung: In C sind zwei Wahrheitswerte, die als
Zahlen verschieden sind, auch als Wahrheitswerte verschieden, selbst wenn sie beide
„wahr“ darstellen! Siehe hierzu die folgende Anmerkung 2.3.
Anmerkung 2.3 Die Mehrdeutigkeit des Wahrheitswertes „wahr“ als Zahlenwert macht
in C gelegentlich Schwierigkeiten. In C gilt jedoch: Alle oben genannten Prädikate liefern
als Wahrheitsswert „wahr“ stets den Zahlenwert 1. Das gleiche gilt für Standardfunktionen,
zum Beispiel für den Vergleich von Zeichenreihen (siehe Unterabschnitt 2.6.3). Wenn
zusätzlich durchgehend die Konstante TRUE (Seite 55) benutzt wird und Wahrheitswerte
auf keine andere Art erzeugt werden, hat „wahr“ die eindeutige Darstellung 1.
In diesem Fall können einige weitere logische Funktionen einfach dargestellt werden. Zum
Beispiel
x y f(x,y)
F F
T
F T
F
T F
F
T T
T
f (x, y) = x ⇔ y (Äquivalenz).
In C: c = (a == b);
x y f(x,y)
F F
F
F T
T
T F
T
T T
F
f (x, y) = xXORy (XOR, exklusives ODER).
In C: c = (a != b);
2
2.5.5
Adressen
Adressen und Zeiger wurden in Abschnitt 2.4 eingeführt. Adressen identifizieren Speicherplätze für Variablen oder Konstanten (siehe Abbildung 2.6). Variable, die Adressen als
Werte annehmen, heißen Zeiger.
58
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
Datentypen für Adressen: Ist <typ> ein zulässiger Datentyp in C, so ist <typ> * der
Datentyp der Adressen von Variablen, die Werte vom Datentyp <typ> enthalten.
Rechnerintern sind Adressen in C natürliche Zahlen, deren explizite Werte jedoch nicht
von Interesse sind (siehe Seite 43).
Wertebereich: Es gibt keinen festen Wertebereich, jedoch für jede Variable eines Programmlaufs eine eindeutige Adresse. Zeiger können außer Adressen von Variablen (und
Konstanten) in C auch den Wert NULL – in anderen Programmiersprachen oft NIL – annehmen. Dieser Wert ist von allen anderen Adressen verschieden und besagt, daß der
Zeiger nicht auf eine Variable zeigt.
Konstante: Adressen können in C-Programmen nicht explizit (wie z. B. Zahlen) hingeschrieben werden. Es ist aber durchaus möglich, mit dem Sprachelement const eine bestimmte Adresse als Konstante zu deklarieren und ihr einen Namen zu geben, wie z. B.
'
float
const float
x;
y =13.02;
/*Variable*/
/*Konstante*/
float
const float
*x1;
*x2;
float
const float
*const x3 = &u;
*const x4 = &u;
&
$
/*Variabler Zeiger auf Variable*/
/*Variabler Zeiger auf Konstante*/
/*Konst. Zeiger auf Variable*/
/*Konst. Zeiger auf Konstante*/
%
Tabelle 2.14: Konstante und Variable in C
in den letzten beiden Deklarationen von Tabelle 2.14.
Ein-/Ausgabe: Es ist nicht möglich und macht auch keinen Sinn, in C Adressen einzugeben. Es ist möglich und gelegentlich sinnvoll, Adressen eines C-Programms auszugeben. Die Adressen sollten als natürliche Zahlen oder in Hexadezimaldarstellung z. B. mit
printf ausgegeben werden.
Operationen mit Adressen: Mit dem Operator & wird von einer benannten Variable
(oder einer benannten Konstante, siehe Seite 44) die Adresse gewonnen und kann z. B.
einem Zeiger zugewiesen werden. Man spricht von Adreßbildung oder Referenzierung. Mit
dem Operator * gelangt man von einer Adresse zu dem Wert an dem entsprechenden
Speicherplatz. Man spricht von Wertbildung oder Dereferenzierung. Das Programmbeispiel
in Tabelle 2.15 führt zur Ausgabe der Zahlen 111 und 112. Adressen können mit dem
Operator & auch von Variablen, die keinen Namen haben (siehe Seite 43), gebildet werden.
Die Bezeichnungen Referenzierung und Dereferenzierung werden (unabhängig von C) allgemein benutzt, um von einem „Objekt“ auf einen „Verweis auf das Objekt“ überzugehen
und umgekehrt. Allerdings ist manchmal nicht unmittelbar und eindeutig klar, was ein
2.5. WERTEBEREICHE, OPERATIONEN, AUSDRÜCKE
'
&
#include <stdio.h>
main()
{
int
x = 111;
int
*xz;
printf("x = %d\n", *(&x));
xz = &x;
*xz = 112;
printf("x = %d\n", x);
}
59
$
%
Tabelle 2.15: Verwendung von Referenzierung & und Dereferenzierung * in C
Objekt und was ein Verweis auf ein Objekt ist. Im Einzelfall sind dazu klare Festlegungen
zu treffen.
Zwischen Adressen gibt es die Vergleichsopertionen == und !=, die Wahrheitswerte liefern.
C erlaubt auch bestimmte Rechenoperationen mit Adressen. Diese sind jedoch nur im
Zusammenhang mit Reihungen sinnvoll und werden daher in Unterabschnitt 2.6.1 behandelt.
2.5.6
Aufzählungstypen
In Programmen sind oft auch Daten zu bearbeiten, die nicht numerischer Natur und
auch nicht als Zeichenreihen anzusehen sind. Ein typisches Beispiel sind Monate oder
Wochentage. Sie sind keine Zahlen und, da sie in verschiedenen Sprachen unterschiedlich
benannt sind, auch keine Zeichenreihen. Man kann sie in Programmen mittels Zahlen oder
Zeichenreihen darstellen, aber das ist unnatürlich und kann zu Fehlern führen, z. B. wenn
man die Summe Februar + März bildet.
Einige Programmiersprachen kennen den Datentyp Aufzählung (enumeration). In C kann
man, wie in dem Programmausschnitt in Tabelle 2.16 dargestellt, das Sprachelement enum
<name> benutzen. Damit werden in dem Beispiel die neuen elementaren Datentypen enum
Wochentage und enum Ostseeanrainer im Programm definiert. Die Elemente dieser Datentypen werden jeweils explizit benannt und aufgezählt. Die Namen der Elemente müssen
untereinander und von allen anderen Namen von Variablen und Konstanten des Programms verschieden sein (siehe jedoch Abschnitt 2.8).
Wertebereich, Konstanten, Variablen: Die Namen bezeichnen die (konstanten) Werte eines
neuen Datentyps. Weitere Werte hat der Datentyp nicht. Die Namen werden in Program-
60
'
&
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
#include <stdio.h>
main()
{
int h;
int *ph;
enum Ostseeanrainer
{
Daenemark, Schweden, Finnland, Russland,
Estland, Lettland, Litauen, Polen, Deutschland
};
enum Wochentag
{
Mo, Di, Mi, Do, Fr, Sa, So
};
enum Ostseeanrainer
land1, land2;
enum Wochentag
tag1 = So, tag2;
land1 = Polen;
land2 = Daenemark;
if (land1 == land2)
.
.
.
$
%
}
Tabelle 2.16: Aufzählungstypen in C (Programmausschnitt)
men zur direkten Angabe von Werten des Datentyps benutzt. Variable werden auf die
übliche Art erklärt, z. B. tag1 im Programm von Tabelle 2.16. Mit expliziten Konstantendeklarationen wie z. B.
const enum Wochentag
tag3=Do;
können weitere Namen für die Werte eingeführt werden. Für diese explizit erklärten Konstanten ist auch eine Adreßbildung möglich (z. B. &tag3), nicht jedoch für die Namen, die
bei der Deklaration des Datentyps benutzt werden (&Do wird bei der Übersetzung nicht
akzeptiert).
Ein-/Ausgabe: Eine direkte Ein- oder Ausgabe über die Namen der Werte einer Aufzählung ist nicht möglich.
Operationen mit Aufzählungswerten: Es sind Zuweisungen zu Variablen und Vergleiche
2.6. REIHUNGEN, ZEICHENREIHEN, SÄTZE
61
auf Gleichheit und Ungleichheit möglich. Wichtig ist, daß Aufzählungstypen als Indexbereiche von Reihungen verwandt werden können (siehe Seite 64).
Anmerkung 2.4 C-intern sind Aufzählungswerte ganze Zahlen vom Typ int. Den Namen eines Aufzählungstyps werden bei der Deklaration beginnend bei 0 in aufsteigender
Reihenfolge natürliche Zahlen als Werte zugeordnet. Im Beispiel von Tabelle 2.16 hat
Deutschland den Wert 8. Durch explizite Wertfestlegung in der Definition kann davon
abgewichen werden. Würde in Tabelle 2.16 z. B. Do = -5 geschrieben werden, so hätten
Mo, Di, Mi die Werte 0, 1, 2 und Do, Fr, Sa, So die Werte -5, -4, -3, -2. Auch mehrere
Namen mit dem gleichen numerischen Wert sind möglich.
Mit Aufzählungswerten kann in C wie mit ganzen Zahlen gerechnet werden. Dadurch ist es
möglich, beliebig unsinnige Dinge zu tun. Zum Beispiel kann Finnland + Do ausgerechnet
oder geprüft werden, ob So kleiner als Schweden ist. In einigen Programmiersprachen, z. B.
Pascal oder Modula-2, sind Aufzählungstypen echte, von ganzen Zahlen und untereinander
verschiedene Datentypen. Siehe zum Beispiel Wirth [Wirt1996], Seite 22.
Es wird empfohlen, die numerischen Eigenschaften von Aufzählungstypen
nach Möglichkeit nicht zu benutzen.
Einige reale Wertemengen wie z. B. Wochentage oder Monate tragen eine natürliche Anordnung, bei anderen wie z. B. den Staaten der Europäischen Union ist keine natürliche
Anordnung gegeben. Selbst bei einer natürlicher Anordnung ist die Benutzung der numerischen Ordnung oft nicht sinnvoll, da die natürliche Anordung in der Regel zyklisch8 ist
(Dezember liegt vor Januar).
2
2.6
Reihungen, Zeichenreihen, Sätze
In den meisten Programmiersprachen, auch in C, stehen zwei Arten von zusammengesetzten Datentypen zur Verfügung – Reihungen und Sätze. Siehe auch die Übersicht auf
Seite 37.
2.6.1
Reihungen
Allgemeines: Reihungen bestehen aus Werten des gleichen (elementaren oder abgeleiteten) Datentyps. Die einzelnen Bestandteile einer Reihung werden über einen Index
angesprochen. Die englische Bezeichnung für Reihung ist array. Im Deutschen spricht man
oft auch von einem Feld, manchmal auch von einem Vektor.
8
Sie ist damit gar keine Ordnung im mathematischen Sinne. Siehe Seite 616.
62
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
Die Bezeichnung Feld wird in diesem Buch ausschließlich für die Bestandteile
eines Satzes benutzt (siehe Unterabschnitt 2.6.4, Seite 68).
Die Bezeichnung Vektor sollte man nur im Zusammenhang mit Vektorräumen benutzen.
Abstrakt gesehen ist der Bereich, aus dem die Indizes gewählt werden, der Indexbereich,
eine beliebige nichtleere endliche Menge. Sind die Indizes die n-Tupel eines Produkts
endlicher Mengen (siehe Abschnitt A.4, Seite 615, im Anhang), so spricht man von einer n-dimensionalen Reihung, im Fall n ≥ 2 von einer mehrdimensionalen Reihung. In
Programmiersprachen werden Indexbereiche aus Intervallen ganzer Zahlen oder Wertebereichen von Aufzählungstypen oder Produkten hieraus gebildet.
Reihungen in C: In C gibt es für Reihungen das Sprachmittel
<datentyp>
<name>[<größe>];
Dabei ist <datentyp> ein in C zugelassener Datentyp, <name> der Bezeichner der Reihung und <größe> eine positive natürliche Zahl, die die Anzahl der Elemente der Reihung
angibt. Im Beispiel von Tabelle 2.17 wird die Reihung ir deklariert. Sie besteht aus 4 Ele'
&
$
#include <stdio.h>
#define LAENGE
4
main()
{
int
ir[LAENGE];
ir[0] = 9;
ir[1] = 10;
ir[2] = 11;
ir[3] = 12;
printf("ir[0] = %d
ir[3] =
%d\n", ir[0], ir[3]);
}
%
Tabelle 2.17: Reihung in C
menten, die ganze Zahlen als Werte aufnehmen. Vorbesetzungswerte sind zulässig. Man
könnte im Beispielprogramm statt der vier Einzelzuweisungen auch die Deklaration erweitern
int
ir[ ] = {9,10,11,12};
Die Größe braucht dann auch nicht angegeben zu werden, sie ergibt sich aus der Zahl der
zugewiesenen Vorbesetzungswerte.
2.6. REIHUNGEN, ZEICHENREIHEN, SÄTZE
63
In C ist der Indexbereich einer Reihung von n Elementen (n ≥ 1) stets die Menge der
natürlichen Zahlen {0, 1, 2, · · · , n − 1}. Abbildung 2.7 zeigt die Variablen, die durch das
Programm von Tabelle 2.17 eingerichtet werden. Vergleiche auch Abbildung 2.6.
.....
...
...
...
.....
....
9
α
...................
........
...
.....
......... ............
..........
...
.........
.
. .. .
..
...
...
...
...
....
.
ir[0]
........ .......
......
..
.
......
....... ........
10
β
...................
........
...
.....
......... ...........
...........
..
..........
.
. ..
..
...
...
...
...
....
.
ir[1]
........ ......
......
.
..
.....
........ .......
11
γ
...................
........
...
.....
......... ...........
...........
..........
.
.
. .. .
..
...
...
...
...
....
.
........ .......
.....
.
.
......
........ .......
ir[2]
12
δ
...................
........
...
.....
......... ............
..........
...
.........
.
. .. .
..
...
...
...
...
....
.
........
...
.
.
...
.......
ir[3]
Abbildung 2.7: Variablen einer Reihung
Sie werden im Programm über die indizierten Namen ir[0], ir[1], ir[2], ir[3] angesprochen. Durch den Ablauf des Programms erhalten sie die Werte 9, 10, 11, 12. Die
Adressen dieser Variablen sind α, β, γ und δ.
Die Größe einer Reihung, d. h. die Anzahl ihrer Elemente, wird in C bei der Übersetzung des Programms festgelegt und muß eine positive natürliche Zahl sein. Sie kann im
Programm direkt als numerischer Wert geschrieben werden, z. B. ir[4]. Zweckmäßiger
ist, sie wie im Beispiel durch eine Textersetzungsdefinition #define festzulegen. In C gibt
es auch die Möglichkeit, die Größe durch eine Variable (oder Konstante) mit Vorbesetzungswert anzugeben. Z. B. kann im Programm von Tabelle 2.17 statt #define LAENGE
4 auch int LAENGE = 4; geschrieben werden. Es wird jedoch dringend geraten, diese
Möglichkeit nicht zu benutzen, da sie einerseits bei falschen (z. B. negativen) Angaben
zu sehr unübersichtlichem Fehlerverhalten führen kann und andererseits gegenüber der
Größenangabe durch #define keine Vorteile bietet.
Indexwerte zur Adressierung von Elementen einer Reihung können durchaus dynamisch
zur Ablaufzeit des Programms bestimmt werden. Darin liegt ja gerade der Vorteil von
Reihungen! Zur Berechnung eines Indexwertes kann jeder Ausdruck, der eine passende
natürliche Zahl liefert, genommen werden. Zum Beispiel könnte man im Programm von
Tabelle 2.17 statt ir[2] = 11; auch ir[(ir[1]-ir[0]+1)] = 11; schreiben (was allerdings in diesem Fall nicht sehr sinnvoll wäre). Der Index, der für die Adressierung eines
Feldes einer Reihung benutzt wird, muß eine natürliche Zahl aus dem zulässigen Bereich
sein. Unzulässige Indexwerte können zu sehr schwer zu findenden Fehlern führen.
Mehrdimensionale Reihungen in C: C kennt keine mehrdimensionalen Reihungen.
Da jedoch in C Reihungen von beliebigen zulässigen Datentytpen gebildet werden können,
kann man mehrdimensionale Reihungen durch Schachtelung eindimensionaler Reihungen
gewinnen. Das soll am Beispiel der Matrixmultiplikation erläutert werden. In Tabelle 2.18
64
'
&
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
#include <stdio.h>
#define L
15
main()
{
int
i,m,n;
float
A[L][L];
float
B[L][L];
float
C[L][L];
.
.
.
for (m=0; m<L; m=m+1)
{
for (n=0; n<L; n=n+1)
{
A[m][n] = 0.;
for (i=0; i<L; i=i+1)
{
A[m][n] = A[m][n] + (B[m][i])*(C[i][n]);
};
};
};
.
.
.
}
$
%
Tabelle 2.18: Matrixmultiplikation in C
ist ein Programmausschnitt angeben, der die Matrixmultiplikation zweier quadratischer
Matrizen der Ordnung L zeigt.
Reihungen über Aufzählungen in C: Die Gleichsetzung von Aufzählungstypen mit
natürlichen, bei 0 beginnenden Zahlen (siehe Anmerkung 2.4, Seite 61) hat auch Vorteile.
Sie erlaubt es, Aufzählungstypen unmittelbar als Indexbereiche zu nutzen. So kann man
z. B. mit Hilfe der Deklarationen
float
enum
wochenumsatz[7];
{Mo, Di, Mi, Do, Fr. Sa, So};
2.6. REIHUNGEN, ZEICHENREIHEN, SÄTZE
65
mit wochenumsatz[Sa] den Umsatz einer Gastwirtschaft am Sonnabend der laufenden
Woche bezeichnen. Auch mehrdimensionale Reihungen sind möglich. Die Deklarationen
float
enum
enum
jmumsatz[12][10];
{Jan, Feb, Mar, Apr, Mai, Jun, Jul, Aug, Sep, Okt, Nov, Dez};
{j1990, j1991, j1992, j1993, j1994, j1995, j1996, j1997, j1998,
j1999};
gestatten es, den Umsatz im Mai 1995 über jmumsatz[Mai][j1995] anzusprechen.
2.6.2
Zeigerarithmetik in C
Im Programm DYNREIHUNG (Tabellen 2.3 und 2.4, Beispiel 2.1, Seite 38) wird auch
eine Reihung benutzt, allerdings eine, deren Größe von Programmlauf zu Programmlauf
verschieden ist und deren Speicherplatz am Beginn des Programmlaufs durch einen Funktionsaufruf malloc bereitgestellt wird. Es werden zahl Speicherplätze, die jeweils einen
Wert vom Typ int aufnehmen können, angelegt, also Variablen vom Typ int. Die Adresse der ersten dieser Variablen wird als Wert einer Variablen vom Typ int*, einem Zeiger,
mit Namen zeiger zugewiesen. Mit zeiger+1 wird die zweite Variable adressiert, mit
zeiger+2 die dritte usw. Der Inhalt der ersten Variablen wird durch *(zeiger), der
der zweiten durch *(zeiger+1) usw angegeben. Auf diese Weise kann man so rechnen,
als hätte man eine Reihung mit Indexbereich 0, 1, 2, · · · , zahl − 1, und das wird im Programm DYNREIHUNG auch so gemacht. Dabei wird allerdings eine von [ ] verschiedene
Notation benutzt.
Generell gilt in C das folgende. Ist α eine Adresse einer Variablen für Werte vom Typ
<typ>, so ist α + i auch wieder eine Adresse dieses Typs. i ist dabei ein Ausdruck für eine
ganze, durchaus auch negative Zahl. Dabei wird so getan, als sei die durch α gegebene
Variable Element einer hinreichend großen Reihung von Variablen vom Typ <typ>, zu
der auch das Element mit der Adresse α + i gehört. In Abbildung 2.7 zeigen sowohl
α + 1 als auch δ − 2 auf die Variable mit Adresse β. Im Programm ließe sich das (etwas
umständlich) als &(ir[0])+1 und &(ir[3])-2 formulieren. Es liegt in der Verantwortung
des Programmierers, sicherzustellen, daß mit Zeigerarithmetik gebildete Adressen auch
wirklich zu Variablen des Typs <typ> gehören. C führt keine Überprüfung aus.
Wie dargelegt, kann man in C die Adressierung der Elemente einer im Programm deklarierten Reihung wahlweise durch die Indexangabe mit [ ] oder durch Zeigerarithmetik
ausdrücken. Diese Wahlmöglichkeit hat man auch, wenn die Reihung gar nicht explizit
deklariert wird, sondern wie im Programm DYNREIHUNG (Seite 41) implizit durch eine
Variablengenerierung mit malloc entsteht. Im Programm DYNREIHUNG kann man z. B.
ohne weiteres statt summe = summe + *(zeiger+i); auch summe = summe + zeiger[i];
schreiben. Die Möglichkeiten
66
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
• Die Reihung wird im Programmtext mit fester Größe deklariert.
• Die Reihung wird im Programmlauf durch Variablengenerierung und Speicherung
der Anfangsadresse in einem Zeiger realisiert.
sowie
• Die Elemente der Reihung werden über Indexangabe [ ] angesprochen.
• Die Elemente der Reihung werden über Zeigerarithmetik angesprochen.
können beliebig kombiniert werden. Ob man feste Reihungsdeklaration oder Variablengenerierung zum Beginn eines Programlaufes wählt, hängt vom zu lösenden Problem ab. In
welcher Form man die Elemente der Reihung adressiert, ist eine Frage des Programmierstils, also eigentlich Geschmackssache.
Anmerkung 2.5 Es gibt Aufgaben, bei denen Variablen des gleichen Datentyps bearbeitet werden, aber der dafür benötigte Gesamtplatz weder zum Zeitpunkt der Programmerstellung noch zu Beginn eines Programmlaufes bekannt ist, sondern sich erst während
des Programmlaufes ergibt. Für diese Aufgaben kann man Reihungen nicht benutzen,
sondern muß auf andere Datentrukturen und Zugriffstechniken zurückgreifen, wie z. B.
Listen (Kapitel 8) oder Bäume (Kapitel 9).
2
Anmerkung 2.6 Obwohl mit Reihungen, die im Programm deklariert werden, und mit
Reihungen, die man durch Variablengenerierung erzeugt, auf die gleiche Weise gearbeitet
werden kann, gibt es zwischen den beiden Arten von Reihungen auch einen wesentlichen
Unterschied. Das soll am Beispiel der Programme in den Tabellen 2.17 und 2.19 erläutert
werden.
.....
....
...
..
....
......
α
.............................
.....
..
........
.....................
..
.........
.
. .. .
..
...
...
...
...
....
.
........
...
.
.....
....
...
.
...
........
...
....
.....
9
α
........
......... ............
....
..
......
.......................
........ .......
......
.
..
.....
........ .......
10
β
........
......... ...........
.
.....
..
........
...................
........ .......
......
..
.
......
........ ........
11
γ
..................
........
...
.....
.
......... ...........
........
........ .......
......
.
..
.....
........ .......
12
δ
..................
........
...
.....
.
............................
........
...
.
..
..
........
ar
Abbildung 2.8: Variablen einer dynamisch generierten Reihung
Die Abbildungen 2.7 und 2.8 zeigen die zugehörigen Variablen mit ihren Werten, Adressen
und Namen. ar ist der Name eines Zeigers, der die Adresse und den Wert α hat. Über
Dereferenzierung dieser Variablen und anschließender Adreßmodifikation über [ ] (oder
Zeigerarithmetik) kommt man zu den unbenannten Variablen mit den Adressen α, β, γ,
und δ.
2.6. REIHUNGEN, ZEICHENREIHEN, SÄTZE
'
&
#include <stdio.h>
#define LAENGE
4
main()
{
int
*ar;
ar = (int *)malloc(sizeof(int)*LAENGE);
ar[0] = 9;
ar[1] = 10;
ar[2] = 11;
ar[3] = 12;
printf("ar[0] = %d
ar[3] = %d\n", ar[0], ar[3]);
}
67
$
%
Tabelle 2.19: Durch Variablengenerierung erzeugte Reihung in C
Der im Programm von Tabelle 2.17 (Abbildung 2.7) auftretende Name ir (ohne eckige
Klammern) hat eine besondere Bedeutung. Er bezeichnet keine Variable, ist aber ein
Name des Programms. Er bezeichnet direkt eine Adresse, in unserem Fall α. ir kann
nicht wie ar ein Wert zugewiesen werden. Wird das Programm in Tabelle 2.17 um die
Deklaration int *zi erweitert, so haben die Anweisungen
zi
zi
=
=
ir;
&(ir[0]);
die gleiche Wirkung. Sie weisen zi den Wert α zu.
2.6.3
2
Zeichenreihen
Zeichenreihen sind endliche Folgen von Zeichen. Die Zeichen werden aus einer nicht leeren, endlichen Menge, dem Alphabet, genommen. Ist auf dem Alphabet eine lineare Ordnung (siehe Abschnitt A.5, Seite 616) gegeben, so ergibt sich daraus eine lexikographische
Ordnung auf den Zeichenketten. Zeichenreihen werden auch Zeichenketten genannt, die
englische Bezeichnung ist string. Zu weiteren Einzelheiten über Zeichenreihen siehe die
allgemeinen Erläuterungen in Abschnitt 3.6, Seite 112. In Programmiersprachen bilden
Zeichenreihen einen wichtigen Datentyp. Als Operationen mit Zeichenreihen benötigt man
Vergleichsoperationen und Operationen mit Teilzeichenreihen (Suchen, Entfernen, Einfügen). Ein wichtiger Sonderfall des Einfügens von Teilzeichenreihen ist das Zusammensetzen von Zeichenreihen (Konkatenation).
68
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
In C sind Zeichenreihen, anders als in einigen anderen Programmiersprachen, kein elementarer Datentyp, sondern Reihungen von Elementen vom Typ char. Allerdings gibt es
einige Besonderheiten:
• Da Zeichenreihen ihrer Natur nach von variabler Länge sind, Reihungen in C jedoch
stets eine feste Länge haben, wurde die Festlegung getroffen, jede Zeichenreihe mit
dem zusätzlichen Zeichen Binärnull (siehe Seite 53) abzuschließen. Dieses Zeichen
belegt zwar ein Element in der Reihung, wird aber nicht zur Zeichenreihe gezählt.
Die leere Zeichenreihe besteht demnach nur aus dem Zeichen Binärnull.
• Es gibt in der Standardbibliothek (siehe Fußnote 3 auf Seite 38) von C eine Reihe
leistungsfähiger Funktionen zur Bearbeitung von Zeichenreihen [LoweP1995].
• Zeichenreihen lassen sich in C direkt, also als Konstanten angeben. Dazu wird die
Zeichenreihe in Anführungstriche gesetzt. Man beachte, insbesondere bei Größenangaben, daß im Programm stets auch noch die abschließende Binärnull gespeichert
wird.
Beispiele:
char
char
zr1[ ]
*zr2
=
=
”Zeichenreihe1”;
”Zeichenreihe2”;
Die beiden Deklarationen haben jedoch nicht die gleiche Bedeutung. Vergleiche Anmerkung 2.6.
• Für die Ein-/Ausgabe von Zeichenreihen mit den Standardfunktionen printf und
scanf gibt es das Format %s.
2.6.4
Sätze
Allgemeines: Sätze (record) bestehen aus mehreren Werten, die i. a. zu unterschiedlichen (elementaren oder abgeleiteten) Datentypen gehören. Die einzelnen Bestandteile
eines Satzes heißen Felder (field). Alle Sätze des gleichen Datentyps sind auf die gleiche
Art aus Feldern aufgebaut. Die Felder werden über Feldnamen angesprochen. Zusammen
mit dem Feldnamen ist auch der Datentyp eines Feldes festgelegt.
Statt Satz wird im Deutschen auch Verbund gesagt. Für Feldname gibt es auch die Bezeichnung Selektor (selector). In C heißen Sätze Strukturen (structure) und Felder (member). Die Bezeichnung Struktur ist sehr allgemein und wird in diesem Buch nicht für
Datentypen benutzt. Benutzt wird natürlich das entsprechende Sprachelement struct
aus C. Zu den Begriffen Satz und record ist anzumerken, daß sie auch noch eine andere
wichtige Bedeutung haben: Sie bezeichen die elementaren Komponenten einer Datei oder
einer Datenbank.
2.6. REIHUNGEN, ZEICHENREIHEN, SÄTZE
69
Sätze in C: Mit dem Sprachkonstrukt struct kann man in C einen abgeleiteten Datentyp definieren, d. h den Aufbau seiner Sätze festlegen. Für diesen Datentyp können Variablen und Konstanten deklariert und wahlweise die Felder analog zu Reihungen (Seite 62)
mit Vorbesetzungswerten versehen werden. Wichtiger als die Deklaration von Variablen im
Programm ist bei Satz-Datentypen die dynamische Variablengenerierung zur Ablaufzeit
des Programms. Satz-Datentypen bilden die Basis für die Konstruktion von Datenstrukturen (siehe Übersicht Seite 38 und Kapitel 7 „Allgemeines zu Datenstrukturen“). Wie
Sätze in C-Programmen benutzt werden können, soll am Beispiel des Programms LVA
gezeigt werden.
Beispiel 2.3 (Programm LVA)
Aufgabenstellung, Lehrverantaltungsdatei, Kommandos: Es sei eine Textdatei gegeben, in der zeilenweise Lehrveranstaltungseinträge gespeichert sind. Siehe Tabelle 2.20.
Sie enthält pro Lehrveranstaltung die Nummer, den Namen, den Namen des Dozenten
'
80003
80009
90093
10913
90067
90055
90017
80013
80037
80113
60009
-1
&
Markovketten
Analysis
Graphalgorithmen
abcd
Programmierung
Java
Modellierung
Zahlentheorie
Codierungstheorie
Stochastik
Niederlaendisch
*
Markov
Euler
Scala
efg
Stiege
Boles
Soluz
Mueller
Mayr-Helm
Bernoulli
Tenhoff
*
Di
Mi
Di
Di
Di
Mi
Mo
Mo
Fr
Di
Do
*
18
10
18
10
10
10
10
14
08
10
20
*
18
12
20
12
12
12
12
16
10
12
22
*
A321
H1
H5
H5
G
F
H5
H5
H3
H5
A408
*
*
Do
Do
*
Do
*
Mi
Do
*
Do
*
*
*
08
10
*
10
*
08
10
*
10
*
*
*
10
12
*
12
*
10
12
*
12
*
*
*
H2
A209
*
F
*
H1
A315
*
A209
*
*
$
%
Tabelle 2.20: Lehrveranstaltungsdatei
und Angaben zu Zeit und Ort. Es ist ein C-Programm zu schreiben, das die Lehrveranstaltungsdatei einliest und dann mit dem Benutzer einen Dialog beginnt. In diesem Dialog
gibt der Benutzer Anweisungen, die das Programm bearbeitet und dann beantwortet. Es
sollen zunächst nur die Kommandos hilfe, help, ende und lvanamen realisiert werden.
In Tabelle 2.21 ist die Wirkung der Kommandos hilfe und help zu sehen. Kommando
lvanamen bewirkt, daß eine Tabelle der Lehrveranstaltungen, aufsteigend sortiert nach
Lehrveranstaltungsnamen, ausgegeben wird (siehe Tabelle 2.22)
Das Programm LVA: In den Tabellen 2.23, 2.24 und 2.25 sind die ersten drei Teile
des Programms LVA zu sehen. Teil I ist der Programmkopf. In Teil II werden globale
Größen deklariert: Strukturen, Routinen, Variable. Bei den Strukturen wird der Satztyp
lvstz (Lehrveranstaltungssatz) eingeführt. Die Sätze dieses Typs enthalten Felder für die
70
'
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
$
LVA: Name der Eingabedatei? lvadat
<LVA-Anweisung>: hilfe
LVA: Die zulaessigen Anweisungen sind zur Zeit (November 1998):
ende
Beendet den Programmlauf.
hilfe
Diese Anweisung.
help
Diese Anweisung.
lvanamen
Ausgabe einer Liste aller Lehrveranstaltungen
<LVA-Anweisung>: help
LVA: Die zulaessigen Anweisungen sind zur Zeit (November 1998):
ende
Beendet den Programmlauf.
hilfe
Diese Anweisung.
help
Diese Anweisung.
lvanamen
Ausgabe einer Liste aller Lehrveranstaltungen
&
'
Tabelle 2.21: Anweisungen des Programms LVA
<LVA-Anweisung>: lvanamen
80009
Analysis
80037
Codierungstheorie
90093
Graphalgorithmen
90055
Java
80003
Markovketten
90017
Modellierung
60009
Niederlaendisch
90067
Programmierung
80113
Stochastik
80013
Zahlentheorie
10913
abcd
<LVA-Anweisung>: ende
&
%
Euler
Mayr-Helm
Scala
Boles
Markov
Soluz
Tenhoff
Stiege
Bernoulli
Mueller
efg
Mi
Fr
Di
Mi
Di
Mo
Do
Di
Di
Mo
Di
10
08
18
10
18
10
20
10
10
14
10
12
10
20
12
18
12
22
12
12
16
12
H1
H3
H5
F
A321
H5
A408
G
H5
H5
H5
Do
*
Do
*
*
Mi
*
Do
Do
Do
*
$
08
*
10
*
*
08
*
10
10
10
*
10
*
12
*
*
10
*
12
12
12
*
H2
*
A209
*
*
H1
*
F
A209
A315
*
%
Tabelle 2.22: Tabelle der Lehrveranstaltungsnamen
Lehrveranstaltungsnummer, den Lehrveranstaltungsnamen, den Namen des Dozenten und
Stundenplanangaben. Sie enthalten außerdem zwei Verweisfelder (*links und *rechts),
die für den Aufbau eines Binärbaumes9 benutzt werden. Bei den Routinen sind die Unterprogramme hilfe, aufbau, lvanamen und lvaneu, in die das Gesamtprogramm zerlegt
worden ist, aufgeführt. Als globale Variable wird schließlich noch der Zeiger namensbaum
deklariert. Er enthält die Adresse der Wurzel des aufzubauenden Binärbaums.
9
Binärbäume werden ausführlich in Abschnitt 9.1 behandelt.
2.6. REIHUNGEN, ZEICHENREIHEN, SÄTZE
'
/****************************************************************/
/* Programm LVA
*/
/* Liest eine Liste von Lehrveranstaltungsangaben ein und baut */
/* daraus eine programminterne baumartige Datenstruktur auf.
*/
/* Es wird vorausgesetzt, dass die Eingabeliste korrekt ist.
*/
/* Nach dem Aufbau der Datenstruktur geht das Programm in eine */
/* Abfrageschleife, in der es Anweisungen erwartet.
*/
/*
*/
/* Zulaessige Anweisungen sind:
ende, hilfe, help, lvanamen */
/*
(hier sollen weitere folgen) */
/****************************************************************/
#include <stdio.h>
#include <malloc.h>
#define
TRUE
1
#define
FALSE 0
typedef
int
boolean;
&
71
$
%
Tabelle 2.23: Programm LVA (Teil I)
In Teil III (Tabelle 2.25) ist das Hauptprogram main wiedergegeben. Es besetzt einen Puffer vor und ruft dann das Unterprogramm aufbau auf, mit dem die Lehrveranstaltungsdatei eingelesen und die programminterne Datenstruktur, der Binärbaum, aufgebaut wird.
Anschließend wird in einer Schleife das nächste Kommando abgefragt und ausgeführt.
Das Unterprogramm aufbau ist als Teil IV und Teil V des Programms LVA in den Tabellen
2.26 und 2.27 zu sehen. Es erfragt den Namen der Eingabedatei, öffnet diese und legt mit
dem ersten Eintrag der Eingabedatei als Wurzel einen Binärbaum an. In einer Schleife
werden dann die weiteren Einträge der Eingabedatei an der Stelle, die in alphabetischer
Anordnung dem Lehrveranstaltungsnamen entspricht, in den Binärbaum eingefügt.
Der von aufbau angelegte Binärbaum ist in Abbildung 2.9 dargestellt. Die Abbildung
zeigt nur die Lehrveranstaltungsnamen und die Verweise, die übrigen Felder der Sätze sind
nicht zu sehen. Jeder Satz ist ein Knoten in dem Binärbaum und hat Verweise auf einen
linken und einen rechten Nachfolger. Ist der Verweis NULL, gibt es keinen Nachfolger, andernfalls zeigt der Verweis auf einen Knoten, der Wurzel des linken (rechten) Unterbaumes
ist. Die lexikographische Ordnung der Lehrveranstaltungsnamen10 ist in dem Binärbaum
auf folgende Art festgehalten: Im linken Unterbaum eines jeden Knotens sind alle Namen
kleiner/gleich dem Namen des Knotens, im rechten Unterbaum sind sie größer.
In aufbau wird das Unterprogramm lvaneu, das in Tabelle 2.28 zu sehen ist, aufgerufen.
Dieses fordert Speicher für den nächsten Satz an (malloc) und liest den Eintrag aus der
Eingabedatei.
10
Man beachte, daß das kleine a in abcd kleiner ist als jeder Großbuchstabe.
72
Markov..........
◦......................
............ ◦
............
.......
............
.
.
.
.
ketten
.
.......
.
.
.
.
.
.......
.........
..
............
Analysis
.......
.......
.......
.......
.......
.......
.......
.......
.......
.......
....... ...
.
....................
• ........◦...
abcd
....
...
...
....
...
.
.
.
...
...
...
...
....
.
.
.
. .....
...........
.........
Graph...
◦........
......◦
.........
........
...
algorithmen
........
.
.
.
.
.
.
.
...
.
........
..
........
........
.........
........
.
.
.
.
.
.
.
.
.........
........
.........
........
........
.
.
.
.
.
.
.
..
..............
................
Codierungstheorie
• •
Program...
◦.........
......◦
.........
........
...
mierung
........
.
.
.
.
...
.
.
.
.
.
........
..
........
........
.........
........
.
.
.
.
.
.
.
.
.........
........
.........
........
........
.
.
.
.
.
.
.
. ..
..............
................
...
...
...
...
...
...
...
.
..........
.......
..
Java
◦ •
.
...
...
...
...
.
.
..
...
...
...
...
.
.
...
...
...
. ....
................
...
• •
Modellierung
• ........◦...
Zahlentheorie
◦ •
• •
Stochastik
• •
....
...
...
....
...
.
.
....
...
...
...
...
.
.
...
.. ...
.............
.......
Niederländisch
...
...
...
...
...
...
...
...
...
.. .
..............
.....
Abbildung 2.9: Binärer Suchbaum der Lehrveranstaltungen
..
...
...
...
...
.
.
..
...
...
...
...
.
.
...
...
...
. ...
............
.
.
......
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
............
...........
...........
............
............
.
.
.
.
.
.
.
.
.
.
.
...
............
...........
...........
............
. ......................
.
.
.
.
.....................
2.6. REIHUNGEN, ZEICHENREIHEN, SÄTZE
'
/***********************************************************/
/*
Strukturen
*/
/***********************************************************/
struct lvstz
/* Satz fuer eine Lehrveranstaltung
*/
{ int
lvanr;
char
lvaname[51];
char
lvadozent[26];
struct lvstz
*links;
struct lvstz
*rechts;
/*
Stundenplanangaben 1.Tag
*/
char
tag1[3];
char
anf1[3];
char
ende1[3];
char
raum1[11];
/*
Stundenplanangaben 2.Tag
*/
char
tag2[3];
char
anf2[3];
char
ende2[3];
char
raum2[11];
};
typedef
struct lvstz lvasatz;
/***********************************************************/
/*
Routinen
*/
/***********************************************************/
void
hilfe(void);
void
aufbau(void);
void
lvanamen(lvasatz *lvalauf);
lvasatz
*lvaneu(FILE *fd);
/***********************************************************/
/*
Globale Variable und Konstanten
*/
/***********************************************************/
lvasatz
*namensbaum=NULL;
/* Zeiger auf
*/
/* Wurzel des Namensbaumes */
& /***********************************************************/
73
$
%
Tabelle 2.24: Das Programm LVA (Teil II)
Tabelle 2.29 zeigt die Routine hilfe, die beim Kommando gleichen Namens aufgerufen
wird, als Teil VII des Programms LVA.
Die Routine lvanamen, die das Kommando gleichen Namens ausführt und eine nach Lehrveranstaltungsnamen aufsteigend sortierte Liste ausgibt, ist in Tabelle 2.30 zu sehen. Sie
74
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
'
/**************************************************************/
/*
Hauptprogramm
*/
/*
Ruft Routine aufbau zum Anlegen der Datenstruktur auf, */
/*
erwarten dann Anweisungen und ruft die entprechenden
*/
/*
den Routinen auf.
*/
/**************************************************************/
main()
{
int
i;
boolean
gueltig;
char
anwpuffer[256]; /* Eingabepuffer fuer
*/
/* aktuelle Anweisung
*/
for (i = 0; i < 256; i=i+1)
anwpuffer[i] = ’\0’;
aufbau();
&
/* Einlesen Liste und Aufbau
$
Datenstruktur */
while (TRUE) /* Einlesen, Erkennen und Ausfuehren
*/
{
/* der Anweisungen
*/
gueltig = FALSE;
printf("<LVA-Anweisung>: ");
scanf("%s", anwpuffer);
if (strcmp(anwpuffer, "ende") == 0)
{exit(0);};
/* Anweisung "ende"
*/
if (strcmp(anwpuffer, "hilfe") == 0)
{gueltig = TRUE; hilfe();};
if (strcmp(anwpuffer, "help") == 0)
{gueltig = TRUE; hilfe();};
if (strcmp(anwpuffer, "lvanamen") == 0)
{gueltig = TRUE; lvanamen(namensbaum);};
if (!gueltig)
{printf("Unzulaessige LVA-Anweisung: %s\n",
anwpuffer);};
};
}
%
Tabelle 2.25: Das Programm LVA (Teil III)
startet bei der Wurzel und bearbeitet jeden Knoten folgendermaßen: Zuerst werden die
Knoten des linken Unterbaumes bearbeitet, dann wird der Lehrveranstaltungsname des
Knotens ausgegeben und als letztes die Knoten des rechten Unterbaumes bearbeitet. Dazu ruft sich das Unterprogramm selber auf, man spricht von rekursiven Aufrufen (siehe
2.6. REIHUNGEN, ZEICHENREIHEN, SÄTZE
'
/**************************************************************/
/*
Routinen aufbau
*/
/*
*/
/*
Liest die Lehrveranstaltungsdatei ein und baut eine
*/
/*
programminterne Datenstruktur in Form eines binaeren
*/
/*
Suchbaums auf.
*/
/*
Ruft Routine lvaneu auf.
*/
/**************************************************************/
void aufbau(void)
{
char
dateiname[256];
lvasatz
*lvaaktuell, *lvasuch, *lvasuch1;
FILE
*fd;
printf("LVA: Name der Eingabedatei? ");
scanf("%s", dateiname);
fd = fopen(dateiname, "r");
if (fd == NULL)
{
printf("LVA: Eingabedatei kann nicht geoeffnet werden\n");
exit(0);
};
lvaaktuell = lvaneu(fd);
if (lvaaktuell->lvanr < 0)
{printf("LVA: Keine Eingabedaten\n"); exit(0);};
namensbaum = lvaaktuell;
lvaaktuell = lvaneu(fd);
&
Tabelle 2.26: Das Programm LVA (Teil IV)
75
$
%
Abschnitt 2.8).
Der rekursive Ablauf der Prozedur lvanamen ist in Abbildung 2.10 dargestellt. Man sieht
in Tabelle 2.30, daß bei jedem Knoten vier Arbeitsschritte ausgeführt werden
1. links: Der linke Unterbaum wird bearbeitet (durch den rekursiven Aufruf von lvanamen
mit der Wurzel des linken Unterbaumes als Parameter).
2. ausgeben: Der Lehrveranstaltungsname des aktuellen Knotens wird ausgegeben.
3. rechts: Der rechte Unterbaum wird bearbeitet (durch den rekursiven Aufruf von
lvanamen mit der Wurzel des rechten Unterbaumes als Parameter).
76
'
}
&
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
while (lvaaktuell->lvanr >=0 )
{
lvasuch = namensbaum;
while (lvasuch != NULL)
{
lvasuch1 = lvasuch;
if (strcmp(lvasuch->lvaname, lvaaktuell->lvaname) >= 0)
{
if (lvasuch->links == NULL)
{
lvasuch->links = lvaaktuell;
lvasuch = NULL;
}
else
{lvasuch = lvasuch->links;};
}
else
{
if (lvasuch->rechts == NULL)
{
lvasuch->rechts = lvaaktuell;
lvasuch = NULL;
}
else
{lvasuch = lvasuch->rechts;};
}
};
lvaaktuell = lvaneu(fd);
};
$
%
Tabelle 2.27: Das Programm LVA (Teil V)
4. zurück: Nach Bearbeitung des rechten Unterbaumes ist die Bearbeitung des aktuellen Knotens beendet und es geht zum Aufrufer zurück. Das ist im Programmtext
allerdings nicht explizt erkennbar, die Routine lvanamen benutzt die Anweisung
return nicht.
Für jeden Knoten ist in Abbildung 2.10 die Aufrufstufe angegeben. Weiter sind in Rechtecken die Arbeitsschritte für einen Knoten, die nicht durch Arbeitsschritte auf tieferer
2.6. REIHUNGEN, ZEICHENREIHEN, SÄTZE
'
/**************************************************************/
/*
Routine lvaneu
*/
/*
*/
/*
Erhaelt als Eingabe den Dateideskriptor der Eingabedatei.*/
/*
Fordert Speicherplatz an, liest die Daten der naechsten */
/*
Lehrveranstaltung aus der Datei und baut einen Lehrver- */
/*
anstaltungssatz auf.
*/
/*
Liefert die Adresse des aufgebauten Satzes zurueck.
*/
/**************************************************************/
lvasatz *lvaneu(FILE *fd)
{
lvasatz
*lvaaktuell;
lvaaktuell = (lvasatz *) malloc(sizeof(lvasatz));
if (lvaaktuell == NULL)
{
printf("LVA Einlesen Daten: Nicht genuegend Speicher vorhanden\n");
exit(0);
};
fscanf(fd, " %d %s %s %s %s %s %s %s %s %s %s",
&lvaaktuell->lvanr,
&lvaaktuell->lvaname,
&lvaaktuell->lvadozent,
&lvaaktuell->tag1, &lvaaktuell->anf1,
&lvaaktuell->ende1, &lvaaktuell->raum1,
&lvaaktuell->tag2, &lvaaktuell->anf2,
&lvaaktuell->ende2, &lvaaktuell->raum2);
lvaaktuell->links = NULL;
lvaaktuell->rechts = NULL;
return(lvaaktuell);
}
&
77
$
%
Tabelle 2.28: Das Programm LVA (Teil VI)
Stufe11 unterbrochen werden, zusammengefaßt. Markovketten wird auf Stufe 1 bearbeitet und führt beim ersten Schritt zur Bearbeitung von Analysis auf Stufe 2. Dieser Knoten hat keinen linken Unterbaum, so daß Schritt links nicht zur nächsten Stufe
führt. Es wird der Name Analysis ausgegeben. Danach führt Schritt rechts zur Bearbeitung des Knotens Graphalgorithmen. Von diesem geht es nach der Bearbeitung von
Codierungstheorie, Ausgabe des Namens und der Bearbeitung von Java zurück und
danach kehrt auch Analysis zurück. Anschließend geht es auf Stufe 1 weiter mit der
11
3 ist tiefer als 1 !
78
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
'
/**************************************************************/
/*
Routine hilfe
*/
/*
*/
/*
Gibt die aktuell gueltigen Anweisungen des Programms
*/
/*
zusammen mit einer kurzen Beschreibung aus.
*/
/**************************************************************/
void hilfe(void)
{
printf("\n");
$
printf("LVA: Die zulaessigen Anweisungen sind zur Zeit (November 1998):\n");
printf("
ende
Beendet den Programmlauf.\n");
printf("
hilfe
Diese Anweisung.\n");
printf("
help
Diese Anweisung.\n");
printf("
lvanamen
Ausgabe einer Liste aller Lehrveranstaltungen\n");
printf("\n");
}
&
%
Tabelle 2.29: Das Programm LVA (Teil VII)
Ausgabe des Namens Markovketten usw.
2.7
2
Programmsteuerung
Vorbemerkungen: Elektronische Rechner werden heutzutage für eine große, kaum
noch zu überblickende Vielfalt von Aufgaben eingesetzt. Das ist nur möglich, weil die
Ablaufsteuerung der Rechner, also die ablaufenden Programme, in Abhängigkeit von den
bearbeiteten Daten unterschiedliche Programmteile durchlaufen. Auf der Hardwareebene (sowohl Hardwaremaschine als auch konventionelle Maschine, Tabelle 2.1) sind dafür
Sprungbefehle, insbesondere bedingte Sprungbefehle, die Voraussetzung. Siehe hierzu Unterabschnitt 4.2.2, Seite 133. Auf der Ebene der Programmiersprachen hat man daraus eine
Reihe von Sprachkonstrukten entwickelt, die zur Ablaufsteuerung (Programmsteuerung 12 ,
Es wird auch oft die Bezeichnung Programmkontrolle benutzt. Das ist jedoch schlechtes Deutsch, da
das Wort Kontrolle anders als das englische Wort control nicht Steuerung bedeutet.
12
2.7. PROGRAMMSTEUERUNG
'
79
/**************************************************************/
/*
Routine lvanamen
*/
/*
*/
/*
Gibt die vollstaendige Liste der Lehrveranstaltungen
*/
/*
in alphabetisch aufsteigender Ordnung der
*/
/*
Lehrveranstaltungsbezeichnungen aus.
*/
/**************************************************************/
void lvanamen(lvasatz *lvalauf)
{
if (lvalauf->links != NULL)
{lvanamen(lvalauf->links);};
$
printf("%6d %20s
%10s %2s %2s %2s %4s %2s %2s %2s %4s\n",
lvalauf->lvanr,
lvalauf->lvaname, lvalauf->lvadozent,
lvalauf->tag1, lvalauf->anf1, lvalauf->ende1,lvalauf->raum1,
lvalauf->tag2, lvalauf->anf2, lvalauf->ende2,lvalauf->raum2);
if (lvalauf->rechts != NULL)
{lvanamen(lvalauf->rechts);};
}
&
%
Tabelle 2.30: Das Programm LVA (Teil VIII)
program control) von Programmen dienen und in diesem Abschnitt dargestellt werden.
In alten Programmiersprachen, z. B. frühe Versionen von FORTRAN, war in der Struktur der Programme die Verwandtschaft zur Assemblersprache noch zu erkennen. Einige
Anweisungen eines Programms wurden mit einer Marke (label) versehen und mit der
Anweisung goto wurden bedingte und unbedingte Sprünge zu Marken realisiert. Auch
das Ende von Schleifen wurde über Marken festgelegt. Seit dem Aufkommen moderner
Programmiersprachen – Algol60, C und Pascal gehörten zu den ersten – sind Marken
und goto-Anweisungen nicht mehr nötig. Zwar sind sie in den meisten Programmiersprachen, auch C, noch erlaubt, werden aber kaum noch benutzt. Im folgenden wird auf diese
Sprachkonstrukte nicht mehr eingegangen.
Allgemeiner Aufbau von Programmen: Bei allen Unterschieden im einzelnen haben
Programme in modernen Programmiersprachen den folgenden allgemeinen Aufbau:
• Definition von Konstanten, Variablen, Funktionen, Datentypen.
• Folge ausführbarer Anweisungen (executable statement).
80
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
Markovketten
1
links
Analysis
2
links
ausgeben
rechts
Niederländisch
5 links
ausgeben
rechts
zurück
Graphalgorithmen 3
links
Modellierung
4 zurück
Codierungstheorie
links
ausgeben
Programmierung
3 ausgeben
rechts
rechts
Zahlentheorie
4 links
zurück
Stochastik
5 links
4
Graphalgorithmen 3
ausgeben
rechts
ausgeben
rechts
Java
links
zurück
4
ausgeben
rechts
zurück
Zahlentheorie
4 ausgeben
rechts
zurück
Graphalgorithmen 3
zurück
Programmierung
3 zurück
Analysis
2
zurück
abcd
2 ausgeben
Markovketten
1
ausgeben
rechts
abcd
2
links
Programmierung
3
links
Modellierung
4
links
ausgeben
rechts
rechts
zurück
Markovketten
1 zurück
Abbildung 2.10: Rekursive Aufrufe der Prozedur lvanamen
Man kann von dem Definitionsteil und dem Anweisungsteil eines Programms sprechen. In
den meisten Programmiersprachen, darunter auch C, sind Definitionsteil und Anweisungsteil eines Programms getrennt und der Anweisungsteil folgt dem Definitionsteil. In einigen
Programmiersprachen können in einem Programm Definitionen und ausführbare Anweisungen gemischt werden. Datendefinitionen und Datentypen wurden in den Abschnitten
2.4 und 2.5 behandelt. Ausführbare Anweisungen – kurz: Anweisungen (statement) – sind
2.7. PROGRAMMSTEUERUNG
81
• Anweisungsblöcke
• Elementare Anweisungen
• Alternativen
• Schleifen
• Unterprogrammaufrufe
Eine Folge von Anweisungen, die durch Blockklammern eingeschlossen wird, bildet einen
Anweisungsblock. Blockklammern sind oft begin und end. In C werden geschweifte Klammern verwendet. Ein Anweisungsblock wird wie eine einzige Anweisung behandelt und
kann überall dort im Programm auftreten, wo eine Anweisung stehen darf, also z. B auch
in einem übergeordneten Anweisungblock. Die Programme haben eine Blockstruktur.
Anmerkung 2.7 Einige Programmiersprachen, darunter auch C, lassen zu, daß ein Anweisungsblock nicht nur aus ausführbaren Anweisungen besteht, sondern auch einen Definitionsteil enthält. Die dort definierten Variablen, Konstanten, Datentypen, Funktionen
gelten nur innerhalb des Anweisungsblocks. Ihr Gültigkeitsbereich (scope) ist der entsprechende Block. Man sagt, die Variable (Konstante etc) sei in dem entsprechenden Block
lokal. Es wird empfohlen, innerhalb von Anweisungsblöcken Definitionen zu vermeiden. In
Funktionen jedoch sind lokale Gültigkeitsbereiche nicht nur erlaubt, sondern für die Programmierung von Unterprogrammen wesentlich. Das wird in Abschnitt 2.8 im einzelnen
erläutert.
2
Anmerkung 2.8 In manchen Programmiersprachen, z. B. Algol68, liefert jede Anweisung auch einen Wert. Dieser kann für Zuweisungen, Abfragen, in Ausdrücken usw. benutzt werden oder unberücksichtigt bleiben. In C liefern Anweisungen keine Werte. Zu
Unterprogrammaufrufen in C siehe Abschnitt 2.8.
2
Elementare Anweisungen: Die wichtigste elementare Anweisung ist die Zuweisung.
Dabei wird einer Variablen durch einen Ausdruck ein Wert zugewiesen. Anweisungen,
Werte und Ausdrücke wurden in den Abschnitten 2.4 und 2.5 behandelt. Beispiele für
weitere elementare Anweisungen sind in C break (z. B. zum vorzeitigen Verlassen einer
Schleife) oder return (Übergabe eines Funktionswertes am Ende eines Funktionsaufrufes;
siehe Abschnitt 2.8). Zu den elementaren Anweisungen gehört auch die leere Anweisung
(empty statement, null statement), die es in den meisten Programiersprachen, auch C,
gibt.
82
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
Alternativen: Alternativen werden auch Auswahlanweisungen genannt. Alternativen
prüfen Bedingungen, die Wahrheitswerte liefern, und entscheiden in Abhängigkeit von
dem Wert der Bedingungen, welche Anweisungen ausgeführt werden und welche nicht.
Die wichtigste Alternative ist die if-Anweisung. Sie hat die allgemeine Form
if <bedingung> then <anweisung1> else <anweisung2>
<bedingung> liefert einen Wahrheitswert. Ist dieser true, so wird <anweisung1> ausgeführt, andernfalls <anweisung2>. Der else-Teil darf fehlen. In C wird die Bedingung in
runde Klammern gesetzt und das Wort then weggelassen. <anweisung1> und <anweisung2> heißen Programmzweige und sind häufig Anweisungsblöcke.
In Programmen treten oft geschachtelte if-Anweisungen auf. Tabelle 2.31 zeigt eine schematische Schachtelung in C-Schreibweise. Um diese umständliche und aufwendige Schreibif (...) {........}
else
{
if (...) {........}
else
{
if (...) {........}
else
{
.....
}
}
}
Tabelle 2.31: Schachtelung von if-Anweisungen
weise zu vereinfachen, gibt es in C die Sprachmittel else if und switch (siehe Kernighan/Ritchie [KernR1988]). Die entsprechenden und zum Teil weiterreichenden Sprachmittel anderer Programmiersprachen, z. B. case-Anweisung, sind in Kowalk [Kowa1996],
Abschnitt 5.3, beschrieben.
Schleifen: Schleifen (loop) werden auch Wiederholungsanweisungen oder Iterationen
genannt. Schleifen haben die allgemeine Form
Solange <bedingung> tue <anweisung>
oder
2.8. UNTERPROGRAMME
83
Tue <anweisung> bis <bedingung>
<bedingung> ist die Schleifenbedingung, <anweisung> der Schleifenrumpf und im Allgemeinen ein Anweisungsblock. Bei der ersten Form spricht man auch von Schleifenkopf
und Schleifenkörper. Die erste Form der Schleifenanweisung wird generell, auch in C, mit
dem Sprachkonstrukt while ausgedrückt. Dabei wird der Schleifenrumpf nicht ausgeführt,
wenn <bedingung> schon beim ersten Test false liefert. Für die zweite, weniger häufig
benutzte Form von Schleifen gibt es das Sprachkonstrukt until, in C do ... while. Bei
dieser Schleifenform wird der Schleifenrumpf stets mindestens einmal ausgeführt.
Oft kommen in Programmen Schleifen vor, bei denen ein Zähler erhöht oder erniedrigt
werden muß, bis ein Zielwert erreicht ist. Es ist dann zweckmäßig, die Prüfung, ob der
Endzählerstand schon erreicht ist, mit der Inkrementierung oder Dekrementierung des
Zählers zu verbinden. Viele Programmiersprachen, auch C, verwenden für Schleifen dieser
Art das Sprachkonstrukt for, man spricht von for-Schleifen. FORTRAN verwendet DO.
Unterprogrammaufrufe:
2.8
Werden im nächsten Abschnitt (2.8) behandelt.
Unterprogramme
Allgemeines: Schon sehr früh merkte man bei der Programmierung (in Assembler),
daß es sehr nützlich ist, bestimmte Programmstücke in einem Programm so zur Verfügung zu stellen, daß sie unter einem identifizierenden Namen an unterschiedlichen Stellen
des Programms ablaufen. Das Konzept wurde mit Erfolg in höhere Programmiersprachen übernommen. Der allgemeine Name für solche Programmstücke ist Unterprogramm
(subprogram, subroutine) 13 . Das Ausführen eines Unterprogramms heißt Unterprogrammaufruf (subroutine call).
Unterprogramme dienen der flexiblen Erweiterung der benutzten
Programmiersprache.
Man kann (und sollte) mit Unterprogrammen ein Programm sauber gestalten und klar
gliedern. Allerdings kann man mit einem Übermaß an Unterprogrammen des Guten auch
zuviel tun. Ein weiterer Vorteil von Unterprogrammen ist, daß der von ihnen benötigte
Platz im Arbeitsspeicher nur einmal gebraucht wird. Speicherplatzersparnis war früher
ein wichtiger Gesichtspunkt und ist für sehr große Unterprogramme immer noch von
Bedeutung. Da heutzutage jedoch Geschwindigkeit wichtiger als Speicherplatzersparnis
ist, werden Unterprogramme, bei denen das möglich ist, von den Compilern zunehmend
expandiert, d. h die sich aus ihrer Übersetzung ergebenden Maschinenbefehle werden an
allen Aufrufstellen (in-line), in das übersetzte Programm eingefügt.
Machmal werden Unterprogramme auch Routinen genannt. Diese Bezeichnung wird jedoch oft auch
gleichbedeutend mit Programm benutzt.
13
84
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
Ein Unterprogrammaufruf bedeutet, daß im aufrufenden Programm von dem Unterprogramm eine bestimmte Arbeit erwartet wird. Im allgemeinen werden dafür die zu bearbeitenden Daten im aufrufenden Programm bereitgestellt. Geschieht das durch explizite Angabe dieser Daten, so spricht man von Parametern (auch Eingabeparameter) des
Unterprogrammaufrufs. Die zu bearbeitenden Daten können auch implizit bereitgestellt
werden, indem das Unterprogramm von vornherein ihre Namen oder Adressen im aufrufenden Programm kennt. Entsprechend kann ein Unterprogramm einen berechneten Wert
– das Ergebnis (result) – an das aufrufende Programm zurückliefern. Es ist auch möglich,
daß ein aufgerufenes Unterprogramm bestimmte Arbeiten durchführt, aber kein Ergebnis
zurückliefert. Unterprogramme, die kein Ergebnis zurückliefern, werden oft Prozeduren
(procedure) genannt und Unterprogramme mit Ergebnis heißen i. a. Funktionen (function).
In diesem Buch wird der Bezeichnungsweise von C gefolgt. Daher bedeutet Funktion14 stets Unterprogramm, unabhängig davon, ob ein Ergebnis
geliefert wird oder nicht.
Anmerkung 2.9 (Makros) Unterprogramme werden manchmal auch durch Makros
(macro) in ein Programm eingefügt. Allgemein sind Makros Anweisungen zur Textersetzung, die der Compiler oder ein davor ablaufendes Vorbereitungsprogramm (Präprozessor) ausführt, ehe mit der eigentlichen Übersetzung begonnen wird. #define LAENGE 4
ist z. B. ein typischer Makro in C. Durch Makros definierte Programmteile sind keine
echten Unterprogramme, da die Unterprogrammeigenschaft nach der Textersetzung nicht
mehr sichtbar ist.
2
Aufrufstruktur und Gültigkeitsbereiche Das Programmstück eines Gesamtprogramms, in dem der Programmablauf beginnt, heißt Hauptprogramm (main program). In
C erhält es den Namen main. In diesem Programm wird es i. a. Unterprogrammaufrufe
geben. Diese Unterprogramme können während ihres Ablaufs selber wieder Unterprogramme aufrufen. Bei normalem Programmablauf wird jedes aufgerufene Unterprogramm zum
aufrufenden Programm zurückkehren und schließlich wird sich das Hauptprogramm irgendwann beenden15 . Fehler- und Sondersituationen können dazu führen, daß von dieser
Aufrufstruktur abgewichen wird.
In C, wie in vielen anderen Programmiersprachen, haben sowohl das Hauptprogramm als
auch jedes Unterprogramm den auf Seite 79 beschriebenen allgemeinen Aufbau. Das heißt,
in jedem dieser (Teil-)Programme gibt es einen Definitionsteil und einen Anweisungsteil,
und dieser ist aus Anweisungsblöcken, elementaren Anweisungen, Alternativen, Schleifen
14
Falls keine Funktion im mathematischen Sinne (siehe Abschnitt A.3, Seite 614, im Anhang) gemeint
ist.
15
Zu reaktiven Systemen siehe Abschnitt 5.1, Seite 152.
2.8. UNTERPROGRAMME
85
und Funktionsaufrufen (also Unterprogrammaufrufen) zusammengesetzt. Die in einem
Haupt- oder Unterprogramm definierten Variablen, Konstanten und Datentypen sind dort
lokal (siehe auch Anmerkung 2.7). Ihr Gültigkeitsbereich ist das entsprechende Hauptoder Unterprogramm. Außerhalb sind sie nicht sichtbar und ihre Namen können dort
ganz andere Objekte bezeichnen.
Diese Zusammenhänge sollen an einem Beispiel verdeutlicht werden. Es seien ein Hauptprogramm H und zwei Unterprogramme U1 und U2 (in C oder einer anderen Programmiersprache) gegeben. U1 wird nur aus dem Hauptprogramm H und U2 nur aus dem
Unterprogramm U1 aufgerufen. Wie oft und an welchen Stellen die Unterprogramme aufgerufen werden, ist programmlaufabhängig. In Abbildung 2.11. wird ein Programmlauf
H
.
.
.
.
.
.
.
.
.
U1
U2
0
t1
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
t2
t3
.
.
.
.
.
.
.
.
.
t4
t5
t
..
......................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
.
Abbildung 2.11: Nichtrekursive Unterprogrammaufrufe
gezeigt, in dem jedes Unterprogramm genau einmal aufgerufen wird. In keinem Programmlauf ist die Schachtelungstiefe größer als 2, und in jedem Fall wird ein Unterprogramm
erst bis zum Schluß durchlaufen, ehe es erneut aufgerufen wird. Es seien DH, DU1 , DU2
die lokalen Definitionsteile von H, U1 bzw U2 . Dann sind bei dem Ablauf in Abbildung
2.11 die Größen von DH in den Intervallen [0, t1 ] und [t4 , t5 ] gültig. Die von DU1 gelten
in den Intervallen [t1 , t2 ] und [t3 , t4 ] und die von DU2 im Intervall [t2 , t3 ].
Wenn U1 im Intervall [t1 , t2 ] abläuft, können weder die Größen von DH noch die von
DU2 angesprochen werden. Es gibt jedoch einen wesentlichen Unterschied: DH war im
Intervall [0, t1 ] gültig und der zum Zeitpunkt t1 gültige Zustand muß auch zum Zeitpunkt
t4 gelten. DH ist in [t1 , t2 ] vorhanden, jedoch inaktiv. Im Gegensatz dazu wird DU2
außerhalb des Intervalls gar nicht benötigt und in der Tat wird bei vielen Compilern (in
der Regel auch bei C-Compilern) der zu DU2 gehörende Speicherplatz freigegeben, d. h.
außerhalb von [t2 , t3 ] existert DU2 gar nicht. Man kann daher, wenn man ganz exakt sein
will, zwischen Gültigkeitsbereich, Lebensdauer und Aktivitätsintervall der Daten eines
Haupt- oder Unterprogramms unterscheiden. Z. B. ist DU1 im Unterprogramm U1 gültig,
lebt bei dem Programmablauf von Abbildung 2.11 von t1 bis t4 und ist in ihm von t1 bis
t2 und von t3 bis t4 aktiv.
86
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
Parameter- und Ergebnisübergabe: Obwohl, wie dargelegt, ein aufrufendes und ein
aufgerufenes Programm den jeweils anderen Definitionsteil nicht sehen, muß es natürlich
zwischen beiden einen Datenaustausch geben. Dazu muß eine beiden Programmen bekannte Beschreibung der auszutauschenden Daten vorhanden sein. In Analogie zu Datentypen
nennt man diese oft den Typ des Unterprogramms. Der Typ eines Unterprogramms legt
fest, von welchem Datentyp die an das Unterprogramm zu übergebenden Parameter und
das zurückgelieferte Ergebnis sind. Als Sonderfall ist dabei zugelassen, daß es keine Parameter gibt und/oder daß kein Ergebnis zurückgeliefert wird. In C gibt es hierfür das
Sprachelement void.
Am saubersten sind aufrufendes und aufgerufenes Programm getrennt, wenn bei einem
Aufruf nur Werte an das Unterprogramm übergeben werden, die Variablen des Aufrufers
aber nicht bekannt sind. Für Parameter, bei denen nur Werte übergeben werden, spricht
man von Wertübergabe (call by value). C z. B. kennt nur Wertübergabe. Der zu übergebende Wert kann an der Aufrufstelle im aufrufenden Programm direkt als Konstante, über
eine zu dereferenzierende Variable oder einen komplizierteren Ausdruck bereitgestellt werden. Im Unterprogramm wird der Wert benutzt, z. B. für Berechnungen. Da es sich nicht
um eine Variable handelt, kann er nicht geändert werden. In einigen Programmiersprachen
gibt es auch die Möglichkeit, einen Variablennamen als Parameter anzugeben, und dem
Unterprogramm wird die Adresse (und nicht der Wert) der Variablen übergeben. Man
spricht von Adreßübergabe (call by reference). In diesem Fall kann im Unterprogramm der
Wert der Variablen natürlich geändert werden.
Wertübergabe von Parametern sichert eine saubere Trennung zwischen aufrufendem und
aufgerufenem Programm. Es gibt jedoch auch Fälle, in denen es zweckmäßig ist, einem
Unterprogramm die Adresse von Variablen des Aufrufers bekanntzugeben und ihm zu
erlauben, die Werte dieser Variablen zu ändern. Zum Beispiel ist es nicht sinnvoll, die
Werte einer großen Reihung in den Datenbereich eines Unterprogramms zu kopieren, um
sie dort zu bearbeiten und dann zurückzuliefern. In C kann man das Problem dadurch
lösen, daß man explizit festlegt, daß der übergebene Parameterwert nicht vom Typ <typ>,
sondern vom Typ „Adresse (einer Variablen oder Konstanten) vom Typ <typ>“ ist. Zu
Einzelheiten siehe Lowes/Paulik [LoweP1995].
„Zurückliefern eines Ergebnisses“ bedeutet, daß im aufrufenden Programm an der Aufrufstelle nach der Ausführung des Unterprogrammaufrufs ein Wert vom Typ des Ergebnisses zur Verfügung steht. Dieser Wert kann einer passenden Variablen zugewiesen oder
zur Berechnung eines Ausdrucks genutzt werden. Wie dargelegt, kann ein Unterprogramm,
das Adressen von Variablen des Aufrufers kennt, im aufrufenden Programm Änderungen
vornehmen. Man spricht dann manchmal von Nebeneffekten (Seiteneffekt, side effect).
Wenn möglich, sollen Nebeneffekte vermieden werden. Das ist jedoch nicht immer sinnvoll oder möglich. Zum Beispiel macht es in C Sinn, Zeichenreihen von einer Variablen
in eine andere zu kopieren und dabei zu zählen, wie lang die kopierte Zeichenreihe ist.
Ein entsprechendes Unterprogramm könnte als Eingabeparameter die Adressen der Quell-
2.8. UNTERPROGRAMME
87
und der Zielvariablen bekommen, als „Nebeneffekt“ den Inhalt der Zielvariablen ändern
und als Ergebnis die Zahl der kopierten Zeichen zurückliefern.
Globale Variable: Für den Datenaustausch zwischen aufrufendem und aufgerufenem
Programm sind Parameterübergabe und Ergebnisrücklieferung der „offizielle“ Weg. Daß
Adressen, die an das aufgerufene Programm übergeben werden, zu Nebeneffekten führen,
ist eine leichte, aber oft notwendige Abweichung von der offiziellen Linie. Bei manchen
Programmieraufgaben ist es zweckmäßig, noch weiter von dieser Linie abzuweichen. Als
Beispiel sei die Bearbeitung einer Baumstruktur (siehe Kapitel 9) genannt, an der eine
Reihe verschiedener Unterprogramme beteiligt sind. Man könnte bei jedem Unterprogrammaufruf dem Unterprogramm die Adresse des Wurzelknotens und andere benötigte
allgemeine Informationen über den Baum als Parameter übermitteln. Das wäre jedoch unpraktisch und aufwendig. Zweckmäßiger ist es, die entsprechenden Variablen einheitlich
und unter gleichem Namen in allen Unterprogrammen und dem Hauptprogramm zur Verfügung zu haben. In den meisten Programmiersprachen, auch C, kann das durch geeignete
Sprachkonstrukte festgelegt werden. Man spricht dann von globalen Variablen.
Auch Unterprogramme selbst müssen als solche definiert werden. Zur vollständigen Konstruktion eines Unterprogramms gehört die Festlegung des Namens, des Typs der Parameter und des Typs des Ergebnisses. Außerdem muß es programmiert werden, d. h.
sein Definitionsteil und sein Anweisungsteil müssen spezifiziert werden. In Abhängigkeit
von der Stelle im Programm, an der das Unterprogramm definiert wird und von anderen
Angaben hat auch jedes Unterprogramm einen Gültigkeitsbereich. In der Praxis ist es
gelegentlich nützlich, für Unterprogramme einen eingeschränkten Gültigkeitsbereich zu
haben. Oft ist jedoch ein globaler Gültigkeitsbereich für alle Unterprogramme eines Gesamtprogramms vorzuziehen. In diesem Fall kann jedes Unterprogramm an jeder Stelle
aufgerufen werden.
Rekursive Aufrufe: Die meisten Programmiersprachen lassen zu, daß ein Unterprogramm sich selbst aufruft. Man spricht von einem rekursiven Unterprogrammaufruf (rekursiver Funktionsaufruf, recursive function call) oder auch von Rekursion (recursion). Ein
erstes Beispiel für die Anwendung rekursiver Unterprogramme ist die Routine lvaname
für die Ausgabe von Lehrveranstaltungsnamen. Siehe Tabelle 2.29. Sie benutzt die in
Abbildung 2.9 gezeigte Baumstruktur. Die einzelnen rekursiven Aufrufe sind in Abbildung 2.10 wiedergegeben. Rekursion ist eine sehr wichtige Technik bei der Formulierung
von Programmen und Algorithmen und wird in Teil II „Algorithmen“, Teil III „Einfache
Datenstrukturen“ und in Teil IV „Allgemeine Graphen“ intensiv benutzt.
Die Bezeichnungen Rekursion und rekursiver Unterprogrammaufruf werden auch benutzt,
wenn ein Unterprogramm sich nicht direkt, sondern indirekt aufruft. Man spricht auch von
gekoppelter Rekursion oder indirekten rekursiven Unterprogrammaufrufen. Abbildung 2.12
zeigt einige Beispiele für Aufrufschemata. Darin stellen Rechtecke Haupt- oder Unterpro-
88
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
H
H
H
H
...
...
...
...
...
.........
.........
..
...
...
...
...
...
.........
.........
..
...
...
...
...
...
.........
........
...
...
...
...
...
...
.........
.........
..
U1
U1
U1
U1
...
...
...
...
...
..........
........
..
...
...
...
...
...
..........
.......
...
U2
A
.........
...
..
.
..
.
...
.
.................................
..
B
...
........
........
...
....
..
...
...
...
...
...
...
...
..........
........
..
.........
...
..
.
..
.
...
.
................................
.
..
........
........
...
....
..
...
...
U2
U2
C
D
Abbildung 2.12: Aufrufschemata für nichtrekursive, direkt rekursive und indirekt rekursive
Unterprogramme
gramme dar. Ein Pfeil bedeutet, daß bei einem Ablauf das Haupt- oder Unterprogramm
am Pfeilanfang das Unterprogramm am Pfeilende aufrufen kann, aber nicht muß. Teil A
des Bildes zeigt ein nichtrekursives Aufrufschema. Es entspricht dem Beispiel in Abbildung 2.11. Teil B stellt ein einfaches rekursives Aufrufschema dar. Das Unterprogramm
U1 kann sich selber aufrufen. In Teil C ist eine gekoppelte Rekursion zu sehen, bei der die
Unterprogramme U1 und U2 sich gegenseitig aufrufen können. Teil D schließlich dient als
Beispiel für eine Mischung von einfacher und gekoppelter Rekursion.
Ein Programm ist genau dann nichtrekursiv, wenn sein Aufrufschema (als
gerichteter Graph aufgefaßt) keine Kreise enthält.
Bei rekursiven Unterprogrammen sind Gültigkeitsbereich, Lebensdauer und Aktivitätsintervall komplizierter als bei nichtrekursiven. Sie hängen von der Aufrufstufe eines Unterprogramms ab. Das soll am Beispiel der Abbildung 2.13 erläuter werden. Es gibt
ein Hauptprogramm H und zwei Unterprogramme, U1 und U2 . U1 kann in H und U2
in U1 aufgerufen werden. Darüber hinaus kann U2 seinerseits U1 aufrufen. Es liegt das
Aufrufschema von Teil C, Abbildung 2.12 vor. Es kann jetzt vorkommen, daß ein Unterprogramm schon dann erneut aufgerufen wird, wenn es noch nicht zum Schluß gekommen
ist. Die Schachtelungstiefe der Abläufe ist nicht mehr begrenzt; bei einem fehlerhaften
Programmablauf kann sie im Prinzip über alle Grenzen wachsen. Abbildung 2.13 zeigt
einen Ablauf mit zwei Aufrufen eines jeden Unterprogramms (U1 (1), U1 (2), U2 (1), U2 (2)).
Jedes der beiden Unterprogramme läuft in zwei Aufrufstufen mit einem eigenen Definitionsteil ab. Für DU1 (1), den Definitionsteil der ersten Aufrufstufe von U1 , gilt z. B.: Er
ist in U1 (1) gültig, lebt von t1 bis t8 und ist von t1 bis t2 und von t7 bis t8 aktiv.
2.8. UNTERPROGRAMME
H
89
.
.
.
.
.
.
.
.
.
U1 (1)
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
U2 (1)
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
U1 (2)
.
.
.
.
.
.
.
.
.
U2 (2)
0
t1
t2
t3
t4
.
.
.
.
.
.
.
.
.
t5
.
.
.
.
.
.
.
.
.
t6
t7
t8
t9
t
..
.....................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
..
Abbildung 2.13: Rekursive Unterprogrammaufrufe
Externe Prozeduren: Den bisherigen Betrachtungen über Unterprogramme lag die
Annahme zugrunde, daß ein Gesamtprogramm mit seinem Hauptprogramm und seinen
Unterprogrammen als eine Einheit programmiert und übersetzt wird. Dem Compiler sind
dann zur Übersetzungszeit alle Definitionsteile, insbesondere auch die Typen aller Unterprogramme bekannt (siehe Seite 86). Es kommt aber häufig vor, daß in einem Programm
Unterprogramme aufgerufen werden, die nicht zusammen mit dem Hauptprogramm übersetzt werden. Diese Unterprogramme sollen externe Prozeduren genannt werden. Um die
Aufrufe von externen Unterprogrammen übersetzen zu können, muß der Compiler allerdings ihren Typ kennen. Es müssen bei der Übersetzung Typbeschreibungen der externen
Unterprogramme vorhanden sein. In C werden .h-Dateien (z. B. stdio.h) dafür benutzt.
Bei externen Prozeduren ist es besonders zweckmäßig, den Unterprogrammaufruf als Erweiterung der benutzten Programmiersprache aufzufassen (siehe Seite 83). Man ruft mit
dem Unterprogramm einen Dienst auf. Externe Prozeduren lassen sich einteilen in
• Unterprogramme, die zum Gesamtprogramm gehören, aber aus besonderen Gründen getrennt übersetzt und später durch Binden (siehe Unterabschnitt 4.3.2, Seite
144) hinzugefügt werden. Gründe für eine getrennte Übersetzung können z. B sein:
Das Programm wird für eine Übersetzung zu groß, mehrere Programmierer arbeiten
am gleichen Programm, man will das Unterprogramm in einer anderen Programmiersprache schreiben als das Hauptprogramm.
• Systemdienste. Hierunter versteht man Unterprogramme, die wesentliche und not-
90
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
wendige Dienste bieten; Dienste, die aber nicht als direkte Konstrukte in der Programmiersprache vorhanden sind. In C spricht man von Standardfunktionen. Systemdienste sind zum Teil normale, getrennt übersetzte Unterprogramme, die häufig
anfallende Aufgaben übernehmen, wie z. B. Formatumsetzungen, Bearbeitung und
Vergleiche von Zeichenreihen, mathematische Berechnungen elementarer Funktionen. Zum Teil sind es Unterprogramme, über die ein Zugang zu den Diensten des
Betriebssystems (siehe Unterabschnitt 4.3.1, Seite 139) bereitgestellt wird. Hierzu
sind zum Beispiel die Unterprogramme, mit denen Ein- und Ausgaben durchgeführt
werden, zu zählen. Systemdienste für eine Programmiersprache gehören i. a. dazu,
wenn man einen Compiler für diese Sprache bezieht. Man spricht vom Laufzeitsystem (run time system) des Compilers16 . Die Programme eines Laufzeitsystems
werden i. a. in einer Laufzeitbibliothek zusammengefaßt (siehe Seite 144).
Etwas Ähnliches wie Systemdienste sind Unterprogramme aus Unterprogrammbibliotheken, auch kurz Bibliotheken (library) genannt. Hierbei handelt es sich um
Sammlungen von Unterprogrammen für spezielle Aufgaben, z. B. für Matrizenrechnung oder Computergrafik. Diese Sammlungen gehören in der Regel nicht zum Leistungsumfang einer Compilerlieferung.
• Prozedurfernaufrufe (remote procedure call, RPC). In modernen Rechensystemen
werden viele Aufgaben durch Arbeitsteilung gelöst. Eine Form der Arbeitsteilung,
die sich als sehr zweckmäßig erwiesen hat, ist Client-Server-Betrieb. Dabei läuft für
eine zusammengehörende Klasse von Aufgaben ein eigenes, im allgemeinen umfangreiches Programm ab, der Server (Dienstleister). So gibt es z. B. für Datenbankaufgaben einen Datenbankserver, für rechenintensive Aufgaben eine Rechenserver,
auch Compute-Server genannt, für den Zugang zum Netz einen Netzserver usw.
Programmläufe, die einen dieser Dienste von einem Server anfordern, heißen Kunden (client). Kunde und Server können Programmabläufe auf dem gleichen Rechner
sein. Häufiger ist es, daß Kunde und Server auf verschiedenen Rechnern, die vernetzt
sind (siehe Abschnitt 22.3, Seite 576), ablaufen17 . Kunde und Server können auf
unterschiedliche Art miteinander kommunizieren. Eine bequeme und hierfür häufig benutzte Form der Interprozeßkommunikation ist der Prozedurfernaufruf. Der
Kunde ruft den benötigten Dienst so auf, als wäre es ein internes Unterprogramm.
Wie dabei Daten zwischen Kunden und Server ausgetauscht werden, ist für Prozedurfernaufrufe normiert. Welche Bedeutung die Daten haben und wie der Server
arbeitet, ist natürlich anwendungsabhängig.
Auf einem gegebenen Rechner sind die Laufzeitsysteme verschiedener Compiler der gleichen Programmiersprache nicht notwendigerweise in allen Punkten identisch, denn nicht für alle Systemdienste
gibt es normierte Festlegungen.
17
Ist für ein (eventuell auch für mehrere) Serverprogramm ein eigener Rechner reserviert, so ist es
üblich auch diesen als Server zu bezeichnen.
16
2.8. UNTERPROGRAMME
91
Literatur
Zum Schichtenaufbau eines Rechensystems siehe das Buch von Tanenbaum [Tane1990].
Das Standardwerk für C ist Kernighan/Ritchie [KernR1988]. Es gibt eine deutsche Übersetzung [KernR1990] sowie ein Lösungsbuch für die Aufgaben [TondG1989]. Es sei auch
auf das Buch Lowes/Paulik [LoweP1995] hingewiesen. Eine breit angelegte Einführung in
die Informatik mit C ist in Impagliazzo/Nagin [ImpaN1995] zu finden.
Zu den Grundbegriffen der Programmierung, wie sie in diesem Kapitel dargestellt wird,
siehe auch Goos [Goos1996], Abschnitt 8.1 . In Appelrath/Ludewig ([AppeL1995], Kapitel
2) werden die Grundbegriffe der Programmierung am Beispiel der Programmiersprache
Modula-2 erläutert. Ein weiterführendes Buch, das Programmiersprachen allgemein behandelt, ist Sethi [Seth1996].
Darstellung und Bedeutung von Werten werden ausführlich und aus allgemeinerer Sicht
bei Kowalk ([Kowa1996], S.99-126) behandelt. Das gleiche gilt für Ausdrücke ([Kowa1996],
S.126-142). In dem Buch von Langsam/Augenstein/Tenenbaum [LangAT1996], Kapitel 1,
werden Datentypen von C und Klassen von C++ behandelt. Dabei wird auch auf abstrakte Datentypen eingegangen.
Unterabschnitt 2.5.4 „Wahrheitswerte“ gibt einen allerersten Einblick in die (mathematische) Logik. Ausführlichere, aber immer noch als Einführung für Informatiker gedachte
Darstellungen findet man bei Goos [Goos1997], Kapitel 4, Kowalk [Kowa1996], Anhang
A und Aho/Ullman [AhoU1995], Kapitel 12, 13, 14, 15.
Anweisungen, Anweisungsfolgen und Programmsteuerung sind ausführlich in Kowalk
([Kowa1996], S.143-172) beschrieben.
In Kowalk ([Kowa1996], S.211-249) findet man auch eine ausführliche Darstellung von
Unterprogrammen. Zu diesem Punkt siehe auch Sethi [Seth1996].
Rekursion wird ausführlich in Kapitel 2 von Aho/Ullman [AhoU1995] behandelt.
Literatur zu ergänzenden Gebieten: Es sollen hier einige wichtige, zur Programmierung gehörende Gebiete, die in diesem Kapitel nicht angesprochen werden konnten,
erwähnt werden.
Zeiten sind wichtige Werte, auf deren Darstellung und Bearbeitung in diesem Kapitel nicht
eingegangen wurde. Für C wird auf das Kapitel „Termine und Zeiten“ in Lowes/Paulik
[LoweP1995] verwiesen.
Große Programme werden nur dann sauber und erfolgreich sein, wenn man eine systematische Programmierung einhält. Ein Hilfsmittel dazu sind Module. Ein Programmodul ist eine Zusammenfassung von Daten und Operatoren, für die Zugriffe und Aufrufe
nicht beliebig, sondern nur in kontrollierter Form möglich sind. Zu Modulen siehe Kowalk
([Kowa1996], S.255-263).
92
KAPITEL 2. PROGRAMMIEREN I: SEQUENTIELLE PROGRAMME
Eine nützliche Methode, systematisch zu programmieren, ist strukturierte Programmierung. Dabei wird ein Programm schrittweise verfeinert. Für die Programmsteuerung sind
nur Sequenzen, Alternativen und Schleifen, aber nicht rekursive Aufrufe zugelassen. Auf
strukturierte Programmierung wird kurz in Abschnitt 5.1.2, Seite 155 eingegangen. Eine
ausführlichere Darstellung findet man in Goos [Goos1996], Kapitel 9.
In neuerer Zeit hat sich objektorientiertes Programmieren als besonders erfolgreich für systematisches Programmieren erwiesen und starke Akzeptanz gefunden. Charakteristisch
für diese Art des Programmierens ist die Zusammenfassung von Objekten zu Klassen.
Bearbeitung von Objekten ist ausschließlich über vorgegebene Methoden möglich. Besonders wichtig ist die Möglichkeit, auf einfache Art aus gegebenen Klassen neue zu erzeugen
sowie Eigenschaften und Methoden auf diese zu übertragen, zu vererben. In allgemeiner Form wird objektorientiertes Programmieren bei Kowalk [Kowa1996], Abschnitt 8.2,
und Goos [Goos1996], Kapitel 10, behandelt. Spezielle sprachspezifische Darstellungen
findet man in den Lehrbüchern über objektorientierte Programmiersprachen, z. B. C++
(Stroustrup [Stro1991], Lippman [Lipp1992], RRZN [RRZN1993]), Java (Arnold/Gosling
[ArnoG1996a], Doberkat/Dißmann, [DobeD1999], Flanagan [Flan1996]), Smalltalk (Bothner/Kähler [BothK1998]). Die objektorientierte Programmiersprache C++ liegt auch
einigen allgemeinen Darstellungen von Algorithmen und Datenstrukturen zugrunde, z. B.
Sedgewick [Sedg1998], Schaerer [Scha1994] oder das schon erwähnte Buch Langsam/
Augenstein/Tenenbaum [LangAT1996].
Programme sollen fehlerfrei arbeiten. Das wichtigste Hilfsmittel hierfür ist das Testen. Es
sei auf die Bücher von Appelrath/Ludewig [AppeL1995], Kapitel 4, und Kowalk [Kowa1996],
Kapitel 12, hingewiesen.
Unter Softwaretechnik versteht man Methoden, mit denen komplexe Programmsysteme
erstellt und in Betrieb genommen werden. Es sei auf Kowalk [Kowa1996], Kapitel 13, und
auf das umfangreiche zweibändige Werk von Balzert ([Balz1996] und [Balz1998]) hingewiesen. Einen wichtigen Teilbereich der Softwaretechnik bilden objektorientierte Analyse
und objektorientierter Entwurf. Siehe hierzu Kowalk [Kowa1996], Kapitel 18. Spezielles
Lehrbücher sind Rumbough et al. [RumbBPEL1991] sowie Coad/Yourdon [CoadY1991]
und [CoadY1991a].
Schließlich soll auch das Gebiet der Programmverifikation erwähnt werden. In diesem Gebiet bemüht man sich, die Korrektheit von Programmen automatisch und mit formalen
Methoden nachzuweisen. Die Korrektheit eines Programms wird nachgewiesen, indem
bewiesen wird, daß es genau das tut, was seine Spezifikation verlangt. Trotz vieler Erkenntnisse und jahrelanger Erfahrung ist Programmverifikation immer noch nicht von
großer praktischer Bedeutung. Zur Einführung in die Programmverifikation siehe Appelrath/Ludewig [AppeL1995], Abschnitt 4.2, Goos [Goos1996], Abschnitt 8.2, oder Kowalk
[Kowa1996], Kapitel 11. Bei Kowalk sind auch Ausführungen zur Semantik von Programmiersprachen zu finden.
Kapitel 3
Darstellung von Daten durch Bitmuster
Rechner werden gebaut, um Aufgaben aus der realen Welt zu bearbeiten und zu lösen.
Einige Beispiele:
1. Numerische Berechnungen aller Art, z. B. Wettervorhersage.
2. Verwaltungsaufgaben, z.B. das Einwohnermeldewesen einer Stadt.
3. Steuerung technischer Prozesse wie z.B. Straßenverkehr, Raumflugkörper, chemische
Prozesse, Walzstraßen, Intensivstationen.
Um solche Aufgabenstellungen in Rechnern nachbilden und bearbeiten zu können, muß für
die Daten und die Bearbeitungsvorschriften der Daten eine Darstellung gefunden werden,
die ein Rechner versteht und verarbeiten kann. Man braucht ein rechnergerechtes Modell
der realen Aufgabenstellung. Die Gewinnung eines solchen Modells geht über mehrere
Stufen entsprechend den verschiedenen Maschinenschichten von Abschnitt 2.1.
Im Inneren moderner Rechner werden alle Informationen durch binäre Elemente, d.h.
durch Elemente, die zwei Zustände annehmen, dargestellt. Es ist also nötig, die Daten
und Bearbeitungsvorschriften der realen Aufgabenstellung auf binäre Informationsdarstellungen zurückzuführen.
Die Darstellung von Daten, insbesondere Zahlen, durch binäre Elemente ist sehr viel älter
als Computer und geht auf Leibniz 1 zurück.
Leibniz, Gottfried Wilhelm, ∗1646 Leipzig, †1716 Hannover. Deutscher Philosoph und Universalgelehrter. Diplomat des Mainzer Kurfürsten, später Hofrat und Bibliothekar in Hannover. Schuf die Philosophie
der Monaden. In seiner Dyadik legte er die Gundlagen für binäre Datendarstellungen. Er entdeckte die
Grundlagen der Differential- und Integralrechnung etwas früher als Newton .2
1
Newton, Sir Isaac, ∗1643 Woolsthorpe bei Grantham (England), †1727 Kensington (heute zu London). Englischer Mathematiker, Physiker und Astronom. Begründer der klassischen theoretischen Physik,
insbesondere der Mechanik. Schuf etwas später als Leibniz, aber unabhängig von ihm, die Grundlagen
der Differential- und Integralrechnung.
2
93
94
KAPITEL 3. DARSTELLUNG VON DATEN DURCH BITMUSTER
3.1
Bits und Bitmuster
Definition 3.1 Ein Element, das zwei Zustände annehmen kann und auch stets genau
einen der beiden Zustände enthält, soll Bitspeicherstelle genannt werden. Der Wert, den
es enthält, heißt Bit.
Die beiden Zustände für Bits können technisch unterschiedlich realisiert sein. Zum Beispiel:
• Strom fließt / Strom fließt nicht
• große Spannung / kleine Spannung
• positive Spannung / negative Spannung
• zwei verschiedene Magnetisierungsrichtungen
Die Zustände sollen unabhängig von der technischen Realisierung mit 0 und 1 bezeichnet werden. Die Symbole 0 und 1 stellen (zunächst) nur die beiden Zustände und keine
Zahlen dar. Die Terminologie ist ungenau: Mit „Bit“ wird oft auch die Speicherstelle bezeichnet, die einen der Werte 0 oder 1 enthält. Ob ein Bit als Speicherstelle oder Wert
der Speicherstelle gemeint ist, wird i. a. durch den Zusammenhang klar.
Unter einem Bitmuster soll eine endliche Folge von Bits verstanden werden. Abbildung
3.1 zeigt eine Folgen von sieben Bitspeicherstellen, die als aktuellen Wert das Bitmuster
0
1
1
0
1
1
0
Abbildung 3.1: Bitspeicherstellen mit Bitmuster
0110110 enthält.
Bitmuster der Länge 8 heißen Bytes. Vom Zusammenhang abhängend kann Byte auch
Speicherstelle für 8 Bits bedeuten.
Bitmuster haben in Rechnern (in der Schicht der konventionellen Maschine) zwei verschiedene Bedeutungen:
• Maschinenbefehle
Maschinenbefehle führen Operationen in Rechnern aus. Sie
◦ verändern Bitmuster und
◦ steuern die Ablauffolge von Operationen.
Maschinenbefehle werden in Abschnitt 4.2 behandelt.
3.2. DARSTELLUNG NATÜRLICHER ZAHLEN
95
• Daten
Daten lassen sich zum einen unterteilen in
◦ Daten des realen Problems und
◦ Daten für die interne Verwaltung im Rechner.
Zum anderen sind Daten
◦ elementare Daten (in der konventionellen Maschine verfügbar) oder
◦ zusammengesetzte Daten (in der konventionellen Maschine nicht verfügbar).
Im folgenden wird nur auf die Darstellung elementarer Daten durch Bitmuster eingegangen. Elementare Daten sind:
a. Zahlen
i. Natürliche Zahlen
ii. Ganze Zahlen
iii. Rationale Zahlen
b. Zeichenreihen
c. Wahrheitswerte
d. Bitmuster im eigentlichen Sinne
Wir wollen den Abschnitt mit einem einfachen, aber wichtigen Satz abschließen.
Satz 3.1 Eine Folge von k Bitstellen kann genau 2k verschiedene Bitmuster annehmen.
Beweis: Durch vollständige Induktion.
(i) Die Behauptung ist richtig für k = 1. Es gibt 21 = 2 Werte, die Werte 0 und 1.
(ii) Die Behauptung sei richtig für k. Bei k + 1 Bitsstellen kommt zu jedem Bitmuster der
Länge k entweder eine 0 oder eine 1 hinzu. Also gibt es 2 · 2k = 2k+1 Bitmuster.
2
96
KAPITEL 3. DARSTELLUNG VON DATEN DURCH BITMUSTER
h h
h h
h h
h
h h
h h h
h h
X
XXX
Tabelle 3.1: Absolute Darstellungen natürlicher Zahlen
XXIX
753 573
Tabelle 3.2: Positionsabhängige Darstellungen natürlicher Zahlen
3.2
Darstellung natürlicher Zahlen
Zunächst einige allgemeine Überlegungen. Natürliche Zahlen werden durch Zeichenreihen
oder Bilder dargestellt. Tabelle 3.1 zeigt einige absolute Darstellungen natürlicher Zahlen,
Tabelle 3.2 zeigt Beispiele für positionsabhängige Darstellungen.
Besonders wichtig sind q-adische Darstellungen, zu denen auch die übliche Dezimaldarstellung gehört.
Als Grundlage dafür zunächst zwei Sätze. Für beide Sätze soll gelten:
k ∈ N (k ≥ 1);
q ∈ N (q ≥ 2);
bn , b0n ∈ N (0 ≤ bn , b0n < q).
Satz 3.2 Jedes m ∈ N mit 0 ≤ m ≤ q k − 1 besitzt eine eindeutige Darstellung
m=
k−1
X
bn q k−1−n
n=0
Satz 3.3 Ist
m1 = b0 q k−1 + b1 q k−2 + · · · + bk−1 q 0
und
m2 = b00 q k−1 + b01 q k−2 + · · · + b0k−1 q 0
so folgt aus b0 > b00 stets m1 > m2 .
Beim Beweisen wird gebraucht:
also
q k − 1 = (q − 1)q k−1 + (q − 1)q k−2 + · · · + (q − 1)q 0
q k > (q − 1)q k−1 + (q − 1)q k−2 + · · · + (q − 1)q 0
(?)
3.2. DARSTELLUNG NATÜRLICHER ZAHLEN
97
Beweis Satz 3.3: Der Satz ist richtig für k = 1.
Sei k ≥ 2. Ist b0 > b00 , so ist b0 − b00 ≥ 1, d. h.
m1 − b00 q k−1 =
≥
>
≥
=
(b0 − b00 )q k−1 + b1 q k−2 + · · · + bk−1 q 0
q k−1
und wegen (?)
k−2
k−3
0
(q − 1)q
+ (q − 1)q
+ · · · + (q − 1)q
0 k−2
0 k−3
0
b1 q
+ b2 q
+ · · · + bk−1 q 0
m2 − b00 q k−1 .
Also m1 > m2 .
2
Beweis Satz 3.2: a. Existenz einer Darstellung
Es gilt
m
= b0 q k−1 + r0 mit 0 ≤ r0 ≤ q k−1 − 1
r0
= b1 q k−2 + r1 mit 0 ≤ r1 ≤ q k−2 − 1
·
·
·
rk−3 = bk−2 q + rk−2 mit 0 ≤ rk−2 ≤ q − 1
Dabei gilt zusätzlich
b0
b1
bk−2
< q, denn sonst wäre m = b0 q k−1 + r0 ≥ q k + r0 ≥ q k
< q, denn sonst wäre r0 ≥ q k−1
·
·
·
< q, denn sonst wäre rk−3 ≥ q 2 ,
wobei m < q k nach Voraussetzung des Satzes gelten soll.
Mit bk−1 := rk−2 ergibt sich schließlich die Darstellung
m = b0 q k−1 + b1 q k−2 + · · · + bk−2 q + bk−1 .
b. Eindeutigkeit
Es habe m zwei verschiedene Darstellungen
m = b0 q k−1 + · · · + bk−1 q 0
m = b00 q k−1 + · · · + b0k−1 q 0 .
Sei ν der erste Index von links, bei dem bν 6= b0ν , und bν > b0ν . Dann gilt nach Satz 3.3
bν q k−ν−1 + bν+1 q k−ν−2 + · · · + bk−1 q 0 > b0ν q k−ν−1 + b0ν+1 q k−ν−2 + · · · + b0k−1 q 0 .
98
KAPITEL 3. DARSTELLUNG VON DATEN DURCH BITMUSTER
Daraus folgt
m − m = bν q k−ν−1 + bν+1 q k−ν−2 + · · · + bk−1 q 0 − b0ν q k−ν−1 b0ν+1 q k−ν−2 + · · · + b0k−1 q 0
> 0.
Dieser Widerspruch zeigt, daß es nur eine Darstellung geben kann.
2
Satz 3.2 liefert die q-adische Darstellung natürlicher Zahlen. Es ist die von der Dezimaldarstellung bekannte Kurzdarstellung durch Positionsschreibweise üblich (Abbildung
3.2). Mit wachsendem k hat jedes m unendlich viele Darstellungen. Falls m > 0, gibt es
q k−1 q k−2
b0
b1
q q q
q q q
q2
q1
q0
bk−3 bk−2 bk−1
Abbildung 3.2: Kurzdarstellung durch Positionsschreibweise
darunter aber nur eine ohne führende Nullen.
Bei Dezimaldarstellungen schreibt man keine führenden Nullen und die Null einstellig. In
Rechnern ist k festgelegt, und man stellt stets alle k Ziffern dar.
Aus Informatiksicht ist noch die folgende Bemerkung wichtig: Bisher haben wir natürliche
Zahlen durch (i. a. andere) natürliche Zahlen ausgedrückt, jedoch noch nicht festgelegt,
daß die natürlichen Zahlen
0, 1, · · · , (q − 1)
durch q verschiedene Zeichen (q-adische Ziffern) dargestellt werden und welches diese
Zeichen sind.
Beispiel 3.1 Im folgenden sind drei übliche und ein ungebräuchlicher Satz von q-adische
Ziffern angegeben.
10-adische (dezimal):
2-adische (dual, binär):
5-adische:
5-adische (unkonventionell):
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
0, 1
0, 1, 2, 3, 4,
2, , 4, #, $
Es muß klar sein, welches Zeichen zu welcher Zahl gehört. Das ist insbesondere für den
letzten Ziffernsatz wichtig.
2
Nach den vorangehenden allgemeinen Betrachtungen soll im folgenden der für Rechner
wesentliche Fall q = 2 und k fest betrachtet werden. Es ergibt sich die Dualdarstellung
natürlicher Zahlen durch Bitmuster der Länge k:
b0 b1 b2 · · · bk−1 = b0 · 2k−1 + b1 · 2k−2 + · · · + bk−1 · 20
3.2. DARSTELLUNG NATÜRLICHER ZAHLEN
99
mit der (erst jetzt zu treffenden) Festlegung: Zeichen 0 entspricht Zahl 0 und Zeichen 1
entspricht Zahl 1. Es wird auch die Bezeichnung Binärdarstellung benutzt.
Der darstellbare Bereich ist:
0, 1, · · · , 2k − 1 .
Die folgenden Bitmusterlängen und Zahlbereiche sind in Rechnern üblich:
k
k
k
= 8
= 16
= 32
0, 1, · · · , 28 − 1 = 255
0, 1, · · · , 216 − 1 = 65.535
0, 1, · · · , 232 − 1 = 4.294.967.295
Beispiel 3.2 Zwei Beispiele für die Dualdarstellung natürlicher Zahlen:
1.
k=8
10010011 = 1 · 27 + 0 · 26 + 0 · 25 + 1 · 24 + 0 · 23 + 0 · 21 + 1 · 21 + 1 · 20
= 128 + 16 + 2 + 1
= 147
2.
k = 10
0111001000 = 0 · 29 + 1 · 28 + 1 · 27 + 1 · 26 + 0 · 25 + 0 · 2 · 24 + 1 · 23 + 0 · 22 + 0 · 21 + 0 · 20
= 256 + 128 + 64 + 8
= 456
2
Wenn auf die Basis der Zahldarstellung ausdrücklich hingewiesen werden soll, wird die
Schreibweise 10010011b = 147d angewandt. Falls die Bedeutungen aus dem Zusammenhang klar werden, kann der Index b und/oder der Index d fortgelassen werden: 10010011 =
147 .
Ein vorgegebenes Bitmuster kann jedoch auch eine andere Bedeutung haben und eine
andere Zahl darstellen. Zum Beispiel gilt bei Zweierkomplementdarstellung der Länge
k = 8 (wird in Abschnitt 3.3 eingeführt): 10010011 = −109 .
Merke: Das Gleichheitszeichen zwischen Bitmustern und dargestellten Objekten ist stets
im Zusammenhang als „stellt dar“ zu interpretieren.
Abschließende Bemerkung: So nützlich, natürlich und häufig die Interpretation von
Bitmustern als Dualdarstellung natürlicher Zahlen auch ist, man kann die Zahlen 0, 1, · · · , 2k−1
auch anders durch Bitmuster der Länge k darstellen.
100
KAPITEL 3. DARSTELLUNG VON DATEN DURCH BITMUSTER
Beispiel 3.3 Darstellung durch Vertauschen von Nullen und Einsen
0
1
9
13
127
128
255
11111111
11111110
11110110
11110010
10000000
01111111
00000000
2
Wieviele Abbildungen von Bitmustern der Länge k auf die natürlichen Zahlen 0, 1, · · · , 2k−1
gibt es (Abbildung 3.3)? Es gibt so viele Darstellungen der ersten 2k natürlichen Zahlen
............................................
...........
........
........
......
......
......
......
.....
.
.
.
.
....
...
.
....
.
..
...
.
.
...
...
.
...
..
...
.
..
...
.
...
..
.
...
....
...
...
..
..
...
..
..
...
...
..
...
..
....
...
...
...
...
..
...
...
...
..
...
..
.
.
...
.
...
...
....
....
....
....
.....
....
.
......
.
.
.
......
...
........
......
.......
..........
...............................................
Bitmuster
der Länge
k
f
(bijektiv)
............................................
...........
........
........
......
......
......
......
.....
.
.
.
.
....
..
.
.
....
.
...
...
.
.
...
...
...
.
.
.
...
.
...
...
.
...
..
.
...
.
.
.
...
....
..
..
...
..
..
...
...
...
...
...
..
.
...
.
.
k
.
...
...
...
...
...
..
...
..
...
.
.
...
...
...
..
...
...
....
...
.....
.....
.
......
.
.
.
.......
....
........
......
...........
........
..........................................
-
natürliche
Zahlen
0, 1, · · · , 2 − 1
Abbildung 3.3: Darstellung natürlicher Zahlen durch Bitmuster
durch Bitmuster der Länge k wie es bijektive Selbstabbildungen der Menge der k-stelligen
Bitmuster auf sich selbst gibt, also 2k ! :
f | f bijektive Selbstabbildung von {0, 1}k = (Anzahl Bitmuster der Länge k)! = 2k ! .
Für k = 8 ergibt das 2k ! = 256! > 100157 > 10300 Möglichkeiten.
Zur Darstellung eines Bereichs natürlicher Zahlen werden nicht immer alle Bitmuster einer
festen Länge k benutzt (Abbildung 3.4).
Beispiel 3.4 Natürliche Zahlen werden zum Teil in Dezimaldarstellung durch binärcodierte Dezimalziffern dargestellt. Zur Darstellung der Zahlen 0, 1, · · · , 9 werden Bitmuster
3.3. DARSTELLUNG GANZER ZAHLEN
.....
................... ............................
.........
.......
.......
......
......
......
.
.
.
.
.
.....
.
.....
...
....
....
.
.
...
..
.
.
...
...
...
.
...
..
.
.........................
.
.
..
.
.
.
.
.
.
...... ....
...
.
.
..
.
.
.
.
.
... ....
...
...
.
.
... ..
..
.
.
...
.. ...
.
.. ...
...
....
....
.. .
..
...
.. ..
...
.. ....
...
.
.
...
...
.
.
...
.
...
...
....
.... ..
...
.......
.... ....
...
.............................
...
..
.
.
...
...
...
...
...
....
...
...
...
.
.
.
.....
.....
.....
.....
......
......
.......
.......
.........
........
................
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.............
Bitmuster
der
Länge k
101
...................................
.............
.........
........
.......
.......
......
.....
.....
.
.
.
.
.....
...
.
.
....
.
...
....
.
.
..
...
.
.
...
...
.
...
..
...
.
.......................
.
.
.
.
...
.
.
....
.
......
.....
...
.
.
.
...
.
.
...
...
...
...
...
.
..
...
...
....
...
...
...
...
..
...
...
...
...
..
...
..
.
.
...
..
.
...
.
.
.
.
...
...
.
.
.
.
.
.
...
...
.
.
.
.
.....
.
...
.
.
.......
....
...
...
.............................
...
...
...
..
...
...
.
...
.
..
....
...
....
....
.....
.....
......
.....
.
......
.
.
.
.
..
.......
.......
.........
...................................................
f
partiell
-
Natürliche
Zahlen
Abbildung 3.4: Darstellung natürlicher Zahlen durch partielle Abbildungen
der Länge
k = 4 verwendet.
0000 →
7
0
0001 →
7
1
0010 →
7
2
..
.
1000 →
7
8
1001 →
7
9
1010
1011
1100
1101
1110
1111















Diesen Bitmustern werden keine
Zahlen, jedoch manchmal Vorzeichen zugeordnet.
2
Anmerkung 3.1 Alle modernen Rechner benutzen zur Darstellung natürlicher Zahlen
die auf Seite 98 eingeführte Dualdarstellung. Die Anordnung der Bits innerhalb eines
Speicherwortes variiert jedoch bei den verschiedenen Rechnertypen. Es sind zwei unterschiedliche Anordnungen in der Praxis üblich, bekannt unter den Namen „big endean“
und „little endean“. Weitere Einzelheiten sind in Anmerkung 4.1, Seite 124, zu finden. 2
3.3
Darstellung ganzer Zahlen
Mit Bitmustern der Länge k können maximal 2k ganze Zahlen dargestellt werden. Welche
Zahlen soll man nehmen und wie darstellen? Es ist sinnvoll, möglichst so viele positive
wie negative zu haben. Wählt man als nichtnegative Zahlen
0, 1, · · · , 2k−1 − 2, 2k−1 − 1 ,
so sind 2k−1 Bitmuster verbraucht. Es bleiben 2k−1 Bitmuster frei. Welche Bitmuster sind
verbraucht? Für die nichtnegativen Zahlen wird naheliegenderweise die Dualdarstellung
natürlicher Zahlen benutzt, und damit haben die nichtnegativen Zahlen in der Darstellung links eine Null. Es bleiben die Bitmuster übrig, die dort eine Eins haben. Eine
102
KAPITEL 3. DARSTELLUNG VON DATEN DURCH BITMUSTER
naheliegende Lösung zur Darstellung der negativen Zahlen wäre, das erste (linke) Bit als
Vorzeichenstelle mit der Zuordnung
und
0 =
ˆ +
1 =
ˆ −
zu nehmen. Die übrigen Bits geben den Betrag an, man hat die Vorzeichen - Betrag Darstellung.
Beispiel 3.5
k=4
Es können 24 = 16 Zahlen dargestellt werden, davon sind 23 = 8 nicht negativ:
0, 1, 2, 3, 4, 5, 6, 7
Ihre Dualdarstellung ist:
0
1
2
3
=
=
=
=
0000
0001
0010
0011
4
5
6
7
=
=
=
=
0100
0101
0110
0111
Für die negativen Zahlen ergeben sich bei Vorzeichen-Betrag-Darstellung die Zuordnungen
−0 = 0
−1
−2
−3
=
=
=
=
1000
1001
1010
1011
−4
−5
−6
−7
=
=
=
=
1100
1101
1110
1111
2
Die Vorzeichen-Betrag-Darstellung wird zur Darstellung ganzer Zahlen in Rechnern nicht
(mehr) benutzt, wohl aber zur Darstellung anderer Zahlen. Ein wesentlicher Nachteil ist
die doppelte Darstellung der Null.
In modernen Rechnern benutzt man statt dessen für ganze Zahlen die Zweierkomplementdarstellung. Abbildung 3.5 zeigt die Zuordnung der Bitmuster einmal für den Fall, daß
nur natürliche Zahlen dargestellt werden und zum anderen für den Fall, daß ganze Zahlen
im Zweierkomplement dargestellt werden. Man sieht:
Ganze Zahlen im
Zweierkomplement (k Bits)
m≥0
m<0
Natürliche Zahlen in
Dualdarstellung (k Bits)
7
→
m
k
7→
2 − |m| .
3.3. DARSTELLUNG GANZER ZAHLEN
103
2k−1 − 1 2k−1
0
?
z
}|
−2k−1
{
−1
|
{z
}
z
}|
{
0
?
|
2k − 1
{z
}
2k−1 − 1
Abbildung 3.5: Zweierkomplementdarstellung ganzer Zahlen
Die darstellbaren nichtnegativen Zahlen behalten die Darstellung, die sie haben, wenn nur
natürliche und nicht auch negative Zahlen darzustellen wären. Eine darstellbare negative
Zahl m hat die gleiche Darstellung, wie sie die natürliche Zahl 2k − |m| hätte, wenn nur
natürliche Zahlen dargestellt würden.
Wie erhält man nun die Bitmuster für die negativen Zahlen? Das soll nur als Algorithmus
und ohne weitere Begründungen erläutert werden. Um das Zweierkomplement eines Bitmusters der Länge k zu gewinnen, werden die folgenden Operationen mit dem Bitmuster
ausgeführt:
1. Logisch invertieren
2. Ergebnis als natürliche Zahl in k Bits auffassen und 1 addieren
3. Überlauf (über 2k − 1) vernachlässigen
Damit hat man
a. die Festlegung der Darstellung einer negativen Zahl m über die Dualdarstellung der
positiven Zahl 2k − |m| und
b. einen Algorithmus, der zu einem Bitmuster das Zweierkomplement bildet.
Es ist nicht selbstverständlich, daß beides das gleiche ergibt. Für −(2k−1 − 1) ≤ m ≤
2k−1 − 1 gilt jedoch:
Darstellung von (−m) = Zweierkomplement der Darstellung von m
Beispiel 3.6 (Fortsetzung von Beispiel 3.5)
(k = 4)
Die Darstellungen der negativen Zahlen ergeben sich als Zweierkomplemente der Beträge:
104
KAPITEL 3. DARSTELLUNG VON DATEN DURCH BITMUSTER
−1
=
−2
−3
−4
−5
−6
−7
=
=
=
=
=
=
Zweierkomplement von 0001 =
1110
+ 1
= 1111
1111
=
=
=
=
=
=
1110
1101
1100
1011
1010
1001
Einige zusätzliche Tests:
= Zweierkomplement von 0000 =
−0
1111
+ 1
=
0000
=
0101
1|0000
= Zweierkomplement von 1011 =
−(−5)
0100
+ 1
0101
Anmerkung:
Das Zweierkomplement von 1000 =
0111
+ 1
= 1000
1000
kann nicht als Zweierkomplement einer darstellbaren nichtnegativen Zahl gewonnen werden. Es gibt zwei Möglichkeiten:
a. 1000 = −8 .
b. Bitmuster 1000 wird zur Darstellung von Zahlen nicht benutzt.
2
k-mal
z }| {
Die Frage, ob das Bitmuster 1 0 · · · 0 zur Darstellung der Zahl −2k−1 genommen werden oder keine Zahl darstellen soll, ist für allgemeines k zu beantworten. Es wird in allen
modernen Rechnern die erste Möglichkeit gewählt. Man hat dann jedoch im negativen Bereich eine darstellbare Zahl mehr als im positiven Bereich, und das muß bei Berechnungen
und Maschinenbefehlen berücksichtigt werden.
3.4. DARSTELLUNG RATIONALER ZAHLEN
105
Abschließende Bemerkung: Außer der Vorzeichen-Betrag-Darstellung und der Darstellung im Zweierkomplement gab es früher auch die Einerkomplementdarstellung ganzer
Zahlen, bei der der negative Wert einer Zahl durch logisches Invertieren gewonnen wird
und die Null zwei Darstellungen besitzt.
Wenn ganze Zahlen bei Gleitpunktdarstellungen (siehe Abschnitt 3.4) als Exponenten
benutzt werden, wird oft die m-Exzeß-Darstellung verwendet. Dabei ist m = 2k−1 und
wird auf den Wert der darzustellenden Zahl addiert. Das (nichtnegative) Ergebnis wird
als natürliche Zahl in Dualdarstellung angegeben. Abbildung 3.6 zeigt die Zurordnung
z
?
−2k−1
}|
|
{
−1
z
0
2k − 1
2k−1 − 1 2k−1
0
{z
}|?
}
|
{z
}
{
2k−1 − 1
Abbildung 3.6: m-Exzeßdarstellung ganzer Zahlen
von Bitmustern zu ganzen Zahlen (vergleiche auch Abbildung 3.5).
Manchmal ist m auch von 2k−1 verschieden, z. B. m = 2k−1 −1. Der Bereich darzustellender
Zahlen ist dann so zu wählen, daß auch weiterhin m + a > 0 für jede Zahl a des Bereichs
gilt.
3.4
Darstellung rationaler Zahlen
Für Aufgaben der realen Welt werden außer natürlichen und ganzen Zahlen auch reelle und
komplexe Zahlen benötigt. Komplexe Zahlen werden als Paare reeller Zahlen dargestellt
und sind keine elementaren Daten.
Reelle Zahlen können rationale Zahlen (z. B. 31 ; 0, 78 ; 0, 55 · · ·), irrational algebraische
√
1
Zahlen (z. B. 2 ; 3− 5 ) oder irrational transzendente Zahlen (z. B. e ; π ; 3eπ ) sein. Sie
werden jedoch alle beim bisherigen Rechnen (ohne Computer) durch endliche Dezimalbrüche approximiert. Dabei sind die Approximationsgenauigkeiten meistens höher als die
Meßgenauigkeiten der realen Welt. Auch in Rechnern werden rationale Zahlen zur Näherung von reellen Zahlen eingesetzt, wobei man möglichst gute Annäherungen haben
will.
106
KAPITEL 3. DARSTELLUNG VON DATEN DURCH BITMUSTER
Es gibt verschiedene Arten der Darstellung rationaler Zahlen in Rechnern. Besonders
wichtig sind Gleitpunktdarstellungen (Gleitkomma, Fließkomma, floating point), und nur
diese sollen im folgenden betrachtet werden. Sie sollen zunächst anhand eines einfachen
Beispiels erläutert werden.
Beispiel 3.7 Es werden Bitmuster der Länge k = 12 betrachtet. Sie werden, wie in
Abbildung 3.7 angegeben, in die Teilbitmuster V, CH und MAN unterteilt. Diesen TeilV
CH
MAN
Abbildung 3.7: Gleitpunktdarstellung in 12 Bits
bitmustern werden Zahlen bzw. Vorzeichen zugeordnet. Die zugeordneten Werte sollen
entsprechend mit v, ch und man bezeichnet werden.
Im Teilbitmuster MAN wird die Mantisse angegeben. Sie stellt in 8 Bits einen Dualbruch
dar. Die Bits sind die Ziffern für die wachsenden Potenzen von 12 . Für die Mantisse MAN =
10011001 ergibt z. B.
man = 1 · 12 + 0 · 14 + 0 ·
1
8
+1·
1
16
+1·
1
32
+0·
1
64
+0·
1
128
+1·
1
256
= 0, 59765625 .
Entsprechend ergibt die Mantisse MAN = 001000000
man = 1 ·
1
8
= 0, 125 .
Das Bit V ist das Vorzeichen. Es bezieht sich auf die Mantisse.
1 =
ˆ −.
0 =
ˆ +
Das Teilfeld CH ist die Charakteristik. Sie legt den Exponenten zur Basis 2 fest. Man
will möglichst gleich viele negative wie positive Exponenten haben und kann z. B. die
Zuordnung nach der 4-Exzeß-Darstellung (siehe Abschnit 3.3) treffen:
CH
CH
CH
CH
CH
CH
CH
CH
=
=
=
=
=
=
=
=
000
001
010
011
100
101
110
111
:
:
:
:
:
:
:
:
ch
ch
ch
ch
ch
ch
ch
ch
=
=
=
=
=
=
=
=
−4
−3
−2
−1
0
1
2
3
.
Die dargestellte rationale Zahl ergibt sich schließlich nach der Regel:
3.4. DARSTELLUNG RATIONALER ZAHLEN
Dargestellte Zahl =
107
v · 2ch · man .
Beispiele:
1 110 1000 1001 = −22 · ( 21 +
=
0 000 1111 1111 =
1
32
− 2, 140625
+
1
)
256
+ 2−4 · ( 21 + 41 +
1
8
+
1
16
+
1
32
+
1
64
+
1
128
+
1
)
256
= 0, 062255859
Ohne Zusatzfestlegungen ist die eingeführte Darstellung von rationalen Zahlen nicht eindeutig:
011010000000 = 22 · 21 = 2
011101000000 = 23 ·
1
4
= 2.
Um Eindeutigkeit zu erreichen, soll die erste Stelle der Mantisse stets 1 sein. Solche Mantissen heißen normalisiert. Die erste der obigen Darstellungen von 2 ist normalisiert, die
zweite nicht. Für eine normalisierte Mantisse m gilt stets 1 > m ≥ 0,5 .
Mit normalisierten Mantissen kann man allerdings die 0 nicht darstellen. Es soll daher
zuätzlich festgelegt werden:
0 000 00000000 = 0 .
2
Der Zahlenbereich, der mit Gleitpunktzahlen nach Beispiel 3.7 dargestellt werden kann,
ist für reale Rechner viel zu klein. In modernen Rechner wird meistens eine Gleitpunktdarstellung nach dem Standard IEEE 754 gewählt. Die dafür gültige Norm ist ANSI/IEEE
8543 [ANSI1987]. Diese soll im folgenden kurz dargestellt werden.
Gleitpunktzahlen nach IEEE 754 gibt es in einfacher Genauigkeit (single precision) und
doppelter Genauigkeit (double precision). Es gibt auch Gleitpunktzahlen in erweiterter
Genauigkeit (extended precision), auf die aber nicht weiter eingegangen werden soll. Abbildung 3.8 zeigt den Aufbau von Zahlen einfacher und doppelter Genauigkeit.
Für das Vorzeichen V gilt
0=
b + 1=
b −.
Von den Werten 0, · · · , 255 (bzw. 0, · · · , 2047) der Charakteristik CH haben 0 und 255
(bzw. 0 und 2047) eine besondere Bedeutung und werden nicht als Exponenten betrachtet.
Die übrigen werden in 127-Exzeß-Darstellung (bzw. in 1023-Exzeß-Darstellung) als Exponent ch zur Basis 2 genommen. Damit ergibt sich der Exponentenbereich −126, · · · , 127
(bzw. −1022, · · · , 1023).
3
ANSI (American National Standards Institute). Zu IEEE siehe Fußnote auf Seite 597.
108
KAPITEL 3. DARSTELLUNG VON DATEN DURCH BITMUSTER
1
8
23
V
CH
MAN
a. Einfache Genauigkeit (32 Bits)
1
11
52
V
CH
MAN
b. Doppelte Genauigkeit (64 Bits)
Abbildung 3.8: Gleitpunktzahlen nach IEEE 754
Der Mantisse MAN = b1 b2 · · · b23 (bzw. MAN = b1 b2 · · · b52 ) wird der Dualbruch b1 · ( 12 )1 +
b2 · ( 12 )2 + · · · + b23 ( 21 )23 (bzw. b1 · ( 12 )1 + b2 · ( 21 )2 + · · · + b52 · ( 21 )52 ) zugeordnet. Es sind
für MAN alle Bitmuster zulässig. Normalisierung wird dadurch erreicht, daß ein weiteres,
nicht wirklich gespeichertes Bit in der Mantisse ganz vorn angenommen wird. Dieses Bit
ist stets 1 und es folgt ihm stets ein (auch nur implizit vorhandenes) Komma. Das ergibt
1
1
man = 1 + b1 · ( )1 + · · · + b23 · ( )23
2
2
1
1
(bzw. man = 1 + b1 · ( )1 + · · · + b52 · ( )52 ) ,
2
2
und es gilt stets
1
1 ≤ man ≤ 2 − ( )23
2
1
(bzw. 1 ≤ man ≤ 2 − ( )52 ) .
2
Die betragsmäßig kleinsten darstellbaren (normalisierten) Zahlen sind somit ±1, 0 · 2−126
(bzw. ±1, 0·2−1022 ). Rechenergebnisse, die auf betragsmäßig kleinere Zahlen führen, wären
bei früheren Gleitpunktdarstellungen zu 0 gesetzt worden oder hätten eine UnterlaufUnterbrechung (Alarme siehe Unterabschnitt 4.2.2) ergeben. Bei Gleitpunktzahlen nach
IEEE 754 ist mit der Zulassung denormalisierter Zahlen (denormalized number) unter
Verlust gültiger Stellen ein gleitender Übergang zu 0 möglich. Dazu werden der Wert
CH = 0 und beliebige nichtnull Mantissenwerte benutzt. Der Exponent ist stets 2−127
(bzw. 2−1023 ). Die Mantisse MAN ist wieder ein Dualbruch, allerdings ohne ein implizit
vorangestelltes Bit 1. Normalisierte Zahlen haben 24 (bzw. 53) gültige (Binär-)Stellen.
Bei denormalisierten Zahlen bestimmt die am weitesten links stehende 1 der Mantisse die
Anzahl gültiger Stellen.
3.5. HEXADEZIMALDARSTELLUNG VON BITMUSTERN
109
Beispiel 3.8
a = 0 00000001 0000 · · · 0 = 2−126 · 1, 0
b = 0 00000000 1111 · · · 1 = 2−127 · (1 − 2−23 )
c = 1 00000000 0110 · · · 0 = −2−127 · ( 14 + 81 )
= −2−127 · 0, 375
d = 0 00000000 000 · · · 01 = 2−127 · 2−23
= 2−150
a ist die kleinste positive normalisierte Zahl. b ist die größte und d die kleinste positive
denormalisierte Zahl. a hat 24, b hat 23, c hat 22 und d hat 1 gültige Stelle.
2
Die Zahl Null wird durch die beiden Bitmuster
V=0
und V = 1
CH = 0
CH = 0
MAN = 0
MAN = 0
dargestellt.
Bei Überlauf über die betragsmäßig größten Zahlen hinweg, sieht IEEE 754 keine denormalisierten Zahlen, sondern die Werte +∞ und −∞ vor. Bei k = 32 ist die Darstellung
+∞ = 0 11111111 00 · · · 0
−∞ = 1 11111111 00 · · · 0
Bei k = 64 ist die Darstellung entsprechend. Mit diesen Werten und normalen Zahlen
kann nach den üblichen Regeln gerechnet werden. Einige Kombinationen wie z.B. +∞ −
∞, +∞ · 0 oder 00 ergeben unbestimmte Ergebnisse. Diese werden mit dem Format
NaN (Not a Number) dargestellt. Tabelle 3.3 zeigt die verschiedenen Formate in einer
Zusammenstellung.
Für eine Reihe weiterer wichtiger Fragen (Rundung, Formatumwandlung, Fehlersituationen u. a.), die hier nicht behandelt werden können, siehe die Norm ANSI/IEEE 854
[ANSI1987].
3.5
Hexadezimaldarstellung von Bitmustern
Lange Bitmuster sind – für Menschen, nicht für Rechner! – unbequem. Daher wird eine
abkürzende Schreibweise durch Hexadezimalzeichen (Sedezimalzeichen) eingeführt:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F
Manchmal werden auch die Kleinbuchstaben a, b, c, d, e, f zugelassen. Die Hexadezimalzeichen sind Abkürzungen für Bitmuster der Länge 4, und es wird die folgende Zuordnung
getroffen:
110
KAPITEL 3. DARSTELLUNG VON DATEN DURCH BITMUSTER
normalisiert
±
0 < CH < Max
beliebiges Bitmuster
denormalisiert
±
00 · · · 0
nichtnull Bitmuster
Null
±
00 · · · 0
00 · · · 0
unendlich
±
11 · · · 1
00 · · · 0
keine Zahl
±
11 · · · 1
nichtnull Bitmuster
Tabelle 3.3: Formate der Gleitpunktdarstellung IEEE 754
0000 = 0
0001 = 1
..
.
0111 = 7
1000 = 8
1001 = 9
1010 =
1011 =
1100
1101
1110
1111
A
B
= C
= D
= E
= F .
Dabei soll vereinbart werden, daß hexadezimal geschriebene Bitmuster immer ein Vielfaches von 4 als Länge haben.
Beispiele:
1001 0010 1000 = 928
1111 1111
= FF
Auch mit Hexadezimalzeichen können natürliche Zahlen dargestellt werden.
Beispiel 3.9
AF = 1010 1111 = 1 · 27 + 1 · 25 + 1 · 23 + 1 · 22 + 1 · 21 + 1 · 20 = 175
2
Die Hexadezimalzeichen können mit der Festlegung
3.5. HEXADEZIMALDARSTELLUNG VON BITMUSTERN
0 =
ˆ
..
.
9
10
11
12
13
14
15
=
ˆ
=
ˆ
=
ˆ
=
ˆ
=
ˆ
=
ˆ
=
ˆ
111
0
9
A
B
C
D
E
F
auch als Ziffern zur Basis 16 (Hexadezimalziffern) aufgefaßt werden. Hexadezimalzeichenreihen sind dann natürliche Zahlen in 16-adischer Darstellung.
Beispiel 3.10
AF = A · 161 + F · 160 = 160 + 15 = 175
2
Es ist kein Zufall, daß in den Beispielen 3.9 und 3.10 die gleiche natürliche Zahl dargestellt
wird, denn
1. Die Hexadezimalzeichen stellen in beiden Darstellungen die gleiche Zahl dar, z. B.
A = 10d
A = 1010 = 1 · 23 + 1 · 21 = 8 + 2 = 10d
Hexadezimalziffern
Dualziffern.
2. Auch für beliebige Zeichenreihen von Hexadezimalziffern ergeben beide Darstellungen die gleiche Zahl
(a) Mit Hexadezimalziffern
h0 h1 h2 · · · hp−1 = h0 · 16p−1 + h1 · 16p−2 + · · · + hp−1 · 160
(b) Mit Dualziffern (k = 4p)
b00 · 2k−1 + b01 · 2k−2 + b02 · 2k−3 + b03 · 2k−4+
b10 · 2k−5 + b11 · 2k−6 + b12 · 2k−7 + b13 · 2k−8+
..
..
.
.
b(p−1)0 · 23 + b(p−1)1 · 22 + b(p−1)2 · 21 + b(p−1)3 · 20
= (b00 · 23 + b01 · 22 + b02 · 21 + b03 · 20) · 24p−4 +
(b10 · 23 + b11 · 22 + b12 · 21 + b13 · 20 ) · 24p−8+
..
..
.
.
(b(p−1)0 · 23 + b(p−1)1 · 22 + b(p−1)2 · 21 + b(p−1)3 · 20 ) · 24p−4p
= h0 · (24 )p−1 + h1 · (24 )p−2 + · · · + hp−1 (24 )0 .
112
KAPITEL 3. DARSTELLUNG VON DATEN DURCH BITMUSTER
Früher waren auch Oktalzeichen für Bitmuster der Länge 3 üblich:
0
1
2
3
=
=
=
=
000
001
010
011
4
5
6
7
=
=
=
=
100
101
110
111
Sie werden kaum noch benutzt.
3.6
Darstellung von Zeichenreihen
Definition 3.2 Ein Alphabet (Zeichenvorrat) ist eine endliche, nicht leere Menge von
Zeichen.
Eine Zeichenreihe (Zeichenkette, string) ist eine endliche Folge von Zeichen aus einem
Alphabet.
Beispiel 3.11
Alphabet1
Alphabet2
Alphabet3
Alphabet4
7038
XAAAAEN
a
A<B
=
=
=
=
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} Dezimalziffern
{A, B, C, · · · , X, Y, Z} Großbuchstaben
{6=, <, >, |, \, @, $, %, !}
{0, · · · , 9, A, · · · , Z, a, · · · , z}
Zeichenreihe über Alphabet1 (auch über Alphabet4)
Zeichenreihe über Alphabet2 (auch über Alphabet4)
Zeichenreihe über Alphabet4
Zeichenreihe über keinem der Alphabete,
wohl aber über Alphabet5 := Alphabet2 ∪ Alphabet3
2
Unter der Länge einer Zeichenreihe wird die Anzahl Zeichen der Zeichenreihe verstanden.
Vereinbarung: Über jedem Alphabet gibt es auch die leere Zeichenreihe λ. Sie hat die
Länge 0.
Beispiel 3.12 Das Alphabet sei
A := Alphabet1 = {0, 1, · · · , 9}. Dann sind
X = 000137;
Beispiele für Zeichenreihen über A.
X = 5;
X=λ
2
3.6. DARSTELLUNG VON ZEICHENREIHEN
113
Operationen mit Zeichenreihen:
1. Zusammensetzen von Zeichenreihen (Konkatenation).
Binäre Operation, bei der die beiden Zeichenreihen hintereinander geschrieben werden. Als Operationszeichen wird ◦ („Kuller“) verwendet.
Beispiel: A = {0, 1, 2, · · · 9}.
X = 310
X = 111
Y = 0777
Y =λ
X ◦ Y = 3100777
Y ◦ X = X ◦ Y = 111
A∗ bildet mit der Verknüpfung ◦ ein (i. a. nicht kommutatives) Monoid, das freie
Monoid über A.
2. Zerlegen einer Zeichenreihe.
abcHHdDxXr kann beispielsweise zerlegt werden in
a bc HHdD xXr
Teilzeichenreihe (substring) einer Zeichenreihe: Zeichenreihe, die aus der gegebenen
Zeichenreihe durch Zerlegung gewonnen werden kann.
3. Entfernen einer Teilzeichenreihe aus einer Zeichenreihe.
4. Einfügen einer Zeichenreihe in eine Zeichenreihe.
Wörter und formale Sprachen: Ist A ein Alphabet, so wird mit A∗ die Menge der
Zeichenreihen über A bezeichnet. A∗ heißt auch Menge der Wörter über A.
Man sagt, B ist eine formale Sprache über A, wenn B eine Teilmenge von A∗ ist.
Beispiel 3.13 A = {0, 1, 2, · · · 9}. Beispiele für formale Sprachen über A
B = {0, 1117, 33333}
C = {X ∈ A∗ | X beginnt mit 1} .
2
Diese Begriffe sind in der Informatik an anderer Stelle wichtig (siehe Seiten 174 und 211),
jedoch weniger für die Darstellung von Zeichenreihen durch Bitmuster.
114
KAPITEL 3. DARSTELLUNG VON DATEN DURCH BITMUSTER
Lexikographische Ordnung: Ist auf dem Alphabet A eine lineare Ordnung gegeben,
so wird daraus eine lineare Ordnung auf der Menge der Wörter über A, die lexikographische
Ordnung. Dazu legt man fest:
1. Zwei Wörter sind gleich, wenn sie gleich lang sind und an allen Stellen übereinstimmen.
2. Von zwei Wörtern, die nicht gleich sind, ist dasjenige kleiner, das an der ersten
Ungleichheitsstelle den kleineren Wert aufweist. Gibt es keine Ungleichheitsstelle,
so ist das kürzere Wort das kleinere.
Diese Festlegung stimmt mit der „alphabetischen Ordnung“ im umgangsprachlichen Sinne
überein.
Codes: In Rechnern heißen die Alphabete Codes. Sie bestehen im allgemeinen aus Großund Kleinbuchstaben, Dezimalziffern sowie Satz- und Sonderzeichen. Besonders wichtig
sind die in den Tabellen 3.4 und 3.5 gegebenen Codes ASCII (American Standard Code
for Information Interchange) und EBCDIC (Extended Binary Coded Decimal Interchange
Code). Die Zeichen beider Codes werden in 8 Bits dargestellt, obwohl ASCII eigentlich
ein 7-Bit-Code ist.
Beispiel 3.14
X1a = 11100111 11110001 10000001
(EBCDIC)
(a-b) = 00101000 01100001 00101101 01100010 00101001 (ASCII)
2
Anmerkung: Manche Zeichenreihen stellen Zahlen dar. Bitmuster für Zeichenreihen,
die Zahlen darstellen, sind i. a. verschieden von den Bitmustern, die die Zahlen direkt
darstellen.
Beispiel 3.15
Als natürliche Zahlen: +073 = 73
Als Zeichenreihen:
+073 =
6
73
Darstellung als Dualzahl in 16 Bits:
+073 = 00000000 01001001
73 = 00000000 01001001
Als Zeichenreihen in EBCDIC-Darstellung:
+073 =
73 =
01001110 11110000 11110111 11110011
11110111 11110011
2
0000
0001
0010
0011
0100
0101
0110
0111
1000
1001
1010
1011
1100
1101
1110
1111
sp
0
@
P
‘
p
!
1
A
Q
a
q
Erläuterungen:
"
2
B
R
b
r
a.
b.
c.
#
3
C
S
c
s
$
4
D
T
d
t
%
5
E
U
e
u
&
6
F
V
f
v
’
7
G
W
g
w
(
8
H
X
h
x
)
9
I
Y
i
y
*
:
J
Z
j
z
+
;
K
[
k
{
,
<
L
\
l
|
–
=
M
]
m
}
.
>
N
b
n
e
/
?
O
_
o
3.6. DARSTELLUNG VON ZEICHENREIHEN
0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
sp bedeutet Leerzeichen
In den Zeilen stehen die linken, in den Spalten
die rechten 4 Bits des Bitmusters
Es gibt auch eine deutsche Variante des Codes. Dabei gilt:
Deutsch:
Ä Ö Ü ä ö ü ß §
International: [ \ ] { | } ˜ @
Tabelle 3.4: ASCII (American Standard Code for Information Interchange)
115
116
0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
sp
&
–
{
}
\
0
/
a
j
e
A
J
1
Erläuterungen:
a.
b.
[
]
b
k
s
c
l
t
d
m
u
e
n
v
f
o
w
g
p
x
h
q
y
i
r
z
B
K
S
2
C
L
T
3
D
M
U
4
E
N
V
5
F
O
W
6
G
P
X
7
H
Q
Y
8
I
R
Z
9
!
b
:
.
$
,
#
<
*
%
@
sp bedeutet Leerzeichen
In den Zeilen stehen die linken, in den Spalten
die rechten 4 Bits des Bitmusters
Tabelle 3.5: EBCDIC (Extended Binary Coded Decimal Interchange Code)
(
)
_
’
+
;
>
=
|
?
”
KAPITEL 3. DARSTELLUNG VON DATEN DURCH BITMUSTER
0000
0001
0010
0011
0100
0101
0110
0111
1000
1001
1010
1011
1100
1101
1110
1111
3.7. EIGENTLICHE BITMUSTER UND WAHRHEITSWERTE
3.7
117
Eigentliche Bitmuster und Wahrheitswerte
Bitmuster im eigentlichen Sinne treten in Anwendungsproblemen selten direkt als Daten auf. Sie werden zur Konstruktion komplexer Daten gebraucht. Für rechnerinterne
Verwaltungsaufgaben werden sie häufig benutzt.
Tabelle 3.6 zeigt die wichtigsten Operationen, die mit Einzelbitwerten ausgeführt werden
können.
NICHT, NOT
Negation
UND, AND
Konjunktion
¬
∧
∨
ODER, OR
Disjunktion
6≡
Ausschließliches ODER
exclusive OR
Operanden
0
1
0
0
1
0
0
1
1
1
0
0
1
0
0
1
1
1
0
0
0
1
1
0
1
1
Ergebnis
1
0
0
0
0
1
0
1
1
1
0
1
1
0
Tabelle 3.6: Logische Operationen
Operationen auf Bitmustern mit mehr als 1 Stelle werden stellenweise ausgeführt, z. B.
¬(11101010 ∧ 11000111) = ¬11000010 = 00111101 .
Negation wird oft auch durch Überstreichen gekennzeichnet:
¬(X ∨ Y ) = X ∨ Y .
Die Reihenfolge der Operationen wird durch Klammerung bestimmt. Regeln zur Vereinfachung sind:
1. ¬ hat Vorrang vor ∨ und ∧
2. ∨ und ∧ sind gleichberechtigt.
118
KAPITEL 3. DARSTELLUNG VON DATEN DURCH BITMUSTER
Wahrheitswerte sind Daten, die nur die Werte wahr und falsch (bzw. TRUE und FALSE)
annehmen. Sie werden für die Ablaufsteuerung in höheren Programmiersprachen benutzt.
Wahrheitswerte können im Prinzip durch ein Bit dargestellt werden:
wahr = 1
falsch = 0 .
In der Praxis wird häufig jedoch ein ganzes Byte benutzt. Z. B.
falsch = 00000000
wahr = X mit X 6= 00000000 .
Für Wahrheitswerte gelten die logischen Operationen wie für Bitwerte. In der Tat kommt
die Bezeichnung „logische Operationen“ von der Aussagenlogik (siehe auch Unterabschnitt
2.5.4).
3.8
Bitmuster in C
In der Programmiersprache C gibt es, anders als in vielen anderen Programmiersprachen,
die Möglichkeit, Bitmuster unmittelbar zu bearbeiten. Diese Bearbeitung hängt jedoch
stark von der jeweiligen Rechnerstruktur ab und wird deshalb in diesem Kapitel und
nicht in Kapitel 2 „Programmieren I“ behandelt. Für die Bitbearbeitung sind alle ganzzahligen Datentypen (siehe Unterabschnitt 2.5.1, Seite 45) und nur diese zugelassen. Es
wird empfohlen, sich für die Bitbearbeitung auf die vorzeichenlosen Datentypen unsigned
char, unsigned short int, unsigned int, unsigned long int zu beschränken. Empfehlenswert ist auch die Definition eines eigenen Datentyps für jede Länge, z. B.
typedef
typedef
typedef
unsigned char
unsigned int
unsigned long int
KURZBIT
BIT
LANGBIT
Für die Bitbearbeitung gibt es in C die im folgenden beschriebenen Operationen.
Verschiebung (shift):
x < < <wert>
bedeutet, daß die Bits der Variablen x um <wert> Stellen nach links verschoben werden.
Von rechts werden Nullbits nachgezogen. Entsprechend bewirkt > > eine Verschiebung
nach rechts. Bei Beschränkung auf vorzeichenlose Datentypen werden von links Nullbits
nachgezogen. Für andere Datentypen ist unbestimmt, ob Nullbits oder Einsbits nachgezogen werden. <wert> ist ein Ausdruck, der einen ganzzahligen Wert liefert. Ist dieser
negativ oder größer als die Anzahl Bits in x, so ist das Ergebnis unbestimmt.
Negation:
∼x
bedeutet, daß jedes Bit von x invertiert wird.
Auf keinen Fall sollte der Negationsoperator ∼ zur Vorzeicheninvertierung
von Zahlen benutzt werden.
3.8. BITMUSTER IN C
119
Logische Verknüpfungen:
x | y
x & y
x ^ y
Der Operator | verknüpft die Bits der Operanden x und y stellenweise (d. h. bitweise)
durch ODER, der Operator & verknüpft stellenweise durch UND und der Operator ^
verknüpft stellenweise durch ausschließliches ODER (d. h. die Verknüpfgung zweier Bits
ergibt genau dann 1, wenn die Bits verschieden sind). Trotz der Bezeichnung gilt:
Bitweise logische Verknüpfungen ergeben keinen Wahrheitswert, sondern ein Bitmuster.
Für Bitmuster sind auch die Vergleichsoperationen == und != zugelassen, die natürlich
einen Wahrheitswert als Resulat ergeben.
Beispiel 3.16 Es soll der Unterschied zwischen logischen Operationen für Wahrheitswerte und logischen Verknüpfungen für Bitmuster an einem kleinen Programm verdeutlicht
werden.
#include <stdio.h>
typedef unsigned int
main()
{
BIT
x,y;
BIT;
x = 0x00000001;
y = 0x00000010;
printf("x = %x y = %x
x|y = %x
x||y = %x\n", x, y, x|y, x||y );
}
x = 1
y = 10
x|y = 11
x||y = 1
2
Für weitere Einzelheiten zur Bitbearbeitung in C sei auf Lowes/Paulik [LoweP1995] verwiesen.
Literatur
Die Ausführungen dieses Kapitels sind bis auf Abschnitt 3.8 dem Skript Stiege [Stie1995b]
entnommen.
120
KAPITEL 3. DARSTELLUNG VON DATEN DURCH BITMUSTER
Kapitel 4
Rechensysteme
4.1
Grobschema der konventionellen Maschine
Die konventionelle Maschine ist das, was ein Systemprogrammierer - jemand, der hardwarenahe Software entwickelt, z. B. ein Betriebssystem - als „Hardware“ sieht. Es ist
jedoch eine virtuelle Maschine, die über der (realen) Hardwaremaschine läuft (vergl.
Abschnitt 2.1). Die Bezeichnung „konventionelle Maschine“ ist nicht allgemein üblich,
jedoch zweckmäßig und zutreffend. Sie wurde von Tanenbaum [Tane1990] eingeführt.
In Abbildung 4.1 sind die wichtigsten Komponenten einer konventionellen Maschine zu
sehen. Der Rechner im engeren Sinne besteht aus Prozessor, Hauptspeicher und Ein/Ausgabeschnittstellen. Diese sind durch ein Übertragungsmedium miteinander verbunden. Die Ein-/Ausgabeschnittstellen verbinden den Rechner mit verschiedenen Geräten
(Bildschirm, Tastatur, Drucker usw.). Man spricht von den peripheren Geräten. In den folgenden Unterabschnitten wird auf die Komponenten der konventionellen Maschine überblicksartig eingegangen.
Anmerkung: Der Rechner im engeren Sinne wird auch Zentraleinheit (central processing unit) genannt. Heutzutage ist diese Bezeichnung aber eher für den Prozessor gebräuchlich, und so wird sie auch in diesem Buch benutzt.
4.1.1
Prozessor
Der Prozessor wird auch Zentralprozessor, CPU, central processing unit, processor, Rechenprozessor oder Rechnerkern genannt. Für ihn ist charakteristisch:
• Er führt Maschinenbefehle aus.
• Er führt Unterbrechungen aus.
• Er besteht aus Registern.
121
122
KAPITEL 4. RECHENSYSTEME
Hauptspeicher
Prozessor
6
6
?
?
Übertragungsmedium
6
6
6
?
?
?
EAS
EAS
EAS
6
6
6
?
?
?
|
{z
zu den peripheren Geräten
}
EAS: Ein-/Ausgabeschnittstelle
Abbildung 4.1: Grobschema der konventionellen Maschine
Es gibt Rechner mit mehreren Rechenprozessoren (Multiprozessorsystem, multiprocessor
system). Im folgenden werden, wenn nicht explizit anders gesagt, nur Einprozessorsysteme
(Monoprozessorsystem) betrachtet.
Auf die Arbeitsweise von Rechenprozessoren wird genauer in Abschnitt 4.2 eingegangen.
4.1.2
Hauptspeicher
Andere Bezeichnungen für den Hauptspeicher sind Arbeitsspeicher oder main memory.
Die Bezeichnung Kernspeicher (engl. core) bezieht sich auf die früher übliche Realisierung
als Magnetkernspeicher und ist veraltet.
Der Hauptspeicher besteht aus Speicherzellen (Wörtern) einer festen Anzahl Bits, d.h.
fester Länge k. k heißt die Wortlänge des Speichers. Abbildung 4.2 zeigt schematisch den
Aufbau des Hauptspeichers. Die Speicherzellen werden von Null aufwärts adressiert, d.h.
numeriert. Der Prozessor und die Ein-/Ausgabeschnittstellen schreiben in den Hauptspeicher und lesen aus dem Hauptspeicher.
Typische Wortlängen des Hauptspeichers sind:
k = 8,
k = 16,
k = 32,
k = 64
4.1. GROBSCHEMA DER KONVENTIONELLEN MASCHINE
..
.
..
.
0
1
···
0
···
1
..
.
..
.
..
.
···
k−2
···
k−1
n−2
2
n−1
{z
Adressen der Wörter
|
123














Nummern


der Bits

eines




Wortes










}
Abbildung 4.2: Schema des Hauptspeichers
Im Fall k = 8 spricht man von Byteadressierung.
Es kommt durchaus vor, daß die Wortlänge k nicht mit der Speicherzugriffsbreite übereinstimmt. Ist zum Beispiel k = 8, so werden beim Zugriff zum Speicher abhängig vom
Rechnertyp 8, 16, 32 oder 64 Bits übertragen. Das geschieht aus Effizienzgründen. Davon
werden jedoch nur die adressierten Anteile bearbeitet. Wird z. B. bei einem Maschinenbefehl als Operand 1 Byte benötigt, so wird nur diese eine Byte im Prozessor bei der
Befehlsausführung benutzt, auch wenn insgesamt 4 Bytes aus dem Speicher übertragen
wurden.
Zur Angabe von Hauptspeichergrößen und anderen Größen in Rechnern werden die folgenden Zahlenbezeichnungen benutzt:
1
1
1
1
Kilo
Mega
Giga
Tera
=
=
=
=
1
1
1
1
K
M
G
T
=
=
=
=
1K·1K
1K·1M
1K·1G
1024
= 210
= 1024 K = 220
= 1024 M = 230
= 1024 G = 240 .
Mit diesen Größen ist z. B.:
216
232
= 26 · 210
= 22 · 230
=
=
4G =
64 K
4096 M
Hauptspeichergrößen werden in Bytes angegeben, auch wenn die Wortlänge anders ist.
Zur Zeit (2012) sind folgende Hauptspeichergrößen üblich:
124
KAPITEL 4. RECHENSYSTEME
PCs:
Großrechner (mainframe):
Größtrechner (z. B. Vektorrechner):
1 GB
mehere GB
1 TB
. . . 8 GB
...
TB
. . . 1 PB
Die Entwicklung von Hauptspeichergrößen ist (immer noch) im Fluß, und es ist damit
zu rechnen, daß die Hauptspeicher der verschiedenen Rechnertypen noch größer werden.
Moore 1 bemerkte 1965, daß sich in integrierten Bausteinen die Zahl der Transistoren pro
Flächeneinheit jedes Jahr verdoppelt. Diese als „Mooresches Gesetz“ bezeichnete Beobachtung ist erstaunlicherweise mit leichter Verlangsamung immer noch gültig, und man nimmt
an, daß diese Entwicklung noch einige Jahre anhalten wird. Die exponentielle Zunahme
der Transistor-Flächendichte führte zu einem rasanten Wachstum der Speichergrössen bei
fallenden Preisen, aber auch zu immer leistungsfähigeren und schnelleren Prozessoren.
Unter der Zykluszeit eines Hauptspeichers versteht man die Gesamtzeit für einen Speicherzugriff. Sie hängt vom Speichertyp ab und liegt im Bereich kleiner 10 ns bis 100
ns.
Beim Hauptspeicher ist zwischen Hauptspeichergröße und Größe des Adreßraumes zu
unterscheiden. Die Größe des Adreßraumes wird durch die Anzahl von Bits festgelegt, die
zur Angabe von Adressen benutzt werden. Werden z. B. 24 Bits für Adressen benutzt, so
lassen sich damit 224 Speicherwörter mit den Adressen 0, · · · , 224 −1 ansprechen. Sollen alle
Speicherwörter direkt adressiert werden können, so muß die Hauptspeichergröße kleiner
oder gleich der Adreßraumgröße sein. Mit besonderen Adressierungstechniken ist es bei
kleinem Adreßraum jedoch möglich, auch größere Hauptspeicher zu benutzen. Für weitere
Einzelheiten siehe den ergänzenden Abschnitt über virtuelle Adressierung, Seite 145.
Anmerkung 4.1 („big endean“ und „little endean“) Auch in Rechnern mit Byteadressierung, also Rechnern, deren Speicherzellen Bytes sind, benutzt man den Begriff
Wort. Man bezeichnet damit größere Speichereinheiten. Es gibt Rechner mit Wörtern zu
2 Bytes, zu 4 Bytes oder zu 8 Bytes. Wörter werden benutzt, um darin Bitmuster, die
Zahlen darstellen, als Einheit zusammenzufassen. Dabei sind in der Praxis zwei unterschiedliche Formen der wortinternen Adressierung bei der Darstellung natürlicher (und
auch ganzer) Zahlen üblich geworden. Sie sind unter den Namen „big endean“ und little endean“ 2 bekannt und sollen am Beispiel der Abbildung 4.3 erläutert werden. Dazu
stellt man sich die Bytes eines Rechners vom Typ big endean als von links nach rechts
adressiert vor (Teil A der Abbildung) und die eines Rechners vom Typ little endean als
von rechts nach links adressiert (Teil B der Abbildung). In beiden Rechnern ist beginnend bei Adresse a die Zeicheneihe UNI–OL gefolgt von zwei Leerzeichen gespeichert. Im
Moore, Gordon E., ∗1929, San Francisco, California. Amerikanischer Chemiker und Physiker. Mitbegründer der Firma Intel (1968), von 1975 - 1997 Präsident von Intel.
2
Die Namen stammen aus Jonathan Swifts Buch Gullivers Reisen, in dem wegen der Frage, ob Eier am großen oder kleinen Ende aufzuschlagen sind, Krieg geführt wird (siehe den Artikel von Cohen
[Cohe1981]).
1
4.1. GROBSCHEMA DER KONVENTIONELLEN MASCHINE
a
A
U
a+1 a+2 a+3
N
I
–
a + 11 a + 10 a + 9 a + 8
B
11
A2
33
C4
a + 11 a + 10 a + 9 a + 8
C
C4
33
A2
11
a+4 a+5 a+6 a+7
O
L
t
t
a+7 a+6 a+5 a+4
t
t
L
O
a+7 a+6 a+5 a+4
t
t
L
O
125
a + 8 a + 9 a + 10 a + 11
11
A2
33
a+3 a+2 a+1
–
I
N
a+3 a+2 a+1
–
I
N
C4
a
U
a
U
Abbildung 4.3: „big endean“ und „little endean“
darauffolgenden Wort, das bei Adresse a + 8 beginnt, ist in beiden Rechnern die natürliche Zahl 295 842 756 (in Hexadezimaldarstellung 11A233C4) gespeichert. Zur Darstellung
natürlicher Zahlen vergleiche Abschnitt 3.2, Seite 96. Man sieht, daß im Fall A die Bits
mit den niedrigen Werten am hinteren Ende des Wortes (Byte 3 des Wortes) gespeichert
sind. Daher die Bezeichnung big endean. Im Fall B sind sie am vorderen Ende des Wortes
(Byte 0 des Wortes) gespeichert, daher die Bezeichnung little endean. Intel-Prozessoren
folgen der little-endean-Konvention, Motorola-Prozessoren, Großrechner der Firmen IBM
und Siemens sowie viele andere Rechnertypen benutzen die big-endean-Konvention. Jede
der beiden Konventionen ist in sich konsistent und führt innerhalb des entsprechenden
Rechensystems nicht zu Schwierigkeiten.
Schwierigkeiten treten auf, wenn Daten von einem System in das andere übertragen werden, z. B. über ein Netz. Werden die drei Wörter von Abbildung 4.3 in aufsteigender
Adreßreihenfolge der Bytes vom System big endean zum System little endean übetragen
und dort in aufsteigender Reihenfolge abgelegt, so ergibt sich die in Teil C der Abbildung gezeigte Speicherbelegung. In ihr ist die Zeichenreihe in den ersten beiden Wörtern
korrekt, die Binärzahl im dritten Wort jedoch nicht. Das dort gespeicherte Bitmuster
entspricht nach little-endean-Konventionen der Zahl
1 + 1 · 16 + 2 · 162 + 10 · 163 + 3 · 164 + 3 · 165 + 4 · 166 + 12 · 167 = 3 291 718 161
Um das Problem zu lösen, könnte man daran denken, bei der Übertragung die Reihenfolge
der Bytes innerhalb eines Wortes umzukehren. Das würde jedoch zu Fehlern bei der
Übertragung von Zeichenreihen führen. Eine einfache Lösung gibt es nicht. Es ist oft
(aber nicht immer) möglich und sinnvoll, alle Daten (auch Zahlen) als Zeichenreihen zu
übertragen, und dann tritt das Problem nicht auf.
2
126
4.1.3
KAPITEL 4. RECHENSYSTEME
Ein-/Ausgabeschnittstellen
Ein-/Ausgabeschnittstellen verbinden den Rechner mit der Außenwelt, d.h. den peripheren Geräten, und es ist wichtig, eine große Zahl unterschiedlicher Geräte an einen Rechner
anschließen zu können. Ziel (bzw. Quelle) der Datentransporte von (bzw. zu) den peripheren Geräten ist der Hauptspeicher. Um den Prozessor nicht mit Transport- und Steuerfunktionen der Ein-/Ausgabe zu belasten, ist man bestrebt, die Datentransporte am
Prozessor vorbeilaufen und möglichst viele Steuerfunktionen durch selbständige, parallel
zum Prozessor arbeitende Komponenten ausführen zu lassen. Ein-/Ausgabeschnittstellen
waren recht uneinheitlich, meist herstellerabhängig. Zum Teil sind sie es noch. Herstellerunabhängige Standardisierungen kommen über den PC- und Workstationmarkt voran.
Es können die folgenden Realisierungen für Ein-/Ausgabeschnittstelle unterschieden werden:
A. Ein-/Ausgaberegister: Bei einer Ausgabe zu einem peripheren Gerät werden die
Daten Wort für Wort per Programm aus dem Hauptspeicher in ein Ein-/Ausgaberegister
des Prozessors transportiert und von dort von dem Peripheriegerät gelesen. Bei einer
Eingabe von einem Peripheriegerät geschieht alles in entgegengesetzter Richtung. Es kann
mehrere Ein-/Ausgaberegister geben. Diese Art von Ein-/Ausgabe ist nur noch in ganz
einfachen Rechnern üblich, z. B. bei Mikrorechnern zur Steuerung von Geräten oder zur
Signalaufnahme.
B. Ein-/Ausgabebausteine und DMA: Das ist die zur Zeit am weitesten verbreitete
Art von Ein-/Ausgabeschnittstellen. Alle modernen PCs und Workstations sind so ausgerüstet. Die Ein-/Ausgabeschnittstellen bestehen aus speziellen Ein-/Ausgabebausteinen
und DMA-Bausteinen. Die Ein-/Ausgabebausteine realisieren verschiedene Kommunikationsfunktionen ganz unterschiedlicher Komplexität. Z. B. gibt es einfache Bausteine für
serielle Schnittstellen und komplexe zur Steuerung von Festplatten oder Netzanschlüssen.
DMA bedeutet direct memory access. Ein DMA-Baustein kann direkt (d.h. ohne Beteiligung des Prozessors) auf den Hauptspeicher zugreifen. DMA-Bausteine und Ein/Ausgabebausteine realisieren im Zusammenspiel den direkten Transport von Daten zwischen Hauptspeicher und Peripheriegeräten. DMA-Bausteine und Ein-/Ausgabebausteine
werden oft zusammen mit zusätzlichem Speicher und eigenem (verstecktem) Prozessor zu
Schnittstellenkarten zusammengebaut.
C. Kanäle: Bei Großrechnern sind Ein-/Ausgabeschnittstellen mit eigener Intelligenz
und speziellen Ein-/Ausgabefunktionen schon seit vielen Jahren im Einsatz. Die Schnittstellen sind in diesem Fall aufgeteilt in Kanäle im Rechner und Geräteanpassungen (Steuereinheiten controller) zwischen Rechner und Peripheriegerät. Kanäle realisieren die Transporte von und zum Hauptspeicher und bieten nach außen eine einheitliche Schnittstelle.
4.1. GROBSCHEMA DER KONVENTIONELLEN MASCHINE
127
Sie können mit Kanalprogrammen in begrenztem Umfang programmiert werden. Geräteanpassungen realisieren die Umsetzung von der einheitlichen Kanalschnittstelle in die
spezifische Schnittstelle des peripheren Gerätes und sind je nach Gerätetyp mehr oder
weniger komplex.
D. USB: USB (universal serial bus) ist ein externes Bussystem mit serieller Übertragung, das sich in den vergangenen Jahren in sehr starkem Maße durchgesetzt hat. Es
hat normierte Stecker und Schnittstellen und verbindet Rechner aller Art mit peripheren
Geräten und auch diese untereinander.
4.1.4
Übertragungsmedium
Das Übertragungsmedium verbindet Prozessor, Hauptspeicher und Ein-/Ausgabeschnittstellen. In aller Regel wird es durch einen Bus oder auch durch mehrere Busse realisiert.
Abbildung 4.4 zeigt schematisch einen Bus. Ein Bus ist ein zentraler Übertragungsweg,
an den mehrere Komponenten, genannt Teilnehmer (Stationen) angeschlossen sind. Diese
schreiben auf den Bus und lesen vom Bus. In Abbildung 4.4 ist das durch zwei Leitungsbündel gekennzeichnet. Auf rechnerinternen Bussen, wie der in Abbildung 4.4 werden
mehrere Bits parallel übertragen. Für Daten und Adressen (manchmal auch noch für
Steuerinformationen) hat man eigene Leitungen. Wer wann wie den Bus benutzen darf,
regelt ein Buszugriffsverfahren.
Für das Übertragungsmedium werden in manchen Fallen auch Direktverbindungen und
andere Lösungen verwendet.
4.1.5
Periphere Geräte
In diesem Unterabschnitt soll auf die wichtigsten peripheren Geräte kurz eingegangen
werden.
A. Speichergeräte: Speichergeräte dienen der Speicherung von Daten in rechnergerechter Form (Bitmuster). Es kann sich um temporäre Speicherung als Zwischenspeicher
bei Bearbeitungsvorgängen oder um längerfristige Speicherung zur Aufbewahrung von
Daten über einen Bearbeitungslauf hinweg handeln.
Es gibt verschiedene Typen von Speichergeräten:
• Magnetplatten(geräte):
Magnetplatten sind rotierende Scheiben mit magnetischen Aufzeichnungen in konzentrischen Spuren und einem Lese-/Schreibsystem, das in radialer Richtung oder
kreisförmiger Richtung (wie bei einem Plattenspieler) mechanisch bewegt wird.
128
Daten
Adressen
KAPITEL 4. RECHENSYSTEME
r
r






































r
r
r
4 4 ··· 4
r
5 ··· 5 5
Teilnehmer
Abbildung 4.4: Bus mit angeschlossenem Teilnehmer
• Magnetband(geräte):
Rotierend abgespulte Bänder mit magnetischen Aufzeichnungen in Längsrichtung
und festem Lese-/Schreibsystem.
• Optische Speicher:
Rotierende Plattenspeicher3 mit optischer oder magneto-optischer Aufzeichnungstechnik. Oft nur einmal beschreibbar.
• Halbleiterspeicher
Die neuere technische Entwicklung erlaubt es, auch längerfristige Speicherung mit
Halbleitern vorzunehmen. Sehr stark haben sich USB-Speichersticks 4 durchgesetzt.
Es ist bemerkenswert, daß man optische Speicherung auch schon mit Tesafilmrollen durchführen
konnte.
4
USB-Speicherstick oder USB-Stick ist ein Scheinanglizimus (ähnlich wie „Handy“). Im Englischen
wird meistens USB flash drive gesagt.
3
4.2. PROZESSOR UND MASCHINENBEFEHLE
129
B. Geräte zur Kommunikation Mensch/Rechner: Bildschirme, Tastaturen und
Mäuse sind heutzutage die wichtigsten Geräte zum Dialog zwischen Mensch und Maschine.
Bildschirme dienen zur Ausgabe, Tastaturen und Mäuse zur Eingabe. Obwohl zwei (mit
Maus drei) Geräte, werden sie zusammenfassend oft als Dialoggerät (Terminal) bezeichnet.
Ausgaben auf Papier sind auch wichtig. Sie erfolgen über Drucker und Plotter. Eingaben
von Papier über Scanner sind noch nicht die Regel, nehmen aber an Häufigkeit und
Bedeutung zu.
Tonausgabe, insbesondere Sprachausgabe ist technisch möglich, wird jedoch nicht stark genutzt. Auch Toneingabe bereitet keine technischen Schwierigkeiten. Es gibt jedoch immer
noch keine ausgereifte Erkennungstechnik für eingegebene Sprache.
Zunehmend gewinnen auch virtuelle Wirklichkeiten (virtual reality) an Bedeutung (Darstellung von Gebäuden, Orten, Filmszenen usw.). Hierfür werden komplexe Geräte zur
Darstellung dreidimensionaler Grafik benutzt und Tonausgaben synchronisiert.
C. Geräte zur Kommunikation Rechner/Außenwelt: Gemeint ist die nicht-menschliche Außenwelt. Sensoren, Aktoren, Analog-/Digitalwandler, Steuerungs- und Regelungskomponenten sind Beispiele für diese peripheren Geräte.
D. Netzanschlüsse: Zur Peripherie eines Rechners zählen auch die Anschlüsse an verschiedene Netze des Nahbereichs (lokale Netze) oder des öffentlichen Bereichs (Datennetze
der Telekom oder anderer Anbieter). Über diese Netze ist der Rechner mit anderen Rechnern oder sonstigen Geräten verbunden.
4.2
4.2.1
Prozessor und Maschinenbefehle
Register des Prozessors
Der Prozessor führt Maschinenbefehle und Unterbrechungen aus. Er besteht aus Registern.
Register sind Speicherstellen für Bitmuster, die bei der Ausführung von Befehlen als Daten
oder Steuerinformationen verwendet werden. Man kann die Register in Rechenregister,
Indexregister und Steuerregister einteilen.
Die Inhalte von Rechenregistern sind Operanden oder Ergebnisse vor Maschinenbefehlen.
Zum Beispiel:
• Die Inhalte zweier Register werden (als Zahlen aufgefaßt und) addiert. Das Ergebnis
steht anschließend in einem der beiden Register.
130
KAPITEL 4. RECHENSYSTEME
• Der Inhalt eines Registers wird um 7 Bits nach links verschoben. Von rechts werden
Nullen nachgezogen.
Die Inhalte von Indexregistern werden als Hauptspeicheradressen interpretiert und zur
Berechnung von Adressen von Operanden und Maschinenebefehlen verwendet.
In modernen Rechnern hat man i. a. Mehrzweckregister (general purpose register). Diese
Register können alle auf die gleiche Art, und zwar sowohl als Rechenregister wie auch als
Indexregister benutzt werden.
Steuerregister enthalten Daten, die den Ablauf im Rechner steuern. Das wichtigste Steuerregister ist der Befehlszähler (Programmzähler, instruction counter, program counter).
Der Befehlszähler enthält die Adresse des nächsten auszuführenden Befehls. Maschinenbefehle sind Bitmuster im Hauptspeicher. Zur Ausführung werden sie in den Prozessor
geholt.
Die übrigen Steuerregister, unter denen insbesondere das Prozessorzustandsregister (processor status register) zu nennen ist, dienen internen Verwaltungszwecken. Siehe auch
Unterabschnitt 4.2.3.
Anmerkung 4.2 Sowohl die Register des Prozessors als auch die Bytes des Hauptspeichers sind in der hier dargestellten Sicht passive Speicherelemente, deren Inhalte die Befehlsausführungen steuern und durch die Befehlsausführungen verändert werden. Daß es
zusätzlich aktive Elemente – man könnte sie „Befehlsausführer“ nennen – geben muß, wird
in dieser Betrachtungsweise nicht sichtbar. Eine arithmetisch-logische Einheit (ALU) wäre
z. B. ein solcher Befehlsausführer. Daß und wie man Befehlsausführer bauen kann, wird
im Gebiet „Rechnerstrukturen“ behandelt.
Beispiel 4.1 Als Beispiel soll der Rechner TUBS85 betrachtet werden. Es handelt sich
hierbei nicht um einen realen Rechner, sondern um ein Programmsystem, mit dem ein
stark vereinfachter Rechner simuliert wird. TUBS85 wurde an der TU Braunschweig entwickelt und wird für Ausbildungszwecke eingesetzt. TUBS85 hat den in Abbildung 4.5
gezeigten Aufbau. Tastatur und Bildschirm sind die des realen Rechners, an dem mit
TUBS gearbeitet wird.
Der Hauptspeicher besteht aus Bytes und ist 16 KB groß, enthält also Bytes mit den
Adressen von 0 bis 16383. Abbildung 4.6 zeigt den Hauptspeicher von TUBS85. Die
Adressen sind hexadezimal dargestellt.
Der Prozessor (siehe Abbildung 4.7) besteht aus 16 Mehrzweckregistern, einem Befehlszähler und einem Anzeigenregister. Die Mehrzweckregister sind 32 Bits, das heißt 4 Bytes,
lang und werden mit Register 0, Register 1, . . . , Register 15 bezeichnet. Der Befehlszähler
ist 16 Bits lang und wird mit BZ bezeichnet. Die Anzeige besteht aus 2 Bits und wird
mit AN bezeichnet. Der Rechner TUBS85 und der zugehörige Assembler SPASS sind
in dem Skript Stiege/Gerns [StieG1996] beschrieben. Reale Rechner sind komplizierter.
Siehe dazu die Literaturhinweise am Ende dieses Kapitels.
2
4.2. PROZESSOR UND MASCHINENBEFEHLE
131
Hauptspeicher
Prozessor
B
U
S
Bildschirm
Tastatur
Abbildung 4.5: Der Rechner TUBS85
132
KAPITEL 4. RECHENSYSTEME
Byte 0000
Byte 0001
Byte 0002
...
...
..
.
Byte 3FFE
Byte 3FFF
Abbildung 4.6: Der Hauptspeicher von TUBS85
1.Byte
2.Byte
3.Byte
4.Byte
..
..
..
..
..
...
..
..
..
..
... .....
..... ...
..... ... ....
..... ... ....
..... ... .....
... ......................................................................................... .... .......................................................................................... .... .......................................................................................... .... .......................................................................................... ....
...
...
...
...
...
....
....
....
....
....
Register 0
32 Bits
Register 1
32 Bits
Register 2
32 Bits
..
.
..
.
..
.
Register 14
32 Bits
Register 15
32 Bits
1.Byte
2.Byte
..
..
..
....
...
...
.. ....
..
..
..
..
..
... .......................................................................................... .... ............................................................................................ ....
..
.
..
..
...
...
...
...
...
...
.
.
.
BZ
16 Bits
AN
2 Bits
Abbildung 4.7: Der Prozessor von TUBS85
4.2. PROZESSOR UND MASCHINENBEFEHLE
4.2.2
133
Maschinenbefehle und Unterbrechungen
Abbildung 4.8 zeigt den schematischen Aufbau eines typischen Maschinenbefehls. Die
Operanden
OP-Code
0
1
···
7
8
···
k−1
Abbildung 4.8: Aufbau eines Maschinenbefehls
Länge ist i. a. ein Vielfaches von 8.
OP-Code (Operationscode, Befehlscode, operation code, instruction code) gibt an, welcher
Maschinenbefehl auszuführen ist.
Operanden sind Eingangs- und Ergebnisdaten sowie Zusatzparameter für den Befehl. Wie
werden nun Maschinenbefehle im Prozessor ausgeführt? Das geschieht in den folgenden, in
Tabelle 4.1 angegebenen Phasen. Sprungbefehle verändern den Inhalt des Befehlszählers.
1. Hole den Befehl, dessen Adresse im Befehlszähler steht, aus dem Hauptspeicher
in den Prozessor.
2. Entschlüssele den Befehl, d.h. bestimme den Befehl über seinen Operationscode.
3. Falls Operanden aus dem Hauptspeicher zu holen sind, und/oder das Ergebnis
dorthin zu speichern ist, ermittele die zugehörigen Adressen.
4. Hole die Operanden aus dem Hauptspeicher, falls welche gebraucht werden.
5. Führe die dem Befehl zugrundeliegende Operation aus.
6. Speichere das Ergebnis in den Hauptspeicher, falls der Befehl das verlangt.
7. Erhöhe den Befehlszähler um die Länge des ausgeführten Befehls, falls der Befehl
kein Sprungbefehl mit erfüllter Sprungbedingung war.
Tabelle 4.1: Phasen der Befehlsausführung
Bedingte Sprungbefehle tun das nur dann, wenn eine zu überprüfende Bedingung erfüllt
ist, die Sprungbedingung. Die neue Adresse im Befehlszähler heißt Sprungziel.
Wenn ein Programm abläuft, gibt es die normale Befehlszählerfortschaltung oder die
Einstellung eines Sprungziels durch einen Sprungbefehl. Es können jedoch Situationen
134
KAPITEL 4. RECHENSYSTEME
eintreten, bei denen bestimmte Aktionen ausgeführt werden, die nicht zum gerade ablaufenden Programm gehören. Dieses muß unterbrochen werden. In Tabelle 4.2 sind die
verschiedenen Fälle aufgeführt.
Im Falle einer Unterbrechung wird die Adresse des nächsten auszuführenden Befehls durch
die Art der Unterbrechung bestimmt. Im Befehlszähler wird eine Unterbrechungsadresse
eingestellt. Das gerade ablaufende Programm darf nicht weiterlaufen, sondern ein anderes
Programm, eine Unterbrechungsroutine, wird vorgezogen.
Wie kann es zu Unterbrechungen kommen? Es können drei Arten von Unterbrechungsursachen unterschieden werden.
A. Alarme
Alarme treten auf, wenn das ablaufende Programm eine besondere Situation verursacht, meistens einen Fehler. Beispiele sind:
◦ Division durch Null
◦ Überlauf bei Addition
◦ unzulässige Operandenadresse
◦ unzulässiger Befehlscode
Es gibt auch einen Maschinenbefehl, mit dem ein Programm gezielt und beabsichtigt einen Alarm erzeugen kann: Systemaufruf. Der Systemaufruf wird bei vielen
Rechnern SVC (supervisor call) genannt. Er dient zum Anruf des Betriebssystems
(siehe Unterabschnitt 4.3.1).
B. Externe Eingriffe
Externe Eingriffe sind Signale, die von außen an den Prozessor gesandt werden.
Beispiele:
◦ Wecksignal einer Uhr
◦ Abschlußmeldung einer Ein-/Ausgabeschnittstelle
◦ Anruf aus dem Netz
C. Maschinenfehler
Fehlersituationen im Rechner, die von der Hardware erkannt werden, führen i. a.
auch zu Unterbrechungen. Beispiele:
◦ Stromausfall
◦ inkorrekte Prüfsumme
Anmerkung: Für Alarm sind auch die Bezeichnungen trap oder synchrone Unterbrechung üblich. Externe Eingriffe werden auch als asynchrone Unterbrechungen bezeichnet.
Oft wird der Begriff interrupt nur im Sinn von externem Eingriff gebraucht.
4.2. PROZESSOR UND MASCHINENBEFEHLE
135
A. Die Adresse des nächsten auszuführenden Befehls wird vom ablaufenden Programm bestimmt.
A.1. Normale Befehlsfortschaltung
Der Befehlszähler wird um die Länge des ausgeführten Befehls erhöht.
A.2. Sprungbefehl
Der ausgeführte Sprungbefehl setzt den Befehlszähler explizit auf einen neuen Wert.
B. Die Adresse des nächsten auszuführenden Befehls wird nicht vom ablaufenden
Programm, sondern durch ein besonderes Ereignis bestimmt. Es kommt zu einer
Unterbrechung (interrupt).
Tabelle 4.2: Adresse des nächsten Befehls
4.2.3
Befehlsklassen
Die Maschinenbefehle sind von Rechnertyp zu Rechnertyp ganz unterschiedlich. Jedoch
gibt es meistens ganz ähnliche Befehlsklassen.
A. Transportbefehle: Transportbefehle kopieren Bitmuster von einer Stelle im Rechner zu einer anderen. Die Quelle des Transportvorgangs bleibt unverändert außer bei
Überlappung mit dem Ziel. Das Ziel wird verändert. Transportrichtungen sind:
Register
Register
Hauptspeicher
←→ Register
←→ Hauptspeicher
←→ Hauptspeicher.
Die Länge der transportierten Bitmuster ist vom Befehl abhängig.
B. Logische Befehle: Logische Befehle manipulieren oder testen Bitmuster, ohne sie
als Zahlen zu interpretieren. Wichtige Operationen sind Negation, UND, ODER, ausschließliches ODER, Testen mit Maske u. a. Siehe dazu auch Abschnitt 3.7. Die Operanden logischer Befehle stehen in Registern oder im Hauptspeicher. Das Ergebnis ersetzt
i. a. einen der Operanden.
C. Arithmetische Befehle: Arithmetische Befehle manipulieren oder testen Bitmuster, die sie als Zahlen (natürliche Zahlen, ganze Zahlen, rationale Zahlen) in verschiedenen Darstellungen interpretieren (siehe Kapitel 3). Auch bei diesen Befehlen stehen
die Operanden in Registern oder im Hauptspeicher, und das Ergebnis ersetzt i. a. einen
der Operanden. Die wichtigsten Operationen sind Addieren, Multiplizieren, Subtrahieren,
Dividieren, Betrag bilden, Vorzeichen wechseln, Vergleichen.
136
KAPITEL 4. RECHENSYSTEME
D. Verschiebebefehle (shift): Diese Befehle verändern den Inhalt eines Registers, indem sie die Bits nach links oder nach rechts verschieben. Dabei gehen nach links (rechts)
hinausgeschobene Bits verloren, und von rechts (links) werden Nullen nachgezogen. Bei
manchen Befehlen werden Einsen nachgezogen oder es handelt sich um Kreisverschiebungen, bei denen die auf einer Seite hinausgeschobenen Bits auf der anderen Seite nachrücken. Es gibt auch Verschiebebefehle, die auf Registerpaaren oder auf Hauptspeicherstellen arbeiten.
E. Sprungbefehle: Sprungbefehle setzen den Befehlszähler. Bedingte Sprungbefehle testen eine Bedingung und verändern den Befehlszähler nur bei erfüllter Sprungbedingung.
Bei unbedingten Sprungbefehlen wird die Sprungadresse in jedem Fall gesetzt. Oft haben Sprungbefehle noch Zusatzeffekte. Z. B. wird bei Unterprogrammsprüngen die Rücksprungadresse gesichert.
F. Transformationsbefehle: Es handelt sich um sehr komplexe Befehle, die nur in einigen (älteren) Rechnertypen vorhanden sind. Texte edieren, Zahlen umwandeln, Tabellen
durchsuchen sind Beispiele für Transformationsbefehle.
G. Visuelle Befehle: Neuere Prozessoren weisen Befehle auf, die grafische Anwendungen und Multimedia-Anwendungen in besonderem Maße unterstützen.
H. Verwaltungsbefehle und privilegierte Befehle: Dienen zur internen Verwaltung
des Rechners. Für normale Benutzer sind nur wenige dieser Befehle erlaubt, z. B. der in
Unterabschnitt 4.2.2 erwähnte Systemaufruf.
Die meisten Verwaltungsbefehle sind privilegierte Befehle. D. h. sie laufen nur im privilegierten Zustand des Prozessors ab. Der privilegierte Zustand, man spricht auch von einem
geschützten Zustand (protected mode), wird durch ein Bit in einem speziellen Steuerregister, dem Prozessorzustandsregister, gekennzeichnet. Selbstverständlich kann das Prozessorzustandsregister nur mit einem privilegierten Verwaltungsbefehl geändert werden.
Nach dem Urladen (boot) ist privilegierter Zustand eingestellt, ebenso nach allen Unterbrechungen. Dadurch ist gesichert, daß nur das Betriebssystem und nicht auch normale
Programme privilegierte Befehle ausführen können. Die Maschinenbefehle, die in jedem
Prozessorzustand ausgeführt werden können, heißen nichtprivilegierte Befehle.
Beispiele für privilegierte Befehle sind:
• EA-Befehle (Befehle an Ein-/Ausgabeschnittstellen)
• Setzen Seiten-/Kacheladreßregister (für virtuelle Adressierung)
• Setzen Prozessorzustandsregister.
4.2. PROZESSOR UND MASCHINENBEFEHLE
137
Nicht bei allen Rechnern wird im Prozessor ein privilegierter und ein nichtprivilegierter
Zustand unterschieden. Insbesondere kennen ältere Mikroprozessoren diese Unterscheidung nicht.
Anmerkung: Die vorgestellten Befehlsklassen beziehen sich auf CISC-Rechner (Complex Instruction Set Computers). Dazu gehören:
• Herkömmliche Mikroprozessoren: Intel, Motorola, NSC u. a.
• „Klassische“ Großrechner wie zum Beispiel 370/390er-Architektur (IBM/Siemens).
CISC-Rechner sind zu unterscheiden von RISC-Rechnern (Reduced Instruction Set Computers), die einen einfacheren Befehlssatz und andere Vereinfachungen aufweisen und somit effizientere und schnellere Prozessorhardware ermöglichen. Moderne Workstations,
aber auch Parallelrechner und möglicherweise künftig zum Teil auch PCs beruhen auf
RISC-Prozessoren5 .
4.2.4
Adressierungsarten
In diesem Unterabschnitt wird untersucht, wie bei der Befehlsausführung Adressen von
Operanden und Befehlen ermittelt werden.
Adressen von Registern (des Prozessors): Adressen von Registern des Prozessors
werden in Maschinenbefehlen direkt als Bitmuster angegeben, häufig in vier Bits. Damit
sind dann 16 Register adressierbar.
Adressen von Hauptspeicherzellen (Hauptspeicheradressen): Hauptspeicheradressen können auf verschiede Arten angegeben werden.
a. Direkt im Maschinenbefehl.
Wird selten gemacht, da Adressen in modernen Rechnern 24 oder 32 Bits lang sind.
Bei zwei Adressen wäre ein Befehl dann 6 oder 8 Bytes lang. Das ist zuviel.
b. Adressierung mit Basis- und Indexregistern.
Es werden ein Bitmuster im Befehl und die Inhalte eines oder mehrerer Register
addiert, um die endgültige Adresse zu erhalten. Den Adreßteil im Befehl nennt man
Distanz (displacement). Diese Adressierungsart ist in fast allen modernen Prozessoren üblich, allerdings gibt es recht unterschiedliche Realisierungen und Benennungen, z. B. auch Adreßsegmente (in Mikroprozessoren). Bei manchen Rechnertypen
gibt es auch negative Distanzen, z.B. zur Modifikation des Befehlszählerinhalts.
5
Das ist zur Zeit (2012) wohl nicht mehr aktuell.
138
KAPITEL 4. RECHENSYSTEME
c. Indirekte Adressierung
Eine (i. a. nach b. gewonnene) Hauptspeicheradresse verweist auf einen Platz, wo
nicht der Operand, sondern die Hauptspeicheradresse des Operanden steht. Indirekte Adressierung ist oft iterierbar, allerdings nur eine begrenzte Zahl von Malen,
z. B. 7.
d. Direkte Operanden (immediate operand)
Diese Operanden werden nicht über eine Adresse angesprochen, sondern stehen als
(Teil)Bitmuster im Befehl. Meistens sind sie 1 Byte lang; es können also nur kleine
Operanden angegeben werden. Bei diesen ist die Angabe als Direktoperand jedoch
nützlich, da der Adressierungsschritt für den Operanden entfällt. Befehle mit direkten Operanden sind in vielen Rechnern vorhanden und werden oft benutzt.
Anmerkung: Die Befehlsklassen sind in den meisten Rechnertypen ähnlich. Die Adressierungsarten variieren zwischen den Rechnertypen recht stark.
Geschichtliche Anmerkung: Rechner, wie sie in diesem Kapitel beschriebene sind,
werden auch Von-Neumann-Rechner (nach John von Neumann 6) genannt, obwohl eher
Zuse 7 als der Erfinder des modernen Computers anzusehen ist. Ein Vorläufer ist Babbage 8.
4.3
Grundsoftware
Zur Grundsoftware zählt man das Betriebssystem und eine Reihe weiterer Programme
wie z. B. die Sprachübersetzer. Nur zusammen mit der Grundsoftware bildet die Hardware eines Rechensystems eine Arbeitsumgebung, in der Anwendungssoftware entwickelt
Neumann, John von (eigentlich Johann Baron von Neumann) ∗1903 Budapest, †1957 Washington,
D.C. Amerikanischer Mathematiker osterreichisch-ungarischer Herkunft. Wirkte in Berlin, Hamburg und
Princeton, New Jersey. Wesentliche Beiträge zur Mengenlehre und mathematischen Logik, Wahrscheinlichkeitstheorie und Spieltheorie, Funktionalanlysis, Quantentheorie. Die Bezeichnung „Von-NeumannRechner“ rührt von seinen Arbeiten zur Struktur programmgesteuerter Rechner [Neum1976] her.
7
Zuse, Konrad, ∗1910 in Berlin, †1995 in Hünfeld bei Fulda. Deutscher Bauingenieur. „Schöpfer der
ersten vollautomatischen, programmgesteuerten und frei programmierbaren, mit binärer Gleitpunktrechnung arbeitenden Rechenanlage. Sie war 1941 betriebsfähig“ [Baue1996]. Entwarf 1943 - 1945 eine algorithmische Sprache, den „Plankalkül“. Sein Wirken hat Zuse in dem Buch „Der Computer – mein LebenswerK“ [Zuse1993] dargestellt. Eine lesenswerte Würdigung Zuses ist der Artikel von Bauer [Baue1996].
6
Babbage, Charles, ∗1792 Teignmouth (Devonshire), †1871 London. Englisher Mathematiker. Professor der Mathematik in Cambridge. Arbeitete viele Jahre an der Entwicklung programmgesteuerter
Rechenmaschinen („Analytical Engine“). Seine Ideen ließen sich jedoch mit den technischen Möglichkeiten seiner Zeit nicht erfolgreich realisieren. Nach seiner Mitarbeiterin Ada Countess of Lovelace (1811 1852, Tochter von Lord Byron) wurde die Programmiersprache Ada benannt.
8
4.3. GRUNDSOFTWARE
139
werden kann. Nur mit dieser wiederum kann letztlich das Rechensystem zur Bearbeitung
von Aufgaben aller Art eingesetzt werden.
4.3.1
Betriebssystem
In Abschnitt 2.1 wurde die Betriebssystem-Maschine als virtuelle Maschine eingeführt,
die auf der konventionellen Maschine läuft. Programme einer Betriebssystem-Maschine
bestehen aus den nichtprivilegierten Befehlen der konventionellen Maschine (siehe Unterabschnitt 4.2.3) sowie speziellen Anweisungen an das Betriebssystem, den Betriebsbefehlen
(Systemaufruf, system call). Die in diesem Sinne definierte Betriebssystem-Maschine wird
oft auch Betriebssystemkern (system kernel) genannt. Unter einem Betriebssystem (operating system) versteht man im allgemeinen jedoch mehr als nur den Betriebssystemkern.
Zum Beispiel gehören in der Regel die Ausführung von Betriebssystem-Kommandos oder
die Funktionen der Dateiverwaltung zum Betriebssystem, aber nicht zum Kern.
Eine brauchbare Arbeitsdefinition für ein Betriebssystem liefert Siegert [Sieg1988].
Definition 4.1 ([Sieg1988]) Ein Betriebssystem . . .
• steuert den Ablauf der Auftragsverarbeitung für einen Benutzer.
• plant die Reihenfolge der Auftragsverarbeitung für verschiedene Benutzer.
• lädt die Programme in den Arbeitsspeicher und startet sie.
• stellt Systemdienste bereit, insbesondere für den Transport von Daten zwischen Programmen und Geräten.
• koordiniert und synchronisiert beim gleichzeitigen Ablauf von mehreren Programmen
den Zugriff auf gemeinsame Betriebsmittel.
• erfaßt die verbrauchten Rechenleistungen und andere Leistungen.
• schützt die Dateien und Programme vor Verlust und unberechtigtem Zugriff.
• registriert aufgetretene Hardwarefehler und versucht sie abzufangen.
Als wichtige Ergänzung zu Definition 4.1 wäre nachzutragen
• stellt Dienste und Funktionen bereit, mit denen der Rechner, auf dem es läuft, in
Netze eingebunden wird.
140
KAPITEL 4. RECHENSYSTEME
Äußere Charakterisierung von Betriebssystemen: Betriebssysteme lassen sich nach
ihrer Wirkung und ihren Schnittstellen nach außen einteilen und beschreiben. Es gibt
Standard-Betriebssysteme und Spezialbetriebssysteme. Standardbetriebssysteme (Mehrzweckbetriebssystem, general purpose operating system) sind das, was man normalerweise
unter einem Betriebssystem versteht. Charakteristisch für sie ist:
• Sie interagieren mit menschlichen Benutzern.
• Sie sind nicht Teil eines umfassenden technischen Systems.
• Sie sind vielseitig nutzbar, z. B.:
◦ Programmentwicklung (Quelltext Codieren, Übersetzen, Binden, Testen, Installieren).
◦ „klassische“ kommerzielle Datenverarbeitung (Rechnungserstellung, Buchhaltung, Projektüberwachung, Lohn- und Gehaltsabrechnung usw.)
◦ „klassische“ technisch-wissenschaftliche Datenverarbeitung (numerische Berechnungen, Simulation usw).
◦ Datenbankanwendungen
◦ Textverarbeitung
◦ Kommerzielle und technisch-wissenschaftliche graphisch unterstützte Dialoganwendungen.
◦ Buchungssysteme (Banken, Reisebüros usw.).
•
..
.
Im Gegensatz zu Standardbetriebssystemen gilt für Spezialbetriebssysteme (special purpose operating system:
• Sie sind für besondere Anforderungen entworfen.
• Sie sind i. a. Teil eines umfassenden technischen Systems.
• Sie weisen nicht den vollen Funktionsumfang eines allgemeinen Betriebssystems auf,
haben andererseits Eigenschaften, die dort fehlen.
Beispiele für Spezialbetriebssysteme sind:
• Realzeitbetriebssysteme (Echtzeitbetriebssystem, real time operating system). Realzeitbetriebssysteme müssen ablaufende Programme so steuern, daß Zeitbedingungen, die oft sehr eng (hart) sind, erfüllt werden. Es gibt auch Hybridsysteme; das
sind Standardbetriebssysteme mit Realzeiteigenschaften.
4.3. GRUNDSOFTWARE
141
• Betriebssysteme für Netzknotenrechner und für (Telefon-)Vermittlungsrechner.
• Betriebssoftware in Rechnern, die als intelligente Steuerungen eingesetzt werden
(Aufzüge, Autos, Haushaltsgeräte, Flugzeuge, Spracherkennung usw.). Es handelt
sich hierbei häufig nicht um echte Betriebssysteme, sondern um einfache Steuersoftware, die den Ablauf der eigentlichen Anwendungssoftware ermöglicht.
Menschliche Benutzer kommunizieren mit einem Betriebssystem über Betriebssystemkommandos. Diese Kommandos werden interpretiert. Der Kommandointerpreter (auch Kommandoentschlüßler genannt) ist kein Teil des Betriebssystemkerns. Bei Unix heißt der
Kommandointerpreter shell (Schale). Es gibt verschiedene Ausführungen der Shell (Cshell, Bourne-shell u.a.).
Unter einem Job (Sitzung, session) versteht man einen zusammenhängenden, vollständigen, durch Kommandos eines Benutzers gesteuerten Arbeitsablauf in einem Rechensystem.
Von Jobs spricht man bei Stapelbetrieb, von Sitzungen bei Dialogbetrieb. Die Kommandosprache eines Betriebssystems wird auch JCL (job control language) genannt. Oft enthält
sie neben Anweisungen, die zu Aktivitäten führen (Übersetzen, Dateiliste anzeigen usw.)
auch Steueranweisungen (if-then-else, while, case u.a.).
Moderne Betriebssysteme bieten oft zusätzliche graphische Schnittstellen für die Benutzer.
Bei diesen ist die Kommandostruktur stark zurückgedrängt und kaum noch erkennbar.
Zur äußeren Charakterisierung gehört auch die Betriebsart von Betriebssystemen.
Einprogrammbetrieb/Mehrprogrammbetrieb. Im Einprogrammbetrieb (single programming,
Einbenutzerbetrieb, single user) läuft ein Benutzerprogramm nach dem anderen ab. Im
Mehrprogrammbetrieb (multiprogramming, Mehrbenutzerbetrieb, multiuser) können mehrere Benutzerprogramme parallel ablaufen. Mehrprogrammbetrieb setzt nicht voraus, daß
die ablaufenden Benutzerprogramme notwendigerweise von verschiedenen Benutzern stammen. In neueren Personalcomputern gibt es (in etwas eingeschränkter Form) Mehrprogrammbetrieb, obwohl nur ein einziger Benutzer an dem Rechner arbeitet. Will man hervorheben, daß ein Betriebssystem zu einem Zeitpunkt nur einen einzigen Benutzer und ein
einziges Dialoggerät zuläßt, dieser Benutzer aber mehrere Programme parallel ablaufen
lassen kann, so spricht man von Mehrprozeßbetrieb (multitasking).
Stapelbetrieb/Dialogbetrieb. Im Stapelbetrieb (batch mode) liegt ein Job als fertige Folge
von Kommandos und Daten vor, i. a. in einer Datei, früher auch auf Lochkarten. Ein
Benutzer stellt einen Job vollständig zusammen und hat danach auf den Jobablauf keinen
Einfluß mehr. Druckausgaben eines Stapeljob werden in einer Datei gesammelt und nach
Beendigung des Jobs ausgedruckt (spooling). Dialogbetrieb (interactive mode) ist dadurch
gekennzeichnet, daß der Benutzer über ein Dialoggerät den Ablauf unmittelbar steuert.
Kommandos, die er eingibt, werden vom Betriebssystem sofort ausgeführt und – eventuell
mit Fehlermeldungen – quittiert. Der Benutzer führt ein Wechselgespräch mit dem Betriebssystem. Darüber hinaus können Programme, die über Kommandos gestartet werden,
Daten im Dialog anfordern und ausgeben. Der Benutzer führt im allgemeinen auch mit
142
KAPITEL 4. RECHENSYSTEME
den ablaufenden Programmen ein Wechselgespräch. Es müssen aber nicht alle Programme
auch wirklich einen Dialog durchführen. So ist es z. B. sinnvoll, wenn Sprachübersetzer
die zu übersetzenden Quellen aus Dateien und nicht im Dialog einlesen.
Bei Betriebssystemen mit graphischen Schnittstellen ist für die Benutzer der Unterschied
zwischen Dialog mit dem Betriebssystem und Dialog mit einem Benutzerprogramm kaum
noch erkennbar. Zunehmend werden unabhängige Rechner und Geräte zu einem Netz
(Rechnernetz, network, computer network) über kleinere (lokales Netz) oder größere (Weitverkehrsnetz) Entfernungen zusammengeschlossen. Man spricht von Netzbetrieb.
Allgemeines Teilnehmerbetriebssystem. Ein allgemeines Teilnehmerbetriebssystem (general purpose operating system) ist ein Betriebssystem, in dem mehrere unterschiedliche und
unabhängige Jobs gleichzeitig ablaufen und für die Jobs folgende Betriebsarten möglich
sind
• Dialogbetrieb,
• Stapelbetrieb,
• Netzbetrieb .
Beispiele für allgemeine Teilnehmerbetriebssysteme sind:
MVS (Multiple Virtual Storage). MVS ist das Hauptbetriebssystem für Großrechner
(mainframe) der IBM und eines der komplexesten Betriebssysteme überhaupt. Literatur: [Stal1992], [John1989], [Paan1986].
BS2000. BS2000 ist das Betriebssystem der Großrechner der Firma SNI (Siemens Nixdorf Informationssysteme) und auch ein sehr komplexes System. Literatur: [Gorl1990].
Unix. Unix ist ein allgemeines, „offenes“ Betriebssystem. Es wurde 1969 von Thompson 9
bei den AT&T Bell Laboratories entwickelt und läuft auf fast allen Workstations,
etlichen Groß- und Größtrechnern und in der Version LINUX zunehmend auch
auf PCs. Literatur: [Bach1986], [Stal1998], [LeffMKQS1988], [Hier1993], [Andl1990].
[GulbO1995]
Keine allgemeine Teilnehmerbetriebssysteme, aber dennoch wichtige allgemeine Betriebssysteme sind die PC-Betriebssysteme
Windows95 / WindowsNT. Windows 95 hat nur noch historisches Interesse. Es basierte auf auf dem einfachen Betriebssystem MS/DOS (Microsoft Disk Operating
Thompson, Ken, ∗ 1943, New Orleans. Amerikanischer Informatiker. Den entscheidenden Durchbruch
von Unix erzielte er zusammen mit Ritchie (siehe Seite 34) nach der Umstellung auf C [RitcT1974]. Zur
Geschichte von Unix siehe Tanenbaum [Tane1994].
9
4.3. GRUNDSOFTWARE
143
System). Spätere Microsoft Betriebssysteme setzten auf WindowsNT auf. Recht bekannt war Windows XP. Die aktuelle Version ist Windows7. Windows Betriebssysteme gehören zu den am stärksten verbreiteten Betriebssysteme für Personalcomputer. Trotz ständiger Erweiterungen und Zusätze bieten sie nur sehr eingeschränkten
Mehrprogrammbetrieb und können nicht als Teilnehmerbetriebssysteme angesehen
werden. Literatur: [Micr1996], [TiscJ1995].
Innere Struktur von Betriebssystemen: Die wichtigsten Bestandteile des Betriebssystemkerns sind
• Prozeß- und Prozessorverwaltung. Startet Benutzerprogrammläufe als Prozesse und
beendet sie. Verwaltet wartende Prozesse und ordnet den rechenbereiten unter ihnen
freiwerdende Prozessoren zu.
• Interprozeßkommunikation. Ermöglicht die Kommunikation zwischen unterschiedlichen Prozessen auf dem gleichen Rechner oder (über Netz) auf verschiedenen Rechnern.
• Zeitverwaltung. Verwaltet die Hardware- und Softwareuhren des Systems. Führt
Weckaufträge aus. Synchronisiert Uhren auf verschiedenen Rechnern.
• Speicherverwaltung. Teilt den Prozessen den benötigten Hauptspeicher zu. Lagert
Prozesse oder Prozeßstücke bei Bedarf auf Plattenspeicher aus, bzw. lagert diese
wieder in den Hauptspeicher ein.
• Ein-/Ausgabe-Kern. Nimmt Aufträge für Datentransporte von anderen Teilen des
Betriebssystems und von Benutzerprogrammen entgegen. Verwaltet und steuert die
Ein-/Ausgabeschnittstelle der Hardware. Steuert die peripheren Geräte. Diese Programme heißen Treiber (driver).
Auch zum Betriebssystem, aber nicht zum Kern gehören
• Kommandointerpretierer. Interpretiert die von Benutzern eingegeben Kommandos
zum Aufruf von Systemdiensten.
• Dateiverwaltung (file management). Erzeugt, löscht und verwaltet Dateien und Dateiverzeichnisse. Führt unter Benutzung der Dienste des Ein-/Ausgabe-Kerns Datentransporte zwischen Programmen und peripheren Geräten durch. Im weiteren
Sinne werden auch Transporte über das Netz zur Dateiverwaltung gerechnet.
• Stapelbetrieb und Spoolfunktionen. Ermöglichen den Ablauf von Jobs ohne Dialog
mit dem Benutzer. Sammeln Druckausgaben in Dateien, drucken Dateien aus.
144
KAPITEL 4. RECHENSYSTEME
• Systemladefunktionen. Programme, die den Aufbau des Betriebssystems nach dem
Einschalten des Rechners steuern. Das Programm, das den Aufbau beginnt, wird
Urlader (bootstrap) genannt.
• Operateurteil. Führt die Kommunikation mit der Operateurkonsole durch.
• Programmlader (loader). Bringt ablauffähige Programme in den Hauptsspeicher und
startet sie. Stellt Programmteile, die während des Ablaufs von Benutzerprogrammen
zusätzlich benötigt werden, zur Verfügung (dynamisches Binden, dynamic linking).
• Bildschirmoberflächen. Fenstersysteme und andere grafische Systeme.
•
4.3.2
..
.
Weitere Programme der Grundsoftware
Weitere Programme der Grundsoftware lassen sich gliedern in Programme, die zum Programmiersystem gehören, allgemeine Dienstprogramme und allgemeine Anwendungsprogramme. Die beiden letzten Klassen sollen hier nur genannt, aber nicht weiter erläutert
werden.
• Programmiersystem. Umfaßt die Programme, die Quellprogramme in eine auf der
Betriebssystem-Maschine ablauffähige Form bringen oder Quellprogramme interpretieren.
◦ Sprachübersetzer. Compiler für höhere Programiersprachen, Assembler.
◦ Binder (linkage editor, linker, binder). Programm, das Programme und Programmstücke, die übersetzt worden sind, zu einem Programm zusammenfügt
(binden). Die ursprünglichen Programme können durchaus in verschieden Programmiersprachen geschrieben worden sein.
◦ Laufzeitbibliotheken. Dateien, die vorübersetzte Programmstücke mit häufig
benutzten allgemeinen Funktionen (z. B. für Ein/-Ausgabe) enthalten. Diese
werden beim Übersetzen/Binden dem übersetzten Progamm hinzugefügt.
◦ Programmbibliotheken. Dateien fertig ablauffähiger Programme.
• Allgemeine Dienstprogramme.
◦ Textedierer.
◦ Grafische Edierer.
◦ Kopier- und Umsetzprogramme.
4.4. VIRTUELLE ADRESSIERUNG
145
◦ Systemgenerierungsprogramme.
◦ Sicherungsroutinen.
..
◦
.
• Allgemeine Anwendungssysteme.
◦ Datenbanksysteme.
◦ Mathematische Programme und Unterprogramme.
◦ Kommerzielle und branchenspezifische Anwendungssysteme.
◦ Simulationssysteme.
◦ Entwurfsunterstützungssysteme.
..
.
◦
4.4
Virtuelle Adressierung
Ausgangspunkt ist die Unterscheidung zwischen Hauptspeichergröße und Größe des Adreßraumes (Seite 124). Schon früh in der Rechnerentwicklung wurde klar, daß es von Vorteil
ist, Adressen, mit denen auf den Hauptspeicher zugegriffen wird (sog. Speicheradressen),
und Adressen, mit denen innerhalb des Prozessors Maschinenbefehle bearbeitet werden
(sog. Programmadressen), zu trennen. Tut man das, so spricht man von virtueller Adressierung (virtual addressing) 10. Als Erfinder der virtuellen Adressierung ist F.-R. Güntsch
anzusehen11 . Sind Programmadressen und Hauptspeicheradressen identisch, so spricht
man von reeller Adressierung (real addressing) (Abbildung 4.9). Die wichtigste Form virtueller Adressierung ist virtuelle Seitenadressierung (virtual page addressing). Dabei sind
sowohl der Programmadreßraum als auch der Hauptspeicher in Blöcke gleicher Größe, genannt Seiten (page), eingeteilt. Einem Programmlauf wird vom Betriebssystem zunächst
ein bestimmtes Anfangstück des virtuellen Adreßraumes zugeteilt. Für jede der zugeteilten
Seiten wird ein Block gleicher Größe auf einem Plattenspeicher, dem Seitenwechselgerät
Erste Rechner mit virtueller Adressierung waren Atlas (1962, University of Manchester zusammen
mit Ferranti und Plessey) und B5000 (1961, Burroughs Corp.) In den frühen 70er-Jahren des vorigen
Jahunderts hatte sich virtuelle Adressierung bei größeren Rechnern allgemein durchgesetzt. Circa 10
Jahre später hatten die PCs nachgezogen.
11
Güntsch, Fritz-Rudolf, ∗ 1925, Berlin. Deutscher Physiker und Computer-Pionier. Führte die virtuelle
Adressierung im Rahmen seiner Dissertation „Logischer Entwurf eines digitalen Rechengerätes mit mehreren asynchron laufenden Trommeln und automatischer Schnellspeicherbetrieb“ ein. Leiter des Bereichs
Großrechner der AEG/Telefunken, Konstanz, wo die Großrechner TR4 und TR440 entwickelt wurden.
Später war Güntsch im Bundesministerium für Forschung und Technologie zuständig für Datenverabeitung und hat maßgeblich am Aufbau der Informatik in Deutschland mitgewirkt.
10
146
KAPITEL 4. RECHENSYSTEME
Reelle Adressierung:
Programmadresse
=
Speicheradresse
=⇒
Speicheradresse
Virtuelle Adressierung:
Programmadresse
.
.........
.........
...
..
Adreßumsetzung
Abbildung 4.9: Schema für reelle und virtuelle Adressierung
(paging device), angelegt. Damit das Programm Befehle ausführen und Daten bearbeiten
kann, müssen die entsprechenden Seiten des Programmraumes aber nicht nur auf dem
Seitenwechselgerät vorhanden sein, sondern sich auch im Hauptspeicher befinden. Eine
zugwiesene Seite des Adreßraumes hat also immer einen Block des Seitenwechselgerätes
als Träger und manchmal, nämlich wenn sie „angesprochen“ wird, zusätzlich einen Seite
des Hauptspeichers. Sie ist dann „eingelagert“. Seiten des Adressraumes, man spricht vereinfachend auch von Seiten des Programms, sind also zu gewissen Zeiten eingelagert. Sie
können zu verschiedenen Zeitpunkten auch unterschiedliche Hauptspeicherseiten als Träger haben. Eine vom Prozessor gelieferte Programmadresse muß bei jedem Speicherzugriff
zunächst in eine Hauptspeicheradresse umgesetzt werde. Das muß sehr effizient geschehen.
Programmabläufe müssen (fast) genau so schnell sein, als gäbe es keine Adreßumsetzung.
Mit ausgefeilter assoziativ arbeitender Hardware, die zum Teil mehrstufig arbeitet, läßt
sich das erreichen.
Während eines Programmlaufs werden aber immer wieder Pogrammadressen erzeugt, die
zu Seiten gehören, die nicht eingelagert sind. Wenn das passiert, kommt es zu einer Fehlseitenunterbrechung (page fault). Das Betriebssystem muß dann eine freie (eventuell freigeräumte) Seite im Hauptspeicher zur Verfügung stellen und auf sie die Programmseite
von dem Seitenwechselgerät einlagern.
Virtuelle Adressierung wurde ursprünglich eingeführt, um den Pogrammläufen auf einem
Rechensystem einen deutlich größeren Adreßraum zuteilen zu können als der vorhandene
Hauptspeicher zuließ. Ein weiterer Vorteil der Adreßumsetzung ist, daß Hauptspeicherseiten, die zu einem Zeitpunkt einem Programmlauf zugwiesen sind, nicht zusammenhängend
sein müssen. Schließlich ist es mit der Adreßumsetzung auch möglich, ohne Zeitverzögerung Zulässigkeitstests auszuführen. Man kann zum Beispiel testen, ob die angesprochene Programmadresse dem Programmlauf überhaupt zugewiesen ist oder ob für sie ein
Schreibzugriff erlaubt ist.
4.4. VIRTUELLE ADRESSIERUNG
147
Literatur
Beschreibungen von Rechensystemen findet man in Kowalk [Kowa1996], Seiten 72-78 sowie
80-87, Goos [Goos1997], Abschnitt 1.5, sowie[Goos1996], Kapitel 11 „Vom Programm zur
Maschine“, und Appelrath [AppeL1995], Abschnitt 1.3.
Grundlegende Werke zur Hardware von Rechensystemen sind Stallings [Stal1996], Tanenbaum [Tane1990], Oberschelp/Vossen [OberV1994] und Hennessy/Patterson [HennP1994].
Das vorliegende Kapitel lehnt sich eng an die Ausführungen im Skript Stiege [Stie1995b]
an.
Allgemeine Lehrbücher zu Betriebssystemen sind Stallings [Stall2001], Tanenbaum [Tane1992],
Brause [Brau1996] und Siegert/Baumgarten [SiegB1998]. Literatur zu speziellen Betriebssystemen ist auf Seite 142 ff angegeben. Die Funktionalität eines Compilers ist in Kowalk [Kowa1996], S. 79-80, kurz beschrieben; ein Standardwerk über Compilerbau ist
Aho/Sethi/Ullman [AhoSU1986].
Virtuelle Adressierung und virtuelle Speicherverwaltung sind in den Lehrbüchern über
Betriebssysteme beschrieben, z. B. Stallings [Stall2001].
Auf das Buch Computer Systems – A Programmers’s Perspective von Bryant und O’Halleron
[BryaO2003] sei besonders hingewiesen. Es behandelt in einheitlicher Sicht und viel ausführlicher als es im vorliegenden Buch möglich das Zusammenspiel von Hardware und
Software.
148
KAPITEL 4. RECHENSYSTEME
Teil II
Algorithmen
149
Kapitel 5
Algorithmen I: Naiv und formalisiert
Die Bezeichnung Algorithmus geht auf Al Chwarismi 1 zurück, Algorithmen selber sind
sehr viel älter. Rechenvorschriften, die den Namen Algorithmus verdienen, gab es schon
in babylonischer Zeit (1800 v. Chr.), zum Beispiel zur Lösung spezieller Klassen quadratischer Gleichungen. Der älteste Algorithmus, der heute noch benutzt wird, ist wohl der
Euklidische Algorithmus (siehe Abschnitt 1.1).
In der ersten Hälfte des 20. Jahrhunderts gab es im Zusammenhang mit der Entwicklung der mathematischen Logik und der Erforschung der Grundlagen der Mathematik
eine große Zahl von Untersuchungen und Ergebnissen zum Algorithmusbegriff. Ein erster
Einblick in dieses Gebiet wird in Abschnitt 5.2 „Der formalisierte Algorithmusbegriff“
gegeben.
Mit dem Aufkommen von digitalen Rechnenanlagen und ihrer Programmierung in der
zweiten Hälfte des 20. Jahrhunderts wurde der Algorithmus zu einem zentralen Begriff
der Informatik. Mit seinem grundlegenden Werk The Art of Computer Programming
([Knut1997], [Knut1998], [Knut1998a]) hat Knuth 2 in besonderem Maße dazu beigetragen, den Begriff des Algorithmus zu klären und seine Bedeutung für die Informatik zu
Al Chwarismi, Muhammad Ibn Musa, ∗um 780 in Choresmien (Gebiet um Chiwa im heutigen Usbekistan), †nach 846, Bagdad. Iranisch-arabischer Mathematiker und Astronom am Hofe des Kalifen Al
Mamun. Verfasser der ältesten systematischen Bücher über Gleichungslehre, indische Zahlen und jüdische Zeitrechnung sowie Verfasser astronomischer und trigonometrischer Tafelwerke und anderer Werke.
Von seinem Namen leitet sich die Bezeichnung Algorithmus ab, der Titel eines seiner Werke (Al-kitab
al-muqtasar fi hisab al-gabr wa al-muqabala = kurzes Buch über das Rechnen der Ergänzung und der
Ausgleichung) ist der Ursprung des Begriffs Algebra.
2
Donald Ervin Knuth, ∗1938 Milwaukee, Wisconsin, USA. Studierte Mathematik am California Institute of Technology, dort Promotion 1963. 1968 Professor für Computer Science an der Stanford University,
seit 1993 dort Professor Emeritus of the Art of Computer Programming. Hauptwerk: The Art of Computer Programming, das er selbst als „... a work-still-in-progress that attempts to organize and summarize
what is known about the vast subject of computer methods and give it firm mathematical and historical
foundations“ bezeichnet (Knuth, http://sunburn.stanford.edu/∼knuth). Geplant sind 7 Bände, seit 1968
sind 3 Bände (Fundamental Algorithms [Knut1997], Seminumerical Algorithms [Knut1998] und Sorting
and Searching [Knut1998a]) in mehreren Auflagen erschienen. Ein vierter Band (Combinatorial Algo1
151
152
KAPITEL 5. ALGORITHMEN I: NAIV UND FORMALISIERT
zeigen.
5.1
5.1.1
Der naive Algorithmus-Begriff
Eigenschaften
Ein Algorithmus ist eine Arbeitsvorschrift, oft eine Rechenvorschrift. Eine exakte Definition soll nicht gegeben werden, wohl aber sollen Eigenschaften genannt werden, die von
einem Algorithmus verlangt werden.
Endlichkeit: Ein Algorithmus muß für jede zulässige Eingabe nach endlich vielen Schritten halten. Ist ein Eingabewert möglich, aber nicht zulässig (z. B. eine negative ganze Zahl,
wenn nur natürliche Zahlen erlaubt sind), so ist unbestimmt, ob der Algorithmus hält und,
wenn ja, mit welchem Ergebnis.
Bei der Formulierung eines Algorithmus als Programm ist es ratsam,
alle prinzipiell möglichen Eingaben auch zu berücksichtigen und die
davon nicht erlaubten über Fehlerbehandlung auszusondern.
Eine Reihe wichtiger Programme und Programmsysteme sind keine Algorithmen, sie enden nach der Bearbeitung eines Eingabewertes nicht. Beispiele sind Systeme zur Verkehrsüberwachung oder zur Überwachung eines Kranken in einer Intensivstation. Auch
Betriebssysteme gehören dazu. Eine Arbeitsvorschrift, die sonst alle Eigenschaften eines
Algorithmus hat, aber nicht bei jedem Eingabewert hält, heißt Rechenmethode. Die als
Beispiele genannten Programmsysteme arbeiten folgendermaßen: Sie sind in einem Wartezustand. Bei Eintreffen eines Eingabewertes (Kommando, Nachricht, Signal,...) werden
die dafür vorgesehenen Aktionen ausgeführt und dann auf den nächsten Eingabewert
gewartet. Aus diesem Grund werden sie auch reaktive Systeme genannt.
Die Wichtigkeit reaktiver Systeme mindert die Bedeutung von Algorithmen und ihrer Untersuchung jedoch keinesfalls. Gerade bei diesen Systemen muß man sich darauf verlassen
können, daß die Bearbeitung eines jeden Eingabewertes beendet wird und das System sich
nicht „aufhängt“, sondern in den Wartezustand zurückkehrt.
Bestimmtheit: Die Beschreibung der Schritte eines Algorithmus muß klar und eindeutig sein. Ob das der Fall ist, hängt zum einen davon ab, für wen der Algorithmus
rithms) ist in Vorbereitung. Knuth ist Autor der Softwaresysteme TEXund METAFONT, der weltweit
am stärksten verbreiteten Software für die Herstellung und den Satz mathematischer und informatischer
Schriften und Bücher (darunter auch das vorliegende Buch). Aus dieser Arbeit ist das fünfbändige Werk
Computers & Typesetting ([Knut1984], [Knut1986], [Knut1986a], [Knut1986b], [Knut1986c]) entstanden.
5.1. DER NAIVE ALGORITHMUS-BEGRIFF
153
bestimmt ist. Arbeitsvorschriften für Menschen können anders formuliert werden als Arbeitsvorschriften für Rechner. Bei Menschen können Kenntnis- und Wissensstand wiederum unterschiedliche Formulierungen zum Erreichen der Bestimmtheit erfordern.
Die Bestimmheit eines Algorithmus hängt zum anderen vom Grad der Formalisierung ab.
Die folgenden Darstellungsarten von Algorithmen weisen einen zunehmenden Formalisierungsgrad auf.
a. Natürliche Sprache.
Als Beispiel kann das Puddingrezept aus Abschnitt 1.3, Seite 25, dienen.
b. Natürliche Sprache mit Regeln.
Der Euklidische Algorithmus aus Unterabschnitt 1.1.1, Seite 3, ist hierfür ein Beispiel.
c. Graphische Darstellungen.
Beispiele für eine graphische Darstellung von Algorithmen sind Flußdiagramme (siehe Abbildungen 5.1, Seite 155, und 5.2, Seite 156) sowie Struktogramme (siehe Abbbildung 5.3, Seite 158).
d. Pseudocode.
Algorithmus IS zum Sortieren durch Einfügen (Tabelle 1.6, Seite 20) ist z. B. in
Pseudocode formuliert.
e. Programmiersprachen.
Beispiele: In diesem Buch ist eine größere Anzahl von Programmen (in C) angegeben.
Eine Übersicht ist in Anhang F „Verzeichnis der Algorithmen und Programme“, Seite
677, zu finden.
f. Vollständig formalisierte Algorithmen (werden in Abschnitt 5.2 behandelt).
Die aufgeführten Darstellungsarten von Algorithmen sind keine vollständige Aufzählung.
Alle mit Ausnahme der formalisierten Algorithmen sind als naive Algorithmen anzusehen
und für sie ist die Festlegung eines „Formalisierungsgrades“ unscharf.
Intersubjektivierbarkeit: Algorithmen müssen von dem „Ausführer“ (Mensch oder
Maschine) verstanden werden. Algorithmen, die in einer Sprache formuliert werden, die
nur der, der den Algorithmus konzipiert, versteht, sind i. a. wenig nützlich.
Eingabe und Ausgabe: Soll ein Algorithmus wirklich ablaufen, müssen ihm die Werte,
mit denen er arbeiten soll, zugeführt werden können. Ebenso muß er Ergebnisse ausgeben
können.
154
KAPITEL 5. ALGORITHMEN I: NAIV UND FORMALISIERT
Effektivität: Alle Aktionen eines Algorithmus müssen effektiv ausführbar sein und alle
Bedingen, die abgeprüft werden, müssen effektiv entscheidbar sein. Z. B. ist die bedingte
Anweisung
Wenn die Riemannsche Vermutung gilt, führe Aktion A aus, sonst Aktion B.
z. Z. nicht effektiv ausführbar. Die Riemannsche Vermutung3 ist sicherlich richtig oder
falsch, aber z. Z. ist nicht bekannt, was gilt.
Anmerkung 5.1 Bis vor einigen Jahren war es üblich, an Stellen wie dieser den inzwischen bewiesenen „großen Satz von Fermat“ 4 zu zitieren. Er besagt, daß die Gleichung
xn + y n = z n
für positive natürliche Zahlen x, y, x, n keine Lösung hat, wenn n > 2. Der Satz wird auch
als „Fermats letzter Satz“ bezeichnet und wurde 1995 von A. Wiles7 bewiesen.
[Georg Friedrich] Berhard Riemann ∗1826 Breselenz (Kreis Lüchow-Danneberg), †1866 Selasca (heute zu Verbania, Piemont, Italien). Deutscher Mathematiker, Professor in Göttingen. Wesentliche Beiträge
zur Theorie der Funktionen einer komplexen Veränderlichen („Riemannsche Zahlenkugel“, „Riemannsche Fläche“, „Riemannscher Abbildungssatz“), Geometrie („Riemannsche Geometrien“) Integralrechnung
(„Riemannsches Integral“), Zahlentheorie („Riemannsche ζ-Funktion“). Die Riemannsche Vermutung betrifft eine Aussage zur Lage der Nullstellen der Riemannschen ζ-Funktion.
4
Fermat, Pierre de ∗17.(?) August 1601 Beaumont-de-Lomagne (Tarnet-Garonne), †12. Januar 1665
Castres bei Toulouse. Französicher Mathematiker. Parlamentsrat in Toulouse. Wichtige Beiträge zur analytischen Geometrie und zur Analaysis. Bedeutendster Zahlentheoretiker seiner Zeit („kleiner Satz von
Fermat“). Zeitgenosse von Pascal 5 , mit dem er über Fragen der Wahrscheinlichkeitsrechnung korrspondierte, und von Descartes 6 , dessen optische Arbeiten er um das „Fermatsche Prinzip“ ergänzte.
5
Pascal, Blaise ∗19. Juni 1623 Clermont-Ferrand, †19. August 1662 Paris. Französischer Philosoph,
Mathematiker und Physiker. Religiös-philosophische und wissenschaftskritische Arbeiten. In der Mathematik Beiträge zur Elementargeometrie, zu den Kegelaschnitten und zur Analysis, insbesondere aber
zur Kombinatorik und Wahrscheinlichkeitsrechnung („Pascalsches Dreieck“). Entwickelte eine der ersten
Rechenmaschinen. Beiträge zur Physik (Kommunizierende Röhren, Verwendung des Barometers zur Höhenmessung).
6
Decartes, René ∗31. März 1596 La Haye-Decartes (Touraine), †11. Februar 1650 Stockholm. Französischer Philosoph, Mathematiker und Naturwissenschaftler. Nach Besuch einer Jesuitenschule ausgedehnte
Reisen durch Europa. Einige Jahre Kriegsdienste (Nassau, Bayern). Aufenthalt in den Niederlanden,
später in Schweden. Führte grundlegende erkenntnistheoretische Begriffe auf mathematischer und physikalischer Basis ein. In der Mathematik Beiträge zur Theorie transzendenter Kurven und zur Algebra.
Besonders wichtig ist seine Grundlegung der analytischen Geometrie („kartesische Koordinaten“). Mitentdecker des Brechunsgesetzes der Optik
7
Wiles, Andrew John ∗11. April 1953 Cambridge, England. Britischer Mathematiker. Eugene Higgins
Professor of Mathematics in Princeton, New Jersey, USA. Zum großen Fermatschen Satz und zu A. Wiles
siehe im WWW
www-history.mcs.st-andrews.ac.uk/history/HistTopics/Fermat’s_last_theorem.html
www.treasure-troves.com/math/FermatsLastTheorem.html
www-history.mcs.st-andrews.ac.uk/history/Mathematicians/Wiles.html .
Siehe auch das Buch von Singh [Sing2000].
3
5.1. DER NAIVE ALGORITHMUS-BEGRIFF
155
'
Flußdiagramme, auch Ablaufdiagramme genannt, geben den Steuerfluß eines
Algorithmus oder Programms wieder. In der einfachsten Form werden die
Zustände „Anfang“ und „Ende“ sowie Handlungen (Rechtecke) und Abfragen/Verzweigungen (Rauten) gezeichnet und durch Pfeile verbunden. Abbildung 5.2 zeigt den Algorithmus EEA (Euklid mit Ein- und Ausgabe) von Seite
...
...
7 als Flußdiagramm.
...
...
qqqqq
................................................
...................
..........
..........
.......
......
....
....
...
.
.....
..
.....
.....
.......
.
.
.
.
.
.
...........
.....
.
.
.
.
.
.
.
..........................
.
.
.
.
.
.
...............................
...
...
...
...
.
.........
.........
..
...
...
...
...
...
...
...
.
.... ..
....... ..
.
.
.......
.......................................... .........
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...........
........
.
.
.
.......
.
.
.
.
...
.....
.....
..
.....
.
..
....
.......
.....
.
.
.
.
.
.
...........
.....
.
.
.
.
.
.....................
.
.
.
.
.
.
.
.
....................................
Anfang
Zustände
...
...
...
...
...
.........
.........
..
$
Ende
qqqqq
...
...
...
...
...
.........
.........
...
Handlungen
...
...
...
...
...
..........
........
...
qqq
Abfragen und Verzweigungen
&
...
..
...
...
...
...
...
..
.
.
...
.
..
...
............... ............
.... .....
....
.. ..
... ......
....
....
...
....
....
....
...
.
.
....
..
.
....
.
.
..
....
.
.
.
.
..
..............................................
.....................................................
.
..
....
.......
....
....
...
.
.
.
....
...
.
....
.
..
.
....
.
.
....
...
.... .......
.... ...
...
ja
nein
%
Abbildung 5.1: Darstellungselemente für Flußdiagramme
5.1.2
Schrittweise Verfeinerung
Schrittweise Verfeinerung (stepwise refinement) ist eine Vorgehensweise, bei der eine zu lösende Aufgabe in immer kleinere und einfachere Teilaufgaben zerlegt wird, bis diese direkt
gelöst werden können. Wenn diese Technik die Arbeitsweise eines fertigen Algorithmus
(vielleicht schon in Form eines Programms) ist, spricht man auch von dem Prinzip „Teile
und Herrsche“. Mehr dazu ist in Unterabschnitt 5.1.3 zu finden.
In diesem Unterabschnitt soll schrittweise Verfeinerung jedoch nicht als Funktionsprinzip von Algorithmen, sondern als Technik zum Finden und Entwerfen von Algorithmen
untersucht werden. Als Beispiel soll das in Abschnitt 1.2 beschriebene Sortieren durch
Einfügen dienen. Das entsprechende Programm ist in den Tabellen 1.7, Seite 22, und 1.8,
156
'
KAPITEL 5. ALGORITHMEN I: NAIV UND FORMALISIERT
Euklidischer Algorithmus mit Ein- und Ausgabe (Algorithmus EEA, Seite 7)
$
........................................................................
...........
........
.......
.....
.....
..
.
.....
..
....
.......
.....
.
.
.
.
.
.
..........
.....
.
.
.
.
.
....................
.
.
.
.
.
.
.
.
.......................................
...
...
...
...
.
.........
........
...
Anfang
Lies Anfangswerte von
n und
m
...
..
...
...
...
...
...
...
...
.
....... ..
.........
...
...
...
...
...
...
....... ..
........
...
Setze m :← n
Dann n :← r
....
.......
........
....
...
...
...
...
...
...
...
...
...
...
.
Teile m durch n
und weise r den
Rest... zu
nein
...
...
...
...
.
.........
........
.....
.
.
.
... ......
...
....
....
....
...
....
....
.
....
.
.
....
....
.
.
....
..
.
.
...
.
......
..
....
....
....
....
.
....
.
.
....
....
.
.
....
.
....
...
....
....
.... ......
.....
...
...
...
...
..
....... ..
.........
...
r=0
?
ja
Gib n aus
...
...
...
...
...
.........
........
.
....................
.
.
.
.
.
.
.
.
.
.
.
.
.
.
................
.............
.........
.............
......
........
....
.....
..
....
...
...
.....
...
........
......
.
.
.
.
.
.............
.
.
.
................................................................
Ende
&
Abbildung 5.2: Flußdiagramm des Euklidischen Algorithmus mit Ein- und Ausgabe
Seite 23, angegeben. Tabelle 5.1 zeigt, wie man mit schrittweiser Verfeinerung vorgehen
könnte, wenn man Sortieren durch Einfügen realisieren möchte.
Schrittweise Verfeinerung ist generell eine nützliche Vorgehensweise beim Entwurf von
Algorithmen und Programmen. Oft wird die Bezeichnung jedoch in einem engeren Sinne,
nämlich als gleichbedeutend zur strukturierten Programmierung benutzt. Bei strukturierter Programmierung sind als Konstruktionslemente von Programmen nur zugelassen:
%
5.1. DER NAIVE ALGORITHMUS-BEGRIFF
1.
1. Schritt
Vorbesetzungen und
Eingabe
1.1
1.2
2.
Sortieren
2.1
3.
Ausgabe
3.1
2. Schritt
Sortierfeld mit 0 vorbesetzen.
1. Sortierwert und
eventuell
weitere
einlesen.
157
3. Schritt
1.2.1 Falls 1. Sortierwert
negativ, Ende, sonst
restliche Sortierwerte
bis maximal 10000
einlesen.
Werte des zweiten 2.1.1 Aktuellen einzuordbis letzten Sortiernenden
Sortierwert
feldes im sortierten
solange mit Vorgänger
Anfansgstück
an
im Feld vertauschen,
der richtigen Stelle
wie der Vorgängerwert
einordnen.
größer als der einzuordnende Sortierwert
ist.
Sortierfeld ausgeben.
Tabelle 5.1: Gewinnung des Programms INSERTSORT durch schrittweise Verfeinerung
1. Sequenz (Folge).
Alle Operationen einer Sequenz werden in der vorgegebenen Reihenfolge genau einmal ausgeführt.
2. Alternative (Auswahl).
Abhängig von einer Bedingung wird eine von zwei Operationen ausgeführt und die
andere nicht. Sprachlich wird eine Alternative meistens durch etwas ähnliches wie
if...then...else ...fi ausgedrückt. Der else -Teil darf leer sein. Es ist auch möglich,
daß genau eine von mehreren Operationen ausgeführt wird. Sprachlich wird das
meistens durch case ausgedrückt.
3. Schleife (Iteration).
Die Operation in einer Schleife wird so oft ausgeführt, wie die Schleifenbedingung es
vorschreibt. Zählbedingungen werden i. a. über for -Schleifen, logische Bedingungen
über while - oder until-Schleifen spezifiziert.
4. Funktionsaufruf.
Funktionsaufrufe werden wie elementare Operationen angesehen. Werden die Funktionen einer Bibliothek entnommen oder sind es Betriebssystemaufrufe, so werden
keine zusätzlichen Bedingungen an die Funktionen gestellt. Gehört die Konstruktion
158
KAPITEL 5. ALGORITHMEN I: NAIV UND FORMALISIERT
der entsprechenden Unterprogramme jedoch zur Programmkonstruktion, so sind keine rekursiven Aufrufe zulässig. Zum Beispiel genügen im Programm LVA (Beispiel
2.3, Seite 69) die Unterprogramme aufbau und lvaneu den Regeln der strukturierten
Programmierung, das rekursive Unterprogramm lvanamen jedoch nicht.
Die Konstruktionselemente der strukturierten Programmierung dürfen beliebig kombiniert
und geschachtelt werden. Als grafische Darstellung werden oft Struktogramme (auch NassiShneiderman-Diagramme genannt) verwendet. Abbildung 5.3 zeigt das Struktogramm
zum Programm Sortieren durch Einfügen (siehe Abschnitt 1.2).
'
Sortieren durch Einfügen (Programm INSERTSORT, Seiten 22 und 23.)
$
Sortierfeld mit 0 vorbesetzen.
1. Sortierwert
einlesen.
.......................................
.
.......................................
......
........................................
......
........................................
......
.................................1
>=
0
?
......
.
.
............Sortierwert
.
.
........................................
.
....
.......................................
......
........................................
......
.......................................
........................................
...... nein
ja
........................................ ..........
......
Sortierwert >= 0 ∧ weniger als 10001 Sortierwerte eingelesen
Ende
Nächsten Sortierwert einlesen
for n := 1 to Anzahl Sortierwerte - 1
j := n - 1
k := Sortierfeld[n]
j >= 0 ∧ Sortierfeld[j] > k
Sortierfeld[j+1] := Sortierfeld[j]
j := j + 1
Sortierfeld[j + 1] := k
for n := 0 to Anzahl Sortierwerte - 1
Sortierfeld[n] ausgeben.
&
Abbildung 5.3: Struktogramm für Programm INSERTSORT
%
Anmerkung 5.2 Es läßt sich zeigen, daß sich alle Algorithmen - also auch solche, die
Rekursionen enthalten, und solche, die wie z. B. Assemblerprogramme nur Sprunganweisungen kennen, durch Konstrukte der strukturierten Programmierung darstellen lassen8 .
Man kann sich demnach, wenn man will, auf diese Konstrukte beschränken.
Streng genommen, lassen sich Aussagen über alle Algorihtmen nur machen, wenn man einen formalisierten Algorithmusbegriff benutzt (siehe Abschnitt 5.2). Aus diesem Grund wird manchmal von einem
„im Volk vordhandenen Wissen (folk theorem)“ gesprochen. Siehe den Aufsatz von Harel [Hare1980] und
die Anmerkungen von Denning [Denn1980].
8
5.1. DER NAIVE ALGORITHMUS-BEGRIFF
5.1.3
159
Entwurfstechniken für Algorithmen
Der Name Entwurfstechniken für Algorithmen ist üblich, gemeint ist aber in erster Linie
nicht der Vorgang des Findens und des Entstehens eine Algorithmus, sonder die typische
Arbeistweise des fertigen Algorithmus.
Im vorigen Jahrhundert sind für viele Probleme Lösungsalgorithmen und -programme gefunden worden, in der Informatik, in Mathematik, Naturwissenschaften und technischen
Wissenschaften; aber auch in anderen Bereichen, insbesondere in den Wirtschaftswissenschaften und in der Liguistik. Dabei handelt es sich ebenso um sehr wichtige Anwendungen
wie auch um eher theoretisch motivierte Fragestellungen. Aus diesem umfangreichen Erfahrungsschatz haben sich einige Grundideen und Vorgehensweisen herauskristallisiert,
die auf große Klassen von Aufgaben angewendet werden konnten. Im folgenden werden
die wichtigsten dieser Entwurfstechniken kurz umrissen. Eine ausführlichere Behandlung
würde Kenntnisse von Datenstrukturen voraussetzen, die wir an dieser Stelle noch nicht
haben und die erst in den Teilen III und IV vermittelt werden.
Für weitere Entwurfstechniken siehe Abschnitt 6.3, Seite 216.
Teile und herrsche
Teile und herrsche9 (divide and conquer) ist eine Lösungmethode, bei der eine Aufgabe
in immer kleinere Aufgaben zerlegt wird, bis schließlich eine ganz einache Lösung für die
Teilaufgaben möglich ist. Diese Teillösungen werden dann zur gesuchten Gesamtlösung
zusammengesetzt. Die Teillösungen werden alle auf die gleiche Art gfunden und daher ist
Rekursion der geignete Ansatz.
Ein typisches Beispiel dafür ist das in Unterabschnitt 6.1.2 beschriebene Mischsortieren.
Ein weiteres typisches Beispiel ist QUICKSORT. Siehe Unterabschnitt 11.2.
Häufig eignen sich Algorithmen, die auf Teile und Herrsche basieren, gut zur parallelen
Bearbeitung. Zu parallelem Quicksort siehe Aufgabe 23.5, Seite 608.
Dynamische Programmierung
Dynamische Programmierung (dynamic programming) ist eine Lösungsansatz, bei dem
wie bei „teile und herrsche “ ein Problem in immer kleinere Teileaufgaben zerlegt wird
und diese zur Gesamtlösung zusammengesetzt werden. Die Lösungen der kleinsten Teilaufgaben werden jedoch nicht über Rekursion erreicht, sondern direkt gefunden und in
einer Tabelle vermerkt. Daher (und nicht vom Codieren) die Bezeichnung “programming”.
Im nachfolgenden Beispiel zur Berechnung von Fibonacci-Zahlen wird der Unterschied
der beiden Vorgehensweisen deutlich.
lat. diviae et impera. Trotz der lateinischen Formulierung ist der Ausspruch wohl nicht antik. Möglicherweise geht er auf Niccoló Machiavelli zurück.
9
160
KAPITEL 5. ALGORITHMEN I: NAIV UND FORMALISIERT
Beispiel 5.1 (Berechnung der Fibonacci-Zahlen) Auf Seite 11 wurden die FibonacciZahlen durch
F0 := 0, F1 := 1 und Fk := Fk−1 + Fk−2 für 2 ≤ k .
rekursv definiert. Es ist unmittelbar klar, wie die Berechnung von fk unter Benutzumg
von „teile und herrsche“ rekursiv zu programmieren ist. Das Beispiel in Abbildung 5.4
zeigt die rekursiven Aufrufe bei der Berechnung von f6 . Es ist leicht zu sehen, daß im
f6
...................................
.........
.............
.............
.........
.............
........
.
.
.
.
.
.
.
.............
..
.............
.........
.............
........
.
.
.
.
.
.
.............
.
.....
.
.
............. ....
.
.
.
.
.
.
.
.
.
................
.. ....
.................
...........
f5
f4
.....................
.........
......
.........
......
.........
......
.........
......
.
.
.
.
.
.........
....
.
.
.
.........
.
.
.
......... .
...........
.
.............
............
.
.
.
................
...
f4
f3
.
..........
.... ...........
......
...
......
....
.
.
.
......
.
......
....
......
. ......
.........
.
..............
.
....................
...
f.3
...
... .....
... ......
....
..
.
....
...
....
. ...
.... ..
...............
.................
...
..
f1
f.2
.......
... ...........
......
....
......
...
......
....
.
.
......
.
..
......
.
.
.
...... ..
........
... .
.
............
.
...................
.
.
f.2
...
... ...
... .....
..
...
.
.
...
...
..
. ...
..............
...............
......
..
f1
f3
......
... ......
....
...
....
..
.
....
...
...
.... ..
.. ....
.
.................
.............
...
.
f0
f1
f.2
..
... ....
... ....
...
..
.
...
...
...
. ...
..............
................
.....
.
..
f1
f0
f2
.......
... ......
....
...
....
..
.
....
...
....
...........
.. ..
.................
..........
..
.
f1
f.2
.....
... ....
... ....
...
..
.
...
..
..
.. .....
.............
..
......
.............
.
.
f1
f0
...
... ...
... .....
..
...
.
.
...
...
..
.. ...
..............
..............
......
..
f1
f0
.......
... ....
... ....
...
...
.
...
..
........
...............
...
....
.........
.
f1
f0
Abbildung 5.4: Rekursive Berechnung der Fibonacci-Zahlen
allgemeinen Fall die Anzahl der Aufrufe exponentiell mit k wächst. Hingegen wird bei
dynamischer Programmierung nacheinander f0 , f1 , . . . fk berechnet und der Aufwand ist
linar in k.
2
Anmerkung 5.3 Dynamische Programmierung wurde 1953 von Richard Bellman 10 zur
Lösung von Optimierungsaufgaben eingeführt. Sie wird in der Literatur über Optimierung
deshalb auch dynamische Optimierung genannt. Von Bellman stammt auch das Optimalitätsprinzip das (vereinfacht ausgedrückt) besagt, dass man ein Optimum nur erhält, wenn
Richard Ernest Bellman ∗20. August 1920 in New York City, New York, USA, † 19. März 1984 in Los
Angeles, California, USA. Amerikanischer Mathematiker. Arbeitete auch in der Theoretischen Physik.
Begründer der Dynamischen Programmierung. Fand einen Algorithmus zur Bestimmenung kürzester
Wege in Graphen (siehe Abschnitt 19.3, Seite 516).
10
5.1. DER NAIVE ALGORITHMUS-BEGRIFF
161
man von einem Optimum ausgeht und bei jedem Schritt aus den gewonnen Zwischenergebnissen ein neues Optimum bestimmt [Bell2003].
2
Rücksetzen
Rücksetzen (backtracking) ist im wesentlichen die vollständige Durchsuchung eines Lösungraumes. Es wird zurückgesetzt, wenn die Suche an einer Stelle nicht fortgesetzt werden
kann oder nicht fortgesetzt werden soll. Meistens wird mit Rekursion gearbeitet.
Beispiel 5.2 (Rucksackproblem) Beim Rucksackproblem (knapsack problem) ist eine
nichtleere endliche Menge I von Gegenständen gegeben, von denen eine Auswahl in einen
Rucksack gepackt werden soll. Die Gegenstände haben eine Gewicht w und einen Wert v.
Der Rucksack11 trägt maximal das Gewicht b. Zulässige Auswahlen J von Gegenständen
sind solche, deren Gesamtegewicht b nicht überschreitet.
X
J := {J ⊆ I|
w(g) ≤ b}
g∈J
Gesucht wird eine zulässige Auswahl J0 von Gegenständen, deren Wert maximal ist:
X
X
v(g) = max
v(g)
g∈J0
J∈J
g∈J
Bei einer algorithmischen Lösung dieser Aufgabe wird man wohl mit einer Durchmusterung aller Teilmengen J von I beginnen und zunächst einmal eine Teilemenge solange
erweitern, bis die Erweiterung zu schwer wird. Dann wird man die größte zulässige Erweiterung prüfen, ob ihr Wert den größten bisher gefundenen Wert übetrifft. Ist das nicht
der Fall, setzt man zurück und fährt mit einer anderen Teilmenge fort. Die zum Schluß
gefundene Lösung ist eine optimale Rucksackfüllung. Die Details des Vorgehens sind in
Aufgabe 5.1 auszuarbeiten.
Wie die Maximumsbildung über alle Teilmengen vermuten läßt, ist das Rucksackproblem
aufwändig. In der Tat, es ist N P-vollständig (siehe Abschnitt 6.2). Wie in Unterabschnitt
6.3 erwähnt und in [Hrom2007], Abschnitt 7.2, ausgeführt, kann man Hilfe von Methoden
der dynamischen Programmierung pseudoplynomielle Lösungen finden.
2
Lokale Suche
Lokale Suche (local search) wird bei Optimierungsfragen eingesetzt. Im Raum der möglichen Lösungen wird eine Nachbarschaftsstruktur eingeführt und es wird geprüft, ob ein
Nachbar der aktuellen Lösung einen besseren Wert der zu optimierenden Funktion liefert.
Wenn das der Fall ist, wird mit diesem Nachbarn fortgefahren. Wenn nein, haben wir ein
11
Besser wohl der Träger ded Rucksacks.
162
KAPITEL 5. ALGORITHMEN I: NAIV UND FORMALISIERT
lokales Optimum gefunden. Normalerweise ist ein lokales Optimum nicht global, also noch
nicht die gewünschte Endlösung. Die Bestimmung minimaler erzeugender Bäume – siehe
Seite 548 – ist ein Beispiel, in dem lokale Suche auch ein globales Optimum liefert.
Gieralgorithmen
Man spricht von einem Gieralgorithmus (greedy algorithm), wenn der Algorithmus bei jedem Schritt mit der Richtung fortfährt, die im Augenblick den größten Nutzen verspricht.
Solche Algorithmen heißen auch gefräßig. Gieralgorithmen werden in erster Linie bei Optimierungsaufgaben eingesetzt. In manchen Fällen führt gieriges Vorgehen zum Optimum,
in anderen nicht. Beispiele für das Finden des Optimums mit einem Gieralgorithmus sind
die Bestimmung eines minimalen erzeugenden Baumes mit dem Algorithmus von Kruskal
(Seite 547) oder mit dem Algorithmus von Prim (Seite 548).
5.1.4
Algorithmen in applikativer Darstellung
Alle bisher behandelten Algorithmen und Programme hatten gemeinsam, daß die auszuführende Anweisungen explizit hingeschrieben wurden und die Reihenfolge des Hinschreibens im wesentlichen die Ausführungsreihenfolge bestimmt. Dabei waren Alternativen, Schleifen, nichtrekursive Funktionsaufrufe und, falls man sich nicht auf strukturierte
Programmierung beschränkt, auch rekursive Funktionsaufrufe zur Steuerung des Ablaufs
zugelasssen. Bei einigen Formulierungen, zum Beispiel beim Euklidischen Algorithmus E,
Seite 4, wurden auch Sprünge benutzt. Algorithmen, die so formuliert werden, heißen
imperativ. Bei Programmen spricht man von imperativer Programmierung oder auch prozeduraler Programmierung. Das wesentliche hieran ist, daß die Anweisungen explizit die
Bearbeitung von Datenobjekten beschreiben. Man kann jedoch auch anders vorgehen und
Algorithmen als Definition von Funktionen (im mathematischen Sinne) oder als Definition von logischen Prädikaten ansehen. Das führt zu der folgenden Einteilung (nach Goos
[Goos1997]):
'
• Algorithmen/Programme in imperativer (prozeduraler) Darstellung bilden und bearbeiten Datenobjekte explizit.
$
• Algorithmen/Programme in applikativer (funktionaler) Darstellung sind Funktionsdefinitionen. Die Ausführung eines Algorithmus/Programms besteht in der Berechnung von Funktionswerten.
• Algorithmen/Programme in logischer Darstellung sind logische
Prädikate. Die Ausführung besteht in der Anfrage, ob ein solches
Prädikat angewandt auf bestimmte Terme (die Eingabe) richtig ist.
&
%
5.1. DER NAIVE ALGORITHMUS-BEGRIFF
163
Diese Darstellungformen werden Programmierparadigmen genannt.
In diesem Unterabschnitt geht es nur um applikative Darstellungen. Ihren Ursprung haben diese Darstellungen im λ-Kalkül von Church.12 McCarthy 13 entwickelte hieraus 1959
am Massachusetts Institute of Technology die Programmiersprache Lisp, die „Mutter aller funktionalen Programmiersprachen“. Moderne funktionale Programmiersprachen sind
z. B. Gofer (siehe [Goos1997]) oder HASKELL [Thom1996], [Dobe2012].
Im Rest dieses Unterabschnitts wird nicht auf funktionale Programmierung eingegangen,
sondern es wird an einigen Beispielen gezeigt, wie Algorithmen in applikativer Darstellung
formuliert werden können. Als Datentypen werden dabei nur die ganzen Zahlen Z und die
booleschen Werte true und false zugelassen. Bei diesen Beispielen folgen wir dem Skript
von Ehrich [Ehri1987].
Die Algorithmen werden als Funktionen geschrieben:
f(x) = x + 1
Der Algorithmus berechnet bei Eingabe eines ganzzahligen Wertes x den Wert x + 1.
Auch Funktionen von mehreren Veränderlichen können dargestellt werde. Zum Beispiel
kann f (x, y) = (x + y)2 als
f(x,y) = x*x + 2*x*y + y*y
geschrieben werden.
Alternativen werden mit if ... fi angebeben, Absolutwert und Signum von x zum Beispiel
als
abs(x) = if x ≥ 0 then x else -x fi
sgn(x) = if x = 0 then 0
else if x > 0 then 1 else -1 fi fi
Rekursionen sind das wichtigste Ausdrucksmittel bei applikativen Darstellungen. Die Fakultätsfunktion x! := x · (x − 1) · · · · · 2 · 1 für x ≥ 1 und 0! := 1 kann z. B. wie folgt
geschrieben werden
fak(x)
=
if x = 0 then 1 else x*fak(x-1) fi
Das ergibt z. B. fak(3) = 3*fak(2) = 3*2*fak(1) = 3*2*1*fak(0) = 3*2*1*1 = 6
Es sollen stets alle ganzen Zahlen als Eingabewerte zugelassen werden, auch wenn für
einige der Algorithmus nicht hält, sondern kreist, streng genommen also kein Algorithmus
mehr ist. Z. B.
fak(-2) = (-2)*fak(-3) = (-2)*(-3)*fak(-4) = ...
Rekursionen können gekoppelt sein. Mit den beiden folgenden rekursiv gekoppelten Funktionen kann man z. B. ohne ganzahlige Division testen, ob eine gerade oder eine ungerade
Zahl vorliegt.
12
13
Zu Church siehe die Fußnote auf Seite 169.
McCarthy, John, amerikanischer Informatiker, Pionier der künstlichen Intelligenz.
164
KAPITEL 5. ALGORITHMEN I: NAIV UND FORMALISIERT
gerade(x)
=
ungerade(x)
=
if x
else
else
if x
else
else
= 0 then true
if x > 0 then
ungerade(x+1)
= 0 then false
if x > 0 then
gerade(x+1) fi
ungerade(x-1)
fi fi
gerade(x-1)
fi
Die vorgestellten Ausdrucksmittel für applikative Algorithmen sollen nun bei drei Beispielen angewandt werden.
Beispiel 5.3
f(x)
=
if x > 100 then x-10 else f(f(x+11)) fi
Für Werte größer als 100 kann das Ergebnis unmittelbar angegeben werden: f(101) =
91, f(102) = 92, f(1000) = 990. Einige Beispiele für Eingaben kleiner oder gleich
100:
f(100)
f(99)
f(98)
=
=
=
f(f(111)
f(f(110)
f(f(109)
=
=
=
f(101)
f(100)
f(99)
=
=
=
91
...
...
=
=
91
91
Diese und weitere Beispiele lassen die Vermutung aufkommen, daß f(x) = 91 für alle x <
101. Die Vermutung ist richtig und kann durch vollständige Induktion bewiesen werden.
Induktionsanfang: Die Behauptung ist richtig für x = 100, wie man direkt nachprüft.
Induktionsschritt: Die Behauptung sei richtig für x = 100, 99, · · ·, n. Zu zeigen ist
f(n-1) = 91. Unter Berücksichtigung der Induktionsvoraussetzung ergibt sich
f(n) für 91 ≤ n ≤ 100
f(n-1) = f(f(n+10)) =
= 91.
2
f(91) für n ≤ 90
Beispiel 5.4
f(x)
g(x)
=
=
if x = 1 then 1 else f(g(x)) fi
if gerade(x) then x2 else 3*x+1 fi
Für x = 0 kreist der Algorithmus: f(0) = f(0) = ...
Falls x < 0, so sind stets x2 und 3*x+1 negativ und das Argument kann niemals den Wert
1 annehmen. Der Algorithmus kreist auch in diesem Fall.
Einige Beispiele für positive x:
f(1) = 1
f(2) = f(1) = 1
f(3) = f(10) = f(5) = f(16) = f(8) = f(4) = f(2) = f(1) = 1
f(4) = 1
5.1. DER NAIVE ALGORITHMUS-BEGRIFF
165
f(5) = 1
f(6) = f(3) = 1
f(7) = f(22) = f(11) = f(34) = f(17) = f(52) = f(26) =
= f(13) = f(40) = f(20) = f(10) = 1
Es wird vermutet, ist aber noch nicht bewiesen, daß f(x) = 1 für alle x ≥ 1.
2
Beispiel 5.5 (Ackermannfunktion) Die Ackermannfunktion14 ist definiert durch
af(x,y)
=
if x ≤ 0 then y+1
else if y ≤ 0 then af(x-1, 1)
else af(x-1, af(x, y-1)) fi fi
Einige Beispiele:
af(0, 0)
af(0, 1)
af(1, 0)
af(1, 1)
af(2, 2)
=
=
=
=
=
=
=
=
=
=
=
=
1
2
af(0, 1) = 2
af(0, af(1, 0)) = af(0, af(0, 1) = af(0, 2) = 3
af(1, af(2, 1)) = af(1, af(1, af(2, 0)))
af(1, af(1, af(1, 1))) = af(1, af(1, 3))
af(1, af(0, af(1, 2))) = af(1, af(0, af(0, af(1, 1))))
af(1, af(0, af(0, 3))) = af(1, af(0, 4)) = af(1, 5)
af(0, af(1, 4) = af(0, af(0, af(1, 3))) = af(0, af(0, af(0, af(1, 2))))
af(0, af(0, af(0, af(0, af(1, 1))))) = af(0, af(0, af(0, af(0, 3))))
af(0, af(0, af(0, 4))) = af(0, af(0, 5) = af(0, 6)
7
Ähnlich läßt sich (über eine sehr lange Herleitung) af(3, 3) = 61 berechnen. Wir wollen
uns einen systematischen Überblick über den Verlauf der Ackermannfunktion für kleine
Werte von x verschaffen.
Nach einigen Beispielen wird man
af(1, y) = y + 2 für y ≥ 1
vermuten und kann das auch leicht zeigen (Aufgabe 5.3).
Ackermann, Wilhelm, ∗1806 in Schönebeck (Kreis Altena), †1962 in Lüdenscheid. Deutscher Logiker.
Studienrat an den Gymnasien in Burgsteinfeld und Lüdenscheid. Fand 1929 mit der später nach ihm
benannten Funktion ein Beispiel für eine rekursive, aber nicht primitiv rekursive Funktion. Die in Beispiel
5.5 angebene Funktion ist eine vereinfachte Variante der ursprünglichen Ackermannfunktion.
14
166
KAPITEL 5. ALGORITHMEN I: NAIV UND FORMALISIERT
In Aufgabe 5.3 ist auch eine geschlossene Darstellung für af(2, y) (y ≥ 1) zu finden und
zu beweisen.
Des weiteren ergib sich für y ≥ 1
af(3, y) = 2y+3 − 3
2
.2
.
.
2
af(4, y) =
22
Beweise auch in Aufgabe 5.3.
2
22
2





(5.1)
(y+3)-mal
−3
(5.2)
265536
2
10log 2 · 65536
106553
−3 = 22
Das ergibt z. B. af(4, 4) = 22
−3 = 22
−3 > 22
,
eine Zahl die jede Realität sprengt.
2
5.2
5.2.1
Der formalisierte Algorithmusbegriff
Markov-Algorithmen
Es gibt unterschiedliche Möglichkeiten, den Begriff eines Algorithmus streng formal zu
fassen. Wir wollen die Formalisierung nach Markov 15 genauer untersuchen. Markov-Algorithmen bearbeiten Zeichenreihen (zu Zeichenreihen siehe Abschnitt 3.6). Wir gehen zunächst von Markov-Methoden (Markov method] aus. Eine Markov-Methode ist gegeben
durch
ein Alphabet A, eine Eingabemenge E (E ⊆ A∗ ), eine Markov-Tafel.
Außerdem legt eine für alle Markov-Methoden einheitliche Übergangsvorschrift (transition rule) fest, wie ein Eingabewort mit Hilfe der Markov-Tafel in aufeinanderfolgenden
Schritten zu transformieren ist und wann dieser Vorgang eventuell hält.
Eine Markov-Tafel (Markov table) ist eine Tabelle mit 5 Spalten und mindestens einer
Zeile. In der ersten Spalte sind die Zeilen von 0 an aufwärts numeriert. In der zweiten und
in der vierten Zeile stehen Wörter aus A∗ . In der dritten und in der fünften Zeile stehen
natürliche Zahlen (siehe Beispiel 5.6).
Markov, Andrei Andrejewitsch (jun), geb. 1903. Russischer Mathematiker. Professor in Leningrad
und Moskau. Arbeiten zur Topologie, toplogischen Algebra und zur Theorie dynamischer Systeme. Führte
1951 die „Markov-Algorithmen“ ein [Mark1954]. Die in diesem Buch behandelten Markov-Algorithmen
sind eine modifizierte Form (Kandzia/Langmaack [KandL1973]) der ursprünglichen Markov-Algorithmen.
Markov, A.A. (jun.) ist Sohn von Markov, Andrei Andrejewitsch, ∗1856 Rjasan, †1922 Petrograd (heute
St.Petersburg). Russischer Mathematiker, Professor in St. Petersburg. Arbeiten zur allgemeinen Analysis
und zur Stochastik („Markov-Eigenschaft“, „Markov-Ketten“).
15
5.2. DER FORMALISIERTE ALGORITHMUSBEGRIFF
167
Die Übergangsvorschrift legt fest, wie ein Wort aus der Eingabemenge E verändert wird.
Die Bearbeitung beginnt stets mit Zeile 0 der Tafel. Es wird geprüft, ob die Zeichenreihe
in Spalte 2 der Tafel eine Teilzeichenreihe des Eingabewortes ist. Wenn nein, bleibt das
Eingabewort unverändert und es wird mit der Zeile, deren Nummer in Spalte 3 steht
fortgefahren. Wenn ja, wird das in der Eingabe am weitesten links stehende Vorkommen
der Teilzeichenreihe durch die Zeichenreihe in Spalte 4 der Tafel ersetzt und mit der
veränderten Eingabezeichenreihe in der Zeile, deren Nummer in Spalte 5 steht, auf die
gleiche Art fortgefahren. Die Bearbeitung endet, wenn in Spalte 2 bzw. Spalte 4 eine
Zeilennummer gefunden wird, zu der in der Tafel keine Zeile existiert. Wir wollen als
solche Zeilennumer stets die erste natürliche Zahl nehmen, die größer ist als alle in der Tafel
vorkommenden Zeilennummern. Wenn die Bearbeitung endet, heißt die Zeichenreihe, die
aus der Eingabezeichenreihe entsteht, die Ausgabe (das Ergebnis) der Bearbeitung (siehe
Beispiel 5.6).
Beispiel 5.6 A = {, ◦, M}
E = A∗
0
1
2
3
1
M 2
◦ 4
◦ 4
2
MM 3
◦◦ 3
λ
2
(λ ist die leere
Zeichenreihe.)
Es sollen nun drei Eingabewörter durchgespielt werde. Die Ablauftabellen zeigen unter
„ZR“ die Zeichenreihe vor der Bearbeitung in der entsprechenden Zeile.
Eingabezeichenreihe: M
ZR Zeile
Schritt 0: M
0
Schritt 1: M
2
Schritt 2: M
4
Ausgabezeichenreihe:
Eingabezeichenreihe: ◦ M
Schritt
Schritt
Schritt
Schritt
Schritt
0:
1:
2:
3:
4:
ZR Zeile
◦M
0
◦M
1
◦ MM
3
MM
2
MM
4
Eingabezeichenreihe: ◦ ◦
Ausgabezeichenreihe: MM
M
168
KAPITEL 5. ALGORITHMEN I: NAIV UND FORMALISIERT
Schritt
Schritt
Schritt
Schritt
Schritt
0:
1:
2:
3:
4:
ZR
Zeile
◦◦
0
◦◦
2
◦◦ ◦
3
◦◦
2
◦◦ ◦
3
..
.
Hält nicht; keine Ausgabezeichenreihe.
Ein Markov-Algorithmus ist eine Markov-Methode, die für jedes Wort der
Eingabemenge E hält.
2
Die Tabelle von Beispiel 5.6 bildet mit der Eingabemenge E = { M, ◦ M} und auch mit
der Eingabemenge E = { M}∗ Algorithmen. Mit der Eingabemenge E = { M, ◦ M, ◦ ◦}
bildet sie keinen Algorithmus.
Zum Abschluß noch ein einfaches Beispiel.
Beispiel 5.7 (Addition zweier natürlicher Zahlen) Es wird das Alphabet A = {|, t}
zugrundegelegt. Natürliche Zahlen werden als Folge von Strichen dargestellt
1:|
3 : |||
7 : |||||||
0 : λ (leere Zeichenreihe).
t wird als Trennzeichen benutzt. Die Addition der natürlichen Zahlen m und n wird darge||| · · · | t ||| · · · |
stellt als | {z } | {z }. Als Eingabemenge ergibt sich E = {|}∗ t{|}∗ . Als Markov-Tafel
m
n
kann 0 t 1 λ 1 genommen werden. Ist die Eingabemenge E = {|, t} und will man
eine Fehlerüberprüfung durchführen (Das Zeichen t muß genau einmal vorkommen!), so
kann man das mit der folgenden Tabelle erreichen:
0 t 3
1 t 4
2 | 4
3 λ 2
λ
t
λ
t
1
2
2
2
Man mache sich klar, daß der Algorithmus bei korrekter Eingabe mit dem korrekten
Ergebnis endet. Andernfalls endet er mit der Ausgabe t als Fehleranzeige.
2
5.2. DER FORMALISIERTE ALGORITHMUSBEGRIFF
5.2.2
169
Churchsche These
Ein Problem heißt intuitiv-algorithmisch lösbar, wenn es einen Algorithmus (im naiven
Sinn) gibt, der es löst. Ein Problem heißt formal-algorithmisch lösbar, wenn es einen
formalisierten Algorithmus gibt, der es löst. Jeder formalisierte Algorithmus muß natürlich
auch die in Unterabschnitt 5.1.1 angegeben Eigenschaften haben.
In den ersten Jahrzehnten des 20. Jahrhunderts wurde von Hilbert 16 und anderen versucht,
den Algorithmusbegriff mittels primitiv-rekursiver Funktionen zu formalisieren. Als jedoch
Ackermann 1928 die später nach ihm benannte Funktion (siehe Unterabschnitt 5.1.4) entdeckte, hatte man ein Beispiel für eine berechenbare, aber nicht primitiv-rekursiv berechenbare Funktion. Primitiv-rekursive Funktionen reichen nicht aus, um alle bekannten
Algorithmen zu erfassen.
Danach wurden in kurzer Zeit eine Reihe verschiedener Formalisierungen des Algorithmusbegriffs gefunden.
1. Allgemein-rekursive Funktionen. Herbrand 17 (1931), Gödel 18 (1934), Kleene 19 (1936).
2. µ-rekursive Funktionen. Kleene (1936).
3. λ-definierbare Funktionen (λ-Kalkül). Church 20 (1936).
4. Turing-berechenbare Funktionen, d. h. mit Turingmaschinen berechenbare Funktionen. Turing 21 (1936).
Hilbert, David ∗1862 in Königsberg, †1943 in Gottingen. Deutscher Mathematiker, Professor in Königsberg, ab 1895 in Göttingen. Grundlegende Beiträge zur Algebra, Geometrie, Analysis („Hilbertraum“)
sowie zur mathematischen Physik. Außerdem wichtige Arbeiten zu den Grundlagen der Mathematik und
zum formalisierten Algorithmusbegriff (siehe die Bücher Hilbert/Bernays „Grundlagen der Mathematik“
([HilbB1968], [HilbB1970]) und Hilbert/Ackermann „Grundlagen der theoretischen Logik“ [HilbA1972].
17
Herbrand, Jaques, ∗1908, †1931. Französischer Philosoph. Von ihm stammt der „Herbrandsche Satz“
zur Quantorenlogik, Ausgangspunkt für Resultate zum Entscheidungsproblem, Widerspruchsfreiheitsbeweise sowie zur Verbindung von Modell- und Beweistheorie.
18
Gödel, Kurt, ∗1906, Brünn, †1978 in Princeton, N.J. Österreichischer Mathematiker und Logiker.
Professor am Institute for Advanced Studies in Princeton, New Jersey. Bedeutende Beiträge zur Logik
und mathematischen Grundlagenforschung (Vollständigkeit der Quantorenlogik erster Stufe, Unvollständigkeitssatz zur Axiomatisierung der Mengelehre („Gödelisierung“).,
19
Kleene, Stephen Cole, ∗1909 in Hartford, Connecticut, †1994 in Madison, Wisconsin. Amerikanischer
Mathematiker und Logiker. Wesentliche Beiträge zur Logik und Theorie der Berechenbarkeit. Führte die
regulären Ausdrücke ein.
20
Church, Alonzo, ∗1903 in Washington, DC, †1995 in Hudson, Ohio. Amerikanischer Mathematiker
und Logiker. Professor an der Princeton University, später an der University of California, Los Angeles.
Arbeiten zur Logik und zum Algorithmusbegriff („Churchsche These“).
21
Turing, Alan Mathison, ∗1912 in London, †1954 in Wilmslow (GB). Britischer Mathematiker. Wirkte
in Cambridge und Manchester, 1936 - 1938 Zusammenarbeit mit A. Church, dabei entstand der Entwurf
der Turingmaschine. Arbeiten zur maschinellen Intelligenz („Turing-Experiment“), zur Codeentschlüsselung (Entschlüsselung deutscher militärischer Codes im Krieg) und zur Entwicklung von elektronischen
16
170
KAPITEL 5. ALGORITHMEN I: NAIV UND FORMALISIERT
5. Markov-Algorithmen. Markov (1951).
Diese Formalisierungen und weitere, die später hinzukamen, stellten sich trotz ihrer Unterschiede als gleich leistungsfähig heraus und es ist auch bis jetzt kein naiver Algorithmus
bekannt, der sich nicht auch formalisiert darstellen ließe. Zusammenfassend:
a. Es gibt intuitiv-algorithmisch lösbare Probleme, die nicht durch primitiv-rekursive
Funktionen lösbar sind (Ackermann).
b. Es läßt sich mathematisch beweisen:
◦ Die Algorithmus-Formalisierungen 1 - 5 sind äquivalent.
◦ Primitiv-rekursive Funktionen lösen eine echte Teilmenge der mit 1 - 5 lösbaren
Probleme.
c. Es ist eine Erfahrungstatsache:
Es ist kein intuitiv-algorithmisch lösbares Problem bekannt, das mit 1 - 5 nicht auch
formal-algorithmisch lösbar wäre.
Church hat daraus den Schluß gezogen, daß intuitiv-algorithmisch lösbar und formalalgorithmisch lösbar das gleiche bedeuten.
Churchsche These: Ein Problem ist genau dann intuitiv-algorithmisch lösbar,
wenn es formal-algorithmisch lösbar ist.
5.2.3
Algorithmisch unlösbare Probleme
Im Zusammenhang mit der Formalisierung des Algorithmusbegriffs entdeckte man, daß
es auch sinnvolle Fragenstellungen – „Probleme“ – gibt, für die kein Lösungsalgorithmus
existiert. Nach der Churchschen These reicht es dazu aus, ein Problem anzugeben, das
mit keinem Algorithmus eines bestimmten Formalismus, zum Beispiel mit keiner Turingmaschine, gelöst werden kann.
Wir wollen als Beispiel eines algorithmisch unlösbaren Problems das Halteproblem betrachten, genauer das Halteproblem für Markov-Methoden. Dazu betrachten wir MarkovMethoden über dem Alphabet {0, 1}, für die alle Bitmuster als Eingabe zugelassen sind,
d. h. für die E = {0, 1}∗. Ist M eine solche Methode, so fragen wir, ob M bei Eingabe
des Bitmusters x hält oder nicht hält, d. h kreist. Das Halteproblem besteht darin, einen
Algorithmus anzugeben, der für jede Markov-Methode M und jede Eingabe x feststellt,
ob M angewandt auf x hält oder kreist.
Rechenanlagen. Nach ihm ist der „Turing award“, die bedeutendste wissenschaftliche Auszeichnung in der
Informatik, benannt.
5.2. DER FORMALISIERTE ALGORITHMUSBEGRIFF
171
Es soll gezeigt werden, daß es keinen Algorithmus gibt, der das Halteproblem löst. Nach
der Churchschen These genügt es, zu zeigen. daß es keinen Markov-Algorithmus gibt, der
das Problem löst.
Um das zu sehen, werden zunächst M und x als eine Zeichenreihe über dem Alphabet
{0, 1, s, z, e} dargestellt. Die Markov-Methode ist durch ihre Markov-Tafel gegeben. Die
Werte in den Spalten 1, 3 und 5 der Tafel sind natürliche Zahlen und können als Bitmuster
dargestellt werden, z. B wie auf Seite 168. Die Werte in den Spalten 2 und 4 sind von
vornherein Bitmuster. Das Ende einer Spalte wird durch das Zeichen s, das Ende einer
Zeile durch das Zeichen z gekennzeichnet. Das Zeichen e schließlich trennt die Darstellung
der Markov-Tafel der Methode M von dem Eingabewert x, der wiederum ein Bitmuster
ist. Trifft man nun die Zuordnung
0
1
s
z
e
→
→
→
→
→
000
001
010
011
100 ,
so wird {0, 1, s, z, e}∗ injektiv in {0, 1}∗ abgebildet und für jedes Paar (M, x) erhält man
eineindeutig ein Bitmuster, das wir B(M, x) nennen wollen. B(M, x) kann man einer
f als Eingabe zuführen. M
f wird dann halten oder kreisen.
zweiten Markov-Methode M
Wir wollen annehmen, daß es eine Markov-Methode M0 gibt, die immer hält, wenn sie
auf B(M, x) angewandt wird und als Ergebnis 0 liefert, wenn M angewandt auf x kreist,
und 1 liefert, wenn M angewandt auf x hält. Das soll für jedes M und jedes x gelten.
Um diese Annahme auf einen Widerspruch zu führen, wird aus M0 eine abgewandelte
Markov-Methode S konstruiert.
Es habe die Markov-Tafel von M0 die Zeilen mit den Nummern 0, 1, 2, . . . , n − 1. Die
Angabe der Zeilennummer n in Spalte 2 oder 4 der Tafel bedeutet dann, daß M0 anhält.
Nach Annahme ist das Ergebnis an dieser Stelle 0 oder 1. Um S zu erhalten, wird die
Tafel von M0 um eine Zeile erweitert:
n
1 n+1 1 n
Sie bewirkt, daß S kreist, wenn M0 eine 1 liefert, und daß S (mit dem Ergebnis 0)
hält, wenn M0 eine 0 als Ergebnis hat. Der gesuchte Widerspruch ergibt sich, wenn man
irgendein x nimmt und S auf B(S, x) anwendet:
#
Wenn S angewandt auf B(S, x) hält, wird M0 angewandt auf B(S, x) mit dem
Ergebnis 1 halten und S angewandt auf B(S, x) kreisen. Wenn S angewandt auf
B(S, x) kreist, wird M0 angewandt auf B(S, x) mit dem Ergebnis 0 halten und
...
.. .
S angewandt auf B(S, x) wird mit dem gleichen Ergebnis halten. ............................
"
!
172
KAPITEL 5. ALGORITHMEN I: NAIV UND FORMALISIERT
Der gefundene Widerspruch zeigt, daß es einen Markov-Algorithmus M0 , der das Halteproblem löst, nicht geben kann. In Abbildung 5.5 wird der Widerspruch veranschaulicht.
M angewandt auf x
kreist.
B(M, x)
..
.....................................................................................................................
..
..
.....................................................................................................................
..
0
..
.....................................................................................................................
.
1
M0
M angewandt auf x
hält.
0
B(S, x)
..
.........................................................................................................................................................
..
..
...................................................................................................................
..
M0
0
..
........................................................................................................................................
......
...... ..
.....
.....
.
...
.
.
...
...
...
....
...
..
...
..
...
...
...
...
..
.
.
...
.
.
.
...
...
...
.....
....
.....
.......
................................
1
S
Abbildung 5.5: Das Halteproblem: Markov-Methoden M0 und S
Anmerkung 5.4 Es gibt keinen Markov-Algorithmus, der das Halteproblem löst. Einen
Algorithmus, der ein Bitmuster testet, ob es eine korrekte Darstellung B(M, x) ist, kann
man jedoch leicht konstruieren. Man kann auch eine Markov-Methode U angeben, die jede
andere Markov-Methode nachspielen kann. Wird U auf B(M, x) angewandt, so kreist U,
wenn M angwandt auf x kreist. Hält M angewandt auf x, so hält auch U angewandt
auf B(M, x) und zwar mit dem gleichen Ergebnis. Man spricht von einer universellen
Markov-Methode
5.2.4
Turing-Maschinen und formale Sprachen*
Die Turing-Maschine.
In Abschnitt 2.1, Seite 29, haben wir virtuelle und reale Maschinen eingeführt. Eine Maschine, die keine virtuelle Maschine ist, also eine reale Maschine muß nicht notwendiger-
5.2. DER FORMALISIERTE ALGORITHMUSBEGRIFF
173
weise physikalisch existieren. Sie kann rein mathematisch definiert sein und als gedankliches Konstrukt existieren. Wie andere mathematische Konstrukte auch, wird sie durch
Abbildungen und einen Satz von Axiomen definiert.
Anschaulich besteht ein Turingmaschine aus einem Band, einem Schreib/Lesekopf und
einer Steuereinheit. Siehe Abbildung 5.6. Das Band ist unendlich und in gleichartige Zeichenpositionen eingeteilt. An den Positionen befinden sich Zeichen aus einem endlichen
Zeichevorrat. Ein besonderes Zeichen ist 2 (Leerzeichen), das besagt, daß diese Stelle des
Bandes nicht beschrieben worden ist. Der Schreib/Lesekopf befindet sich an der durch
den Pfeil gekennzeichneten Stelle des Bandes. Eine Turingmaschine arbeitet taktweise. In
jedem Arbeitsschritt wird das Zeichen unter dem Schreiblesekopf gelesen. In Abhängigkeit
vom Zustand der Steuereinheit und vom gelesenen Zeichen wird ein Zeichen geschrieben
oder auch kein Zeichen geschrieben. Danach wird der Kopf um eine Stelle nach rechts
oder um eine Stelle nach links geschoben oder er bleibt an der gleichen Stelle. Außerdem
wird in Abhängigkeit vom alten Zustand und vom gelesenen Zeichen ein neuer Zustand
der Steuereinheit eingestellt. Die Steuereinheit kann insgesamt nur endlich viele Zustände
annehmen.
Steuereinheit
r r r
...
...
...
...
...
...
...
..........................................................................................
...
...
...
..
.........
........
...
2
2
2
0
A
/
♠
b
9
unendliches Band
$
2
2
r r r
Abbildung 5.6: Turingmaschine
Eine Turingmaschine beginnt stets mit einem ausgezeichneten Zustand, dem Startzustand,
und einer ausgezeicheten Anfangsposition des Bandes. Wenn sie einen anderen ausgezeichneten Zustand erreicht, den Endzustand, hält sie. Man sagt, daß sie kreist, wenn sie den
Endzustand nie erreicht. Wenn eine Turingmaschine hält, hat sie i.a. neue Zeichen auf das
Band geschrieben, die Ausgabe, und dabei die anfangs auf dem Band stehendene Zeichen,
die Eingabe, benutzt. Eine Turingmaschine ist also, ähnlich wie die Markov-Methoden,
eine Maschine zur Umwandlung von Zeichenreihen, die manchmal nicht hält.
Es gibt Turingmaschinen mit mehreren Bändern oder auch mit einseitig unendlichen Bän-
174
KAPITEL 5. ALGORITHMEN I: NAIV UND FORMALISIERT
dern (Halbbänder). Sie sind jedoch alle gleichwertig in dem Sinne „Was die einen können,
können die anderen auch“.
Formale Sprachen und Automaten
Wir haben formale Sprachen in Abschnitt 3.6 auf Seite 113 eingeführt. Es sind mathematisch definierte Sprachen. Sie sind in der Informatik von besonderer Wichtigkeit und
ein Überlappungsgebiet zur Mathematik. Die wichtigsten Impulse stammen allerdings aus
dem Gebiet der Linguistik, und zwar von Noam Chomsky22 . In der Informatik werden formale Sprachen nicht (oder kaum) zur Untersuchung natürlicher Sprachen benutzt. Bei der
Definition von Programmiersprachen, also Sprachen zur Steuerung von Maschinen, dienen
sie mehr der Festlegung der Gestalt, also der Syntax, der Sprache als deren Arbeitsweise,
der Semantik. Ist A ein Alphabet und A∗ die Menge der Wörter über A, so ist jede Teilmenge S ⊆ A∗ eine formale Sprache über dem Alphabet A. Die Menge S := A∗ \ S ist
die komplentäre Sprache (Komplement, complementary language) von S. A∗ ist abzählbar
unendlich, d. h. die Menge der formalen Sprachen über A ist überabzählbar. Von Interesse sind dabei aber nur diejenigen, die durch eine endliche Beschreibung charakterisiert
werden können und das sind abzählbar unendlich viele.
Als besonders geeignet für die Charakterisierung haben sich formale Grammatiken (formal
grammar) erwiesen. Eine (formale) Grammatik besteht aus endlich vielen Ersetzungsregeln der Art: Gehört ein Wort zur Sprache, dann darf man darin einige Teilzeichenreihen
durch andere ersetzen und erhält wieder ein Wort der Sprache. Ausgehend vom leeren
Wort erzeugt (generate) so eine Grammatik eine formale Sprache, wobei durchaus verschiedene Grammatiken die gleiche Sprache erzeugen können. Chomsky führte 4 Klassen
von Grammatiken ein: Typ 0, Typ 1, Typ 2 und Typ 3. Diese bilden eine Hierarchie, d. h.
eine Grammatik vom Typ i ist auch vom Typ i−1. Die von diesen Grammatiken erzeugten
formalen Sprachen heißen wie jene, sie haben z. T. aber auch noch eigene Namen.
Es ist nun aber nicht nur wichtig, formale Sprachen zu erzeugen, sondern auch sie zu erkennen (accept). Dafür werden spezielle Maschinen, oft Automaten genannt, verwendet. Als
Eingabe erhält ein Automat die Grammatik und eine Zeichenreihe. Er soll feststellen, ob
die Zeichenreihe zur Sprache gehört, die von der Grammatik erzeugt wird. Turingmaschinen sind Automaten, die Sprachen, die durch Grammatiken erzeugt wurden, akzeptieren.
Das bedeutet: Zu jeder Sprache gibt es eine Turingmaschine, die bei Eingabe der Grammatik und einer Zeichenreihe der Sprache hält und das positive Ergebnis ausgibt. Wenn
die Zeichenreihe nicht zur Sprache gehört, kann es sein, daß die Turingmaschine auch hält
Chomsky, Avram Noam, ∗ 1928 Philadelphia, Pennsylvania. Amerikanischer Linguistiker. Professor
am Massachusetts Institute of Technology. Legte mit den Grammatik-Typen 0 bis 4 die Grundlage der
formalen Linguistik und schuf indirekt damit eine wesentliche Grundlage der theoretischen Informatik.
Seit 1960 ist Chomsky auch als engagierter politischer Publizist tätig. Er ist einer der schärfsten Kritiker
der amerikanischen Außenpolitk. Erhielt 2004 den Carl-von-Ossietzky-Preis der Stadt Oldenburg.
22
5.2. DER FORMALISIERTE ALGORITHMUSBEGRIFF
175
und das negative Ergebnis ausgibt. Für einige Sprachen kann es jedoch sein, daß es keine Turingmaschine gibt, die für jede Zeichenreihe des Komplements hält. In diesem Fall
kann man nicht entscheiden, ob die Zeichenreihe zur Sprache gehört, denn man weiß ja
nicht, ob die Maschine kreist oder mit ihren Berechnungen noch nicht fertig ist. Man hat
nur eine „halbe Erkennung“. Man sagt, alle durch Grammatiken definierte Sprachen sind
rekursiv aufzählbar (recursivily enumerable), aber nicht alle sind entscheidbar (decidable).
Dieser Fall kann für Sprachen vom Typ 1, Typ2 oder Typ 3 nicht eintreten. Es gibt Turingmaschinen, die in jedem Fall halten. Man kann dann zur Erkennung der Sprache und
auch zur Charkterisierung des Typs andere Automaten benutzen, z. B. Kellerautomaten
für Sprachen vom Typ 2 und endliche Automaten für Sprachen vom Typ 3.
Aufgaben
Aufgabe 5.1
Es ist ein C-Programm zur Lösung des Rucksackproblems anzugeben.
Aufgabe 5.2
Man untersuche die Funktionen:
f (n) = if n = 0 then 1
else g(n) + f (n − 1))
g(n) = if (n = 1) ∨ (n = 0) then 0
else g(n − 1) + f (n − 2)
Für welche n sind sie definiert? Geben Sie eine einfache Darstellung der Funktionen an.
Aufgabe 5.3
Diese Aufgabe bezieht sich auf die Ackermannfunktion af, Beispiel 5.5, Seite 165.
a. Man zeige af(1, y) = y + 2 für y ≥ 1.
b. Geben Sie eine geschlossene Darstellung für af(2, y) für y ≥ 1 an.
c. Besweisen Sie die Gleichungen 5.1 und 5.2.
Literatur
Allgemeines zum naiven Algorithmus-Begriff, wenn auch meist in recht knapper Darstellung23 , ist in vielen Lehrbüchern zu finden, z. B. in Knuth [Knut1997], Goos [Goos1997],
Appelrath/Boles/Claus/Wegener [AppeBCW1998], Appelrath/Ludewig [AppeL1995], Goldschlager/Lister [GoldL1990], Cormen/Leiserson/Rivest [CormLR1990], Gumm/Sommer
Man kann sogar der Meinung sein, daß die Frage „Was ist ein Algorithmus?“ eher philosophischer
Art und eine präzise Antwort für die Untersuchung von Algorithmen und Datenstrukturen nicht nötig
ist ([OttmW1996], Seite 1).
23
176
KAPITEL 5. ALGORITHMEN I: NAIV UND FORMALISIERT
[GummS1998], Aho/Hopcroft/Ullman [AhoHU1983], Aho/Ullman [AhoU1995], Kowalk
[Kowa1996], Horowitz/Sahni [HoroS1978], Kandzia/Langmaack [KandL1973], Saake/Sattler
[SaakS2004].
Strukturiertes Programmieren und schrittweise Verfeinerung werden in Kowalk [Kowa1996],
Goldschlager/Lister [GoldL1990] und Goos [Goos1996] behandelt. Eine ausführliche Darstellung strukturierter Programmierung aus Sicht der Softwaretechnik ist im ersten Band
des zweibändigen Werkes von Balzert ([Balz1996] und [Balz1998]) zu finden.
Entwurfstechniken für Algorithmen findet man bei Hromkovič [Hrom2001], bei Weiss
[Weis1995], bei Saake/Sattler [SaakS2004] und bei Ottmann [Ottm1998].
Die Beispiele zu Algrithmen in applikativer Darstellung stammen aus dem Skript von
Ehrich [Ehri1987].
Einiges zum formalisierten Algorithmusbegriff ist in allen Lehrbüchern, die in die Informatik einführen, zu finden, z. B bei Kowalk ([Kowa1996], S.58-66 oder Appelrath (Abschnitt 1.1 in [AppeL1995]). Ausführlichere Darstellungen bringen Goos (Abschnitt 1.6
in [Goos1997] und Kapitel 13 in [Goos1997a]) sowie Blum (Teil I in [Blum1998]. Die
Darstellung in diesem Buch lehnt sich an Kandzia/Langmaack [KandL1973] an.
Kapitel 6
Algorithmen II: Effizienz und
Komplexität
6.1
Laufzeitanalyse von Algorithmen und Programmen
In diesem Abschnitt werden Algorithmen im naiven Sinne betrachtet.
6.1.1
Vorbemerkungen
Wann ist ein Algorithmus gut? Wann ist ein Programm gut? Dafür gibt es unterschiedliche
Kriterien. In erster Linie muß ein Algorithmus (ein Programm) richtig sein, d.h. er muß die
Aufgabe, für die er konstruiert ist, vollständig und fehlerfrei lösen. Es kann schwierig sein,
das nachzuweisen. Von einem Programm wird man zusätzlich verlangen, daß es einfach
zu bedienen, also benutzerfreundlich ist.
Des weiteren soll ein Algorithmus einfach, d.h. leicht verständlich und leicht zu implementieren sein. Realisiert man einen Algorithmus als Programm, so soll dieses sauber
strukturiert, klar gegliedert und gut dokumentiert sein. Um das zu erreichen, haben sich
Dokumentationsregeln als sehr zweckmäßig erwiesen.
Schließlich sollte ein Algorithmus (ein Programm) auch effizient sein. Allgemein bedeutet Effizienz gute, d.h. möglichst geringe Nutzung von Betriebsmitteln. Die wichtigsten
Meßgrößen hierfür sind Prozessorzeit, Speicherbedarf, Netzbelastung und Ein-/Ausgabeaktivitäten. Wie schon in Unterabschnitt 1.1.3, Seite 10, erläutert, wollen wir bei der Festlegung und Untersuchung der Effizienz eines Algorithmus oder Programms die Laufzeit
(Geschwindigkeit) zu Grunde legen und die anderen Faktoren nicht berücksichtigen. Da
die reale Laufzeit natürlich auch von der Leistungsfähigkeit des benutzten Rechensystems
abhängt und man von dieser abstrahieren will, wird als Maß die „Anzahl Operationen“
in Abhängigkeit vom „Umfang der Eingabe“ genommen. Was eine „Operation“ ist und
was „Umfang der Eingabe“ bedeutet, ist nicht einheitlich definiert, sondern hängt vom
177
178
KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT
betrachteten Problem ab. Beim euklidischen Algorithmus (Abschnitt 1.1) war eine Operation (im wesentlichen) eine ganzzahlige Division mit Rest und als Umfang der Eingabe
wurde die Größe der beiden Zahlen, für die der größte gemeinsame Teiler zu bestimmen
war, genommen. Beim Sortieren durch Einfügen (Abschnitt 1.2) bestand eine Operation
im Vergleichen und Vertauschen von zwei Sortierwerten und der Umfang der Eingabe war
die Zahl der zu sortierenden Elemente.
Im folgenden wird zunächst ein Beispiel in Unterabschnitt 6.1.2 vorgestellt und dann
werden in weiteren Unterabschnitten mathematische Methoden zur Analyse der Laufzeit
von Algorithmen behandelt.
6.1.2
Beispiel: Mischsortieren
Wir wollen die Untersuchungen der Laufzeit von Algorithmen mit einem ausführlichen
Beispiel, dem Mischsortieren, beginnen.
Lösungsansatz und Algorithmen
Sortieren wurde in Abschnitt 1.2, Seite 18, eingeführt. Dort wurde auch ein erstes Sortierverfahren, Sortieren durch Einfügen, untersucht. In diesem Unterabschnitt soll ein
weiteres Sortierverfahren, Mischsortieren (merge sort), vorgestellt werden. Das Verfahren
geht auf John von Neumann1 zurück. Die grundlegende Idee des Verfahrens ist einfach
und in Abbildung 6.1 dargestellt. Die zu sortierenden Werte sind in einer Eingabeliste2
gegeben. Diese Liste wird in zwei gleich große Teillisten aufgeteilt. Jede Teilliste wird für
sich sortiert und die sortierten Teillisten werden zu einer sortierten Ergebnisliste zusammengefügt, gemischt 3. Damit ist das Problem des Sortierens zunächst nur von der der
ursprünglichen Liste auf die beiden Teillisten verlagert worden. Um diese zu sortieren,
geht man rekursiv vor: Jede der Teillisten wird ihrerseits in Teillisten zerlegt usw. Zum
Schluß erhält man Listen, die nur aus einem Elementen bestehen und demnach sortiert
sind.
In einem zweiten Schritt werden dann auf jeder Rekursionsstufe zwei sortierte Teillisten zu
einer sortierten Gesamtliste gemischt. Tabelle 6.1 zeigt die Prozedur MS für das Mischsortieren in Pseudocode. Die Prozedur ruft sich selber rekursiv auf und benutzt außerdem
die Prozeduren SPLIT und MERGE. Auf diese wird weiter unten bei der Diskussion
des Programms MISCHSORT (Seite 180) eingegangen. Zuvor soll ein Beispiel behandelt
werden.
Siehe Fußnote Seite 138.
Listen werden in Kapitel 8 ausführlich behandelt.
3
Das englische Verb to merge heißt verschmelzen. In der EDV-Fachsprache hat sich jedoch der Ausdruck mischen eingebürgert.
1
2
6.1. LAUFZEITANALYSE VON ALGORITHMEN UND PROGRAMMEN
Unsortierte Eingabeliste
Sortierte Teilliste 1
Sortierte Teilliste 2
'
&
...........
...........
...........
...........
...........
...........
...........
...........
........... .....
............
..........
................
.................
.
.
.
.
.
.
.
.
.
...
...........
...........
.
.
.
.
.
.
.
.
.
....
...........
...........
...........
...........
.................
.. .
................
..........
...........
...........
.
.
.
.
.
.
.
.
.
.
...
...........
...........
...........
...........
...........
...........
...........
...........
...........
...........
...........
...........
........... ....
..............
........ .
Unsortierte Teilliste 1
Unsortierte Teilliste 2
Sortierte Ergebnisliste
Abbildung 6.1: Grundidee des Mischsortierens
Prozedur MS (liste L) // Merge Sort
Die Werte in der Liste L werden durch Mischen in aufsteigender
Reihenfolge sortiert.
1
2
3
4
5
6
179
if (L besteht aus weniger als 2 Elementen)
return L
else
{ SP LIT (L1, L2, L);
return MERGE(MS(L1), MS(L2));
};
Tabelle 6.1: Prozedur Mischsortieren
$
%
Beispiel 6.1 Es soll die Liste 10,8,1,4,7,7,2,2,7 durch Mischsortieren sortiert werden. Abbildung 6.2 veranschaulicht die Abläufe mit Hilfe eines Binärbaumes4 . Die Knoten
des Baumes entsprechen den rekursiven Aufrufen von MS .
Links neben jedem Knoten ist die Liste angegeben, mit der die Prozedur aufgerufen wird.
Rechts (kursiv) steht die sortierte Liste, die zurückgeliefert wird. Beim Aufteilen einer
Liste werden ihre Elemente abwechselnd auf die beiden Unterlisten verteilt. Dadurch
kann die erste Unterliste höchstens 1 Element mehr als die zweite Unterliste enthalten.
Beim Mischen werden die beiden sortierten Unterlisten zu einer insgesamt sortierten Liste
verschmolzen.
2
4
Zu Binärbäumen siehe Unterabschnitt 9.1.2.
180
KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT
10 8 1 4 7 7 2 2 ......7.....................u.............1............ 2 2 4 7 7 7 8 10
.
.
..
.......
.......
........
.......
.
.
.
.
.
.
.
.
........
.......
........
.......
........
.
.
.
.
.
.
.
.
........
.......
.........
..... .........
.....
.....
.
.
.
.
.....
....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.
.
.
.
.....
...
.
.
.
.....
.
...
.
.....
.
.
.
.....
...
.
.
.
.....
.
....
.
.....
.
.
.
.....
..
.
.
.
.
...
.......
.
... ..
.
.. ....
.. ...
.
.
...
... .....
..
.
.
.
.
.
...
...
.
..
.
.
.
.
.
...
...
.
...
...
...
...
...
..
...
...
...
..
...
...
.
.
.
.
.
...
...
..
..
.
.
.
.
.
...
...
.
..
.
.
.
.
.
...
...
..
.
.
.
...
.
.
.
...
.
..
...
.
.
.
.
.
.
.
....
.
.
... .....
.
...
..
.
...
.
...
...
...
...
...
...
...
..
.
...
.
.
.
...
...
...
.
...
..
.
..
...
10 1 7 2 7 u 1 2 7 7 10
10 7 7 u 7 7 10
10 7 u 7 10
7 u7
1 2 u1 2
1 u1
2 u2
........
.......
........
........
.......
........
........
........
.......
........
........
.......
........
.......
..
.... ......
....
....
.
.
....
...
.
.
....
.
....
....
...
....
.
.
.
....
...
.
.
....
...
.
....
.
..
.
....
.
.
..
....
.
.
.
...
..
.
.
....
.
....
.....
.
.
.. ....
.
... ..
. ..
.
.. ...
.
.
...
.. .....
...
.
.
.
.
...
...
.
..
.
.
.
.
...
...
...
...
...
...
..
..
...
...
..
..
...
.
.
.
.
.
.
...
...
..
...
.
.
.
.
...
...
.
..
.
.
.
...
...
.
..
.
...
.
.
.
...
.
.
...
.
.
.
.
.
.
.
8 4 7 2 u2 4 7 8
8 7 u7 8
8 u8
7 u7
4 2 u2 4
4 u4
2 u2
10 u 10 7 u 7
Abbildung 6.2: Ein Beispiel für Mischsortieren
Im folgenden wird ein Programm für das Mischsortieren angegeben und diskutiert.
Das Programm MISCHSORT
In den Tabellen 6.2, 6.3, 6.4, 6.5 6.6 ist das Programm MISCHSORT wiedergegeben. Teil
I des Programms (Tabelle 6.2) enthält die globalen Deklarationen: Strukturen, Routinen,
Variable. Die Listen des Sortierverfahrens werden als verkettete Listen (siehe 8.3.1) realisiert. Der Satztyp liste der Listenelemente wird bei den Strukturen eingeführt. Die
Sätze dieses Typ bestehen aus einem Feld für den Sortierwert und einem Feld für den
Verweis auf den Nachfolger in der Liste. Als Datentyp wurden für den Sortierwert ganze
Zahlen gewählt. Es hätte aber auch ein anderer Datentyp festgelegt werden können, z. B.
Zeichenreihen.
Teil II (Tabelle 6.3) des Programms ist das Hauptprogramm. Es liest die zu sortierenden
Zahlen ein, wobei die Hilfsroutine leinfügen benutzt wird, und baut damit eine verkettete Liste auf. Mit dieser Liste als Eingabe wird mittels der Routine misort das eigentliche
Sortieren aufgerufen. Die sortierte Liste wird schließlich mit der Routine ausliste ausgegeben.
Tabelle 6.4 zeigt die Routinen misort und split. Die Routine misort ist eine unmittelbare Übertragung des Pseudocodes der Prozedur MS (Tabelle 6.1) in C. Die Routine
split realisiert in C die Prozedur SPLIT von Tabelle 6.1 und verteilt die Elemente der
aufzuteilenden Liste lein beginnend beim ersten Element abwechselnd auf die Unterlisten
la und lb. Wenn ein Element der aufzuteilenden Liste in eine Teilliste eingefügt wurde,
ist die verbleibende Restliste so aufzuteilen, daß ihr erstes Element an die andere Teilliste
6.1. LAUFZEITANALYSE VON ALGORITHMEN UND PROGRAMMEN
181
angehängt wird. Dies nutzt split für einen rekursiven Aufruf aus und ist so sehr einfach
zu programmieren.
Die Prozedur MISCH von Tabelle 6.1 wird durch die Routine misch in C realisiert.
Diese ist in Tabelle 6.5 wiedergegeben. misch verschmilzt („mischt“) die sortierten Listen
ls1 und ls2 zu einer sortierten Gesamtliste. Ist das Anfangselement von ls1 nicht kleiner
als das Anfangselement von ls2, so wird es in die Gesamtliste übertragen. Andernfalls
wird dafür das erste Element von ls2 genommen. Hat man ein Element aus einer Eingabeliste festgelegt, um es in die Ergebnisliste zu übertragen, so kann man die verbleibende
Arbeit folgendermaßen erledigen: Man sortiert die beiden Restlisten durch Mischen und
die sortierte (Rest-)Gesamtliste wird an das zu übertragende Element angehängt. Diese Tatsache erlaubt auch für misch eine sehr einfache rekursive Programmierung. Die
Arbeitsweise von misch wird in Beispiel 6.2, das die einzelnen Rekursionsschritte zeigt,
sichtbar.
Tabelle 6.6 zeigt die Routinen leinfügen und ausliste. Die Routine leinfügen wird
beim Einlesen der zu sortierenden Werte benutzt, um neue Listenelemente (Sätze) anzulegen. Die Routine ausliste gibt die sortierte Ergebnisliste aus.
Beispiel 6.2 Es sollen die Listen ‹1, 2, 6, 7, 9, 9 › und ‹2.4.7.8 › gemischt werden. Die Symbole ‹ und › stellen die Listenbegrenzungen dar.
‹misch(‹1, 2, 6, 7, 9, 9 ›, ‹2, 4, 7, 8 ›) › =
=
=
=
=
=
=
=
=
‹1, misch(‹2, 6, 7, 9, 9 ›, ‹2, 4, 7, 8 ›) ›
‹1, 2, misch(‹6, 7, 9, 9 ›, ‹2, 4, 7, 8 ›) ›
‹1, 2, 2, misch(‹6, 7, 9, 9 ›, ‹4, 7, 8 ›) ›
‹1, 2, 2, 4, misch(‹6, 7, 9, 9 ›, ‹7, 8 ›) ›
‹1, 2, 2, 4, 6, misch(‹7, 9, 9 ›, ‹7, 8 ›) ›
‹1, 2, 2, 4, 6, 7, misch(‹9, 9 ›, ‹7, 8 ›) ›
‹1, 2, 2, 4, 6, 7, 7, misch(‹9, 9 ›, ‹8 ›) ›
‹1, 2, 2, 4, 6, 7, 7, 8, misch(‹9, 9 ›, ‹ ›) ›
‹1, 2, 2, 4, 6, 7, 7, 8, 9, 9 ›
2
182
KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT
'
$
#define
TRUE
1
#define
FALSE 0
typedef
int
boolean;
/****************************************************************/
/*
Strukturen
*/
/****************************************************************/
struct lst
/* Listenenlement
*/
{ int
sortwert;
/* Sortierwert
*/
struct lst
*nachfolger; /* Zeiger auf Nachf.
*/
};
typedef
struct lst
liste;
/****************************************************************/
/*
Routinen
*/
/****************************************************************/
liste
*misort(liste *lein);
liste
*misch(liste *ls1, liste *ls2);
liste
*leinfuegen(void);
void
split(liste **la, liste **lb, liste *lein);
void
ausliste(liste *lanfang);
/****************************************************************/
/*
Globale Variablen und Konstanten
*/
/****************************************************************/
liste
*elist; /* Liste fuer Eingabe und
*/
/* Endergebnis
*/
/****************************************************************/
&
%
/****************************************************************/
/*
Programm MISCHSORT.
*/
/*
*/
/*
Liest natuerliche Zahlen ein. Die erste eingelesene
*/
/*
negative ganze Zahl beendet die Eingabe.
*/
/*
Die eingelesenen natuerlichen Zahlen werden in einer
*/
/*
verketteten Liste abgelegt und diese Liste nach dem
*/
/*
Verfahren "Mischsortieren" sortiert.
*/
/****************************************************************/
#include <stdio.h>
#include <malloc.h>
Tabelle 6.2: Programm MISCHSORT (Teil I)
6.1. LAUFZEITANALYSE VON ALGORITHMEN UND PROGRAMMEN
'
/***************************************************************/
/*
Hauptprogramm
*/
/*
*/
/*
Liest die zu sortierenden Zahlen ein und baut damit
*/
/*
eine verkettete Liste auf.
*/
/*
Die erste negative Zahl beendet die Eingabe.
*/
/***************************************************************/
main()
{
liste
*listaktuell, *li;
/*
Liste mit Eingabewerten anlegen
*/
183
$
elist = NULL;
listaktuell = leinfuegen();
if (listaktuell->sortwert < 0) exit(0);
elist = listaktuell;
li = leinfuegen();
while (li->sortwert >= 0)
{
listaktuell->nachfolger = li;
listaktuell = li;
li = leinfuegen();
};
/*
/*
printf("\nEingabelisteA\n");*/
ausliste(elist);
*/
elist = misort(elist);
/*
}
&
printf("\nSortierte Ausgabeliste\n");*/
ausliste(elist);
Tabelle 6.3: Programm MISCHSORT (Teil II)
%
184
'
KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT
/****************************************************************/
/*
Routine misort
*/
/*
*/
/*
Erhaelt die zu sortierende Liste als Eingabe und liefert
*/
/*
sie sortiert zurueck. Dazu wird die Eingabeliste in zwei
*/
/*
gleich grosse Listen aufgeteilt (split), diese durch
*/
/*
rekursiven Aufruf von misort sortiert, und die sortierten */
/*
Teillisten zu einer sortierten Gesamtliste gemischt
*/
/*
(misch).
*/
/****************************************************************/
liste
*misort(liste *lein)
{
liste
*la, *lb;
$
if (lein == NULL) return lein;
if (lein->nachfolger == NULL) return lein;
split(&la, &lb, lein);
/*printf("split: liste a\n"); ausliste(la);*/
/*printf("split: liste b\n"); ausliste(lb);*/
return misch(misort(la), misort(lb));
}
/****************************************************************/
/*
Routine split
*/
/*
*/
/*
Teilt eine Eingabeliste in zwei gleich grosse Teillisten
*/
/*
auf, indem das naechste Element der Ausgangsliste
*/
/*
abwechselnd in die erste und die zweite Teilliste einge*/
/*
fuegt wird.
*/
/****************************************************************/
void split(liste **la, liste **lb, liste *lein)
{
liste
*listak;
liste
**la1, **lb1;
int i;
if (lein == NULL)
{ *la = NULL;
*lb = NULL;
}
else
{ *la = lein;
split(lb, &(lein->nachfolger), lein->nachfolger);
};
}
&
%
6.1. LAUFZEITANALYSE VON ALGORITHMEN UND PROGRAMMEN
'
/**************************************************************/
/*
Routine misch
*/
/*
*/
/*
Mischt rekursiv zwei vorsortierte Listen zu einer
*/
/*
Ergebnisliste.
*/
/**************************************************************/
liste
*misch(liste *ls1, liste *ls2)
{
if (ls1 == NULL) return ls2;
if (ls2 == NULL) return ls1;
if (ls1->sortwert <= ls2->sortwert)
{
ls1->nachfolger = misch(ls1->nachfolger, ls2);
return ls1;
}
else
{
ls2->nachfolger = misch(ls1, ls2->nachfolger);
return ls2;
};
}
&
Tabelle 6.5: Programm MISCHSORT (Teil IV)
185
$
%
186
'
KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT
$
/**************************************************************/
/*
Routine leinfuegen
*/
/*
*/
/*
Fordert Speicherplatz fuer neues Listenelement, setzt
*/
/*
Werte ein und liefert es zurueck.
*/
/**************************************************************/
liste *leinfuegen(void)
{
int
i;
liste
*la;
la = (liste *)malloc(sizeof(liste));
if (la == NULL)
{
printf("MISCHSORT Einlesen Daten: Nicht genug Speicher vorhanden\n");
exit(0);
};
scanf("%d", &(la->sortwert));
la->nachfolger = NULL;
return la;
}
/**************************************************************/
/*
Routine ausliste
*/
/*
*/
/*
Gibt die verkette Liste, deren Anfangselement uebergeben */
/*
wurde, aus.
*/
/**************************************************************/
void
ausliste(liste *lanfang)
{
liste
*laus;
laus = lanfang;
while (laus != NULL)
{
printf("%3d\n", laus->sortwert);
laus = laus->nachfolger;
}
printf("\n\n");
}
&
Tabelle 6.6: Programm MISCHSORT (Teil V)
%
6.1. LAUFZEITANALYSE VON ALGORITHMEN UND PROGRAMMEN
187
Effizienz des Mischsortierens
Wir wollen den Aufwand („Anzahl Operationen“) des Mischsortierens für n zu sortierenden
Werte ermitteln. Dazu wird zunächst der Spezialfall n = 2k (k ≥ 1) betrachtet. In diesem
Fall führt das Aufteilen der Listen stets zu zwei gleichlangen Unterlisten. Der Aufwand
für das Sortieren einer Liste von n Elementen ergibt sich aus den folgenden einzelnen
Schritten:
• Aufteilen der Liste in zwei Unterlisten der Länge n2 .
• Mischsortieren jede der beiden Unterlisten.
• Mischen der sortierten Unterlisten.
Aufteilen: Die Routine split (Tabelle 6.4) wird für jedes Element der Liste einmal
aufgerufen und dann noch ein letztes mal mit einem NULL-Adreßwert. Bei den ersten
n Aufrufen ist jeweils ein konstanter Aufwand für das Einketten in die Teilliste und den
nächsten Aufruf von split zu leisten. Dieser Aufwand soll a (a > 0) genannt werden. Ist
b (b > 0) der Aufwand für den letzten Aufruf, so ist der Aufwand für das Aufteilen
a · n + b.
Dieser Aufwand hängt nur von der Anzahl n der Sortierwerte ab, aber nicht von deren
Anordnung. Er ist also im besten wie auch im schlechtesten Fall der gleiche.
Mischsortieren der Unterlisten: Im besten Fall:
n
und im schlechtesten Fall: 2 · WOC( ) .
2
n
2 · BEC( )
2
Mischen: Der beste Fall ergibt sich, wenn in der Routine misch (Tabelle 6.5) zunächst
hintereinander alle Elemente der sortierten Unterliste ls1 in das Ergebnis übertragen und
dann die sortierte Unterliste ls2 an das Ergebnis angehängt wird. Das ergibt n2 Aufrufe
von misch mit einem Vergleich von zwei Sortierwerten und einen Aufruf ohne einen solchen
Vergleich. Der Aufwand ist demnach
c·
n
+d
2
mit positiven Werten c und d. Im schlechtesten Fall werden die Elemente der Listen ls1
und ls2 abwechselnd in das Ergebnis übertragen. Das ergibt n − 1 Aufrufe von misch mit
einem Vergleich von zwei Sortierwerten und einen Aufruf ohne einen solchen Vergleich,
also den Aufwand
c · (n − 1) + d .
188
KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT
Lösen der Rekursionsgleichungen: Aus den obigen Betrachtungen ergibt sich
n
n
BEC(n) = a · n + b + 2 · BEC( ) + c · + d. Stellt man für den schlechtesten Fall die
2
2
entsprechende Gleichung auf und faßt man zusammen, so erhält man
c
n
BEC(n) = (b + d) + (a + ) · n + 2 · BEC( )
2
2
n
WOC(n) = (b − c + d) + (a + c) · n + 2 · WOC( ) .
2
(6.1)
(6.2)
Zum Lösen dieser Rekursionsgleichungen werden Anfangswerte gebraucht. Die Routine misort (Tabelle 6.4) führt für eine einelementige Liste nur zwei Tests durch, also
BEC(1) = WOC(1) = f mit f > 0.
Wir wollen nun die Rekursionsgleichungen 6.1 und 6.2 lösen. Sie sind vom Typ T (1) = A
n
und T (n) = B + C · n + 2 · T ( ) für n = 2, 4, 8, · · ·. Dazu werden wir für einige kleine
2
Werte von n die Gleichung ausrechnen, dann eine Lösung raten und schließlich beweisen,
daß die geratene Lösung richtig ist.
T (1)
T (2)
T (4)
T (8)
T (16)
....
=
=
=
=
=
A
B + C · 2 + 2 · T (1)
B + C · 4 + 2 · T (2)
B + C · 8 + 2 · T (4)
B + C · 16 + 2 · T (8)
....
=
=
=
=
2·A+1·B+2·1·C
4·A+3·B+4·2·C
8·A+7·B+8·3·C
16 · A + 15 · B + 16 · 4 · C
....
Das legt folgende Vermutung nahe:
T (n) = A · n + B · (n − 1) + C · n · log2 (n) für n = 2k und k ≥ 0
Beweis der Richtigkeit durch vollständige Induktion:
n = 1: T(1) = A + 0 + 0 = A.
Sei T (n) = A · n + B · (n − 1) + C · n · log2 (n) für n = 2k : Dann ist
T (2k+1 ) = T (2n) =
=
=
=
=
B + C · (2n) + 2 · T (n)
B + C · (2n) + 2 · (A · n + B · (n − 1) + C · n · log2 (n))
A · (2n) + B · (2n − 1) + C · (2n) + C · (2n) · log2 (n)
A · (2n) + B · (2n − 1) + C · (2n) · (1 + log2 (n))
A · (2n) + B · (2n − 1) + C · (2n) · log2 (2n)
Das ergibt für die Gleichungen 6.1 und 6.2 die geschlossenen Formen
c
BEC(n) = f · n + (b + d) · (n − 1) + (a + ) · n · log2 (n)
2
WOC(n) = f · n + (b − c + d) · (n − 1) + (a + c) · n · log2 (n)
(6.3)
(6.4)
6.1. LAUFZEITANALYSE VON ALGORITHMEN UND PROGRAMMEN
189
Ist sn eine Folge von n Sortierwerten und n eine Zweierpotenz, so gilt für den Sortieraufwand R(sn ) mittels Mischsortieren
BEC(n) ≤ R(sn ) ≤ WOC(n)
(6.5)
Es erscheint plausibel, daß die gefundenen Abschätzungen auch zur Aufwandsbestimmung von Sortierfolgen, deren Länge keine Zweierpotenz ist, benutzt werden können. In
Unterabschnitt 6.1.5, Seite 198, wird das näher ausgeführt.
6.1.3
Boden und Decke
In diesem Unterabschnitt und den folgenden werden einige Definitionen und Ergebnisse
vorgestellt, die eigentlich zur Mathematik gehören. Wegen ihrer Bedeutung für die Analyse
von Algorithmen und in anderen Teilen der Informatik sollen sie jedoch hier behandelt
werden.
Definition 6.1 Es seien x, y ∈ R.
1. Der Boden (floor) von x ist die größte ganze Zahl, die kleiner oder gleich x ist.
Symbolisch: bxc
2. Die Decke (ceiling) von x ist die kleinste ganze Zahl, die größer oder gleich x ist.
Symbolisch: dxe
In der Schreibweise [x] wurde die Funktion Boden schon lange in der Mathematik benutzt.
Die Schreibweisen bxc und dxe wurden 1962 von Iverson5 eingeführt und haben sich
inzwischen durchgesetzt.
Wichtige Eigenschaften der Funktionen Boden und Decke, die sich unmittelbar aus der
Definition ergeben, sind:
x − 1 < bxc ≤ x ≤ dxe < x + 1
b−xc = −dxe
bxc = dxe genau dann, wenn x ∈ Z
bxc + 1 = dxe genau dann, wenn x ∈
/Z
(6.6)
(6.7)
(6.8)
(6.9)
Außerdem ist für x ≥ 0 ist bxc der ganzzahlige Anteil und x − bxc der nichtganzzahlige
Anteil von x.
Iverson, Kenneth E., ∗1920, Camrose, Alberta, Canada, †2004 Toronto, Ontario, Canada. Kanadischer Mathematiker und Informatiker. Entwickelte bei IBM die Programmiersprache APL [Iver1962]
sowie einen zugehörigen Interpreter. APL benutzt eine mathematische Notation und ist kompakt, flexible
und sehr leistungsfähig.
5
190
KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT
Als Beispiel für die Anwendung von Boden und Decke wollen wir noch einmal den Aufwand für das Mischsortieren berechnen, und zwar dieses Mal für Sortierfolgen beliebiger
Länge. Allerdings soll die Fragestellung auch etwas vereinfacht werden: Wir wollen für die
Aufwandsbestimmung nur die Anzahl der Vergleichsoperationen heranziehen. Es bezeichne WOC(n) die maximale Zahl von Vergleichsoperationen, die sich bei der Mischsortierung
einer Eingabefolge der Länge n (n beliebig!) ergeben kann. Es gilt
WOC(1) = 0
WOC(n) = WOC(d n2 e + WOC(b n2 c + n − 1
für n ≥ 1
(6.10)
In der Tat: Es ist stets n = d n2 e + b n2 c und diese Gleichung entspricht dem Aufteilen einer
Liste der Länge n. Beim Mischen der beiden Teillisten werden höchstens n − 1 Vergleiche
ausgeführt.
Wir wollen für WOC eine geschlossene Darstellung finden. Dazu betrachten wir als Beispiel Abbildung 6.3. Sie zeigt eine Eingabe von 9 Sortierwerten nach der Aufteilung in
a1 a2 a3 a4 a5 a6 a7 a8 a......9................u....................
.....
........
.......
........
.
.
.
.
.
.
.
.
........
.......
.......
........
.......
.
.
.
.
.
.
..
........
.......
........
1 3 5 7 9...........................
.....
...
.....
.....
.....
.....
.....
.....
.....
.....
.
.
.
.....
.
...
.....
.
.
.
.
.....
...
.
.
.
.....
.
...
.
.....
.
.
.
.....
...
.
.
.
.
.....
...
.
.
.....
.
.
...
......
.
.
1 5 9 ...... ......
3 7 .............
.
.
.
.
...
.. .....
..
.
.
.
.
.
.
.
...
...
..
...
...
...
...
...
...
...
...
...
..
...
...
...
..
...
...
.
.
.
.
.
...
...
.
..
.
.
.
.
.
...
...
..
..
.
.
.
...
.
.
.
...
..
.
.
.
...
.
.
.
...
.
..
..
.
.
.
.
.
1 9 ............
5 .
3
7 .
.
.
...
..
.
...
..
...
..
...
...
...
...
...
..
.
...
..
.
...
.
.
...
.
..
...
.
.
..
...
a a a a a u
a a a u
a a u
a1 u
a a u
a u
a u
a u
........
........
........
.......
........
........
........
.......
........
........
.......
........
........
......
2 4 6 8.....................
...
...
.
.
....
....
....
....
....
....
....
.
.
.
....
..
.
.
.
....
..
.
....
.
.
....
...
.
.
....
..
.
.
.
....
..
.
.
....
.
......
.
2 6 .... ....
4 8 .............
. ...
. ..
.
.
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
..
..
...
.
.
...
...
..
..
.
.
...
.
.
...
.
.
.
.
...
...
..
...
.
...
.
.
...
.
..
.
...
.
.
.
...
...
...
.
...
a a a a u
a a u
a2 u
a6 u
a a u
a4 u
a8 u
a9 u
Abbildung 6.3: Anzahl Vergleichsoperationen beim Mischsortieren
Listen. Vergleiche auch Abbildung 6.2. Diese Listen sollen gemischt werden. Der Anschaulichkeit halber wollen wir uns vorstellen, daß bei jedem Vergleich einer der Sortierwerte
einen Chip bezahlen muß. Wir wollen anfangs jeden Sortierwert mit so vielen Chips ausstatten, daß genau die beim Mischen notwendigen Vergleiche bezahlt werden können.
Jeder Sortierwert, d. h. jedes Blatt des Baumes, durchläuft maximal so viele Vergleiche,
wie es Knoten im Baum über ihm gibt. So viele Chips soll jedes Blatt zunächst auch
6.1. LAUFZEITANALYSE VON ALGORITHMEN UND PROGRAMMEN
191
bekommen. Wir statten also anfangs a1 und a9 mit je 4 Chips und alle übrigen Blätter
mit je 3 Chips aus.
Das Mischen und Bezahlen soll nun folgendermaßen ablaufen:
1. Mischstufe (von unten): a1 und a9 werden gemischt, a1 bezahlt. a9 durchläuft den
Mischknoten, ohne zu bezahlen. Das heißt a9 bekommt anfangs nur 3 Chips.
2. Mischstufe: a1 , a9 , a3 , a2 , a4 bezahlen je einen Chip. a5 , a7 , a6 und a8 bezahlen nichts
und bekommen jeweils eine um 1 verringerte Anfangsausstattung.
3. Mischstufe: a1 , a3 , a5 , a9 und a2 , a4 , a6 bezahlen je einen Chip, während für a7 und a8 die
Anfangsausstattung um 1 vermindert wird.
4. Mischstufe: Schließlich bezahlen beim letzten Mischvorgang a1 , a2 , a3 , a4 , a5 , a6 , a7 , a9
und für a8 wird die Anfangsausstattung noch einmal um 1 herabgesetzt und beträgt dann
0.
Das ergibt in aufsteigender Reihenfolge der endgültigen Anfangsausstattung
Position
Sortierwert
Anfangsausstattung
1
a8
0
2
a7
1
3
a6
2
4
a5
2
5
a4
3
6
a2
3
7
a3
3
8
a9
3
9
a1
4
Mit ein wenig Übung erkennt man in der Gesamtanfangsausstattung die Formel
0+1+2+2+3+3+3+3+4 =
9
X
k=1
dlog2 ke
Das legt die Vermutung nahe, daß die Funktion
f (n) :=
n
X
k=1
dlog2 ke
(6.11)
für n ≥ 1 die Lösung der Rekursionbedingungen 6.10 ist. Das ist in der Tat so. Wir wollen
nun
1. beweisen, daß die Funktion den Rekursionsbedingungen 6.10 genügt, und
2. eine geschlossene Darstellung für die Summe herleiten.
1. Die durch Gleichung 6.11 gegebene Funktion löst die Rekursionsgleichungen 6.10.
Es ist f (1) = 0.
Es sein n > 1. Wir fassen in der Summe die ungeraden und die geraden Indizes getrennt zusammen und beachten, daß es stets d n2 e Summanden mit ungeradem Index
und b n2 c Summanden mit geradem Index gibt. Außerdem benutzen wir die Beziehungen dlog2 (2j)e = dlog2 j + 1e = dlog2 je + 1 sowie dlog2 (2j − 1)e = dlog2 je für j = 1 und
dlog2 (2j − 1)e = dlog2 (2j)e = dlog2 je + 1 für j ≥ 2. Das ergibt
192
KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT
n
X
f (n) =
k=1
dlog2 ke
n
n
d2e
X
=
j=1
dlog2 (2j − 1)e +
b2c
X
j=1
dlog2 (2j)e
 n
 n


b2c
d2e
lnm
jnk
X
X
− 1 +  dlog2 je +
=  dlog2 je +
2
2
j=1
j=1
= f (d n2 e) + f (b n2 c) + n − 1
2. Eine geschlossen Darstellung für die Summe 6.11.
Um eine geschlossene Darstellung für die Summe 6.11 zu bekommen, verlängern wir sie,
so daß die Anzahl Summanden eine Zweierpotenz wird. Mit m := dlog2 ne ergibt sich
m
m
f (n) + (2 − n) · m =
2
X
k=1
dlog2 ke
= 0 + 1 + 2 + 2+ 3 + 3 + 3 + 3 + ...+ m
= 20 · 1 + 21 · 2 + 22 · 3 + . . . + 2m−1 · m
m
X
j · 2j−1
=
j=1
m
= 2 · (m − 1) + 1
Das ergibt schließlich
f (n) = m · n − 2m + 1 = n · dlog2 ne − 2dlog2 ne + 1
(6.12)
Für n, die Zweierpotenzen sind, vereinfacht sich das zu f (n) = n · log2 n − n + 1.
Anmerkung 6.1 Es sei hier noch ein nützlicher Kunstgriff angegeben, mit dem Summen
m
P
wie
j · 2j−1 ausgerechnet werden können. Für x ∈ R, 0 < x, x 6= 1 gilt
j=1
m
X
j=1
j−1
j·x
m
m
X
d X j
d j
x
x i=
=
dx
dx j=1
j=1
!
m
m
d X j
d X j
=
x −1 =
x
dx j=0
dx j=0
6.1. LAUFZEITANALYSE VON ALGORITHMEN UND PROGRAMMEN
193
d xm+1 − 1
=
dx
x−1
(m + 1) · xm · (x − 1) − (xm+1 − 1)
=
(x − 1)2
Für x = 2 ergibt sich 2m · (m − 1) + 1.
6.1.4
2
Modulo
Ist man nur an der Laufzeitanalyse von Algorithmen und Programmen interessiert, kann
dieser Unterabschnitt übersprungen werden.
Beim euklidischen Algorithmus, Abschnitt 1.1, Seite 3, haben wir die ganzzahlige Division
m = n·q+r mit Rest benutzt. Dabei sind n und m positive ganze Zahlen, q ist der Quotient
und r mit 0 ≤ r < n ist der Rest. Beachtet man, daß q = b m
c und bezeichnet man den
n
Rest mit mod, so hat man in einer anderen Schreibweise
jmk
m=n·
+ m mod n.
n
Das zeigt, wie man eine allgemeine Restfunktion zu beliebigen reellen Zahlen x und y
einführen kann.
Definition 6.2 Die modulo-Operation von x bezüglich y ist definiert durch


x


für y 6= 0
x−y
x mod y :=
y


x für y = 0
(6.13)
Die Bezeichnungen sind uneinheitlich. Die Operation heißt auch „Modulus“. Gelegentlich
wird nicht nur die Operation, sondern auch das Ergebnis so bezeichnet. Manchmal wird
auch y Modulus genannt. Im Englischen wird oft von der „mod operation“ gesprochen. Sowohl im Deutschen als auch im Englischen wird häufig die Ablativform „modulo“ benutzt.
Auch wir wollen die Operation so nennen.
Einige Beispiele sind angebracht.
7 mod 4
7 mod (−4)
(−7) mod 4
(−7) mod (−4)
=
=
=
=
7 − 4 · b7/4c = 7 − 4 · 1
7 − (−4) · b7/(−4)c = 7 − (−4) · (−2)
(−7) − 4 · b(−7)/4c = (−7) − 4 · (−2)
(−7) − (−4) · b(−7)/(−4)c = (−7) − (−4) · 1
Ein weiteres Beispiel
18, 5 mod π = 18, 5 − π · b18, 5/πc = 18, 5 − π · 5 = 2, 792
=
=
=
=
3
−1
1
−3
194
KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT
Die modulo-Funktion erfüllt eine wichtige Eigenschaft der Restbildung: Für y 6= 0 gilt
0 ≤ x/y − bx/yc < 1. Für positives y bedeutet das 0 ≤ x − y · bx/yc < y und für negatives
y ergibt sich 0 ≥ x − y · bx/yc > y. Zusammenfassend
0 ≤ |x mod y| < |y|.
(6.14)
Vergleiche hierzu auch die Ausführungen zur ganzzahligen Division in C auf Seite 47. C
läßt die durch mod gegebene Quotient- und Restbildung zu (6.13 und 6.14). Die für den
Rechner donar gewählte Realisierung entspricht jedoch nicht der hier gegebenen Definition
c definiert, so gilt z. B.
von mod. Wird nämlich der ganzzahlige Quotient durch m/n := b m
n
|(−7/4) · 4| = | − 2 · 4| = 8 > 7.
Als zweistellige Verknüpfung zwischen reellen Zahlen ist die modulo-Operation weder kommutativ noch assoziativ. Sie ist jedoch distributiv: (x) mod (cy) = c(x mod y). (Warum?)
Wichtiger ist jedoch die Arithmetik mit Restklassen. Dazu zunächst die folgende Definition. Es sei z ∈ R, z 6= 0.
Definition 6.3 Wir sagen, daß die reellen Zahlen x und y gleich sind modulo z und
schreiben
x = y (mod z)
wenn x mod z = y mod z.
Es unmittelbar klar, daß Gleichheit modulo z eine Äquivalenzrelation in den reellen Zahlen
ist. Wenn zwei Zahlen zur gleichen Klasse gehören, also gleich sind modulo z, ist ihre
Differenz ein ganzes Vielfaches von z. Aus der Gleichung bn + αc = n + bαc für n ∈ Z und
α ∈ R folgt, daß zwei reelle Zahlen auch nur dann gleich sind modulo z, wenn ihre Differenz
ein ganzes Vielfaches von z ist. Es ist zweckmäßig, das als Hilfssatz zu formulieren.
Hilfssatz 6.1 Es gilt x = y mod z genau dann, wenn x − y ein ganzes Vielfaches von z
ist.
Im weiteren wollen wir uns auf die für die Informatik wichtigsten Anwendungen der
modulo-Operation beschränken. Dazu schränken wir die Operation auf die ganzen Zahlen
Z ein und bilden die Restklassen nach einer natürlichen Zahl q > 1. In der Informatik
ist q meistens eine Zweierpotenz. Für jede ganze Zahl l ∈ Z gibt es dann eine eindeutige
Zerlegung
l
l=
+ l mod q
q
mit (l mod q) ∈ {0, 1, . . . , q −1}. Die Operationen Addition und Multiplikation der ganzen
Zahlen lassen sich auf die Restklassen modulo q übertragen, denn es gilt
Proposition 6.1 Es seien l1 , l2 , l10 , l20 ∈ Z. Gilt l1 = l10 (mod q) und l2 = l20 (mod q), so gilt
auch l1 + l2 = l10 + l20 (mod q) und l1 · l2 = l10 · l20 (mod q).
6.1. LAUFZEITANALYSE VON ALGORITHMEN UND PROGRAMMEN
195
Beweis: Wir benutzen Hilfssatz 6.1. Es ist l1 − l10 = n1 · q und l2 − l20 = n2 · q, also
(l1 + l2 ) − (l10 + l20 ) = (n1 − n2 ) · q. Entsprechend finden wir
l1 · l2 − l10 · l20 = l1 · l2 − (l1 − n1 · q) · (l2 − n2 · q) = (l1 · n2 + l2 · n1 − n1 · n2 · q) · q.
2
Proposition 6.1 bildet die Grundlage für die Operationen mit Zahlen vom Type unsigned
in C. Siehe hierzu Seite 48. Dazu wird ein Bereich darstellbarer natürlicher Zahlen festgelegt: B := {0, 1, 2, . . . , b − 1}. Meistens ist b = 2k − 1, wobei k eine geeignete Wortlänge
ist, z. B. k = 32 (siehe Abschnitt 3.2, Seite 96, und Unterabschnitt 4.1.2, Seite 122). Die
ganzen Zahlen werden in Restklassen modulo b eingeteilt und zu jeder Restklasse gibt es
genau einen darstellbaren Vertreter aus B. Proposition 6.1 garantiert, daß ein mit den
Operationen +, −, · aufgebauter Ausdruck von ganzen Zahlen stets auf den gleichen Wert
aus B führt, unabhängig davon, ob nur das Endresultat oder auch Zwischenergebnisse modulo b genommen werden.
Ist l ∈ B so ist auch b − l ∈ B. Es gilt l + b − l = b und b = 0 (mod b). Da l − l = 0, wird
die durch −l bestimmte Restklasse in B durch b − l repräsentiert.
In C sind für Werte vom Typ unsigned auch die Operationen / und % erklärt. Sind
l1 , l2 ∈ B, so führen l1 / l2 und l1 % l2 nicht aus B heraus und ergeben die erwarteten
Resultate. Gibt es bei der Berechnung eines Ausdrucks Zwischenresultate, die außerhalb
von B liegen, so kann es sein, daß die Operationen / und % unerwartete Ergebnisse liefern.
Für ein Beispiel nehmen wir B = {0, 1, 2, . . . , 216 −1 = 65535}, also Werte vom Typ short
unsigned int. Ausgehend von den Werten l1 = 17 und l2 = 6, wollen wir −l1 / − l2
und −l1 % − l2 bilden. In Tabelle 6.7 sind zwei C-Programme und die Ergebnisse ihrer
Abläufe dargestellt. Was wird man erwarten? Es werden −l1 und −l2 gebildet und die
entsprechenden Vertreter aus B bestimmt. Da sind 216 −17 = 654519 und 216 −6 = 65530.
Beide Programme in Tabelle 6.7 liefern auch diese Werte.
Es ist offensichtlich 65519 / 65530 = 0 und 65519 % 65530 = 65519, aber nur das zweite
Programm in Tabelle 6.7 liefert diese Werte. Aus dem ersten Programm, das sich vom
zweiten nur durch die fehlenden Zwischenspeicherungen von −l1 und −l2 unterscheidet,
ergibt sich unerwarteterweise −l1 / − l2 = 2 und −l1 % − l2 = 65531. Wie läßt sich das
erklären? Es werden Quotient und Rest zunächst im Bereich der ganzen Zahlen gebildet
und man erhält (nach den Regeln des hier benutzten C-Compilers) −17 / − 6 = 2 und
−17 % − 6 = −5. Erst die Ausgabe mit dem Format hu ordnet diesen Werten die entsprechenden Vertreter aus B zu und macht aus −5 den Wert 65531. Proposition 6.1 läßt
sich also nicht auf die Operationen / und % erweitern.
6.1.5
Abschätzungen und Größenordnungen
Definition 6.4 Es seien f : N → R und g : N → R mit f (n) > 0 und g(n) > 0.
1. Groß-O-Notation: f heißt von der Ordnung O von g, in Formeln
f (n) = O(g(n)),
196
'
KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT
#include <stdio.h>
main()
{
unsigned short int l1, l2;
l1 = 17;
l2 = 6;
printf("-l1 = %hu -l2 = %hu\n", -l1, -l2);
printf("-l1/-l2 = %hu\n", -l1/-l2);
printf("-l1\%-l2 = %hu\n", -l1%-l2);
}
$
-l1 = 65519 -l2 = 65530
-l1/-l2 = 2
-l1%-2 = 65531
*****************************************************************
#include <stdio.h>
main()
{
unsigned short int l1, l2;
unsigned short int i1, i2;
l1 = 17;
l2 = 6;
i1 = -l1;
i2 = -l2;
printf("i1 = %hu i2 = %hu\n", i1, i2);
printf("i1/i2 = %hu\n", i1/i2);
printf("i1%%i2 = %hu\n", i1%i2);
}
i1 = 65519 i2 = 65530
i1/i2 = 0
i1%i2 = 65519
&
%
Tabelle 6.7: Unterschiedliche Ergebnisse für die Operationen / und %.
wenn es c > 0 und n0 ∈ N gibt mit f (n) ≤ c · g(n) für alle n ≥ n0 .
2. Groß-Omega-Notation: f heißt von der Ordnung Ω von g, in Formeln
f (n) = Ω(g(n)),
6.1. LAUFZEITANALYSE VON ALGORITHMEN UND PROGRAMMEN
197
wenn es c > 0 und n0 ∈ N gibt mit f (n) ≥ c · g(n) für alle n ≥ n0 .
3. Groß-Theta-Notation: f heißt von der Ordnung Θ von g, in Formeln
f (n) = Θ(g(n)),
wenn es c1 > 0, c2 > 0 und n0 ∈ N gibt mit c1 · g(n) ≤ f (n) ≤ c2 · g(n) für alle n ≥ n0 .
Aus der Definition folgt:
•
•
f (n) = O(g(n)) genau dann, wenn g(n) = Ω(f (n)) und
f (n) = Θ(g(n)) genau dann, wenn f (n) = O(g(n)) und g(n) = O(f (n)).
Regeln
Wir vollen nur Regeln für die Groß-O-Notation angeben. Für die anderen Notationen
ergeben sie sich entsprechend.
1. Falls f (n) ≤ g(n) für alle n ≥ m, so gilt f (n) = O(g(n)) mit c = 1.
Beispiel 1: log2 (n − 1) = O(log2 (n)).
2. Konstante Faktoren haben keine Bedeutung. Falls f (n) = O(g(n)), dann auch
af (n) = O(g(n)) für alle a > 0.
Beispiel 2: Ist f (n) = O(g(n)), so gilt auch a1 f (n) + a2 f (n) + · · · + al f (n) =
(a1 + a2 + · · · + al )f (n) = O(g(n)).
Beispiel 3: Logarithmen können zu beliebiger Basis genommen werden.
Für a > 0, b > 0 gilt: Aus f (n) = O(loga (n)) folgt f (n) = O(logb (n)).
log (n)
In der Tat: Aus n = aloga (n) = blogb (a) a
= blogb (a)·loga (n) folgt logb (n) =
logb (a) · loga (n). D. h. f (n) ≤ c · loga (n) = logbc(n) · logb (n) für alle n ≥ n0 .
Beispiel 4: Ist a > 0, so ist f (n) = O(ln(n)) genau dann, wenn f (n) = O(ln(n+a)).
Gilt f (n) = O(ln(n + a)), so folgt f (n) = O(ln(n)) nach Regel 1.
Es gelte f (n) = O(ln(n + a)), d. h. für alle n ≥ n0
f (n) ≤ c · ln(n + a) = c · ln( n+a
· n) = c · (ln(1 + na ) + ln(n)).
n
Für n ≥ max(a, 2) gilt a ≤ n · (n − 1), also n + a ≤ n2 , d. h. 1 + na ≤ n. Das
bedeutet f (n) ≤ c · (ln(n) + ln(n)) = 2 · c ln(n) für alle n ≥ max(a, 2, n0 ) , also
f (n) = O(ln(n)).
3. Sei f1 (n) = O(g(n), f2(n) = O(g(n), . . . , fl (n) = O(g(n)). Dann ist auch f1 (n) +
f2 (n) + · · · + fl (n) = O(g(n)).
198
KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT
Anwendung auf das Mischsortieren
Wir wollen an das Ergebnis 6.5, Seite 189, anknüpfen. Es sei sn eine Sortierfolge beliebiger
Länge n. Wir setzen k := dlog2 (n)e und nehmen n ≥ 2, also k ≥ 1 an. Mit wachsender
Länge der Sortierfolgen kann der Aufwand für den schlechtesten Fall nicht geringer werden:
W OC(n) ≤ W OC(2k ). Aus Gleichung 6.4, Seite 188, folgt für den Aufwand R(sn )
R(sn ) ≤ f · 2k + (b − c + d) · (2k − 1) + (a + c) · 2k · k
≤ C1 · 2k + C2 · 2k · k
≤ C3 · 2k · k
mit C3 > 0.6 Aus
2k = 2log2 (n) · 2k−log2 (n) ≤ 2 · 2log2 (n) = 2 · n
und
k = log2 (n) + (k − log2 (n)) ≤ log2 (n) + 1 ≤ log2 (n) + log2 (n) = 2 · log2 (n)
ergibt sich schließlich
R(sn ) ≤ 4 · C3 · n · log2 (n) = O(n · ln(n))
Für eine Abschätzung des besten Falls setzen wir k 0 := blog2 (n)c und nehmen n ≥ 4 an.
0
Es gilt BEC(2k ) ≤ BEC(n) und aus Gleichung 6.3, Seite 188, ergibt sich
c
0
0
0
R(sn ) ≥ f · 2k + (b + d) · (2k − 1) + (a + ) · 2k · k 0
2
k0
0
≥ C4 · 2 · k
Berücksichtigt man −1 < blog2 (n)c−log2 (n) ≤ 0, so findet man mit einfachen Rechnungen
ähnlich den obigen
R(sn ) ≥
Zusammenfassend
1
· C4 · n · log2 (n) = Ω(n · ln(n))
4
R(sn ) = Θ(n · ln(n))
Es ist nicht schwer, auch aus Gleichung 6.12 Gleichung 6.15 herzuleiten.
6
Man mache sich klar, daß das auch gilt, falls b − c + d < 0 sein sollte.
(6.15)
6.2. DIE KOMPLEXITÄT VON PROBLEMEN
6.1.6
199
Rekurrenzen und erzeugende Funktionen*
In den vorangehenden Abschnitten 6.1.2 und 6.1.5 haben wir Rekurrenzen zur Bestimmung der Effizienz des Mischsortierens benutzt. Wir haben Lösungen geraten und dann
bewiesen, daß es Lösungen sind. In der Informatik und mehr noch in der Kombinatorik treten sehr viele Rekurrenzen auf. Es ist wichtig, Methoden zu ihrer Lösung zu haben. Einen Überblick über die wichtigsten elementaren Methoden gibt Kapitel 4 in Cormen/Leiserson/Rivest [CormLR1990].
Eine wichtige Technik zu Lösung sind erzeugende Funktionen (generating functions). Das
sind unendliche Folgen von reellen (oder komplexen) Zahlen α0 , α1 , . . . , αn , . . ., die man
jedoch üblicherweise als eine formale unendliche Reihe schreibt
∞
X
αn xn
n=0
Für manche Zahlenfolgen konvergiert diese Reihe innerhalb eines positiven Konvergenzradius und stellt dann dort eine holomorphe Funktion dar. Aber auch wenn sie nicht
konrvergieren, kann man mit diesen Reihen formal rechnen und erhält wichtige Ergebnisse, z. B. Lösungen von Rekurrenzen. Man spricht auch von formalen Potenzreihen.
Erzeugende Funktionen werden in der Mathematik und ihren Anwendungen an vielen
Stellen verwandt. Zu weiteren Einzelheiten sehe man in der angegebenen Literatur nach.
6.2
6.2.1
Die Komplexität von Problemen
Überblick
Normalerweise wird man einen Algorithmus komplex nennen, wenn er eine komplexe
Struktur hat. Er besteht dann aus einer größeren Zahl von Einzelaktionen, die auf vielfältige Weise zusammenwirken. Im Zusammenhang mit der Komplexität von Problemen,
wollen wir unter der Komplexität eines Algorithmus seine Effizienz verstehen. Wir sprechen von der WOC-Komplexität (BEC-Komplexität, AVC-Komplexität) des Algorithmus.
Bisher haben wir die Komplexität des euklidischen Algorithmus (Unterabschnitt 1.1.3),
des Sortierens durch Einfügen (Unterabschnitt 1.2.3) und des Mischsortierens (Gleichung
6.15) untersucht. Von diesen Algorithmen hat Sortieren durch Einfügen die schlechteste,
d. h. größte WOC-Komplexität: Die Anzahl der Vergleichsoperationen wächst quadratisch
mit der Anzahl n zu sortierender Elemente. In den folgenden Kapiteln werden wir eine
Reihe weiterer Algorithmen kennenlernen, deren WOC-Komplexität nicht stärker als nk
mit kleinem k wächst.
Es gibt jedoch eine Reihe von interessanten und auch praktisch wichtigen Problemen, für
k
die man nur Lösungsalgorithmen kennt, deren WOC-Komplexität wie 2n , k ≥ 1 fest,
200
KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT
wächst. Das bedeutet, daß die Ausführungszeiten mit wachsendem Umfang der Eingabe sehr rasch das praktisch Machbare überschreiten. Man spricht von schwierigen (difficult) oder auch unbehandelbaren (intractable) Problemen. Für einige dieser Probleme
hat man nachgewiesen, daß jeder Lösungsgalgorithmus exponentielle WOC-Komplexität
haben muß. Für sehr viele, darunter die interessantesten, hat man das bisher nicht nachweisen können. Trotz intensiver Bemühungen hat man aber bisher auch keinen Lösungsalgorithmus mit polynomieller Komplexität gefunden.
Was man jedoch gefunden hat, sind eindrucksvolle theoretische Resultate. In einer sehr
groben Darstellung besagen sie folgendes: Für sehr viele der schwierigen Probleme kennt
man „nichtdeterministische Lösungsverfahren“ mit polynomieller Komplexität. Dabei wird
hier bewußt offengelassen, was das ist. Die Klasse der mit diesen Verfahren lösbaren Probleme wird mit N P bezeichnet. Die Klasse P der mit deterministischen Lösungsverfahren,
also Algorithmen, polynomieller Komplexität lösbaren Probleme ist in N P enthalten. In
N P hat man nun Probleme gefunden, die in gewissem Sinne als die schwierigsten der
Klasse anzusehen sind, die N P-vollständigen Probleme (N P-complete problem): Gibt es
für ein N P-vollständiges Problem einen Lösungsalgorithmus polynomieller Komplexität,
dann gibt es einen solchen für jedes Problem aus N P und es gilt P = N P. Inzwischen
kennt man eine lange Liste N P-vollständiger Probleme aus vielen unterschiedlichen Gebieten. Für keines ist jemals ein Lösungsalgorithmus polynomieller Komplexität gefunden
worden. Aus diesem Grund wird allgemein angenommen, daß es auch keinen gibt und daß
N P eine echte Obermenge7 von P ist. Eine praktische Konsequenz daraus ist: Weiß man,
daß ein Problem, mit dem man sich beschäftigt, N P-vollständig ist, so sollte man keinen
Lösungsalgorithmus polynomieller Komplexität suchen. Einige Anmerkungen dazu, was
man denn sonst tun sollte, sind in in Abschnitt 6.3 zu finden.
In den restlichen Unterabschnitten dieses Abschnitts soll eingehender auf die angesprochen Fragen eingegangen werden. Dazu wird zunächst festgelegt, was unter polynomieller
und exponentieller Komplexität eines Algorithmus zu verstehen ist. Anschließend wird
ausführlich ein Beispiel eines schwierigen Problems behandelt. Dieses Beispiel wird dann
herangezogen, um die Komplexität von Problemen und die Fragestellung P = N P? exemplarisch zu behandeln.
6.2.2
Komplexität von Algorithmen
Im folgenden wollen wir uns auf die WOC-Komplexität beschränken. Wenn wir von der
Komplexität eines Algorithmus sprechen, ist immer WOC-Komplexität gemeint. WOCn (A)
ist die Anzahl Schritte, die Anzahl Operationen, die der Algorithmus A im schlechtesten
Fall durchführt, wenn er eine Eingabe vom Umfang n bearbeitet.
Inzwischen (August 2010) sieht es so aus, als wenn Vinay Deolalikar von den HP Labs, Palo Alto,
California, einen Beweis für N P 6= P gefunden hätte.
7
6.2. DIE KOMPLEXITÄT VON PROBLEMEN
201
Definition 6.5 I. Algorithmus A hat polynomielle Komplexität (polynomial complexity), wenn es eine natürliche Zahl k gibt, so daß WOCn (A) = O(nk ).
II. Algorithmus A hat exponentielle Komplexität (exponential complexity), wenn
1. für jede natürliche Zahl k gilt WOCn (A) = Ω(nk ) und
k0
2. es gibt eine natürliche Zahl k 0 , so daß WOCn (A) = O(2n ).
Ist A ein Algorithmus mit polynomieller Komplexität, so gibt es c > 0, k ≥ 0 und n0 , so
daß jede Eingabe sn vom Umfang n ≥ n0 nach spätestens
c · nk Schritten
bearbeitet ist:
√
n
k
4
2
3
RA (sn ) ≤ c · n . Beispiele sind n − 3n + 7, n · ln(n), n . Auch m ist mit wachsendem
n(n−1)···(n−m+1)
m
n
≤ nm! .
n und festem m von polynomieller Komplexität, denn es gilt m
=
m!
Hat ein Algorithmus A exponentielle Komplexität, so wächst WOCn (A) schneller als jedes
Polynom in n. Andererseits gibt es k 0 ≥ 0 und c0 > 0, so daß für alle hinreichend großen
k0
n gilt RA (sn ) ≤ c0 · 2n . Einige Beispiele für exponentielle Komplexität:
n
2
1. 10n , denn nk < 10n = 2log2 (10) = 2log2 (10)·n < 2n .
k0
k0
2. nn , denn nk < nn = 2log2 (n)
nk0
3. nln(n) , denn nk < nln(n) = 2log2 (n)
4. n!, denn nk < n! < nn .
k0
= 2log2 (n)·n < 2n
ln(n)
k0 +1
2
< 2n .
Die Abschätzungen gelten stets für alle hinreichend großen n. Auch Beispiel 3 ist nach
der obigen Definition von exponentieller Komplexität, obgleich nln(n) langsamer wächst
α
als 2n für jedes α > 0. Zu den Beispielen siehe auch Aufgaben 6.1 und 6.2.
Die Stirlingsche Formel (Stirling’s approximation)
√
2πn
n n
e
≤ n! ≤
√
2πn
n n+(1/12n)
e
(6.16)
ist neben vielen anderen Dingen auch bei der Bestimmung der Komplexität
von Algo
rithmen von Nutzen. Mit ihrer Hilfe kann man z. B. zeigen, daß 2n
von
exponentieller
n
Komplexität ist. Siehe hierzu Aufgabe 6.3.
6.2.3
Beispiel: Hamiltonkreise
Problembeschreibung
Für dieses Beispiel müssen wir im Vorgriff auf Kapitel 12 einiges aus der Graphentheorie
einführen. Ein schlichter Graph (simple graph) besteht aus einer nichtleeren, endlichen
202
KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT
Menge V von Knoten (vertex) und einer endlichen Menge E von Kanten (edge). Kanten
verbinden jeweils zwei Knoten. Bei schlichten Graphen darf eine Kante keinen Knoten
mit sich selbst verbinden (keine Schlingen) und zwei verschiedene Knoten dürfen durch
höchstens eine Kante verbunden sein (keine Mehrfachkanten). Zwei Knoten, die durch
eine Kante verbunden sind, heißen Nachbarn (neighbor). Abbildung 6.4 zeigt ein Beispiel
......................
......................
......................
.......
.......
.......
....
....
....
...
...
....
....
...
....
...
...
...
...
...
...
...
...
...
....
.....
.....
...
...
...
...
..
...
.
...
...
...
..
.
.
.
.
..
.
.
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
........
.
...
....
..... .....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
......
.
.......
..
... ...
... .....
.....
................................ ...........
........
.................... ..........
.... .....................
.......
...
....
........
.....
..... ....
...
......
........
....
...
......
.......
.....
..
........
....
.....
...
...
.......
....
........
..
.
.
.
.
.
.
....
.
.
.
.
.
.
.
.
...
.
.
.
.
.
......
.......
..
....
...
...
.....
....... ...............
...
...
....
....
.....
..
...
.....
....
...
...
.....
............
...
....
..
..
......
.... ............... ............
...
.....
..
..
......
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
......
...
......
.
.... .....
....
.......
.....
... ...............
...
...
....
....
.......
.....
....
...
...
.....
.......
.....
.......
....
..
..
...... ........
......... .....
.......
....
..
..
........
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.......
....
...
.....
.......
....... ....
....
...
...
........
.... ..........
.........
....
........
...
...
.....
......
....................
.........................
........
....
.....
.......
...
..
......
.....
.....
....
......
........
....
..... ...
.. .................
....
.
...
.
...................
.
....
.
.
.
.
.
.
.
.
.
.
....
..... ..
...
....
...
...
..
.....
....
...
...
...
...
..
.....
.
...
...
..
.
... ......
....
...
....
..
................................................................................................................................................................................................................................
.. ..........
.....
...
..
.
..
..
.....
...
....
...
.....
.
.
.
.
.
.
...
.
.
.
.
.
.....
.
....
...
.
...
..
.
.
.
.
.
....
.
.
......
.
.
.
.
.
.
.
.
.
.
.
.
......
...
....
.
..
......
...... ................................
..
....
..........................
...
....
........................
...
......
...
..
.....
...
.... ...
.
.
.
...
.
...
.
.
.......
.
...
...
.
...
.
...
.
...
.
.
.....
...
...
...
..
..
.....
... ........
...
..
.. ......
..
.
.
.
.
.
.
.
.
..
....
.......
...
..
.
..
.
.
.
.
.
.
.
.
.
.
.
.
.
...
....
.
.. ...
..... .....
.
.
.
.
.
.
.
..
.
.
.
.
.
.
.
.
...
.
.
.
.
.
.
....
.
... ........
......
...
... ....
.
.
.
.
.
.
.
.
.
..
.
.
...
.
.
.
.
.
.
.
.
.
.
.
.
.................
....
...
..
........
.....
...
....
...
.....
...
...
........
.....
...
.... .......
...
... ..........
.....
...
...
..........
.. ..........
...
.....
..
...
....
.....
..
...........
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
...
.
.....
......... ...
..... ......
...
...
...
........
.....
....
....
...
...
...
.........
...
.....
....
....
...
... ................
..
.....
...
.....
....
...
..
.....
...
..
.
.
.
....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.... ......... ...
.
...
....
.....
. ...
...
...
...
...
....
.....
.........
...
...
...
...
.....
.....
........ .......
...
..
....
...
.....
.........
....
...
....
..
...
.....
........
.
.
.
...
.
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
....
.
.
..
...
.........
.... .................... .... .........
....
...
........
....... .....
...........
........................... ........
.........
........
.....
.....
........
...
...
...
..................
..
.
.
....
.
.
...
..
..
..
..
.....
....
..
..
...
...
...
...
...
...
.
..
.
...
.
.
.
....
.
.
.....
.
.
.
.
.
.
.
.
.
.
.
.
.
........................
........................
K1
K3
K2
K5
K4
K6
K7
K8
Abbildung 6.4: Schlichter Graph mit Hamiltonkreis
eines schlichten Graphen.
Ein Weg (path) ist eine Knotenfolge v0 , v1 , v2 , . . . , vn mit n ≥ 1, bei der je zwei aufeinanderfolgende Knoten durch eine Kante verbunden sind. Ein Weg heißt offen (open), wenn
v1 6= vn , andernfalls geschlossen (closed). Ein geschlossener Weg, bei dem nur Anfangsund Endknoten gleich und alle anderen paarweise verschieden sind und bei dem keine
Kante mehrfach auftritt, heißt Kreis (circuit). Ein Kreis, der alle Knoten eines schlichten
Graphen enthält, heißt Hamiltonkreis (Hamiltonian circuit) des Graphen. Ein Graph mit
einem Hamiltonktreis wird hamiltonsch (hamiltonian) genannt.
Besitzt der Graph in Abbildung 6.4 einen Hamiltonkreis? Nach einigem Probieren wird
man vielleicht einen finden, z. B. K8, K3, K7, K1, K5, K4, K2, K6, K8. Gibt es weitere?
Ja, denn man kann den Durchlauf in jedem der Knoten beginnen und dann noch eine
von zwei Durchlaufsrichtungen wählen. Ob es Hamiltonkreise gibt, die davon „wesentlich“
verschieden sind, ist nicht so einfach zu entscheiden.
6.2. DIE KOMPLEXITÄT VON PROBLEMEN
203
Algorithmen zum Finden von Hamiltonkreisen
Gesucht sind Algorithmen, die feststellen, ob ein gegebener schlichter Graph Hamiltonkreise hat oder nicht. Falls ja, sollen sie alle diese Kreise liefern. Daß es solche Algorithmen
gibt, ist leicht zu sehen. Wir bilden alle Permutationen der Knotenmenge und prüfen für
jede Permutation nach, ob zwei in der Permutation benachbarte Knoten auch im Graphen Nachbarn sind und ob es vom letzten Knoten der Permutation eine Kante zum
ersten Knoten gibt. Es ist klar, daß das ein sehr aufwendiges Verfahren ist, denn es sind
n! Permutationen zu bilden und zu überprüfen, wenn n die Anzahl Knoten des Graphen
ist. Schon für den Graphen von Abbildung 6.4, der ja nur „Spielformat“ hat, ergeben sich
40320 Fälle. Für Graphen mit 100 Knoten sind es mehr als 10100 Fälle. Für sie ist das
Verfahren praktisch undurchführbar.
Man kann nun auf die Idee kommen, daß der Aufwand des Algorithmus nur deswegen so
groß ist, weil man ungeschickt und naiv an die Lösung herangegangen ist. Von den Permutationen, die man bildet, sind sehr viele überflüssig und brauchen nicht zu berücksichtigt
werden:
1. Wir brauchen nur Permutationen zu betrachten, die mit einem festen Knoten beginnen, denn zwischen Hamiltonkreisen, die sich auseinander nur durch unterschiedliche
Wahl der Anfangsknoten ergeben, wollen wir keinen Unterschied machen.
2. Alle Permutationen, in denen zwei nicht benachbarte Knoten aufeinander folgen,
kann man streichen. Das bedeutet, daß außer dem letzten Knoten jeder Knoten einer
zulässigen Permutation einen Nachbarn haben muß, der vorher in der Permutation
nicht aufgetreten ist. Der letzte Knoten muß den ersten Knoten als Nachbarn haben.
Mit diesen Feststellungen kann man ähnlich wie beim Mischsortieren (Tabelle 6.1, Seite 179)
einen einfachen Algorithmus angeben, der alle Hamiltonkreise findet, die von einem festen
Knoten ausgehen. Er ist in Tabelle 6.8 zu sehen. Er ist in C-ähnlichem Pseudocode for'
Prozedur HMLT (VERTEX ∗v, int length) // Suche nach Hamiltonkreisen
1
2
3
4
5
6
7
&
{ v→aktiv = TRUE;
for (alle Nachbarn v 0 von v)
{ if (v 0 == start && length == n − 1) Hamiltonkreis gefunden;
if (v 0 →aktiv == FALSE) HMLT (v 0 , length + 1);
}
v→aktiv = FALSE;
}
Tabelle 6.8: Prozedur HMLT
$
%
204
KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT
muliert, benutzt ein in jedem Knotensatz vorhandenes Feld aktiv, das anfangs mit FALSE
vorbesetzt ist. Es wird rekursiv aufgerufen und arbeitet mit Rücksetzen (Zeile 6). In allen
Rekursionstufen ist der Anfangsknoten start der Suche und die Knotenanzahl n des Graphen bekannt. Die Suche startet mit dem Aufruf HMLT (start, 0) und baut beginnend
in start alle Wege auf, in denen keine Knoten mehrfach auftreten. Ein solcher Weg kann
höchstens die Länge n haben. Hat er diese Länge, so wird in Zeile 3 geprüft, ob man vom
Endknoten direkt zum Anfangsknoten kommen kann. Dann und nur dann, wenn das der
Fall ist, hat man einen Hamiltonkreis gefunden.
Anders als beim Mischsortieren ist es hier jedoch nicht ganz einfach, aus dem Algorithmus in Pseudocode ein ablauffähiges C-Programm zu gewinnen. Dort reichte es aus,
einen Satztyp liste einzuführen. Hier müßten Satztypen für Knoten eingeführt und Datenstrukturen und Funktionen für Nachbarschaften angelegt werden. Das führt zu weit
von unserem Thema fort. Deshalb sei nur auf Abschnitt E.5 im Anhang verwiesen, wo
ein vollständiges C-Programm angegeben ist. Wendet man es auf den Graphen von Abbildung 6.4 mit dem Startknoten K8 an, so findet man die in Tabelle 6.9 aufgeführten
maximalen Wege, d. h. Wege, die nicht mehr verlängerbar sind, ohne daß Knoten mehrfach
auftreten. Es sind insgesamt 26 Wege, darunter 4 Wege, die zu Hamiltonkreisen führen.
Wesentlich verschieden sind die Hamiltonkreise K8, K1, K5, K4, K2, K6, K7, K3, K8 und
K8, K6, K2, K4, K5, K1, K7, K3, K8. Die beiden anderen ergeben sich durch Umkehrung
der Durchlaufrichtung.
Ändert man den Anfangsknoten, so findet man bis auf eine Kreisverschiebung die gleichen Hamiltonkreise. Die übrigen Wege sind jedoch nicht identisch. Als Beispiel wenden
wir den Algorithmus HMLT auf den gleichen Graphen, aber mit Startknoten K1 an.
Tabelle 6.10 zeigt das Ergebnis. Es gibt jetzt 30 Wege, darunter auch einige, die alle Knoten des Graphen enthalten, sich aber nicht zu Hamiltonkreisen ergänzen lassen.
K1, K5, K8, K3, K7, K6, K2, K4 ist ein solcher Weg. Ein offener Weg der Länge n, der
alle Knoten eines Graphen enthält, sich aber nicht zu einem Hamiltonkreis ergänzen läßt,
heißt Hamiltonzug (Hamiltonian trail) .
Exponentieller Zeitbedarf von HMLT
In diesem Unterabschnitt soll die Komplexität von HMLT für den schlechtestem Fall
(WOC) untersucht werden. Die Verbesserung des Algorithmus HMLT gegenüber der
direkten Bildung aller Knotenpermutationen ist sehr deutlich. Leider ist sie nicht gut
genug, um auch größere Graphen in akzeptabler Zeit zu bearbeiten. Um das zu sehen,
betrachten wir den Graphen in Abbildung 6.5.
Er hat n = 2k +2 Knoten. Startet man HT ML mit dem Anfangsknoten u0 , so werden unter anderem alle Wege der Länge k von u0 nach uk oder vk gefunden. Alle diese Wege sind
n
von der Form u0 , x1 , x2 , · · · , xk−1 , xk mit xκ = uκ . oder xκ = vκ . Es gibt also 2k = 2 2 −1
√ n
2 . Auch das ist exponentiWege dieser Art. Die Zahl der Wege wächst also mit 21 ·
elles Wachstum und führt schon bei Graphen mittlerer Größe zu extremen Rechenzeiten.
6.2. DIE KOMPLEXITÄT VON PROBLEMEN
K8
K8
K8
K8
K8
K8
K8
K8
K8
K8
K8
K8
K8
K8
K8
K8
K8
K8
K8
K8
K8
K8
K8
K8
K8
K8
K1
K1
K1
K1
K1
K1
K1
K6
K6
K6
K6
K6
K6
K5
K5
K5
K5
K5
K5
K5
K5
K5
K3
K3
K3
K3
K5
K5
K5
K7
K7
K7
K7
K2
K2
K2
K7
K7
K7
K1
K1
K1
K1
K4
K4
K4
K4
K4
K7
K7
K7
K7
K4
K4
K4
K6
K2
K2
K3
K4
K7
K7
K1
K2
K3
K7
K7
K7
K7
K2
K2
K2
K2
K2
K1
K6
K2
K2
K2
K2
K2
K2
K4
K6
K7
K7
K6
K4
K5
K6
K3
K7 K3 K8
K5
K5
K1
K3
K5
K4
K1 K7 K3 K8
K5 K4
K6
K2
K2
K3
K7
K7
K7
K6
K6
K5
K2
K4
K6
K2 K4
K4
K6
205
Hamilton !!
Hamilton !!
K4 K2
K5 K1
K1
K6
K3
K7
K7
K4
K4
K5
K1
K3
K2 K6 K8
K5 K1 K8
K1
Hamilton !!
Hamilton !!
Tabelle 6.9: Bestimmung von Hamiltonkreisen (Anfangsknoten K8)
Nehmen wir als Beispiel einen Graphen vom angegeben Typ mit 100 Knoten. Dann sind
√ 100
mindestens 21
= 5.6294 · 1014 maximale Wege zu finden. Unter der Annahme, daß
2
der benutzte Rechner 100 Nanosekunden braucht, um einen Weg zu finden8 , benötigt der
Rechner 5.6294 · 1016 Nanosekunden. Das sind mehr als 1.78 Jahre. Bei 105 Knoten sind
es mehr als 10, bei 115 Knoten mehr als 323 und bei 150 Knoten mehr als eine halbe
Million Jahre.
Im übrigen sind die vorangehenden Abschätzungen viel zu optimistisch. Das liegt zum
einen daran, daß keineswegs alle zu testenden Wege berücksichtigt wurden und daß na8
Auch noch heute (2006) eine unrealistisch kurze Zeit.
206
K1
K1
K1
K1
K1
K1
K1
K1
K1
K1
K1
K1
K1
K1
K1
K1
K1
K1
K1
K1
K1
K1
K1
K1
K1
K1
K1
K1
K1
K1
KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT
K5
K5
K5
K5
K5
K5
K5
K5
K5
K5
K5
K8
K8
K8
K8
K8
K8
K8
K8
K8
K8
K7
K7
K7
K7
K7
K7
K7
K7
K7
K8
K8
K8
K8
K8
K8
K8
K4
K4
K4
K4
K6
K6
K6
K6
K5
K5
K5
K3
K3
K3
K6
K6
K6
K2
K2
K2
K2
K3
K3
K6
K6
K6
K6
K3
K3
K3
K2
K2
K2
K2
K2
K2
K7
K7
K4
K4
K4
K7
K7
K7
K2
K8
K8
K4
K4
K6
K6
K8
K8
K2
K2
K7
K7
K7
K7
K7
K7
K7
K6
K6
K4
K7
K2
K3
K2
K2
K2
K6
K2
K2
K4
K5
K3
K5
K5
K8
K8
K6
K5
K4
K7
K2
K3
K6
K2
K2
K6
K3
K8
K7
K5
K3
K4
K3
K4
K2
K4
K6
K8
K8
K3
K3
K4
K3
K6
K7 K1
K8 K1
Hamilton !!
Hamilton !!
K5
K7
K7
K6
K2
K4
K6
K5
K4
K6
K3
K7 K3
K4 K5 K1
K5
K8
K8
K5
K3
K2
K4
K6
K3
K4
Hamilton !!
K8 K3
K2
K4 K5 K1
K2 K6
Hamilton !!
Tabelle 6.10: Bestimmung von Hamiltonkreisen (Anfangsknoten K1)
türlich in einem „normalen“ Notebook9 und einem C-Programm mehr als 100 Nanosekunden zur Bestimmung eines maximalen Weges benötigt werden. In Tabelle 6.11 sind die
durch das C-Programm von HMLT ermittelten Wegeanzahlen und Zeiten für Graphen
des gegebenen Typs mit 6 bis 34 Knoten zu sehen. Man erkennt sehr gut das exponentielle Wachstum der Anzahl der untersuchten Wege, der Anzahl Hamiltonkreise und der
9
IMB ThinkPad T43
6.2. DIE KOMPLEXITÄT VON PROBLEMEN
..........
.....................
.........................
....... ...........
....
......
.....
...
....
...
...
...
...
..
...
..
...
...
...
...
...
..
.
...
.
...
1 ................................................................
2 .......................
0 .................................................................
...
.
.
....
....
.....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.......
.
... ........................ .......
... ........................ .......
.................. .......
....
...
....
....
...
....
....
...
....
....
....
....
.
...
....
...
...
...
....
...
....
.
.
.
.
...
.
.
.
....
...
.
.
.
.
.
.
.
...
.
.
.
... ...
.... ...
.....
.
.
...
.
......
....
...
... .......
.... ......
...
...
....
....
...
....
....
....
...
...
...
...
....
...
.
.
.
.
....
.
.
...
....
...
...
....
....
....
...
.......
...
....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.... ........................ .......
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.......
......
.......
.....
...
....
.
.
.
.
.
.
.
.
.
.
...
.
.
...
...
..
..
..
...
..
...
...
.
..
..
.
.
....
1 ................................................................. 2 .......................
0 .................................................................
...
.
.
.
.
.
....
...
.
.....
.
.
.
..
.
.
.
.......
.
.
.
.
.
.
.
.
.
.
.
.
....................
...................
...............
u
v
u
v
u
····
v
207
.........................
.........................
....
.....
...
...
...
...
...
...
...
..
...
...
.
.
....................
........................................................
.
k
... k−1 ....
...
..
.
.
.
...
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.. ........................
.. ........................ ......
.
.
.
.
.
.
....
..
...
...
....
....
....
....
....
...
....
...
....
...
....
.... ......
...
.......
...
......
.
.
...
.
... ......
.
...
.
...
...
.
.
...
.
....
....
..
.
.
.
.
...
....
....
..
.
.
.
.
....
..
...
.
.
.
.
.
.
.... ........................ ......
.
.
.
.
.
.
...... ...............
.....
.
...
...
.
.
.
...
...
.....
....
..
..
........................................................
.....................
...
..
k
... k−1 ....
...
..
.
.
...
.
.
....
.
.
......
..
.
.
.
.
.
.
.
.
.
.
....................
..................
u
u
v
v
Abbildung 6.5: Die Anzahl der Wege wächst exponentiell
Rechenzeiten. Diese sind bis ca. 18 Knoten mit der relativ ungenauen Zeitmessung des
Systems10 nicht meßbar, liegen dann bis 24 Knoten im Sekunden- und ab 26 Knoten im
Minutenbereich, um schließlich ab 34 Knoten in den Stundenbereich überzugehen. Danach
war die Geduld des Autors erschöpft. Die Rechnungen wurden jeweils mit den Anfangsknoten U0 und U1 ausgeführt. Für U1 ist die Zahl der Wege geringer und damit auch die
Rechenzeiten. Das Wachstumsverhalten ist das gleiche.
Zum Vergleich ist in der letzten Spalte der Tabelle die Anzahl der Wege angegeben, die
sich aus der obigen Abschätzung ergibt. Es fällt auf, daß die Anzahl der Hamiltonkreise
durch 2n/2 gegeben ist und daß die Anzahl Wege, die durch die Abschätzung gegeben
ist, halb so groß ist. Das ist auf die spezielle Struktur der Graphen zurückzuführen und
leicht zu erklären. Jeder von u0 ausgehende Hamiltonkreis, der als zweiten Knoten u1 oder
v1 hat, enthält genau einen der oben angegebenen Teilwege und ist auf der Reststrecke
eindeutig bestimmt. Die Umkehrung der Durchlaufrichtung, d.h. v0 wird zweiter Knoten,
verdoppelt die Anzahl der Hamiltonkreise.
Die Daten der Tabelle ergeben überdies, daß die mittlere Zeit zur Bestimmung eines
maximalen Weges ungefähr bei 30 µs liegt und mit der maximal möglichen Länge, also
der Knotenzahl, langsam wächst.
Bei genauerem Hinsehen stellt man fest, daß Algorithmus HMLT zwei Fragestellungen
beantwortet:
1. Er liefert eine Liste aller Hamiltonkreise eines Graphen, die von einem festen Anfangsknoten ausgehen.
Wie wir gesehen haben, gibt es Graphenklassen, in denen die Anzahl der Hamiltonkreise exponentiell mit der Anzahl der Knoten wächst. Daher muß jeder Algorithmus, der alle Hamiltonkreise findet, exponentielle WOC-Komplexität haben. Für
jeden Hamiltonkreis muß nämlich mindestens eine „Operation“ ausgeführt werden,
z. B. Eintrag in eine Liste.
2. Gibt es einen Hamiltonkreis in dem Graphen?
10
C-Funktionen time und difftime. Siehe [LoweP1995].
208
KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT
Anz. Knoten
6
8
10
12
14
16
18
20
22
24
26
28
30
32
34
Startknoten
U0
U1
U0
U1
U0
U1
U0
U1
U0
U1
U0
U1
U0
U1
U0
U1
U0
U1
U0
U1
U0
U1
U0
U1
U0
U1
U0
U1
U0
U1
Anz. Wege
24
20
84
62
276
206
900
638
2916
2030
9444
6446
30564
20654
98916
66350
320100
213806
1035876
698966
3352164
2229038
10847844
7205678
35104356
23302958
113600100
75379502
367617636
243872558
Anz. Hamiltonkrs
8
8
16
16
32
32
64
64
128
128
256
256
512
512
1024
1024
2048
2048
4096
4096
8192
8192
16384
16384
32768
32768
65536
65536
131072
131072
Sekunden
nm
nm
nm
nm
nm
nm
nm
nm
nm
nm
nm
nm
1
nm
2
1
7
4
24
16
84
55
293
193
1018
717
3564
2314
12726
7932
Absch. Wege
4
8
16
32
64
128
256
512
1024
2048
4096
8192
16484
32768
65536
nm: Nicht meßbar. (Das System gibt 0 Sekunden an)
Tabelle 6.11: Anzahl Wege und Rechenzeit in Abhängigkeit von der Knotenzahl
Dies ist ein Teilproblem von 1. Die Lösung soll in einer der Antworten „ ja, dies ist
ein Hamiltonkreis“ oder „nein, es gibt keinen Hamiltonkreis“ bestehen. Der Umfang
der Antwort erfordert keinen Aufwand. HMLT ist so zu modifizieren, daß er beim
ersten gefundenen Hamiltonkreis mit „ ja“ abbricht und den gefundenen Hamilton-
6.2. DIE KOMPLEXITÄT VON PROBLEMEN
209
kreis ausgibt oder nach kompletter Abarbeitung aller maximalen Wege „nein, es gibt
keinen Hamiltonkreis“ meldet.
(a) Antwort: ja
Wenn man „Glück hat“, erwischt man jedes Mal eine richtige Kante und hat
nach n untersuchten Knoten einen Hamiltonkreis gefunden. Kann der Aufwand
exponentiell werden, wenn man „Pech hat“? Ja, das ist möglich und soll in
Aufgabe 6.4 untersucht werden.
Wie kommt es zu Glück oder Pech? Das liegt an Zeile 2 des Algorithmus
HMLT . Wie kommt man zu allen Nachbarn v 0 eines Knotens v? Das hängt von
der Datenstruktur ab, die man zur Darstellung von Nachbarschaften gewählt
hat, und für einen gegebenen Graphen und einen gegebenen Anfangsknoten
kann die gewählte Datenstruktur günstig oder ungünstig sein.
(b) Antwort: nein
Der Aufwand ist im allgemeinen exponentiell. Siehe Aufgabe 6.5.
Da Algorithmus HMLT das Problem, alle Hamiltonkreise zu finden, für größere Graphen
auch nicht effizient löst, kann man versucht sein, ihn zu verfeinern und weitere graphentheoretische Eigenschaften von Hamiltonkreisen zu berücksichtigen. Man sollte das jedoch
nicht tun. Man weiß nämlich, daß das Finden eines Hamiltonkreises ein N P-vollständiges
Problem ist (siehe Unterabschnitte 6.2.1 und 6.2.4). Am Beispiel von HMLT wollen
wir jedoch eine andere wichtige Betrachtungsweise einführen und auf die Frage „Was ist
Nichtdeterminismus?“ eine erste Antwort geben. Tabelle 6.12 zeigt den „Algorithmus“
'
$
&
%
Prozedur NDHMLT (VERTEX ∗v, int length) // Nichtdeterministische
Suche nach einem Hamiltonkreis
1 { v→aktiv = TRUE;
2
for (einen Nachbarn v 0 von v)
3
{ if (v 0 →aktiv == FALSE) NDHMLT (v 0 , length + 1);
4
if (v 0 == start && length == n − 1)
5
{ Ende: Hamiltonkreis gefunden;
6
}
7
else
8
{ Ende: Kein Hamiltonkreis;
9
}
10
}
11 }
Tabelle 6.12: Prozedur NDHMLT
NDHMLT . Er unterscheidet sich von HT ML nur durch kleine Änderungen. Die ent-
210
KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT
scheidende Änderung befindet sich in Zeile 2. Statt aller Nachbarn des Knotens v wird
nur irgendein Nachbar von von v geprüft. Dieser Unterschied ist wesentlich. Bei HT ML
werden stets alle Nachbarn von v untersucht. Unbekannt, weil von der Implementierung
der Graphstrukturen abhängig, ist nur die Reihenfolge, in der das geschieht. Man muß
gewährleisten, daß das Endergebnis nicht von der Reihenfolge abhängt. Bei NDHT ML
wird stets nur ein Nachbar von v betrachtet und es bleibt unbestimmt, welcher das ist.
Nach spätestens n untersuchten Knoten endet der Ablauf von NDHT ML mit einem gefundenen Weg. Dieser Weg braucht nicht maximal zu sein und nur, wenn man „Glück
gehabt hat“, ist es ein Hamiltonkreis. Die Prüfung, ob man Glück gehabt hat oder nicht,
ist ganz einfach und wird in NDHT ML in Zeile 4 ausgeführt.
6.2.4
Die Problemklassen P, N P und weitere
Vorbemerkungen
Unter der Komplexität eines Problems wollen wir die beste, d. h. kleinste, WOC-Komplexität unter allen Algorithmen, die das Problem lösen, verstehen. Es läßt sich zum Beispiel
zeigen (und das soll in Abschnitt 11.4 getan werden), daß jeder Sortieralgorithmus, der nur
Vergleiche der Sortierwerte benutzt, eine WOC-Komplexität von mindestens O(n · ln(n))
haben muß. Es ist eine große Zahl von Problemen bekannt, für die man nur Lösungsalgorithmen mit exponentieller Komplexität kennt. Dazu gehören natürlich alle Probleme,
bei denen der Umfang der Lösung exponentiell mit dem Umfang der Eingabe wächst,
wie zum bei dem Problem, alle Hamiltonkreise eines Graphen zu finden. Aber es gehören
auch Probleme dazu, bei denen der Umfang der Lösung höchstens polynomiell mit dem
Umfang der Eingabe wächst. Diesen gilt unser besonderes Interesse.
Bevor dies geschehen kann, muß jedoch einiges präzisiert werden: Was ist ein Problem,
eine Lösung eines Problems? Was ist ein Lösungsalgorithmus? Will man diese Fragen
streng, d. h. exakt beantworten, so muß man auf formalisierte Algorithmusdarstellungen
zurückgreifen. Es soll hier nur angedeutet werden, wie das geschieht. Als formales Hilfsmittel werden in der Regel Turingmaschinen benutzt (siehe Unterabschnitt 5.2.4, Seite
172). Es wird ein Eingabealphabet festgelegt und Wörter aus diesem einer Turingmaschine
als Eingabezeichenreihen zugeführt. Am Beispiel von Hamiltonkreisen soll das erläutert
werden. Wir legen über dem Eingabealphabet eine Codierung von schlichten Graphen fest
und suchen eine Turingmaschine, die Graphen mit einem Hamiltonkreis erkennt („akzeptiert“). Als Grundlage für die Konstruktion der Turingmaschine soll der naive Algorithmus
HMLT dienen. Ein solche Turingmaschine ist ein formalisierter Algorithmus, der für jede
Eingabe mit einem der folgenden Ergebnisse hält:
1. Die Zeichenreihe ist keine korrekte Darstellung eines schlichten Graphen.
2. Es handelt sich um einen Hamiltongraphen.
6.2. DIE KOMPLEXITÄT VON PROBLEMEN
211
3. Der Graph ist kein Hamiltongraph.
Die Länge der Eingabezeichenreihe ist der Umfang der Eingabe, die Anzahl Schritte der
Turingmaschine ist die Anzahl der Operationen. Die Syntaxprüfung unter Nummer 1 ist
bei „vernünftiger“, d.h. naheliegender Codierung mit polynomiellem Aufwand möglich und
kann z. B. durch eine vorgeschaltete Turingmaschine erledigt werden. Wir wollen daher
nur Eingabezeichenreihen betrachten, die korrekte Darstellungen von schlichten Graphen
sind. Bei wiederum vernünftiger Codierung wächst der Länge der Eingabezeichenreihe nur
polynomiell mit der Anzahl Knoten des dargestellten Graphen und die Anzahl Schritte
der Turingmaschine hängt nur polynomiell von der Anzahl Operationen im naiven Algorithmus HT ML ab. Wir können die Zeichenreihen, die Hamiltongraphen darstellen, als
eine formale Sprache auffassen. Die Zeichenreihen, die Nicht-Hamiltongraphen darstellen,
bilden die dazu komplementäre (formale) Sprache. Zu den Begriffen Alphabet und formale
Sprache siehe Abschnitt 3.6, Seite 112. Unsere Turingmaschine erkennt also sowohl Hamiltongraphen als auch Nicht-Hamiltongraphen (im schlechtesten Fall) in exponentieller
Zeit.
Formal ist ein Problem (problem) eine formale Sprache und eine Problemlösung (problem
solution) eine Turingmaschine, die die Zugehörigkeit einer Eingabe zu dieser Sprache erkennt. Die komplementäre (formale) Sprache (complementary language) charakterisiert
das komplementäre Problem (complementary problem). Es ist nicht unmittelbar einsichtig, aber zutreffend, daß wichtige naiv formulierte Probleme, wie z. B. Sortierprobleme
oder Optimierungsprobleme sich als formale Sprachen auffassen lassen. Für Komplexitätsbetrachtungen gilt außerdem so etwas Ähnliches wie die Churchsche These (Unterabschnitt 5.2.2, Seite 169): Ein naiver Algorithmus und jede seiner (vernünftig codierten)
Formalisierungen sind beide von polynomieller oder beide von exponentieller Komplexität.
Problemklassen
Wir führen die Komplexitätsklassen P und EX P ein. Die Klasse P besteht aus allen Problemen, für die ein Lösungsalgorithmus polynomieller Komplexität existiert. Die Klasse
EX P besteht aus allen Problemen, für die es einen Lösungsalgorithmus polynomieller oder
exponentieller Komplexität gibt. EX P ist eine echte Oberklasse von P, denn sie enthält
Probleme, für die nachweislich kein Lösungsalgorithmus polynomieller Komplexität existiert. Sowohl die Klasse P als auch die Klasse EX P werden durch Algorithmen, also
deterministische Lösungsverfahren definiert. Das bedeutet, daß für jede Eingabe x beim
Halten feststeht, ob sie zur betreffenden Sprache gehört oder nicht. Für jedes Problem
S ∈ P existiert ein Algorithmus, der auch das zu S komplementäre Problem löst. Es ist
also P = COP, wobei COP die Menge der komplementären Probleme von P ist. Ganz
entsprechend gilt EX P = COEX P.
Wie im einleitenden Überblick (Seite 199) ausgeführt, interessiert uns im besonderen Maße die Problemklasse N P. Diese Probleme gehören zu EX P, es ist aber unbekannt, ob
212
KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT
sie in P liegen. Um diese Klasse zu definieren, wollen wir zunächst einmal klären, was
eine nichtdeterministische Turingmaschine (non-deterministic Turing machine) ist. Eine
normale (deterministische) Turingmaschine hat bei gegebenem Eingabewert einen eindeutig bestimmten Ablauf. Bei einer nichtdeterministischen Turingmaschine ist der Ablauf
nicht notwendigerweise eindeutig, sondern es kann Stellen geben, an denen verschiedene
Fortsetzungen möglich sind. Es ist dabei unbestimmt, welche Fortsetzung eintritt. Eine
nichtdeterministische Turingmaschine steht bei einer gegebenen Eingabe für eine Schar
von möglichen Abläufen. Deterministische Turingmaschinen, bei denen nur ein Ablauf
möglich ist, sind ein Spezialfall. Eine nichtdeterministische Turingmaschine erkennt die
Sprache S, d. h. löst das S entsprechende Problem, wenn für jedes x ∈ S (mindestens)
einer der möglichen Abläufe zur Akzeptanz von x führt.
Mit Hilfe von nichtdeterministischen Turingmaschinen wollen wir nun die Probleme der
Klasse N P charakterisieren. Als Beispiel soll der „Algorithmus“ NDHT ML (Tabelle 6.12)
betrachtet werden. Wir nehmen an, er sei als nichtdetermistische Turingmaschine codiert,
orientieren uns aber am Pseudocode der Tabelle. Ein Ablauf wird ausgehend vom Anfangsknoten zunächst auf nichtdeterministische Art einen Weg bis zu einem ersten schon
besuchten Knoten finden. Das geschieht in den Zeilen 1 bis 3. Wird Zeile 4 erreicht, so
ist der Weg gefunden. Der Rest läuft deterministisch ab und besteht nur in der Prüfung,
ob der gefundene Kreis ein Hamiltonkreis ist oder nicht. Ausgehend von diesem Beispiel
legen wir fest:
Ein Problem gehört zur Klasse N P, wenn
1. ein Lösungsverfahren existiert, das nichtdeterministisch in polynomieller Zeit einen
Lösungsvorschlag, ein Zertifikat (certificate), generiert, von dem deterministisch in
polynomieller Zeit festgestellt wird, ob es eine Lösung ist und
2. mindestens ein Ablauf auch eine Lösung liefert.
Salopp ausgedrückt: Ein Problem gehört zu P, wenn in polynomieller Zeit eine Lösung
gefunden werden kann. Ein Problem gehört zu N P, wenn in polynomieller Zeit eine
Lösung verifiziert werden kann.
Aus dieser Definition folgt nicht unmittelbar, daß ein Problem aus N P stets auch mit
einem deterministischen Verfahren exponentieller Komplexität gelöst werden kann. Es
läßt sich aber zeigen, daß das so ist: N P ⊂ EX P.
NDHMLT zeigt, daß „Ist G ein Hamiltongraph?“ ein Problem aus N P ist. Das komplementäre Problem ist „Ist G kein Hamiltongraph?“. Es ist aus CON P. Man kann
NDHMLT nicht heranziehen, um zu zeigen, daß dieses Problem auch aus N P ist, und
man nimmt an, daß es auch keine anderen nichtdeterministischen polynomiellen Lösungsverfahren dafür gibt. Wenn das so ist, gilt N P =
6 CON P. Es gilt jedoch N P ∩CON P =
6 ∅.
Es ist nämlich P = COP und somit P Teilklasse von N P und von CON P. Abbildung 6.6
zeigt, wie die bisher betrachteten Komplexitätsklassen zusammenhängen. Das Bild zeigt
6.2. DIE KOMPLEXITÄT VON PROBLEMEN
213
.........................................................................................
.....................................
.......................
......................
.................
.................
...............
...............
.............
.............
............
.
.
.
.
.
.
.
.
.
.
..........
.......
.
.
.
.
.
..........
.
.
.
.
.........
......
.
.
.
.
.
.
.
.
.........
......
.
.
.
........
.
.
.
.
........
.....
.
.
.
.
.
.
.......
....
.
.
.
.......
.
.
.
......
.....
.
.
.
.
.
......
....
.
.
......
.
.
.
......
....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.........................
.........................
......
..................
..................
.
.
.
.
.
.
....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.............
......................
.........
.
...
.....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
..........
..........
....
.......
.......
.
.
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
....
.
.
.
.
.
.
.
........
........
.....
......
.
.
...
.
.
.
.
.
.
.
....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.......
.......
....
.....
..
.
.
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
......
......
...
....
.....
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
.
.
......
......
....
....
.
.
.
...
.
...
.
.
.
.
.
.
.
.
.
.
.....
.....
....
...
.
..
.....
.
.
.
.
.
.
.
.
....
....
...
..
...
..
.
.
.
.
.
.
.
.
...
...
...
..
..
..
.
.
.
.
...
.
.
.
...
...
.
.
..
.
.
.
..
.
.
.
.
.
...
.
...
..
..
..
...
.
.
...
...
..
..
.....
...
.....
..
...
...
..
..
...
..
..
..
.
...
.
...
..
....
...
..
..
..
..
...
.
.
..
.
..
.
...
.
.
.
...
.
.
.
...
.
...
.
.
.
...
.
.
.
.
.
.
...
...
.
..
...
.......................
...
...
...
...
...
...
.....
...
...
...
...
...
...
...
...
...
...
....
..
..
...
..
....
...
....
....
...
...
...
...
.
.
.
.....
.
.
.
.....
.
.
...
.
.
.
.
.
...
......
.....
.
.....
.....
...
...
......
...
......
...
.....
......
...
......
..
......
....
......
......
...
...
.......
.......
...
.......
......
.......
...
...
........
........
.................
.......
.......
...
.
.
....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.........
.........
....
.........
..........
..........
...
.........
....
............
..........
.............................
.....
.....
...............
.
............
.....
.....
......................
............... ...................................
...............
.....
......
...................................................................................
....................................................................................
.....
......
.
.
.
.
.
......
......
......
......
......
......
.......
.......
.......
.......
........
.
.
.
.
.
.
.
.
........
........
.........
........
.........
.........
..........
...........
..........
...........
..........
.
.
.
.
.
.
.
.
.
.
.
.............
.............
...............
..............
.................
.......................
..................
.......................................
.......................
...................................................................................
EX P
NP
N P ∩ CON P
CON P
P
Abbildung 6.6: Komplexitätsklassen
den Stand, der zur Zeit (2006) als der wahrscheinlichste gilt. Es ist sicher, daß EX P eine
echte Oberklasse von N P und von CON P ist. Für die anderen Beziehungen sind auch
noch die folgenden anderen Möglichkeiten offen:
1. P = N P = CON P
2. P ⊂ N P = CON P
3. P = N P ∩ CON P ⊂
NP
CON P
Reduktion und Vollständigkeit
In der Wissenschaft, aber auch im täglichen Leben, ist es oft möglich, ein Problem dadurch zu lösen, daß man es auf ein anderes, schon gelöstes Problem zurückführt, reduziert
(reduce). Für Probleme in formalisierter Darstellung wollen wir das folgendermaßen präzisieren: Es sei S eine formale Sprache (ein Problem) und es wird eine (deterministische
oder nichtdeterministische) Turingmaschine T (ein Lösungsverfahren) gesucht, die eine
zulässige Eingabe x genau dann akzeptiert, wenn x ∈ S. Es seien weiter ein Problem S 0
und eine Turingmaschine T 0 gegeben, wobei T 0 genau die x0 ∈ S 0 akzeptiert. Abbildung
6.7 zeigt, wie man S auf S 0 reduzieren kann. Eine Reduktionsmaschine R wandelt x in
x0 um. Dabei muß sichergestellt sein, daß jedes für S zulässige x in ein für S 0 zulässiges
x0 transformiert wird. Anschließend wird x0 von T 0 bearbeitet. Es muß sichergestellt sein,
daß dann und nur dann x0 von T 0 akzeptiert wird, wenn x ∈ S.
214
KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT
....................................................................................................................................................................................................................................................................................................
...
...
..
...
...
...
...
...
...
...
...
...
. ..
......................................................................................................
..
....
...
...
...
....
..
...
...
...
...
...
..
x
'
R
&
T
$
x0
%
$
'
..
.................................................................................
..
T
...
...
..
...
...
...
...
...
...
...
...
...
.
..
......................................................................................................
..
....
...
...
...
..
...
....
..
...
...
...
..
ja
0
&
%
....................................................................................................................................................................................................................................................................................................
Abbildung 6.7: Reduktion von Problemen
Die Maschine R wird nicht umsonst arbeiten. Es kostet eine gewissen Aufwand, x in x0
umzuwandeln Für uns sind Transformationen wichtig, die in polynomieller Zeit möglich
sind. Es ist nicht schwer, zu sehen, daß dann die Komplexität von T 0 die Komplexität von
T bestimmt. Daraus folgt: Gehört S 0 zu Komplexitätsklasse P (bzw. N P, EX P), so gehört auch S zu dieser Klasse. Theoretisch kann man zeigen, daß es in jeder dieser Klassen
Probleme gibt, auf die sich jedes Problem der Klasse reduzieren läßt. Man spricht von
vollständigen Problemen (complete problem) und unterscheidet P-Vollständigkeit, N PVollständigkeit und EX P-Vollständigkeit. Es gibt auch CON P-vollständige Probleme;
hierauf soll jedoch nicht weiter eingegangen werden. Wir wollen uns im folgenden auf
N P-vollständige Probleme als dem wichtigsten Fall und auf die Frage „P = N P?“ beschränken.
Wenn es möglich ist, ein N P-vollständiges Problem auf ein anderes Problem aus N P
zu reduzieren, dann ist offenbar auch das zweite Problem N P-vollständig. Der große
Durchbruch begann, als es Cook 11 1971 gelang [Cook1971], die N P-Vollständigkeit eines bekannten Problems, des Erfüllbarkeitsproblems (satisfiabilty problem) explizit nachzuweisen. Seitdem sind Hunderte von bekannten und weniger bekannten Problemen als
N P-vollständig nachgewiesen worden, in der Regel durch (manchmal recht kunstvolle)
Reduktionen. Auch das Problem, in einem Hamiltongraphen einen Hamiltonkreis zu finden, gehört dazu. Siehe auch die Literaturangaben auf Seite 223.
Cook, Stephen Arthur ∗1939 Buffalo, New York. Amerikanischer Informatiker. Zur Zeit (2006) Professor an der University of Toronto, Canada. Schuf gleichzeitig mit Karp 12 und Edmonds 13 die moderne
Theorie der N P-Vollständigkeit.
11
Karp, Richard M. ∗3.Januar 1935. Amerikanischer Informatiker. Zur Zeit (2006) Professor an der
University of California at Berkeley. Wies 1972 [Karp1972] unter Benutzung von Reduzierbarkeit die
N P-Vollständigkeit einer großen Zahl wichtiger kombinatorischer Probleme nach.
12
Edmonds, Jack ∗1961 (?). Kanadischer Mathematiker und Informatiker. Bis 1999 Professor an der
University of Waterloo, Canada. Grundlegende Beiträge zur Kombinatorik, Graphentheorie und Algorithmik. Opfer akademischen Mobbings, was 1991 bis 1993 zu einem Zerwürfnis mit der Universität führte.
13
6.2. DIE KOMPLEXITÄT VON PROBLEMEN
215
Ein schönes Beispiel für die Anwendung von Reduktion ist auf Seite 512 zu finden. Dort
wird durch Reduktion auf Hamiltonwege gezeigt, daß das Problem, einen kürzesten Weg
in einem Graphen mit negativen Liniengewichten zu finden, N P-vollständig ist.
Für alle N P-vollständigen Probleme hat man bisher vergeblich nach einem polynomiellen
Algorithmus gesucht. Würde man für eines dieser Probleme einen finden, so wäre P =
N P. Man hat aber auch für keines nachweisen können, daß es keinen Algorithmus mit
polynomieller Komplexität geben kann. Könnte man das für eines der Probleme, so wäre
N P ⊃ P und alle N P-vollständigen Probleme lägen in N P \ P.
Abschließende Bemerkungen
Im einführenden Überblick 6.2.1 haben wir Probleme, für die man nur Algorithmen exponentieller Komplexität kennt, als schwierig bezeichnet. Für einige von ihnen kann man
nachweisen, daß es nur Lösungsalgorithmen exponentieller Komplexität geben kann. Für
die übrigen besteht noch Hoffnung. Vielleicht findet man einen Lösungsalgorithmus polynomieller Komplexität. Allerdings ist bei den N P-vollständigen Problemen diese Hoffnung
recht gering. Bei den dann noch verbleibenden Problemen wollen wir zwei Gruppen kurz
erläutern.
N P-harte Probleme: Ein Problem S heißt N P-hart (N P-hard), wenn sich jedes Problem aus N P auf S reduzieren läßt, aber nicht bekannt ist, ob S zu N P gehört. Es
sind solche Probleme aus der theoretischen Informatik bekannt. Für sie ist die Hoffnung
auf effiziente Lösungsalgorithmen noch geringer. N P-vollständige Problem haben einen
Lösungsalgorithmus polynomieller Komplexität genau dann, wenn P = N P. N P-harte
Problem können einen Lösungsalgorithmus polynomieller Komplexität nur dann haben,
wenn P = N P.
Nicht-vollständige Probleme aus N P: Es gibt Probleme aus N P, von denen man
einerseits nicht weiß, ob sie N P-vollständig sind, für die man aber andererseits einen
polynomiellen Lösungsalgorithmus bisher vergeblich gesucht hat. Ein bekanntes Problem
dieser Art ist das Graphisomorphieproblem. Dabei werden Algorithmen gesucht, die (im
einfachsten Fall) von zwei schlichten Graphen mit verschiedenen Knotenmengen feststellen, ob sie bis auf die Knotenbezeichnungen identisch sind. Probleme der hier betrachteten
Art kann man dadurch „lösen“, daß man ihre N P-Vollständigkeit feststellt. Das ist in den
vergangenen Jahren immer wieder einmal gelungen. Man kann ein solches Problem aber
auch dadurch lösen, daß man einen polynomiellen Lösungsalgorithmus findet. In seltenen,
spektakulären Fällen ist auch das gelungen. Zum Beispiel kennt man inzwischen polynomielle Lösungsverfahren für die lineare Optimierung [Khac1979], [Karm1984] oder das
Primzahlproblem14 [AgraKS2004].
14
Es ist festzustellen, ob eine Zahl Primzahl ist oder nicht.
216
KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT
6.3
Näherungslösungen schwieriger Probleme*
Es gibt sehr viele interessante und für die Praxis wichtige schwierige Probleme, die meisten davon sind N P-vollständig und viele sind Optimierunugsprobleme. Man kann es sich
gar nicht leisten, die Hände in den Schoß zu legen und zu sagen „Da kann man halt nichts
machen“. Mit den üblichen Entwurfstechniken für Algorithmen: Teile und herrsche, dynamische Programmierung, Rücksetzen, lokale Suche, Gieralgorithmen (Unterabschnitt
5.1.3, Seite 159) kann man nur in Ausnahmefällen die gewünschten Lösungen erhalten.
Auch mit ad hoc konstruierten Algorithmen kommt man nur selten und nur bei Aufgabenstellungen mit kleiner Eingabemenge weiter. Aus diesem Grund wächst seit einigen
Jahrzenten die Zahl der Lösungsansätze und Lösungsvorschläge, mit denen man bei diesen Problemen wenigstens etwas weiter kommt. Es ist naheliegend, daß diese Lösungen
zunächst für spezielle Probleme gefunden werden und sich übergeordnete, methodenorientierte Zusammenhänge erst nach und nach herausstellen. Aus diesem Grund und auch,
weil die Entwicklung sehr rasch und intensiv verläuft, gibt es kaum Lehrbücher, die das
Gebiet als Ganzes behandeln. Zu nennen ist hier in erster Linie Hromkovič Algorithms
for Hard Problems [Hrom2001]. Schwierigkeitsgrad und Umfang diese Buches übersteigen jedoch deutlich das, was in der Grundausbildung in Informatik zugemutet werden
kann. Zum Glück hat der gleiche Autor in einem unkonventionellen Buch über theoretische Informatik ([Hrom2007], erste Auflage [Hrom2001a]) in zwei Kapiteln den Stoff auch
„grundausbildungsgerecht“ dargestellt. Für eine Einführung in die Informatik ist das aber
immer noch zu viel. Deshalb sollen im folgenden die zur Zeit wichtigsten Bereiche des
Gebiets knapp und überblicksartig beschrieben und einige wenige Anwendungen genannt
werden.
Aus Erfahrung gut
Lange, bevor man etwas von N P-Vollständigkeit wußte, ja sogar lange, bevor es moderne
Rechner gab, haben gute Disponenten die Probleme der Routenplanung recht zufriedenstellend gelöst. Das war allerdings eher Intuition und Erfahrung als Informatik.
Ein schon eher als Informatik anzusehendes Beispiel ist der Simplex-Algorithmus zur Lösung linearer Optimierungsprobleme15 . Es ist bekannt, daß der Simplex-Algorithmus in
einigen Fällen exponentielle Laufzeiten hat. Obwohl man inzwischen weiß (siehe oben),
daß lineare Optimierung zu P gehört und man brauchbare polynomielle Lösungsalgorithmen hat, ist der Simplex-Algorithmus immer noch das meist genutzte Instrument der
linearen Optimierung.
15
Lineare Optimierung wird in diesem Buch nicht behandelt. Siehe Neumann/Morlock [NeumM1993]
6.3. NÄHERUNGSLÖSUNGEN SCHWIERIGER PROBLEME*
217
Beschränkung auf Teilpropleme
Wenn man sich auf spezielle Teilprobleme beschränkt, dann gibt es für diese häufig Lösungsalgorithmen polynomieller Komplexität, obwohl das Ausgangsproblem N P-vollständig
ist. Zum Beispiel gibt es für die meisten schweren Probleme auf Graphen polynomielle
Lösungsalgorithmen, wenn man sich auch Bäume beschränkt. Siehe auch „minimale Rundwege“ Seite 552.
Pseudopolynomielle Algorithmen
Wir fangen mit dem Primzahltest an. Es sei n ≥ 3 eine natürliche Zahl und es soll festgestellt werden, ob n eine Primzahl ist. Wir teilen n durch 2, 3 · · · , n − 1 und wissen
nach n − 2 Schritten, ob n einen echten Teiler√ hat oder nicht. Wenn wir es geschickter
machen wollen, teilen wir nur durch 2, 3, · · · , b nc und kommen schneller zum Ergebnis.
n ist der Umfang der Eingabe, also haben wir einen effizienten polynomiellen Algorithmus! Oder etwa nicht? Schauen wir uns den Aufwand einmal an. Bei n = 106 haben
wir 1 Million Divisionen, bei n = 1012 sind es 1 Billion Divisionen. Andererseits haben
wir im ersten Fall nur 6 Dezimalziffern, im zweiten nur 12. Das sieht nicht nach einem
effizienten Algorithmus aus. Was wir suchen, ist ein effizienter Lösungsalgorithmus, also
einen der polynomiell in der Anzahl der Ziffern, die zur Darstellung von n gebraucht werden, arbeitet. Probleme, deren Eingabe aus einer oder mehreren ganzen Zahlen besteht,
heißen Zahlprobleme (integer-valued problems). Bei einem Zahlproblem, dessen Eingabe
eine ganze Zahl n ist, sprechen wir von einer Lösung polynomieller Komplexität, wenn der
Algorithmus polynomiell in der Länge der Darstellung von n, also polynomiell in Ω(ln(n))
ist. Ist der Algorithmus polynomiell in n, so sprechen wir von einer pseudopolynomiellen
(pseudopolynomial) Lösung. Für den Primzahltest ist also ein pseudopolynomieller Lösungsalgorithmus rasch gefunden. Ob es auch einen polynomiellen Lösungsalgorihtmus
gibt, war lange Zeit unklar. Der 2004 veröffentlichte und auf Seite 215 genannte Algorithmus von Agrawal/Kayal/Saxena – AKS-Algorithmus [AgraKS2004] – beantwortete die
Frage im positiven Sinn. Übrigens hat hat nach Gleichung 1.9 auf Seite 13 der euklidische
Algorithmus polynomielle Komplexität.
Für uns ist nun folgende Frage wichtig: Gibt es Zahlprobleme, die N P-vollständig sind,
für die jedoch pseudopolynomielle Lösungalgorithmen mit praktisch brauchbaren Ergebnissen existieren? Ja, das ist der Fall. Ein Beispiel ist das Rucksackproblem (siehe Beispiel
5.2, Seite 161). Zu Einzelheiten siehe Hromkovič [Hrom2007]. Schön wäre es, wenn alle
Zahlprobleme pseudopolynomielle Lösungen aufweisen würden. Man hätte dann Näherungsverfahren für eine große Klasse schwieriger Probleme. Leider ist das nicht so. Zum
Beispiel kann man nachweisen, daß das Problem des Handlungsreisenden (siehe Unterabschnitt 21.3.2, Seite 555) keine pseudopolynomiellen Lösungsalgorithmen zuläßt. Solche
Probleme heißen stark N P-vollständig (strongly N P-complete).
218
KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT
Approximative Algorithmen
Approximationsalgorithmen werden zur Lösung von schweren Optimierungsproblemen
eingesetzt. Statt eine optimale Lösung zu fordern, geben wir uns mit einer „fast optimalen“ Lösung zufrieden. Diese wollen wir jedoch mit wesentlich weniger Aufwand, d. h.
in polynomieller Zeit erhalten. Wir beginnen mit einem Beispiel aus der Planungstheorie.
Beispiel 6.3 (Listenplanung) Wir haben K Aufträge und m gleichartige Maschinen.
Jeder Auftrag kann auf jeder Maschine bearbeitet werden und braucht überall die gleiche
Zeit tk . Die Aufträge sind unabhängig voneinander und können in beliebiger Reihenfolge bearbeitet werden. Ein Auftrag, dessen Bearbeitung begonnen wurde, wird auf der
entsprechenden Maschine bis zum Ende ausgeführt und nicht unterbrochen. Eine Zuordnung der Aufträge zu den Maschinen, bei der jedem Auftrag genau eine Maschine und
eine Anfangszeit zugeordnet ist, sich auf einer Maschine keine Bearbeitungen überlappen
und keine Maschine leersteht, solange noch unbearbeitete Aufträge vorhanden sind, heißt
Vergabeplan (schedule). Unter der Gesamtdurchlaufszeit (makespan) versteht man die Zeit
vom Beginn der Bearbeitung bis zum Ende des letzten Auftrages. Diese Zeit soll minimiert werden. Für diese Zielfunktion gibt es (mindestens) einen optimalen Vergabeplan.
Das Finden eines solchen ist N P-vollständig. Für m = 2 gibt es eine pseudopolynomielle
Lösung, für m ≥ 3 ist das Problem stark N P-vollständig (Garey/Johnson [GareJ1979],
[SS8], Seite 238). Man braucht Näherungslösungen. Es hat sich herausgestellt, daß man
ohne viel Aufwand Näherungslösungen angeben kann, die ganz brauchbar sind. Das sind
Listenpläne (list schedules). Ein Listenplan ergibt sich aus einer irgendwie angeordneten
Liste aller Aufträge, indem auf einer freien oder frei gewordenen Maschine der nächste Auftrag aus der Liste begonnen wird. Abbildung 6.8 zeigt zwei Vergabepläne für 7
2
4
6
M1
k1
k6
M2
k2
k5
M3
k3
k4
8
10
×
×
×
×
×
× × × × × ×
× × × × ×
× × × × × ×
× × × × ×
× × × × × ×
× × × × ×
× × × × × ×
× × × × ×
× × × × × ×
× × × × ×
× × × × × ×
2
k7
k5
4
6
8
k1
k3
k2
k4
k6
k7
10
× × × ×
× × × ×
× × × ×
× × × ×
× × × ×
× × × ×
× × × ×
× × × ×
× × × ×
× × × ×
× × × ×
× × × ×
× × × ×
× × × ×
× × × ×
× × × ×
Abbildung 6.8: Listenplan und optimaler Vergabeplan
Aufträge und 3 Maschinen. Der erste enspricht der Liste (k1 , k2 , k3 , k4, k5 , k6 , k7 ), der zweite gehört zur Liste (k1 , k2 , k5 , k6 , k3, k4 , k7 ) und ist optimal. Die Ausführungszeiten sind
t1 = 5, t2 = 5, t3 = 4, t4 = 4, t5 = 3, t6 = 3, t7 = 3.
Es ist nun nicht schwer, abzuschätzen, um wieviel ein optimaler Vergabeplan besser sein
kann als ein beliebiger Listenplan. Es sei Tl die Gesamtdurchlaufszeit eines Listenplanes
6.3. NÄHERUNGSLÖSUNGEN SCHWIERIGER PROBLEME*
219
und Topt die Gesamtdurchlaufszeit eine optimalen Vergabeplanes. Ein optimaler Vergabeplan braucht mindestens die Zeit eines längsten Auftrags,d. h.
T opt ≥ tk (k = 1, 2, . . . , K)
Außerdem wird mindestens die Zeit verstreichen, die m Maschinen brauchen, um den
gesamten Auftragsbestand abzuarbeiten
K
Topt
1 X
tk
≥
m k=1
Es sei k0 der Auftrag16 , der beim Listenplan als letzter fertig wird und somit die Gesamtdurchlausfzeit bestimmt. sk0 sei sein Startzeitpunkt. Vor diesem Zeitpunkt müssen alle
Maschinen beschäftigt gewesen sein, denn sonst wäre k0 früher gestartet worden. Es kann
auch sein, daß auch noch danach alle Maschinen aktiv waren. Also
sk 0 ≤
1 X
tk
m
k6=k0
und das heißt
K
Tl = sk0 + tk0
1 X
1
≤
+(1 − )tk0
m
m
k=1
Daraus folgt
Tl ≤ Topt + (1 −
1
)Topt
m
also
1
)Topt
(6.17)
m
Das Beispiel zeigt, daß beliebige Vergabepläne höchsten um den Faktor 2 schlechter sind
als optimale Vergabepläne. Ob das ausreichend ist, hängt von der Anwendung ab.
2
Tl ≤ (2 −
Für einige Optimierunsaufgaben gibt es polynomielle Approximationsschemata (polynomial approximation scheme) Das sind Approximatonsalgorithmen mit polynomieller Komplexität, mir denen eine beliebig gute Näherung erreicht werden kann, allerdings auf Kosten komplizerter werdender Algorithmen wachsender Komplexität.
Nicht für alle Probleme gibt es polynomielle Approximationsalgorithmen. Zum Beispiel
gibt es keinen für das Problem des Handlungsreisenden. Siehe Unterabschnitt 21.3.2, Seite
555.
16
Es kann auch einer von mehreren Aufträgen sein.
220
KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT
Lokale Suche und Heuristiken
Lokale Suche wurde in Unterabschnitt 5.1.3, Seite 161, als eine Entwurfstechnik für Algorithmen eingeführt. Man kann sie auch bei schwierigen Problemen einsetzen. Allerdings
ist dann in aller Regel der Gesamtsuchraum viel zu groß. Man findet häufig lokale Optima
und diese sind wiederum nur in seltenen Fällen globale Optima. Man geht folgendermaßen vor: Von einem geeignet erscheinenden Anfangspunkt im Lösungsraum sucht man in
dessen Nachbarschaft eine Lösung, die die Ziefunktion verbessert. Das macht man nach
dem Gierprinzip und betrachtet die Lösung innerhalb der Nachbarschaft, die die größte
Verbesserung liefert. In der Mathematik hat man für dieses Vorgehen den vornehmeren
Namen steilster Abstieg17 (steepest descent) gewählt. Man spricht auch von einer Gradientensuche.
Gibt es keine Verbesserung, so hat man ein lokales Optimum. Ist man damit nicht zufrieden, so kann man an einer anderen Stelle mit der lokalen Suche beginnen. Man kann aber
auch versuchen, das lokale Optimum zu umgehen. Dafür sind eine Reihe von Heuristiken
(heuristics) vorgeschlagen worden. Beispiele dafür sind:
Tabusuche. Tabusuche (tabu search) versucht, sich zu merken, welche früheren Schritte
nicht zum Erfolg geführt haben, und hält diese in einer Verbotsliste (Tabuliste) fest.
Simulierte Abkühlung. Ähnlich wie Tabusuche ist Simulierte Abkühlung (simulated
annealing) eine Vorgehensweise, die es erlaubt, lokale Optima wieder zu verlassen.
Dafür nimmt man zufallsgesteuerte Verschlechterungen der Zielfunktion in Kauf.
Genetische Algorithmen. Genetische Algorithmen (genetic algorithms) betrachten die
Menge der Lösungen als eine Population, die es mit Vorgehensweisen aus der Genetik
zu verbessern gilt.
Ameisenalgorithmen Ameisen benutzen Duftstoffe (Pheronome) zur Markierung der
Wege, die sie durchlaufen. Werden zwei unterschiedlich lange Wege zum gleichen Ziel
benutzt, so wird nach einiger Zeit die Pheronomkonzentration auf dem kürzeren Weg
größer sein als auf dem längeren. Nach diesem Prinzip können Ameisenalgorithmen
(ant colony optimization, ACO) bei der Verbesserung von Lösungen aus kürzesten
Wegen helfen.
Randomisierte Algorithmen
Bei randomisierten Algorithmen (randomized algorithm) werden in den Ablauf Zufallselemente eingebaut. Nimmt man dabei in Kauf, daß mit einer geringen Wahrscheinlichkeit
17
Wird ein Maximum der Zielfunktion gesucht, spricht man vom steilsten Anstieg.
6.3. NÄHERUNGSLÖSUNGEN SCHWIERIGER PROBLEME*
221
das Ergebnis falsch ist, so spricht man von einem Monte-Carlo-Algorithmus18 (Monte Carlo algorithm). Nimmt man in Kauf, daß der Algorithmus zwar stets das richtige Ergebnis
liefert, jedoch unter Umständen sehr lange rechnet, so spricht man von einem Las-VegasAlgorithmus (Las Vegas algorithm). Weiter unten wollen wir uns anhand von Beispiel 6.4
einen Monte-Carlo-Algorithmus für ein einfaches Kommunikationsprotokoll ansehen. Als
Beispiel für einen Las-Vegas-Algorithmus werden wir im Kapitel über Sortieren in Unterabschnitt 11.2.3, Seite 328, ein randomisiertes Quicksort vorstellen. Einige Anmerkunghen
dazu, wie man zu den dafür gebrauchten Zufallszahlen kommt, sind in Abschitt B.5, Seite
626, in Anhang zu finden.
Obwohl wir hier randomisierte Algorithmen als Methoden einführen, mit denen schwierige
Probleme wenigstens ansatzweise gelöst werden können, ist bis jetzt kein randomisierter
Algorithmus bekannt, der in polynomieller Zeit ein N P-vollständiges Problem löst, und
man vermutet, daß es einen solchen unter der Voraussetzung N P =
6 P auch nicht gibt.
Wozu braucht man dann randomisierte Algorithmen? Nun, nicht nur N P-vollständige
Probleme sind schwer zu lösen. Das unten erläuterte Kommunikationsprotokoll ist ein
Beispiel. Außerdem helfen randomisierte Algorithmen manchmal auch bei grundsätzlichen
Problemen weiter, z. B. bei den auf Seite 215 genannten nicht-vollständigen Problemen
aus N P. So waren randomisierte Primzahltests lange bekannt, ehe nachgewiesen wurde,
daß das Problem in P liegt. Auch auf dem Gebiet der Graphisomorphie gib es Neues.
Vor kurzem hat Schweitzer [Schw2009] in seiner Dissertation ein „Schraubenkasten“ genanntes randomisiertes Verfahren zur effizienten Lösung des Graphisomorphieproblems
veröffentlicht.
Beispiel 6.4 (Randomisiertes Kommunikationsprotokoll) Bei manchen Aufgabenstellungen ist es sinnvoll, einen Datenbestand mehrfach und an mehreren verschiedenen
Orten zu halten. Idealerweise sollte an allen Orten exakt der gleiche Datenbestand vorhanden sein. In der Praxis wird das kaum zu erreichen sein, auch dann nicht, wenn nur an
einer festen Stelle Änderungen vorgenommen werden und diese dann im Sinne einer Aktualisierung an den anderen Stellen „nachgezogen“ werden. Der Einfachheit halber wollen
wir uns auf zwei Orte und zwei Datenbestände beschränken. Zu bestimmten Zeitpunkten
wird es notwendig sein, zu überprüfen, ob die Inhalte beider Datenbestände identisch sind.
Für das folgende ist es zweckmäßig, die Datenbestände als Folgen von Bits anzusehen,
wobei ein Test auf Gleichheit nur notwendig ist, wenn beide Bitfolgen gleich lang sind.
A = a1 , a2 , . . . , an
und
B = b1 , b2 , . . . , bn
Wir wollen mit A und B auch die Orte bezeichnen, an denen sich die Datenbestände
befinden. Bei einem sehr großen Datenbestand haben wir zum Beispiel 4 Terabytes Daten. Das sind 8 × 4 × 240 = 245 > 3 × 1013 Bits. Es ist also n ≥ 3 × 1013 . Nun weiß
man, daß man mindestens n Bits von einer Stelle zur anderen transportieren muß, um
18
Die Bezeichnung wurde ursprünglich in der Numerik bennutzt.
222
KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT
mit Sicherheit Gleichheit der Datenbestände nachzuweisen. Das läßt sich beweisen. Dieser
Transport ist sehr zeitaufwendig. Da er auch fehleranfällig ist, machen ihn Sicherheitsprotokolle noch aufwändiger. Eine randomisierte Form des Vergleichs erlaubt jedoch eine viel
einfacherere Übertragung, allerdings auf Kosten einer gewissen Fehlerwahrscheinlichkeit.
Der randomisierte Algorithmus benutzt Primzahlen. Mit P rim(n2 ) wollen wir die Menge
2
der Primzahlen bezeichnen, die kleiner als n2 sind. Man weiß, daß |P rim(n2 )| ∼ lnnn2 gilt.
Der randomisierte Algorithmus arbeitet nun folgender maßen:
1. Man wählt mit gleicher Wahrscheinlichkeit unter den Primzahlen in P rim(n2 ) eine
Zahl p aus. Wir gehen davon aus, daß bekannt ist, wie man das tut, und daß man
das effizient machen kann.
2. Die Bitfolge in A betrachten wir als natürliche Zahl in Dualdarstellung und teilen
sie durch p. Der Rest sei s.
s := nummer(A) mod p
s und p werden als Bitmuster betrachtet und zu B geschickt.
3. Nach dem Empfang von s und p wird in B
t := nummer(B) mod p
berechnet. Falls s = t, so ist die Ausgabe gleich. Falls s 6= t, so ist die Ausgabe
ungleich.
Der Algorithmus soll nun untersucht werden. Zunächst einmal der Kommunikationsaufwand: Es ist s < p und p < n2 . Um die Werte von s und p zu übertragen und dazu einige
Verwaltungszeichen brauch man ungefähr 2 × dlg n2 e ∼ 4 ld n Bits. Für n = 245 sind das
weniger als 200 Bits und die kann man ganz einfach übertragen.
Als nächstes zur Fehlerbetrachtung: Sind die Datenbestände gleich, so meldet der Algprithmus gleich. Wenn die Datenbestände ungleich sind, kann es sein, daß der Algorithmus ebefalls die Antwort gleich gibt, und das ist ein Fehler. Wie häufig kann das passieren? Das passiert nur, wenn A 6= B, aber nummer(A) mod p = nummer(B) mod p. Sei
r := nummer(A) mod p. Es ist nummer(A) = n1 p + r und nummer(B) = n2 p + r mit
verschiedenen natürlichen Zahlen n1 und n2 . Es ist also |nummer(A) − nummer(B)| =
|(n1 − n2 )|p. Eine falsche Antwort gibt der Algorithmnus demnach nur, wenn die gewählte
Primzahl |nummer(A) − nummer(B)| teilt. Das wiederum ist nur möglich, wenn p eine
der Primzahlen der (bis auf die Reihenfolge) eindeutig bestimmten Primfaktorenzerlegung
|nummerA) − nummer(B)| = pi11 pi22 · · · pikk
ist. Wir wollen zeigen, daß k ≤ n−1. Dazu stellen wir zunächst |nummer(A)−nummer(B)| <
2n fest, denn der Wert in Betragsstrichen laßt sich mit n Bits darstellen. Wäre nun k ≥ n,
so wäre im Widerspruch hierzu
pi11 pi22 · · · pikk ≥ p1 p2 · · · pn > 1 · 2 · 3 · · · n = n! > 2n
6.3. NÄHERUNGSLÖSUNGEN SCHWIERIGER PROBLEME*
223
Weil mit gleicher Wahrscheinlichkeit als p jede der Primzahlen aus {1, 2, 3, . . . n2 } gewählt
werden kann, ist die Wahrscheinlichkeit eine der maximal n − 1 ungünstigen zu erwischen
Pf ehler =
n−1
ln n2
n−1
∼
<
|P rim(n2 |
n2 / ln n2
n
Für n = 1013 ergibt sich Pf ehler < 3 · 10−12 und das ist eine sehr kleine Wahrscheinlichkeit.
2
Aufgaben
Aufgabe 6.1 Diese Aufgabe gehört zu Beispiel 3 in Unterabschnitt 6.2.2. Zeigen Sie:
α
1. Es gilt nln(n) < 2n für α > 0 und alle hinreichend große n.
α
2. Untersuchen Sie den Verlauf von 2n − nln(n) für einige Werte von α.
Aufgabe 6.2 b. Beispiel 4: Geben Sie für ein festes k > 0 ein n0 an, so daß nk < n! für
alle n ≥ n0 .
Aufgabe 6.3 Zeigen Sie, daß
2n
n
von exponentieller Komplexität ist.
Aufgabe 6.4 Geben Sie ein Beispiel für eine Klasse Hamiltonscher Graphen, bei der
HTML eventuell exponentielle Laufzeit braucht, um einen Hamiltonkreis zu finden.
Aufgabe 6.5 Geben Sie ein Beispiel für eine Klasse von Gaphen ohne Hamiltonkreis, bei
der HMLT exponentielle Zeit braucht, um das festzustellen.
Aufgabe 6.6 Wieso ist CON P eine echte Teilklasse von EX P?
Literatur
Zur Laufzeitanalyse von Algorithmen und Programmen siehe Kowalk ([Kowa1996], Seiten 455-461). Einen guten Einblick gibt auch Weiss ([Weis1995], Kapitel 2). Ausführliche
Darstellungen findet man in Kapitel 3 des Buches von Aho/Ullman [AhoU1995] und in
Kapitel 4 des Buches Cormen/Leiserson/Rivest [CormLR1990]. Spezialwerke sind Sedgewick/Flajolet [SedgF1996] und Gonnet/Baeza-Yates [GonnB1991]. Eine wahre Fundgrube
sind die Bücher von Knuth ([Knut1997], [Knut1998], [Knut1998a]). Hingewiesen sei auch
auf Greene/Knuth [GreeK1981], insbesondere aber auf das mit viel didaktischem Einfühlungsvermögen geschriebene Buch Graham/Knuth/Patashnik [GrahKP1994].
224
KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT
Für Rekurrenzen und erzeugende Funktionen sei außer auf das schon genannte Buch
Cormen/Leiserson/Rivest [CormLR1990] auf Knuth [Knut1997], insbesondere aber auf
Petkovs̆ek/Wilf/Zeilberger [PetkWZ1997] und Wilf [Wilf1990] hingewiesen.
Eine knappe, aber lesenswerte Einführung in Algorithmen und ihre Komplexität findet
man in dem Buch „Applied and Algorithmic Graph Theory“ von Chartrand/Oellermann
[CharO1993]. Ausführlichere Darstellungen von N P-Vollständigkeit sind in Aho/Hoprcroft/
Ullman [AhoHU1974] und in Büchern über Theoretische Informatik, z. B. Blum [Blum1998],
zu finden.
Ein Standardwerk über N P-Vollständigkeit ist [GareJ1979]. Es entält eine umfangreiche
Liste N P-vollständiger Probleme. Wenn man sich nicht sicher ist, ob ein schwieriges
Problem vorliegt oder nicht, sollte man immer zunächst in diesem Buch nachsehen.
Zu Algorithmen, speziell zu Fragen der Komplexität und der Theorie der Algorithmen, existiert auch eine Reihe von Handbüchern. Genannt seien Gonnet/Baeza-Yates [GonnB1991],
von Leeuwen [Leeu1990] und Atallah [Atal1999].
Literatur zu Näherunsglösungen schwieriger Probleme
Als Gesamtübersicht sei auf die schon erwähnten Bücher von Hromkovič [Hrom2001]
und [Hrom2007] hingewiesen. Für pseudopolynomielle Algorithmen siehe Garey/Johnson
[GareJ1979]. Die in Beispiel 6.3 vorgestellte Approximation von optimalen Vergabeplänen durch Listenpläne stammt von Graham [Grah1966] und war wahrscheinlich die erste exakte Abschätzung der Güte eines Approximationsalgorithmus. Sie wurde eigentlich
für Anomalien bei Listenplänen entwickelt. Siehe hierzu Kapitel 3 in Coffman/Denning
[CoffD1973]. Zu Approximationsalgorithmen siehe auch Kapitel 37 in Cormen/Leiserson/
Rivest [CormLR1990], sowie die spezialisierten Werke von Vazirani [Vazi2001] und Hochbaum [Hoch1997]. Zur lokalen Suche und zu Heuristiken gibt es eine größere Zahlvon Büchern: Rego/Alidaee [RegoA2005], Rayward-Smith/ Osman/Reeves/Smith [RaywORS1996],
Glover/Kochenberger [GlovK2003] und weitere. Zur stochastischen Suche siehe man Spall
[Spal2003]. Randomisierte Algorithmen sind in Motwani/Raghavan [MotwR1995] beschrieben.
Teil III
Einfache Datenstrukturen
225
Kapitel 7
Allgemeines zu Datenstrukturen
7.1
Sätze und Vetretersätze
In Abschnitt 2.4, Seite 37, wurden Datenstrukturen (data structure) als Datentypen eingeführt, die weder direkt in der Hardware noch direkt in der Programmiersprache definiert
sind.1 Sie müssen durch zusätzliche Programme oder Programmteile realisiert werden. Einfache Datenstrukturen wie z. B. Listen (Kapitel 8) oder Suchbäume (Kapitel 9) werden
in diesem Teil des Buches behandelt. Teil IV ist komplexeren Datenstrukturen, nämlich
Allgemeinen Graphen gewidmet. Diesen und anderen Datenstrukturen ist gemeinsam,
daß sie aus Sätzen bestehen. Zu Sätzen und Feldern siehe Unterabschnitt 2.6.4, Seite
68. Wir wollen uns auch weiterhin auf den Fall beschränken, Sätze und daraus gebildete
Datenstrukturen nur innerhalb eines Programmlaufes zu betrachten. Auf Sätze und Datenstrukturen in Dateien und Datenbanken wird nur in Abschnitt 9.4 kurz eingegangen.
Die Überlegungen gelten jedoch weitgehend auch für diese.
Für Datenstrukturen muß die Frage „Was ist ein Satz“? etwas genauer untersucht werden.
Die entscheidende Frage ist „Was identfiziert einen Satz innerhalb einer konkreten Datentruktur?“ Ein Satz ist ein Bitmuster eines bestimmten Aufbaus, das an einer definierten
Stelle des Adreßraumes des Programmlaufs steht. Ein Bitmuster gleichen Aufbaus an einer
anderen Stelle des Adreßraumes ist ein anderer Satz. C und andere Programmiersprachen
sorgen nicht dafür, daß die Bitmuster, d.h. die Inhalte, verschieder Sätze auch verschieden
sind. Das wäre auch nicht sinnvoll. Allerdings gewährleisten sie, daß sich die Bitmuster
verschiedener Sätze nicht überlappen. Es gibt explizit aufzurufende Ausnahmen, die sich
bei guter Programmierung fast immer vermeiden lassen. Der Aufbau eines Satzes aus
Feldern wird durch den Satztyp (Satzklasse, record type, record class) bestimmt. Einfache Datenstrukturen wie z. B. Listen bestehen i. A. aus Sätzen einer einzigen Satzklasse.
Streng genommen, muß man zwischen einer Datenstruktur als Datentyp und einer konkret vorliegenden Realisierung unterscheiden. Wir wollen das nicht tun. Aus dem Zusammenhang wird klar werden,
was gemeint ist.
1
227
228
KAPITEL 7. ALLGEMEINES ZU DATENSTRUKTUREN
Komplexere Datenstrukturen wie z. B. Graphen (siehe Kapitel 13) können durchaus aus
Sätzen unterschiedlichen Typs gebildet sein.
Was ist zu tun, wenn nun ein und der gleiche Satz in einer Datenstruktur mehrfach
auftreten soll? Als Beispiel nehmen wir im Vorgriff auf Unterabschnitt 8.3.1 an, daß eine
verkettete Liste vorliegt. Abbildung 8.1 zeigt die Verkettung mit Hilfe von Adreßfeldern.
Soll in dieser Liste ein Satz mehrfach auftreten, so funktioniert die Lösung nicht, denn in
ein und dem gleichen Adreßfeld können nicht mehre Verweise eingetragen sein. Man hilft
sich, indem man Vertretersätze (Zeigersatz, reference record) einführt. Das sind Sätze, die
für den eigentlichen Satz in der Kette stehen. Abbildung 7.1 zeigt zwei Vertretersätze eines
◦............................................................................................... ◦............................................................................................... ◦............................................................................................... ◦................................................................................................ •
.
.
..
...........................................................................................◦
...
.........................................................................................◦
..
...........................................................................................◦
..
• .............................................................................................◦...
..
.
.
.....
........
..................
.......... ..
......
.......... .....
......
..........
.
.
.
.
......
.
.
.
.
..
......
..........
......
..........
......
..........
......
..........
......
.........
.
.
.
.
.
.
.
......
.
.
..
......
..........
......
..........
......
.........
......
..........
......
.........
.
.
.
.
.
.
.
.
.
.
......
.........
......
.........
......
..........
...... .
..........
........
. ...........
.................................................
Abbildung 7.1: Vertretersätze in eine Liste
Satzes in einer Liste. Auf die gleiche Art kann ein Satz durch Vertretersätze in verschiedene
Ketten eingefügt werden. Vertretersätze sind nicht nur für Verkettungen notwendig und
nützlich, sondern auch an anderen Stellen, z. B. bei Listenrealisierung durch Reihungen.
Ein Nachteil von Vertretersätzen ist, daß es in einem Satz i. a. keine Rückverweise auf
seine Vertretersätze gibt.
7.2
Schlüssel
Häufig haben die Sätze eines Klasse ein Feld2 , dessen Inhalt den Satz eindeutig kennzeichnet. Man spricht von einem Schlüsselfeld (key field) oder kurz von einem Schlüssel.
Die Werte, die in einem Schlüsselfeld auftreten können, bilden die Schüsselwertmenge
(Schlüsselwertbereich, Schlüsselwertraum). Diese kann sehr groß sein, z. B. ist die Menge der Zeichenreihen der Länge mindestens 15 bestehend aus Großbuchstaben größer als
2615 . In vielen Fällen ist die Zahl der Schlüsselwerte, die in einer gegebenen Datenstruktur
real auftreten, sehr viel kleiner. Oft, aber nicht immer, sind Schlüsselwertmengen linear
2
Unter Umständen kann das auch eine Kombination aus mehreren Feldern sein.
7.2. SCHLÜSSEL
229
geordnet, z. B. lexikographisch. In der Regel wird dann die Ordnung für den Aufbau und
die Bearbeitung von Datenstrukturen benutzt.
Bei den getroffenen Annahmen ist es Aufgabe des Programmlaufes3, dafür zu sorgen, daß
bei Datenstrukturen mit einem Schlüsselfeld ein Schlüsselwert höchstens einmal auftritt.
Wenn es kein identifizierendes Schlüsselfeld gibt, kann es sein, daß sich zwei verschiedene
Sätze einer Klasse nur durch ihre Adressen, aber nicht durch ihre Inhalte unterscheiden.
Natürlich können zwei analog aufgebaute konkrete Datenstrukturen, z. B. zwei Listen,
zwei verschiedene Sätze mit identischen Schlüsselwerten enthalten.
Des öfteren werden in Datenstrukturen auch Felder einer Satzklasse benutzt, die keine
Schlüsselfelder sind, deren Inhalte einen Satz also nicht eindeutig identifizieren. In Personalsätzen ist das z. B. das Feld Nachname. Man spricht von Sekundärschlüsseln (secondary
key) und nennt in diesem Fall zur Betonung des Unterschieds identifizierende Schlüssel
häufig auch Primärschlüssel (primary key) Auch Sekundärschlüssel haben einen (möglicherweise linear geordneten) Schlüsselwertbereich und auch sie werden zum Aufbau und
zur Bearbeitung von Datenstrukturen benutzt. Wir wollen uns allerdings in den folgenden
Kapiteln auf Primärschlüssel beschränken.
Auf Schlüssel soll noch etwas genauer eingegangen werden. Die Menge der möglichen
Schlüsselwerte, der Schlüsselwertraum, soll mit K bezeichnet werden. k := |K| ist die
Anzahl der möglichen Schlüsselwerte. Die Menge der in einer Datenstrutur wirklich vorkommenden Schlüsslewerte soll R heißen. Manchmal ist sie während der Bearbeitung
konstant. Ist sie das nicht, wird sie im Allgemeinen während der Bearbeitung wachsen,
gelegentlich auch schrumpfen. Die Anzahl real vorkommender Schlüsselwerte ist r := |R|.
Nicht selten ist k >> r. Als dritte Menge wollen wir S einführen. Das ist die Menge von
Plätzen, die die Sätze der Datenstruktur im Speicher (Adreßraum) belegen. Ihre Anzahl
ist s := |S|. Für Primärschlüssel gilt natürlich s = r. Bei Sekundärschlüsseln gilt i. A.
s > r. Wir nehmen weiter an, daß auf K eine lineare Ordnung gegeben ist.
Schlüssel werden in Datenstrukturen nicht nur zur eindeutigen Identifizierung von Sätzen
benutzt, sondern auf vielfältige Weise auch zum Aufbau von Datenstrukturen und zur Implementierung von Operationen auf diesen. Die folgenden Kapitel zeigen viele Beispiele
dafür. Für die Operationen werden jeweils Effizienzbetrachtungen durchgeführt und dabei
die in Kapitel 1 eingeführten Größen bestimmt: Für den schlechtesten Fall (WOC), für
den besten Fall (BEC) und für den mittleren Fall (AVC). Bei gegebenem Schlüsselwertraum K gilt WOC für alle R einer gegebenen Datenstruktur – z. B. für alle Suchbäume
– und hängt nur von Umfang r ab. Um Aussagen für den mittleren Fall AVC machen
zu können, müssen Annahmen über die Häufigkeit, mit der Schlüsselwerte auftreten, gemacht werden. Dazu betrachten wir an dieser Stelle nur die wichtigste Operation: SEARCH.
Dabei wird ein Schlüsselwert s aus K angegeben und in der Datentruktur der Satz mit
diesem Schlüsselwert gesucht. Wir nehmen zunächst einmal an, daß es einen Satz mit
3
D.h. des Programms, das Datenstrukturen aufbaut und verwaltet.
230
KAPITEL 7. ALLGEMEINES ZU DATENSTRUKTUREN
diesem Schlüsselwert in der Datenstruktur gibt. Wir nehmen weiter an, daß für jeden
Schlüsselwert s ∈ R die Wahrscheinlichkeit ps , daß dieser Wert gesucht wird, bekannt ist
und daß ebenso der Aufwand ts , den Satz mit dem Schlüssel s in der Datentruktur zu finden, angegeben werden kann. Dann wird man für diesen Fall sinnvollerweise
P die mittlere
4
Komplexität durch den Erwartungswert definieren, also AV C := E(ts ) =
ps ts . Nur in
s∈R
seltenen Fällen wird in der Praxis die Wahrscheinlichkeit ps bekannt sein. Wie in solchen
Fällen üblich, nimmt man dann an, daß alle Schlüsselwerte
die gleiche Wahrscheinlichkeit
P
1
= 1r . Das ergibt ACV = 1r
ts .
haben, nämlich ps = |R|
s∈R
Wenn s ein Schlüsselwert ist, zu dem kein Satz in der Datenstruktur existiert, wird es
schwieriger. Die Feststellung „Es gibt in der Datestruktur keinen Satz mit Schlüsselwert s“
erfordert bei vielen Datenstrukturen die Bestimmung der zu s gehörenden Lücke. Unter
der Voraussetzung R 6= ∅, also daß in der Datenstruktur Sätze existieren, wird s in eine
Lücke (gap) in K fallen, die durch zwei, eventuell auch nur durch einen Schlüsselwert
aus R begrenzt ist. Welche Lücken es gibt und wie groß sie sind, wird durch R bestimmt
und ist für verschiedene R sehr unterschiedlich. In der Praxis wird es i. A. so sein, daß
eine Operation SEARCH deutlich häufiger mit einem existierenden Schlüsselwert auftritt
als mit einem, der nicht in der Datenstruktur vorkommt. Wird angenommen, daß letztere
mit gleicher Wahrscheinlichkeit auftreten (was auch nicht sehr praxisgerecht ist), so ist
die Wahrscheinlichkeit einer Lücke proportional zu ihrer Größe. Auf keinen Fall ist es naheliegend und plausibel anzunehmen, daß alle Lücken mit der gleichen Wahrscheinlichkeit
auftreten.
4
Zu den Grundbegriffen der Wahrscheinlichkeitstheorie siehe Kapitel B, Seite 619, im Anhang.
Kapitel 8
Listen
8.1
8.1.1
Terminologie und Grundlagen
Definition und Beispiele
Listen sind die einfachsten und wohl auch die häufigsten Datenstrukturen in der Informatik.
Definition 8.1 Eine Liste (list) ist leer oder eine endliche Folge von Elementen.
Diese Definition ist sehr allgemein. Wir wollen zusätzlich verlangen, daß alle Elemente
vom gleichen Datentyp sind. Wir wollen die Elemente von Listen Sätze nennen, auch
wenn es sich um Werte eines elementaren Datentyps handelt. Zu Sätzen und Feldern
siehe Unterabschnitt 2.6.4, Seite 68, insbesondere siehe auch Kapitel 7. Als endliche Folgen sind Listen Abbildungen eines Anfangsstücks der natürlichen Zahlen in die Menge
der Listenelemente (vgl. Abschnitt A.3 im Anhang). Die Anzahl Zahlen im Anfangsstück
ist die Länge der Liste. Die leere Liste hat die Länge 0. Für Listen gibt es unterschiedliche Schreibweisen. Wir wollen Listen in runde Klammern setzen: (a0 , a1 , . . . , an ) oder
(K1,K2,. . ., KM). Gelegentlich werden die Klammern auch weggelassen.
Beispiele
a. (2, 3, 5, 7, 11, 13, 17, 19)
Die ersten 8 Primzahlen.
b. (Helium, Neon, Argon, Krypton, Xenon, Radon)
Die Edelgase in aufsteigender Reihenfolge der Ordnungszahlen.
c. (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
Die Monatslängen in Nicht-Schaltjahren.
d. Die Zeichen einer Zeichenreihe bilden eine Liste, ebenso die Zeilen eines Textes.
231
232
KAPITEL 8. LISTEN
e. (0.00,
(-1.15,
(1.00,
Eine Liste
0.00, 0.00, 0.00, 0.00)
2.89, 0.00)
1.00, 1.00, 7.00, 1.00)
von variabel langen Zahlenlisten.
f. Bibliothekskartei.
Der Buchbestand einer kleinen Privtabiliothek sei auf Karteikarten erfaßt. Diese seien
nach Autoren geordnet und mögen außer den Autorennamen noch den Titel und weitere
Angaben enthalten. Jede Karte kann als Satz einer Liste aufgefaßt werden. Dieses Beispiel
zeigt, daß Listen nicht nur in der Informatik und erst recht nicht nur in Programmen
vorkommen. Wir wollen allerdings im weiteren Listen und andere Datenstrukturen immer
als programminterne Konstrukte vertehen.
2
Eine Teilliste (Unterliste, Teilfolge) ist eine Liste, die einige Elemente der ursprünglichen
Liste in der gleichen Reihenfolge enthält. Eine Teilliste, die Anfangsstück einer gegebenen
Liste ist, heißt Präfix. Ist sie Endstück, wird sie Suffix genannt. Das erste Element einer
Liste heißt Kopf (head). Die verbleibende Liste wird als Restliste (tail) bezeichnet.
8.1.2
Operationen mit Listen
Listenoperationen legen fest, wie man Listen aufbaut, d. h. verlängert oder verkürzt, und
wie man in einer Liste etwas findet.
Listen mit positionsabhängigen Operationen
Als Bilder eines Anfangsstücks der natürlichen Zahlen weisen die Elemente einer Liste eine
(strikte) lineare Ordnung auf. Ein Element der Liste wird durch seine Position indentifiziert. Die Zählung beginnt bei 0 . Dabei ist es durchaus möglich und oft auch sinnvoll,
daß ein und das gleiche Element an mehreren Stellen der Liste steht. Achtung: Oft meint
man damit, das der gleiche Wert, z. B. eine Zahl, an verschiedenen Stellen der Liste steht.
Nach den Festlegungen in Abschnitt 7.1, Seite 227, sind das dann verschiedene Sätze. Ist
wirklich der gleiche Satz gemeint, so werden i. a. Vertretersätze benutzt.
Wir wollen in Listen einzelne Sätze suchen (finden, search, find), einfügen (insert) und löschen (entfernen, delete, remove). Darüber hinaus soll es möglich sein, die Listenelemente
nacheinander in einem Listendurchlauf (traversing a list) zu bearbeiten. Dazu müssen wir
auf den Nachfolger (successor) bzw. den Vorgänger (predecessor) eine Satzes zugreifen
können. Zu diesem Zweck führt man bei der Bearbeitung einer Liste einen Aktualitätszeiger (current pointer). Dieser wird bei Such- und Einfügeoperationen gesetzt. Bei der
Bearbeitung von Listen setzen wir voraus, daß es eine Beschreibung für die Liste als Ganzes gibt (Listenbeschreibung, list description) Darin gibt es einen Verweis auf das erste
und auf das letzte Element der Liste und eine Angabe über ihre Länge. Außerdem gibt
es einen Verweis auf das aktuelle Listenelement, den Aktualitätszeiger. Diese Angaben
8.1. TERMINOLOGIE UND GRUNDLAGEN
233
werden bei Listenveränderungen auf dem Laufenden gehalten und können unhabhängig
vom Umfang der Liste in einer Operation abgefragt werden.
Wir betrachten die folgenden positionsabhängigen Operationen:
1. SIZE
Als Ergebnis wird die Länge n der Liste zurückgeliefert, 0 wenn die Liste leer ist.
n − 1 ist die höchste Position in der Liste.
2. SEARCH
Es wird das Element der Liste, das an einer gegebenen Position steht, gesucht und
bereitgestellt. Ist die Liste leer oder die angegebene Position zu groß, so wird ein
Nullverweis (NIL) bereitgestellt.
3. INSERT
Die Liste wird um ein Element vergrößert. Es wird eine Position angegeben und
das Element an dieser Stelle in die Liste eingefügt. Dahinterstehende Listenelemente werden in der Position um 1 nach hinten verschoben. Fehlermeldung, falls die
angegebene Position negativ oder um mehr als 1 größer als die letzte Position der
Liste ist.
4. DELETE
Die Liste wird um ein Element verkürzt. Es wird eine Position angegeben und das
Element an dieser Stelle aus der Liste entfernt, es wird gelöscht1 . Dahinterstehende
Listenelemente werden in der Position um 1 nach vorn verschoben. Fehlermeldung,
falls die angegebene Position nicht in der Liste vorhanden ist.
5. NEXT
Ausgehend vom aktuellen Listenelement (Aktualitätszeiger) wird das darauffolgende
Listenelement bereitgestellt. Der Aktualitätszeiger wird weitergeschaltet. Fehlermeldung, falls der Aktualitätszeiger undefiniert ist oder kein nächstes Element existiert.
6. PREVIOUS
Ausgehend vom aktuellen Listenelement (Aktualitätszeiger) wird das vorangehende
Listenelement bereitgestellt. Der Aktualitätszeiger wird zurückgeschaltet. Fehlermeldung, falls der Aktualitätszeiger undefiniert ist oder kein vorangehendes Element
existiert.
Mit diesen Operationen lassen sich leicht weitere Operationen, die ganze Listen bearbeiten, aufbauen. Man kann eine Liste als Teilliste in eine andere Liste einfügen, als Spezialfall
Man spricht von „Löschen“, obwohl im allgemeinen der Satz nach dem Entfernen aus der Liste durchaus weiter existiert und z.B. an einer anderen Stelle wieder eingefügt werden kann. Die Bezeichnung
remove wäre geeigneter.
1
234
KAPITEL 8. LISTEN
(Konkatenation) kann eine Liste an eine andere angefügt werden. Eine Liste kann in Teillisten zerlegt werden. Eine Teilliste kann gelöscht werden.
Listen mit schlüsselwertabhängigen Operationen
Wir nehmen an, daß die Sätze in der Liste ein identifizierendes Schlüsselfeld aufweisen und
daß die Schlüssewertmenge linear geordnet ist. In diesem Fall kann die Liste in aufsteigedner Reihefolge der Schlüsselwerte aufgebaut und diese Werte statt der Listenpostion zum
Suchen der Sätze benutzt werden. Das gilt auch dann, wenn nachfolgende Einfügungen
und Löschungen die Position der Elemente in der Liste verändert haben. Gelegentlich ist
es vorteilhaft, in der Liste die entgegengesetze Ordung wiederzugeben. Man spricht von
aufsteigend bzw. absteigend geordneten (sortierten) Listen. Auch bei der Benutzung von
Schlüsselwerten sollen Sätze gesucht, eingefügt und gelöscht werden können. Außerdem
wird auch hier ein Aktualitätszeiger geführt. Wir führen die folgenden schlüsselwertabhängigen Operationen ein und benutzen dabei auch wieder eine Listenbeschreibung.
1. SIZE
Als Ergebnis wird die Länge n der Liste zurückgeliefert, 0 wenn die Liste leer ist.
2. SEARCH
Es wird ein Element der Liste nach seinem Schlüsselwert gesucht und bereitgestellt.
Wird das Element nicht gefunden, so wird ein Nullverweis (NIL) zurückgeliefert.
3. FIND
Entspricht SEARCH. Ist ein Satz mit dem gegebenen Schlüsselwert nicht in der Liste
und dieser kleiner als der größte und größer als der kleinste existierende Schlüsselwert, so wird der Satz mit dem nächstkleineren Schlüsselwert bereitgestellt. Anderfalls wird ein Nullverweis zurückgeliefert2 .
4. INSERT
Es wird ein weiteres Element in die Liste eingefügt. Das geschieht an der Stelle der
Liste, die dem Schlüsselwert des einzufügenden Satzes (in aufsteigender Sortierung)
entspricht. Ist ein Satz mit gleichem Schlüsselwert in der Liste schon vorhanden, so
erfolgt eine Fehlermeldung.
5. DELETE
Es wird das Listenelement mit dem gegebenen Schlüsselwert gesucht und aus der
Liste der entfernt, es wird gelöscht. Existiert kein Element mit diesem Schüsselwert,
so erfolgt eine Fehlermeldung.
Üblicherweise werden die Begriffe SEARCH und FIND synonym benutzt. Die hier gewählte Form der
zusätzlichen Positionierung für FIND ist nützlich, aber selten.
2
8.2. BINÄRSUCHE
235
6. MIN
Es wird das Listenelement mit dem kleinsten Schlüsselwert, also das erste Element
der Liste zurückgeliefert. Fehlermeldung, falls Liste leer ist.
7. MAX
Es wird das Listenelement mit dem größten Schlüsselwert, also das letzte Element
der Liste zurückgeliefert. Fehlermeldung, falls Liste leer ist.
8. NEXT
Ausgehend vom aktuellen Schlüsselwert (Aktualitätszeiger) wird das Listenelement
mit nächstgrößerem Schlüsselwert gesucht und zurückgeliefert. Der Aktualitätszeiger wird weitergeschaltet. Fehlermeldung, falls es keine Listelemente mit größeren
Schlüsselwerten gibt.
9. PREVIOUS
Ausgehend vom aktuellen Schlüsselwert (Aktualitätszeiger) wird das Listenelement
mit nächstkleinerem Schlüsselwert gesucht und zurückgeliefert. Der Aktualitätszeiger wird zurückgeschaltet. Fehlermeldung, falls es keine Listelemente mit kleineren
Schlüsselwerten gibt.
In Abschnitt 8.3 soll untersucht werden, wie sich Listen und Operationen mit ihnen realisieren lassen. Werden Listen als Reihungen aufgebaut, so kann mit einer speziellen Technik, der Binärsuche, in ihnen sehr effizient gesucht werden. Ihre Bedeutung wegen wird
Binärsuche vorab in einem eigenen Abschnitt vorgestellt.
8.2
Binärsuche
Es sei eine Reihung (siehe 2.6.1) von Werten eines elementaren Typs gegeben, z. B. ganze
Zahlen oder Zeichenreihen. Für die Werte gebe es eine lineare Ordnung und die Werte
seien in der Reihung in aufsteigender Reihenfolge gespeichert. Duplikate wollen wir nicht
zulassen, siehe jedoch Anmerkung 8.1. Binärsuche (binary search) ist ein Verfahren, mit
dem in einer solchen Reihung ein Element mit einem gegebenen Wert sehr effizient gefunden werden kann. Gibt es kein Element mit diesem Wert, so wird auch das sehr effizient
festgestellt. Die Idee ist einfach: Wir suchen das mittlere Element der Reihung und vergleichen seinen Wert mit dem Suchwert. Bei Gleichheit haben wir das gesuchte Element
gefunden. Ist der Suchwert kleiner, wird in der ersten Reihungshälfte weitergesucht, ist
er größer, in der zweiten. Das Verfahren endet, wenn die Teilreihung, in der weitergesucht werden soll, die Länge 0 oder 1 hat. Im ersten Fall gibt es keinen Eintrag mit dem
gegebenen Suchwert. Im zweiten Fall genügt ein weiterer Vergleich, um das gesuchte Reihungselement zu finden oder festzustellen, daß es ein solches nicht gibt. Schließlich muß
236
KAPITEL 8. LISTEN
noch eine Vereinbarung getroffen werden, welcher von zwei Kandidaten das mittlere Element einer Reihung gerader Länge ist. Wir wollen uns auf das Element mit kleinerem
Index festlegen.
Beispiel 8.1 Die Werte SEHEN, DER, RUHEN, HAT, ANTON, ZAHN, TANNE, BESUCH, HUT, KNABE,
LAGE, ROT, QUARK, PFAU, NOCH, NEIN, LUST, ZANK seien in einer Reihung in aufsteigender
Reihenfolge der Werte gespeichert. Siehe Tabelle 8.1. Wir wollen mit Binärsuche den Wert
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ANTON
BESUCH
DER
HAT
HUT
KNABE
LAGE
LUST
NEIN
NOCH
PFAU
QUARK
ROT
RUHEN
SEHEN
TANNE
ZAHN
ZANK
Tabelle 8.1: Alphabetisch geordnete Liste als Reihung
TANNE suchen. Es ergibt sich der in Tabelle 8.2 dargestellte Ablauf. Suchen wir den Wert
TONNE, so erbgibt sich der Ablauf von Tabelle 8.3. Das Suchen von TANK können wir in
Tabelle 8.4 verfolgen.
2
Indexintervall
[0, 17]
[9, 17]
[14, 17]
Mittelposition
8
13
15
Wert
NEIN
RUHEN
TANNE
Vergleich
größer
größer
gleich
Tabelle 8.2: Binärsuche des Wertes TANNE
8.2. BINÄRSUCHE
237
Indexintervall
[0, 17]
[9, 17]
[14, 17]
[16, 17]
leer
Mittelposition
8
13
15
16
Wert
NEIN
RUHEN
TANNE
ZAHN
Vergleich
größer
größer
größer
kleiner
nicht gefunden
Tabelle 8.3: Binärsuche des Wertes TONNE
Indexintervall
[0, 17]
[9, 17]
[14, 17]
[14, 14]
Mittelposition
8
13
15
14
Wert
NEIN
RUHEN
TANNE
SEHEN
Vergleich
größer
größer
kleiner
ungleich
nicht gefunden
Tabelle 8.4: Binärsuche des Wertes TANK
In Tabelle 8.5 ist Binärsuche als Unterprogramm in C angegeben. Das Programm erwartet
als Eingabe die Indexwerte low und high und den zu suchenden Wert compare. Die Reihung, in der zu suchen ist, ist als globaler Parameter array einzurichten. Der Einfachheit
halber sind die Werte ganze Zahlen. Es ist einfach, das Programm so abzuändern, daß
andere Werte, z. B. Zeichenreihen, verwendet werden.
Als Ergebnis liefert das Programm -1, wenn der gesuchte Wert nicht in der Reihung auftritt, und den Index in der Reihung anderenfalls. Das Programm arbeitet rekursiv. Die
Rekursion endet in einer der Anweisungen 1, 2 oder 9. Es ist Aufgabe des Rahmenprogramms, das BINSEARCH zum ersten Mal aufruft, die Reihung array richtig einzurichten
und für die Parameter low und high die richtigen Anfangswerte zu setzen.
Binärsuche ist ein einfaches und leistungsfähiges Verfahren, dessen Programmierung jedoch Tücken aufweisen kann. Nicht zu Unrecht steht im englischsprachlichen Wikipedia3 :
Binary search is one of the trickiest “simple” algorithms to program correctly. Die Lektüre
der dort zitierten Anmerkungen von Bently [Bent2000], Seite 34, und Kruse [KrusTL1997],
Seite 280, wird sehr empfohlen. Von besonderer Bedeutung sind Fehler, die in neuerer Zeit
gefunden wurden und die erst sichtbar wurden, als in der Praxis extrem große Reihungen
durchsucht werden mußten. Die Fehler ergeben sich aus Überläufen des Zahlenbereichs
für Indizes. Im Programm BINSEARCH von Tabelle 8.5 wird in Zeile 8 des mittlere Element einer Liste mit med = low + (high - low) /2 berechnet. Es wäre näherliegend
3
http://en.wikipedia.org/wiki/Binary_search
238
'
&
KAPITEL 8. LISTEN
int
{
1
2
3
4
5
6
7
8
9
10
11
12
13
}
BINSEARCH (int low, int high, int compare)
$
if (high < low) return -1;
if (high == low)
{ if( array[high] == compare)
{ return high; }
else
{ return -1; }
}
med = low + (high - low) / 2;
if (array[med] == compare) return med;
if (array[med] < compare)
{ BINSEARCH (low, med - 1);}
else
{ BINSEARCH (med + 1, high);}
%
Tabelle 8.5: Programm zur Binärsuche in einer Tabelle
und einfacher, die Berechnung mit (low + high) / 2 auszuführen, und so hat man es
auch jahrelang gemacht. Mathematisch sind beide Rechnungen gleichwertig. Im endlichen Bereich von Darstellungen ganzer Zahlen durch Bitmuster (Abschnitt 3.3, Seite 101)
ist das aber nicht der Fall. Das Zwischenergebnis low + high kann aus dem darstellbaren Bereich hinausführen und unübersichtliche Folgefehler erzeugen4 . Sind low und high
korrekte Darstellungen nichtnegativer Zahlen und gilt low < high, so bleiben in der Anweisung 8 alle Zwischenergebnisse und das Endergebnis im darstellbaren Bereich und sind
nichtnegativ.
Komplexität der Binärsuche
Wir nehmen an, es liege eine Reihung der Länge n vor mit aufsteigend geordneten Werten.
Duplikate sollen nicht vorkommen. Wir wollen die Komplexitätswerte W OC(n), BEC(n)
und AV G(n), gemessen in der Anzahl Aufrufe von BINSEARCH, bestimmen.
Es ist BEC(n) = 1, denn man kann mit Glück den gesuchten Wert beim ersten Zugriff
finden.
Berechnung von W OC(n). Dafür kann die Anzahl Schritte genommen werden, die es
4
http://googleresearch.blogspot.com/2006/06/extra-extra-read-all-about-it-nearly.html
8.3. REALISIERUNG VON LISTEN
239
braucht, um das letzte Element der Reihung zu finden. Mit 1 weiteren Schritt kann dann
entschieden werden, ob der vorgegebene Wert gefunden wurde oder nicht in der Reihung
vorkommt. Das letzte Element der Reihung ist erst bei einem Intervall der Länge 1 das
mittlere Element und bei jedem Halbieren wird mit der größeren Hälfte fortgefahren. Sei
nun k durch 2k−1 < n ≤ 2k eindeutig bestimmt. In diesem Fall kann das letzte Element
der Reihung nicht nach k − 1 Teilungen in einem Intervall der Länge 1 liegen (warum?).
Es werden also mindesten k Schritte zum Finden des Elementes benötigt. Andererseits ist
man spätestens nach k Halbierungen immer bei einem Intervall der Länge 1. Wir haben
gezeigt
W OC(n) = dld (n)e + 1 d. h. W OC(n) = Θ(ln(n))
(8.1)
Für die mittlere Komplexität AV C(n) folgt aus Gleichung 8.1 AV C(n) = O(ln(n). Setzt
man voraus, daß alle Werte mit gleicher Wahrscheinlichkeit gesucht werden, so gilt sogar
AV C(n) = Θ(ln(n)). Das soll hier nicht bewiesen werden.
Anmerkung 8.1 a. Binärsuche liefert auch ein Ergebnis für Werte, die nicht in der
Reihung vorhanden sind. Es ist leicht zu sehen, daß auch hierfür Gleichung 8.1 gilt.
b. Die obige Berechnung der Komplexität gilt nicht mehr, wenn Duplikate zugelassen
werden. Das sieht man leicht für den Extremfall, daß alle Reihungselemente den gleichen
Wert haben. Im allgemeinen Fall bilden die Duplikate, also die Reihenelemente mit gleichem Schlüssel, ein zusammenhängendes Teilintervall der Reihung. Es ist klar, daß die
Anzahl Schritte, mit Binärsuche ein solches Teilintervall zu treffen, im schlechtesten Fall
nicht größer sein, kann als die Anzahl Schritte, die man braucht, um einen nur einmal
vorkommenden Wert zu finden. Genauer soll das hier nicht untersucht werden.
2
8.3
8.3.1
Realisierung von Listen
Verkettete Listen
Es ist naheliegend, Listen dadurch zu realisieren, daß man in jedem Element einen Zeiger
(pointer) speichert, der auf das nachfolgende Element zeigt. Im Englischen wird oft von
einem link gesprochen und verkettete Listen heißen linked lists. Es ist oft sinnvoll, doppelt
verkette Listen (doubly linked lists) zu benutzen. In dieser hat jedes Element einen zusätzlichen Zeiger auf das vorangehede Element der Liste. Abbildung 8.1 zeigt eine schematische
Darstellung. Jeder Satz hat zwei Verweisfelder, schwarze Kreise zeigen NIL-Verweise an.
Diese Verkettung ist nicht möglich, wenn ein Satz mehrfach in einer Kette vorkommt
oder in mehr als eine Kette eingefügt werden soll. Die Adreßfelder in dem Satz können
nur einmal belegt werden. Als Lösung bieten sich die schon in Abschnitt 7.1 eingeführten
Vertretersätze an. Wenn die Ketten, in die ein Satz eingefügt werden soll, vorher bekannt
sind und der Satz in jeder dieser Ketten höchstens einmal auftritt, kann man an Stelle von
Vertretersätzen auch mehrfache Verkettungsfelder benutzen. Abbildung 8.2 zeigt einen
240
KAPITEL 8. LISTEN
◦.............................................................................................. ◦............................................................................................... ◦.............................................................................................. ◦............................................................................................... •
..
..
..
.........................................................................................◦
..........................................................................................◦
.........................................................................................◦
...
..
...
• ............................................................................................◦...
.
.
.
Abbildung 8.1: Doppelt verkettete Liste
............
........................
.
........................
...............................
.............................
........................
....
.......................
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
..............................
.......
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
........................
.
....
.
.
.
.
.
.
...
........................
..................
..................................
........................
....
◦
◦
............
◦............................................................................... .....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
.......................
.......................
........................
.
.
.
.
.
.
.
.
.......
.
.
.
.
.
.
.
.
.
.
◦..
...................................
..........
........................
...
........................
....
..... .........................
.............................
....................
Abbildung 8.2: Mehrfache Verkettungsfelder
Satz in zwei verschiedenen Ketten. Mehrfache Verkettungsfelder eignen sich nicht für
mehrfaches Einfügen eines Satzes in die gleiche Kette und sind auch weniger flexibel als
Vertretersätze.
Mischsortieren, das in Unterabschnitt 6.1.2, Seite 178, vorgestellt wurde, liefert ein gutes
Beispiel für die Anwendung von (einfach) verketteten Listen. Es zeigt ausführlich, wie
Listenzeiger in C benutzt werden können und welche Vorteile rekursive Aufrufe bei der
Bearbeitung von Listen bieten.
Realisierung positionsabhängiger Operationen
SIZE: Diese Operation kann anhand der Listenbeschreibung stets in einer Operation
ermittelt werden.
W OCSIZE (n) = AV CSIZE (n) = BECSIZE (n) = 1
(8.2)
SEARCH(p): Es ist das Listenelement mit der Positionsnummer p zu suchen und bereitzustellen. Durch Vergleich mit der Listenlänge kann in einer Operation festgestellt werden,
ob das Element existiert oder nicht. Wenn es existiert, werden beginnend am Listenanfang
8.3. REALISIERUNG VON LISTEN
241
solange nacheinander Listenelemente durchlaufen, bis die Positionsnummer erreicht ist.
Der Aufwand wird in der Anzahl durchlaufener Sätze gemessen:
W OCSEARCH (n) = n;
AV CSEARCH (n) =
n+1
;
2
BECSEARCH (n) = 1
Zur Herleitung von AVC: Man braucht i + 1 Zugriffe, wenn das Element an der Position
i (i = 0, 1, . . . , n − 1) gesucht wird. Wir nehmen an, daß jede der n möglichen Positionen mit gleicher Wahrscheinlichkeit n1 auftritt. Dann ergibt sich für den Erwartungswert
(Mittelwert)
AV CSEARCH (n) =
1
1
1
1
1 n(n + 1)
n+1
· 1 + · 2 + · · · · n = · (1 + 2 + · · · + n) = ·
=
n
n
n
n
n
2
2
Zusammenfassend
W OCSEARCH (n) = O(n);
AV CSEARCH (n) = O(n);
BECSEARCH (n) = O(1) (8.3)
Man kann den Suchzugriff etwas schneller machen, wenn man berücksichtigt, daß das
letzte Listenelement über die Listenbeschreibung direkt errreicht werden kann. Liegen
Rückwärtsverweise vor, so werden die Elemente der zweiten Listenhälfte schneller erreicht,
wenn man mit dem Durchlauf am Ende de Liste anfängt. Diese Verbesserungen bringen
jedoch nicht viel. Die Gleichungen 8.3 gelten weiterhin.
INSERT(p): Ein gegebener Satz wird an Position p als neues Listenelement eingefügt. Es
sind zwei Fälle zu unterscheiden.
a. Die Position p existiert in der Liste. Dann wird mit SEARCH(p) der entsprechende Satz
gesucht und der neue Satz als sein Vorgänger in die Liste eingekettet. Siehe schematische
Abbildung 8.3, in der Satz L vor Satz G eingekettet wurde.
.
.
.
· · ·................................................. ◦..................................................................... ◦..................................................................... · · ·
.
.
.
.
.
.
.
.
.
· · ·..............................................................◦... ................................................................◦... ............................................ · · ·
F
G
.
.
.
.
· · ·........................................... ◦..................................................................... ◦..................................................................... ◦..................................................................... · · ·
.
.
.
.
.
.
.
.
.
.
.
.
· · ·...........................................................◦.. .................................................................◦.. ................................................................◦... ............................................ · · ·
F
L
G
Abbildung 8.3: Einketten eines Satzes
b. p ist um 1 größer als die größte existierende Listenposition. Über die Listenbeschreibung
wird direkt auf das letzte Element zugegriffen und an dieses der neue Satz angekettet.
Der Aufwand für das Einketten eines Satzes ist von der Größe des Datenbestandes unabhängig. Daher hängt der Aufwand für das Einfügen nur vom Aufwand für das Suchen ab.
Analog zu 8.3 gilt:
242
KAPITEL 8. LISTEN
W OCIN SERT (n) = O(n);
AV CIN SERT (n) = O(n);
BECIN SERT (n) = O(1)
(8.4)
DELETE(p): Der Satz an Position p wird ausgekettet. Der Aufwand wird wieder durch
das Suchen bestimmt:
W OCDELET E (n) = O(n);
BECDELET E (n) = O(1) (8.5)
AV CDELET E (n) = O(n);
Siehe auch Anmerkung 8.2.
NEXT: Über den Aktualitätszeiger wird direkt auf den aktuellen Satz zugegriffen und
über diesen direkt auf seinen Nachfolger in der Liste. Der Aufwand für die Operation ist
unabhängig von der Länge der Liste.
W OCN EXT (n) = O(1);
AV CN EXT (n) = O(1);
BECN EXT (n) = O(1)
(8.6)
PREVIOUS: Die Operation entspricht NEXT in Rückwärtsrichtung, ist aber nur sinnvoll,
wenn eine doppelt verkettete Liste vorliegt.
W OCP REV IOU S (n) = O(1);
AV CP REV IOU S (n) = O(1);
BECP REV IOU S (n) = O(1)
(8.7)
Realisierung schlüsselwertabhängiger Operationen
Wir nehmen an, daß ein Schlüsselwert k höchstens einmal in der Liste vorkommt. Außerdem setzen wir voraus, daß die Liste in aufsteigender Reihenfolge der Schlüsselwerte
angeordnet ist. Die Realisierung der schlüsselwertabhängigen Operationen entspricht weitgehend der für positionsabhänge Operationen. Die Operationen SIZE, MIN, MAX, NEXT
und PREVIOUS benutzen nur die Listenbeschreibung und können in konstanter Zeit unabhängig von der Größe der Liste realisiert werden. Für sie gelten die Gleichungen 8.2
entsprechend.
SEARCH(k) und FIND(k): Beginnend beim ersten Element der Liste werden nacheinander
die Schlüsselwerte aller Kettenglieder mit k verglichen, bis entweder der Schlüsselwert
gleich k ist oder zum ersten Mal ein Schlüsselwert größer als k gefunden wird oder die
Liste endet. Im ersten Fall hat man das gesuchte Listenelement gefunden. In den beiden anderen Fällen hat man festgesellt, daß es einen Satz mit Schlüsselwert k nicht in
der Liste gibt. SEARCH(k) liefert einen Nullverweis (NIL) zurück. FIND(k) liefert einen
Nullverweis zurück, wenn k kleiner als der kleinste oder größer als der größte in der
Liste existierende Schlüsselwert ist. Anderenfalls läßt sich ein Satz mit nächstkleinerem
Schlüsselwert bestimmen und dieser Satz wird zurückgeliefert. Wenn ein Satz mit dem
Schlüsselwert k gefunden wird, ist der Aufwand – gemessen in der Zahl der Vergleiche –
durch die Gleichungen 8.3 korrekt angegeben. Wird ein Listenelement mit Schlüsselwert
8.3. REALISIERUNG VON LISTEN
243
k nicht gefunden, so braucht man dafür natürlich auch mindestens einen und höchsten
n Vergleiche. Zur Berechnung eines Mittelwertes müßte man etwas über die Verteilung
von Lücken wissen. Einige Bemerkungen dazu findet man in Abschnitt 7.2. Mit welcher
Wahrscheinlichkeit die Lücken auch auftreten, der Mittelwert kann nicht größer sein als
der Aufwand im schlechtesten Fall, d. h. die Gleichungen 8.3 sind auch in diesem Fall
korrekt.
INSERT(k): Der Satz mit dem neuen Schlüsselwert ist an der richtigen Stelle einzuketten.
Dazu ist die zu diesem Schlüsselwert gehörende Lücke zu bestimmen. Die Gleichungen 8.4
bleiben gültig, ebenso der Hinweis auf Abschnitt 7.2.
DELETE(k): Der Satz mit Schlüsselwert k ist zu finden und dann auszuketten. Zum Aufwand gilt das dort Gesagte. Die Gleichungen 8.5 gelten weiterhin. Siehe auch Anmerkung
8.2.
Zu etwas anderen Problemstellungen bei Listen mit Schlüsselwerten siehe Aufgabe 8.1.
Anmerkung 8.2 Es kommt vor, daß ein Satz aus einer Liste ausgekettet werden soll,
dessen Adresse bekannt ist, der also nicht zuvor gesucht werden muß. Es ist sinnvoll,
dafür eine eigene Anweisung zu benutzen, z. B. DELETE(addr). Eine effiziente Realisierung
erfordert allerdings doppelt verkettete Listen.
2
8.3.2
Realisierung von Listen durch Reihungen
Eine weitere Realisierungsmöglichkeit für Listen sind Reihungen (siehe 2.6.1, Seite 61).
Ein Anfangsstück der Reihung enthält die Sätze der Liste, und zwar einen Satz pro Listenelement. Der Rest ist unbelegt. In Abbildung 8.4 hat die Reihung m Einträge. Davon
sind n durch eine Liste belegt. Die Einträge n, n + 1, . . . , m − 2, m − 1 sind frei. Die Position eines Satzes in der Liste ist gleich dem Index des Reihenelementes, in dem der Satz
enthalten ist.
Ein Nachteil von Reihungen ist ihre feste Größe. Listen, die länger sind, passen nicht hinein. Eine Reihung innerhalb des Programmlaufs nach Bedarf zu vergrößern ist im Prinzip
zwar möglich, aber umständlich und wird normalerwereise nicht gemacht. Hingegen ist
es einfach, die Reihungsgröße von Programmlauf zu Programmlauf neu festzulegen. In
Kapitel 2 wird auf Seite 38 ein Beispiel vorgestellt.
Realisierung positionsabhängiger Operationen
SIZE: Wir setzen wieder eine allgemeine Listenbeschreibung voraus. Darin ist die aktuelle Länge der Liste, die Größe der Reihung und der Index des aktuellen Listenelements
enthalten. Die Information kann mit einem Zugriff erhalten werden. Es gelten wieder die
Gleichungen 8.2.
244
KAPITEL 8. LISTEN
0
R0
1
R1
•
•
•
n−1
Rn−1
n
•
•
•
m−1
Abbildung 8.4: Liste als Reihung
SEARCH(p): Das Listenelement mit der Positionsnummer p wird durch einen Zugriff zum
Reihenelement p gefunden.
W OCSEARCH (n) = O(1);
AV CSEARCH (n) = O(1);
BECSEARCH (n) = O(1)
(8.8)
INSERT(p): Ein gegebener Satz wird an Position p als neues Listenelement eingefügt.
Vorher werden die dahinterliegenden Sätze um eine Position verschoben. Die Liste habe
vor dem Einfügen n Sätze. Es werden ein Zugrif für das Speichern des neuen Satzes
und n − p Kopieroperationen für das vorangehende Verschieben benötigt. Im besten Fall
haben wir 0 Verschiebungen, im schlechtesten n. Wenn wir annehmen, daß jede der n + 1
Einfügstellen mit gleicher Wahrscheinlichkeit auftritt, ergibt sich als Mittelwert für die
Anzahl Verschiebungen
n−1
1
0
1
n(n + 1)
n
n
+
+···+
+
=
·
=
n+1 n+1
n+1 n+1
n+1
2
2
D. h. es gelten die Gleichungen 8.4.
DELETE(p): Der Satz an Position p wird aus der Reihung entfernt. Das geschieht, indem
alle Sätze, die hinter Position p stehen, um 1 nach vorn verschoben werden und der Platz
des ursprünglich letzten Listenlementes freigegeben wird. Wie nicht schwer zu sehen,
gelten die Gleichungen 8.5.
NEXT und PREVIOUS: Die aktuelle Position wird um 1 erhöht bzw. vermindert und auf
das entsprechende Reihungselement zugegriffen. Bei Bereichsüberschreitung erfolgt Fehlermeldung. Es gelten die Gleichungen 8.6 und 8.7.
8.4. KELLER, SCHLANGEN, HALDEN
245
Realisierung schlüsselwertabhängiger Operationen
Die Liste sei durch eine Reihung realisiert, es gibt identifizierende Schlüsselwerte aus
einem linear geordneten Wertebereich und die Liste sei in aufsteigender Reihenfolge der
Schlüsselwerte angelegt. Die Operationen SIZE, MAX, MIN, NEXT und PREVIOUS können
mit Hilfe der Listenbeschreibung in konstanter Zeit realisiert werden.
SEARCH(k) und FIND(k): Man könnte – wie bei Verkettungen – in der Reihung linear
suchen, bis man den Satz mit dem gegebenen Schlüsselwert gefunden hat. Die Tatsache,
daß man in Reihungen mit den Indizes der Reihenelemente rechnen kann, erlaubt jedoch
ein deutlich effizienteres Suchen, nämlich Binärsuche. Diese ist in Abschnitt 8.2 vorgestellt
worden. Dort sind auch Abschätzungen zu Komplexität angegeben.
INSERT(k): Es ist zunächst die Stelle, d.h. die Lücke im Schlüsselwertbereich, zu finden, an
der der neue Satz einzufügen ist. Das entspricht der Suche nach einem nicht vorhandenen
Schlüssel. Dann ist der belegte Teil der Reihung um ein Element zu erweitern und die
Sätze hinter der Lücke um eine Position zu verschieben. Schließlich ist der neue Satz in das
freigewordene Reihungselement zu speichern. Die Komplexität wird durch das Verschieben
bestimmt, das im schlechtesten Fall gleich der der Listenlänge ist.
DELETE(k): Es ist mit Binärsuche der zu löschende Satz in der Liste zu bestimmen.
Beginnen mit diesem Listenelement werden die nachfolgenden Sätze um eine Position nach
vorn verschoben und dann das letzte von der Liste belegte Reihungselement freigegeben.
Das Suchen erfolgt in logarithmischer Zeit, das Verschieben in linearer Zeit.
8.4
8.4.1
Keller, Schlangen, Halden
Keller
Definition, Operationen, Realisierung
Keller (Stapel, stack) sind Listen mit eingeschränkten Zugriffsmöglichkeiten. Sie sind in
der Informatik von besonderer Bedeutung und wurden 1957 von F.L. Bauer 5 und K.
Samelson 6 eingeführt (Patentanmeldung!). Mit der Anweisung PUSH wird ein neues erstes
Bauer, Friedrich Ludwig, ∗ 1924 Regensburg. Deutscher Mathematiker und Informatiker. Professor
an der Universität Mainz und an der Technischen Universität München. Baute in München den ersten Studiengang Informatik in Deutschland auf. Schrieb zusammen mit Gerhard Goos eines der ersten
deutschsprachige Lehrbücher zur Einführung in die Informatik: [BaueG1971] und [BaueG1974]. Arbeiten
zu angewandten Mathematik, Programmiersprachen und Softwaretechnogie, Kryptologie. Reichte zusammen mit Klaus Samelson 1957 ein Patent auf das Kellerprinzip ein, wofür er 1988 den IEEE Computer
Pioneer Award erhielt.
6
Samelson, Klaus, ∗ 1918, † 1980. Deutscher Mathematiker und Informatiker. Professor an der Universität Mainz und an der Technischen Universität Münchengen. Trug wesentlich zur Klärung informatischer
Grundbegriffe bei: Unterprogramm, Kellerprinzip, Compiler. Der Informaitikbereich der Universität Hildesheim hat die Adresse Samelsonplatz 1. Für eine Würdigung Samelsons siehe Langmaack [Lang2002]
5
246
KAPITEL 8. LISTEN
Element in den Keller eingefügt. Mit der Anweisung POP wird das erste Element aus
dem Keller entfernt und dem Aufrufer zur Verfügung gestellt. Häufig gibt es auch eine
Operation TOP, die das erste Element zur Verfügung stellt, den Keller aber nicht verändert.
In der Regel sind die Elemente eines Kellers Sätze einer festen Satzklasse. Duplikate dürfen
auftreten, Schlüsselwerte werden nicht beachtet.
Ein Keller kann leer sein. In diesem Fall liefern POP und TOP den Wert NIL. Die Kapazität eines Kellers ist begrenzt. Kellerüberlauf, d. h. der Versuch, mit PUSH ein Element
in einen vollen Keller einzufügen, führt zu einer Fehleranzeige. In der theoretischen Informatik werden auch Keller unbegrenzter Kapazität betrachtet. Keller realisieren die
Bearbeitungsstrategie LIFO (last in first out). Statt LIFO wird auch oft LCFS (last come
first served) gesagt.
Realisierung durch Verkettung
Diese Realisierung entspricht der Realisierung von verketteten Listen. Es wird eine Kette
auf- und abgebaut. Zur Verwaltung benutzt man zwei Zeiger, siehe Abbildung 8.5. Der
klrbas
klrzgr
...
...
..
...
...
...
...
...
..................................................................................................................................................................................................................................................................................................................................
...
.
.
.........
..........
.........
........
..
..
..
..
..
..
..........................................................................................
...........................................................................................
...........................................................................................
.........................................................................................
..
..
..
..
.
.
.
..
..............................................................................................
..............................................................................................
.............................................................................................
...........................................................................................
..
.
.
.
......................................................
......................................................
......................................................
......................................................
......................................................
◦
•
◦
◦
◦
◦
◦
◦
•
◦
Abbildung 8.5: Realisierung eines Kellers durch Verkettung
Zeiger klrbas (Kellerbasis) zeigt auf den ersten Satz der Liste, d. h auf das unterste Element des Kellers. Der Zeiger klrzgr (Kellerzeiger) zeigt auf den letzten Satz der Liste,
also auf das oberste Element des Kellers. Mit PUSH wird der Satz (oder Vertretersatz) am
Ende der Kette angehängt. Bei POP wird das letzte Element der Kette entfernt. Doppelte
Verkettung erlaubt, POP in konstanter Zeit auszuführen. Ein leerer Keller wird durch den
Wert NIL im Kellerzeiger angezeigt. Eine Anzeige, daß der Keller voll ist, gibt es bei dieser Implementierung nicht. Allerdings wird in der Regel das Anhängen eines Satzes (oder
Vertretersatzes) an die Kette mit einer dynamischen Speicherplatzanforderung (z. B. mit
malloc) verbunden sein und die kann unter Umständen zum Fehler „Kein Speicherplatz
mehr vorhanden“ führen. Es kann manchmal sinnvoll sein, außer Kellerbasis und Kellerzeiger auch die Kellerfüllung, also die Anzahl Elemente, die im Keller lagern, mitzuführen.
und [Lang1999].
8.4. KELLER, SCHLANGEN, HALDEN
247
Realisierung durch Reihungen
Diese Realisierung entspricht der Realisierung von Listen durch Reihungen. Siehe Abbildung 8.4. Die Kellerbasis braucht nicht exlizit gespeichert zu werden, denn sie ist durch
das Reihenelement mit Index 0 gegeben. Der Kellerzeiger entält den höchsten belegten
Index, bzw. einen negativen Wert, wenn der Keller leer ist. Kellerüberlauf ist möglich und
ergibt sich, wenn ein Element eingekellert werden soll, aber die Reihung voll ist.
Aufwand
In beiden besprochenen Realisierungen sind PUSH und POP sehr effiziente Operationen:
W OCP U SH (n) = W OCP OP (n) = O(1).
8.4.2
Keller: Beispiele und Anwendungen
Beispiel 8.2 (Ausdrücke) Wir wollen den aritmtischen Ausdruck (3+4)∗(2+5) auswerten. Dazu wird er zuerst in umgekehrte polnische Notation (UPN, reverse Polish notation,
RPN) 7 umgeformt: 3 4 + 2 5 + ∗. Die nachfolgende Auswertung zeigt Tabelle 8.6.
Symbol
Anfang
3
4
+
2
5
+
∗
Actionen
Keller
leer
push 3
3
push 4
3, 4
pop 4, pop 3, push 7 = 3 + 4 7
push 2
7, 2
push 5
7, 2, 5
pop 5, pop 2, push 7 = 5 + 2 7, 7
pop 7, pop 7, push 49 = 7 ∗ 7 49
Tabelle 8.6: Auswertung eines Arithmetischen Ausdrucks
Keller eignen sich nicht nur zur Auswertung von Ausdrücken, sondern können auch zur Gewinnung von deren UPN eingestzt werden. Das kann man z. B. mit zwei Kellern ereichen.
Es sei (3 + (4 ∗ 2)) + 5) umzuformen. Tabelle 8.7 zeigt den Ablauf. Bei einer schließenden
UPN ist eine klammerfreie Schreibweise von Ausdrücken, bei der zunächst die Operanden hingeschrieben werden und ihnen dann der darauf anzuwendene Operator folgt. Sie wurde aus der von J.
Łukasiewicz 8 eingeführten Polnischen Notation, bei der der Operator den Operanden vorangeht, von
Ch. Hamblin 9 entwickelt.
8
Łukasiewicz, Jan, ∗1878 Lemberg (heute Lviv, Ukraine), † 1956 Dublin, Irland. Polnischer Philosoph,
Mathematiker und Logiker.
9
Hamblin, Charles, ∗ 1922, † 1985. Australischer Philosoph, Logiker und Computerpionier.
7
248
KAPITEL 8. LISTEN
Symbol
Anfang
(
3
+
(
4
∗
2
)
)
+
5
Ende
Hauptkeller
leer
leer
3
3
3
3, 4
3, 4
3, 4, 2
3, 4, 2, ∗
3, 4, 2, ∗, +
3, 4, 2, ∗, +
3, 4, 2, ∗, +, 5
3, 4, 2, ∗, +, 5, +
Nebenkeller
leer
(
(
(, +
(, +, (
(, +, (
(, +, (, ∗
(, +, (, ∗
(, +
+
+
Tabelle 8.7: Umformung in umgkehrte polnische Notation
Klammer oder am Ende der Eingabezeichenreihe wird ein Operator in den Hauptkeller
geschrieben. Es wird bei dem Ablauf vorausgesetzt, daß der gegebene Ausdruck korrekt
ist und bis auf äußere Klammern überall Klammern gesetzt sind.
Keller eignen sich gut zur Auswertung von arithmetischen Ausdrücken und werden deshalb
auch in Taschenrechnern eingesetzt. Bei Taschenrechnern der Firma Hewlett Packard
basiert sogar die Bedienoberfläche auf UPN, also dem Kellerprinzip.
2
Beispiel 8.3 (Unterprogrammaufrufe)
Es soll zunächst allgemein auf die Speicherverwaltung in Programmläufen eingegangen
werden. Das folgende gilt für die meisten Betriebssysteme und setzt virtuelle Adressierung (Abschnitt 4.4, Seite 145) voraus. Wird vom Betriebssystem ein Programm zum
Ablauf gebracht, also zu einem Prozeß gemacht, so wird ihm ein Adreßraum zugewiesen
und dessen Seiten auf dem Seitenwechselgerät gespeichert. Die Seiten des Adreßraumes,
die während des Ablaufs angesprochen werden, erhalten zusätzlich zeitweilig eine Hauptspeicherseite als Träger.
Für die Verwaltung des zugewiesenen Adreßraumes wollen wir annehmen, daß ein übersetztes C-Programm abläuft. Programme in anderen Programmiersprachen verhalten sich
jedoch im wesentlichen gleichartig. Die Verwaltung wird von Programmen ausgeführt, die
zum Laufzeitsystem (Seite 90 und Seite 144) des Compilers gehören. Aus der Sicht des
Betriebssystems sind diese Programme Teil des Benutzerprozesses. Das vom Compiler
(und Binder) erzeugte Objektprogramm hat den in Abbildung 8.6 gezeigten Aufbau. Er
ergibt sich aus der Aufteilung des Quellprogramms in Definitionsteil und Anweisungsteil (Seite 80). Der Anweisungsteil wird ganz überwiegend zu den Maschinenbefehlen des
8.4. KELLER, SCHLANGEN, HALDEN
Befehle
Konstanten
Globale Variable
249
Lokale Variable
Abbildung 8.6: Speicherorganisation in Prozesse
Befehlsbereichs. Auch ein Teil der Konstanten des Konstantenbereichs ergibt sich aus
dem Anweisungsteil. Die restlichen Konstanten werden im Definitionsteil festgelegt oder
sind im Quellcode nicht sichtbare Verwaltungsdaten. Globale und lokale Variable bilden
zusammen den Variablenbereich. Auch sie werden ergänzt um Verwaltungsdaten, die im
Quellcode nicht erkennbar sind, z. B. Zustandsdaten für Unterprogrammaufrufe. Dem Betriebssystem sind nur Befehlsbereich, Konstantenbereich und Variablenbereich bekannt.
Befehlsbereich und Konstantenbereich sind schreibgeschützt, werden aber unterschieden,
da Konstanten auch ausführungsgeschützt sind. Die Speicherverwaltung des Laufzeitsystems hat die Aufgabe, den Variablenbereich zu verwalten. Üblicherweise verwaltet sie
den Bereich für globale Variable als Halde (s.u) und den Bereich für lokale Variable als
einen oder mehrere Keller.
Im Bereich für globale Variable befinden sich die Variablen, die im Hauptprogramm und
allen Unterprogrammen einheitlich zur Verfügung stehen. Zum Bereich für globale Variable gehört aber auch ein Teilbereich, aus dem die Speicherverwaltung dynamisch angeforderte Speicherplätze (malloc und free) zuteilt. Dieser Bereich wird Halde (heap)
genannt. Halden werden in Unterabschnitt 8.4.4 näher betrachtet. Es kann passieren,
daß der anfangs für die Halde bereitgestellte Adreßraum nicht ausreicht. Bevor das als
Fehlermeldung an das Benutzerprogramm gemeldet wird, sollte die Speicherverwaltung
beim Betriebssystem eine Vergrößerung des Adreßraumes beantragen10 und erst, wenn
das abgelehnt wird, „Kein Speicher mehr vorhanden“ melden.
Das Hauptprogramm und jedes Unterprogramm hat eigene lokale Variable. Für diese
lassen sich jedoch im allgemeinen keine festen Speicherplatze zuordnen. Unterprogramme
lassen sich nämlich rekursiv aufrufen und brauchen dann einen Satz lokaler Variabler für
jede Aufrufstufe, vergleiche Abbildung 2.13, Seite 89. Für die Verwaltung der Aufrufstufen
und ihrer lokalen Variablenbereiche hat sich die Verwendung von Kellern als sehr nützlich
erwiesen.
Beim Aufruf eines Unterprogramms muß der Zustand des aufrufenden Programms so gesichert werden, daß dieses nach Rückkehr aus dem Unterprogramm fehlerfrei weiterlaufen
kann. In den frühen Tagen der maschinennahen Programmierung – „Assemblerprogrammierung“ – reichte dafür ein Maschinenbefehl aus. Mit ihm wurde zur Startadresse des
Streng genommen, ist der volle Adressraum von Anfang an da. Vergrößert wird nur der Teil, dessen
Seiten Platz auf dem Seitenwechselgerät zugewiesen ist (siehe Abschnitt 4.4, Seite 145).
10
250
KAPITEL 8. LISTEN
Unterprogramms gesprungen und gleichzeitig die Adresse für den Rückspung an die Aufrufstelle gesichert. Benutzt man höhere Programmierprachen wie z. B. C, so muß mehr
Aufwand getrieben werden. Bei jedem Unterprogrammaufruf:
1. Müssen die Registerinhalte des Aufrufers gesichert und der Anfangszustand der
Register des aufgerufenen Unterprogramms eingestellt werden.
2. Muß die Rücksprungadresse gesichert werden.
3. Muß die Basisadresse des lokalen Variablenbereichs des aufgerufenen Unterprogramms gesetzt werden.
4. Müssen die Werte der an das Unterprogram direkt zu übergebenden Parameter, die
Aufrufliste (calling list), bereitgestellt werden.
5. Schließlich muß dafür gesorgt werden, daß freier Platz für den lokalen Variablenbereich zur Verfügung steht. Eventuell müssen einige lokale Variable mit einem
Anfangswert versehen werden.
Die Daten zu den Punkten 1 bis 3 haben bei gegebener Rechnerarchitekur und gegebenem
Compiler für alle Unterprogramme das gleiche Format. Zu ihrer Speicherung kann man
einen Keller mit Programmaufrufblöcken benutzen. In Beispiel 8.4 wird erklärt, in welcher
Form diese Verwaltung durch die konventionelle Maschine unterstützt werden kann. Die
Speicherbeiche zu den Punkten 4 und 5 können zusammengefaßt werden (Abbildung 8.7).
Für unterschiedliche Unterprogramme sind sie jedoch verschieden lang. Das macht die
Aufrufliste
lokale Variable
...
..
..
................................................................................................................................................................................................................................................................................................................................................................................................................
...
...
...
Abbildung 8.7: Speicher für die Aufrufliste und die lokalen Variablen eines Unterprogramms)
Verwaltung dieser Bereiche in einem zweiten Keller etwas schwieriger. Trotzdem wird
in allgemeinen diese Lösung gewählt. Bei PUSH und POP muß stets auch die Länge des
zuzuweisenden bwz. freizugebenden Bereichs bekannt sein.
Da die Zahl der Aufrufstufen wegen der Möglichkeit rekursiver Aufrufe ablaufabhängig ist,
kann es vorkommen, daß die dafür benutzten Keller überlaufen. Die Speicherverwaltung
sollte dann so vorgehen wie oben für die Halde beschrieben.
2
Beispiel 8.4 (Keller in der konventionellen Maschine) Wegen der Bedeutung von
Kelleraufrufen hat man für die meisten modernen Rechner die Befehlssätze der konventionellen Maschine um Kellerbefehle erweitert. Dazu benutzt man spezielle Register des
8.4. KELLER, SCHLANGEN, HALDEN
251
Prozessors, die Kellerregister. Üblich sind ein Kellerbasisregister, in dem die Anfangsadresse des aktuellen Kellerbereichs steht, und ein Kellerzeiger, der auf das aktuelle (oberste)
Kellerelement zeigt. Ein drittes Register, das Kellerendregister, ist manchmal auch vorhanden und gewährleistet, daß der zugewiese Kellerbereich nicht überschritten wird. Die
Maschinenbefehle push und pop schreiben die Inhalte eines Satzes von Mehrzweckregistern
in den Keller aus bzw. laden sie aus diesem. Der Befehl call sichert bei einem Unterprogrammansprung die Rückkehradresse im Keller. Der Rücksprung erfolgt mit return.
Arithmetische und logische Operationen direkt auf dem Keller sind möglich. Der Befehl
mult könnte beispielsweise die ersten beiden Kellerelemente durch ihr Produkt ersetzen.
Befehle dieser Art sind in der konventionellen Maschine kaum noch üblich.
2
8.4.3
Schlangen
In einer Schlange muß man sich hinten anstellen und ist vorn an der Reihe. Die entsprechende Datenstruktur heißt auch Schlange (Warteschlange, queue). Für die Bearbeitung
von Schlangen gibt es die Operationen ENQUEUE, mit der ein Element als letztes in die
Schlange eingefügt wird, und DEQUEUE, mit der das erste Element aus der Schlange entnommen wird. In der Regel sind die Elemente einer Schlange Sätze einer festen Satzklasse.
Duplikte dürfen auftreten, Schlüssel werden nicht beachtet. DEQUEUE angewandt auf eine
leere Schlange führt zu einer Fehlermeldung. Der Platz für eine Schlange ist begrenzt.
ENQUEUE für eine volle Schlange führt auch zu einer Fehlermeldung. Schlangen realisieren
die Bearbeitungsstrategie FIFO (first in first out). Statt FIFO wird auch oft FCFS (first
come first served) gesagt.
Realisierung durch Verkettung
Abbildung 8.8 zeigt die Realisierung einer Schlange durch Verkettung. Der Kette werden
am Anfang Elemente entnommen und am Ende angefügt. Die Verwaltung geschieht mit
quend
qustart
...
...
...
...
..
..
...
..
.......................................................................................................................................................................................................................
.............................................................................................................
.
..
.
..........
.......
........
........
...
...
.......
.......
.......
......
.....................................................................................
......................................................................................
......................................................................................
.....................................................................................
..
..
..
..
.
..
..
..
.............................................................................................
............................................................................................
............................................................................................
...........................................................................................
..
..
..
.
......................................................
......................................................
......................................................
......................................................
......................................................
◦
•
◦
◦
◦
◦
◦
◦
Abbildung 8.8: Realisierung einer Schlange durch Verkettung
•
◦
252
KAPITEL 8. LISTEN
den Zeigern quend und qustart. Die Opertionen ENQUEUE und DEQUEUE können in konstanter Zeit ausgeführt werden, letztere dank der doppelten Verkettung. Wie bei Kellern,
die durch Verkettung realisiert werden, wird „volle Schlange“ nur gemeldet werden, wenn
die Speicherverwaltung keinen dynamischen Speicher mehr zuteilen kann.
Realisierung durch Reihungen
Die Realisierung einer Schlange durch eine Reihung entspricht der Realisierung eines Kellers durch eine Reihung (Seite 247). Es gibt aber einen wichtigen Unterschied. Es kann
vorkommen, daß das letzte Element der Reihung in die Schlange eingefügt wurde, aber inzwischen am Anfang der Schlange Kette wieder Reihungselemente frei geworden sind. Um
diese Platz zu nutzen und nicht zu früh „kein Speicher mehr vorhanden“ zu melden, organisiert man die Schlange in den Reihungselementen kreisförmig. Die Einzelheiten bleiben
dem Leser überlassen.
8.4.4
Halden und Prioritätswarteschlangen
Die Begriffe Halde (heap) und Prioritätswarteschlange (priority queue) werden in der
Informatik in einem doppelten Sinne gebraucht. Eine der Bedeutungen wird im folgenden
beschrieben, die andere in Abschnitt 11.3, Seite 329.
Halden in der dynamischen Speicherverwaltung
Anfangs wurde der Begriff Halde (heap) nur im Sinne der in Unterabschnitt 11.3.1 eingeführten Datenstruktur benutzt. Später kam die hier besprochene Bedeutung hinzu. In
dieser Bedeutung ist eine Halde ein Speicherbreich, aus dem eine verwaltenden Instanz,
eine Speicherverwaltung, Speicherblöcke auf Anforderung zuteilt. Freigegebene Blöcke werden der Halde wieder hinzugefügt. Ein wichtiges Beispiel ist die Speicherverwaltung eines
Programmlaufs, wie sie in Beipiel 8.3 dargestellt ist. Da die Anforderungen und Freigaben zu unterschiedlichen Zeiten erfolgen, wird der freie Platz der Halde immer „löchriger“.
Die Speicherverwaltung muß versuchen, trotzdem den zur Verfügung stehenden Platz der
Halde möglichst gut zu nutzen.
Das ist nicht schwer, wenn ausschließlich Blöck einer festen Länge vergeben und freigegeben werden. Die Halde wird in solche Blöcke eingteilt. Die Organisation beschränkt sich
dann darauf, die Menge der unbelegten Blöcke zu verwalten. Das kann z. B. mit einer
verketteten Liste freier Blöcke geschehen. Eine andere Verwaltung der Halde, die weniger
Platz verbraucht und i. a. schneller ist, benutzt Bitlisten. Jeder Bitposition entspricht eineindeutig ein Block der Halde. Der Wert des Bits zeigt den Belegungzustand des Blockes
an.
In vielen Fällen wäre es jedoch sehr unvorteilhaft, Speicherplatz stets in Blöcken gleicher
Größe zu vergeben und zurückzugeben. malloc ist ein gutes Beispiel. D. h. wir brauchen
8.4. KELLER, SCHLANGEN, HALDEN
253
Verfahren zur Zuteilung und Rückgabe von Blöcken variabler Länge. Man spricht von
dynamischer Speicherverwaltung (dynamic memory management) oder auch von dynamischer Speichertzuteilung (dynamic storage allocation). Der insgesamt zur Verfügung stehende freie Speicher wird als eine Verkettung von maximal zusammenhängenden Blöcken
verwaltet. In jedem Block sind seine Länge und ein Verweis auf den nächsten freien Block
verzeichnet. Eine Anforderung an die Speicherverwaltung besteht dann im wesentlichen
aus einer Längenangabe. Die Speicherverwaltung sucht in der Kette freier Blöcke einen
passender Größe, schneidet ein Stück der benötigten Länge ab und aktualisiert die Kette
freier Blöcke. In dem gefundenen Block wird seine Länge eingetragen und der auf das Längenfeld folgende Teil wird durch Adreßübergabe dem Anforderer zu Verfügung gestellt.
Bei der Rückgabe eines Blockes wird nur seine Adresse angegeben und die Speicherverwaltung aktualisiert die Liste freier Blöcke. Eine Rückgabe kann zur Vergrößerung eines
existierenden Freispeicherblocks führen, aber ebenso gut auch zur Einkettung eines neuen,
möglicherweise kleinen Freispeicherblocks. Dadurch kann die oben erwähnte Löcherstruktur immer stärker werden.
Bei einer gegeben Anfordungslänge muß ein Freispeicherblock gefunden werden, der mindestens diese Länge aufweist. Das einfachste Verfahren ist, den ersten passenden Block
zu finden. Man spricht von first fit. Man kann auch etwas mehr Aufwand treiben und
unter den passenden Freispeicherblöcken einen kleinster Länge suchen. Dann spricht man
von best fit. Best fit beugt einer Zersplitterung des Speichers besser vor, aber nicht in
dem Maße, wie man das erwarten würde. Deswegen wird häufig das deutlich einfachere
Verfahren first fit gewählt.
Was passiert nun, wenn eine Speicheranforderung kommt und es keinen freien Block mit
dieser Länge gibt? Zunächst einmal kann die Speicherverwaltung versuchen, die Halde,
wie auf Seite 249 beschrieben, zu vergrößern. Geht das nicht, so wird sie den Fehler „Kein
Plaltz mehr vorhanden“ melden. Das kann passieren, obwohl der insgesamt noch zu Verfügung stehende freie Platz für die Anforderung ausreichen würde. Er ist jedoch zu stark
zerplittert. Man spricht von externer Fragmentierung (external fragmentation). Man kann
dann auf die Idee kommen, die belegten Blöcke und die Freispeicherblöcke so in der Halde
zu verschieben, daß ein großer Freispeicherblock entsteht. Tut man das, so spricht man
von Speichersammeln (garbage collection). Für einige Spezialfälle der Speicherverwaltung
ist das sinnvoll. Für den sehr wichtigen Fall der Speicherverwaltung in Programmläufen
(siehe Beispiel 8.3) ist das nicht der Fall. Die Adresse eines zugewiesenen Speicherblocks
wird vom anfordernden Programm möglicherweise an vielen verschieden Stellen benutzt,
eventuell an andere Programmteile, z. B. Unterprogramme, weitergereicht. Es ist praktisch
nicht möglich festzustellen, wo diese Adresse gebraucht wird. Eine Adreßverschiebung des
Blocks durch die Speicherverwaltung müsste ohne Änderung der Stellen geschehen, an
denen die Blockadresse benutzt wird. Das ist nicht zulässig.
Es gibt die Möglichkeit, Speicherplatz einer Halde in Böcken unterschiedlicher, aber nicht
frei wählbarer Längen zu verwalten. Zum Beispiel können Blöcke nur in den Längen
254
KAPITEL 8. LISTEN
8, 16, 32, · · · , 2k zugewiesen werden. Dieses Verfahren bietet für die Verwaltung einige
Vorteile. Es heißt buddy system. Einzelheiten sind bei Knuth [Knut1997], Seiten 442ff,
zu finden. Auch in einem buddy system kann es zu externer Fragmentierung kommen.
Da die Länge der zugwiesenen Blöcke im allgemeinen größer ist als die wirklich benötigte Länge, gibt es auch unbenutzten Platz innerhalb der Blöcke. Dieser wird interne
Fragmentierung (internal fragmentation) genannt.
Prioritätswarteschlangen
Bei der Bearbeitung von Aufträgen werden diese meistens in einer Schlange verwaltet.
Dabei kommt es oft vor, daß die Aufträge Prioritäten haben. Ein Auftrag wird nach allen
Aufträgen mit höherer Priorität bearbeitet und vor allen mit niedriger Priorität. Außerdem werden alle Aufträge seiner Prioritätsklasse, die bei seiner Ankunft schon warten,
vor ihm bearbeitet. Das entspricht einer Warteschlange, bei der ein ankommender Auftrag in die niedrigste Position seiner Priorität eingeordnet wird. Man kann das auch als
n Warteschlangen ansehen, jeweils eine für jede Priorität. Zu Bearbeitung wird dann der
erste Auftrag der nichtleeren Warteschlange höchster Priorität genommen. Als Beispiel
kann der Scheduler eines Betriebssystems dienen (Seite 568).
Wenn es eine feste, nicht zu große Zahl n von Priotitäten gibt und diese von 0 bis n − 1
numeriert sind, gibt es eine sehr einfache und effiziente Realisierung der Prioritätswarteschlange. Man legt eine Reihung mit n Elementen an, je eine für jede Prioriät. Jedes
Element enthalt einen Zeiger auf auf den Anfang und einen auf das Ende der entsprechenden Schlange. Siehe Abbildung 8.9. Ein Zeiger first zeigt auf die erste nichtleere
first
...
...
..
...
...................................................................................................................................
.
.........
.........
...
0
quend0
qustart0
1
quend1
qustart1
•••
•••
k
quendk
qustartk
•••
•••
n−1
quendn−1
qustartn−1
Abbildung 8.9: Prioritätswarteschlange als Reihung von Einzelschlangen
Priorität. Die Schlangen der einzelnen Prioritäten kann man als Reihungen implementieren oder, wenn man etwas mehr Flexibilät braucht, in einem Pool von Blöcken fester
Länge, wie oben beschrieben.
Es kommt durchaus vor, daß Prioritätswarteschlangen gebraucht werden, bei denen die
Anzahl der möglichen Prioritäten groß ist und nicht von vornherein festliegt. Dann bietet
8.4. KELLER, SCHLANGEN, HALDEN
255
eine effiziente Implementierung deutlich mehr Schwierigkeiten. Eine mögliche Implementierung benutzt ausgewogene Suchbäume und soll in Aufgabe 9.4, Seite 303, untersucht
werden. Als weitere Lösungmöglichkeit werden des öfteren Halden nach Unterabschnitt
11.3.1, Seite 329 genannt. Diese sind jedoch für diesen Zweck nicht geeignet.
Aufgaben
Aufgabe 8.1 Untersuchen Sie Listenrealisierung durch Verkettung für die folgenden Fälle:
1. Der Fall, daß die Sätze identifizierende Schlüsselwerte haben, die jedoch keine Ordnung aufweisen bzw. deren Ordnung nicht benutzt werden soll. Ein Schlüsselwert
darf in der Liste höchstens einmal vorkommen.
2. Die Sätze haben nichtidentifizierende Schlüsselwerte, sogenannte Sekundärschlüssel
(secondary key). Die Reihenfolge in der Liste soll aufsteigend sortiert nach Sekundärschlüsselwerten sein.
3. Die Sätze haben identifizierende Schlüsselwerte, können jedoch mehrfach in der Liste
auftreten können.
Literatur
Listen sind ein Standardthema in allen Lehrbüchern, die Datenstrukturen behandeln, z. B.
Cormen/Leiserson/Rivest [CormLR1990] Teil III, Ottmann/Widmayer [OttmW1996], Noltemeier [Nolt1972], Kowalk [Kowa1996], Weiss [Weis1995].
Mit besonderer Brücksichtigung von C werden Listen in Kruse/Tondo/Leung [KrusTL1997]
und Aho/Ullman [AhoU1995] behandelt.
256
KAPITEL 8. LISTEN
Kapitel 9
Suchbäume
9.1
9.1.1
Binäre Suchbäume
Allgemeines zum Suchen
In Kapitel 8 haben wir die Sätze eines Datenbestandes als Listen angeordnet. Wir haben
gesehen, daß das Suchen eines Satzes i. A. aufwendig ist, weil die Zahl der Vergleichsoperationen linear mit der Größe des Datenbestandes wächst. In diesem und im nächsten
Kapitel werden wir Datenstrukturen und Algorithmen kennenlernen, mit denen das Suchen wesentlich effizienter realisiert werden kann. Wie bei den schlüsselwertabhängigen
Operationen bei Listen (siehe Seite 234) wollen wir annehmen, daß es für die Sätze eindeutig identifizierende Schlüsselwerte gibt. Die wichtigsten Verfahren für das Suchen eines
Satzes nach seinem Schlüsselwert sind Verfahren mit Schlüsseltransformation und Verfahren mit Schlüsselvergleich.
Verfahren mit Schlüsseltransformation. Bei einer Schlüsseltransformation wird der
Schlüsselwert von einem Adressierungsalgorithmus in eine Speicheradresse umgerechnet.
Schematisch ist das in Abbildung 9.1 dargestellt. Schlüsseltransformation bietet sehr ef..........................................................
...................
............
...........
........
.........
.......
......
......
.
.
.
.
.
....
....
...
.
...
....
...
..
...
..
....
..
.
.
.
......
...
.
.
.
.......
.
.
.....
........
........
...........
...........
.................
...............................................................
Algorithmus
Schlüsselwert
...
...
...
...
.
....... ..
........
...
..
...........................................................................................................................................
..
Speicheradresse
Abbildung 9.1: Suchen mit Schlüsseltransformation
257
258
KAPITEL 9. SUCHBÄUME
fizienten Zugriff bei Suchoperationen. Das Verfahren eignet sich jedoch nicht für die vollständige Bearbeitung aller Sätze in aufsteigender Reihenfolge. Schlüsseltransformation
wird im nächsten Kapitel behandelt.
Verfahren mit Schlüsselverlgeich.
Es wird ein gegebener Schlüsselwert mit einem Wert in einem strukturierten Datenbestand
verglichen. Bei Gleichheit hat man den gesuchten Satz gefunden. Bei Ungleichheit wird
weitergesucht. Weist die Schlüsselwertmenge keine lineare Ordnung auf oder wird diese
nicht benutzt, so entspricht dies dem Suchen in einer Liste (Kapitel 8). Liegt eine lineare
Ordnung in der Schlüsselwertmenge vor, so kann man bei Ungleichheit prüfen, ob der
Suchwert kleiner oder größer als der Vergleichswert ist. In Abhängigkeit davon wird die
Suche in einer von zwei disjunkten Teilmengen fortgesetzt. Die Schlüsselwerte der anderen
Teilmenge braucht man nicht mehr zu berücksichtgen und kommt so unter Umständen
mit sehr viel weniger Vergleichen zum Ziel. Eine erste Realisierung dieses Vorgehens haben
wir im vorigen Kapitel mit der Binärsuche kennengelernt (Abschnitt 8.2, Seite 235). Im
vorliegenden Kapitel wird diese Vorgehensweise mit einer anderen Datenstruktur, nämlich
mit Bäumen, in erster Linie mit Binärbäumen realisiert.
9.1.2
Binärbäume
Bäume sind Graphen und werden als solche in Abschnitt 14.4, Seite 382, ausführlich
behandelt. Hier wollen wir uns auf spezielle Bäume, die Binärbäume, beschränken. Diese
haben wichtige zusätzliche Eigenschaften und sind in besonderem Maße für Suchaufgaben
geeignet. In Beispiel 2.3, Seite 69, wurde eine Lehrveranstaltungsdatei als Binärbaum eingeführt. Siehe insbesondere Abbildung 2.9. Der Binärbaum wurde in C als Satz (struct)
mit einem Schlüsselfeld (Lehrveranstaltungsname), und zwei Verweisfeldern (linker und
rechter Nachfolger) realisiert und zur Ausgabe der Lehrveranstaltungsnamen in aufsteigender alphabetischer Reihenfolge benutzt. Ein weiteres Beispiel ist in Abbildung 9.2 zu
sehen. Die Knoten des Baumes sind die Werte aus Tabelle 8.1. Mit Hilfe dieser Beispiele
wollen wir zur Definition eines Binärbaumes kommen. Es gibt eine nichtleere Menge V
von Knoten. Jeder Knoten kann höchstens einen linken Nachfolger (left successor) und
höchstens einen rechten Nachfolger (right successor) haben. Ein Knoten ist Vorgänger
(predecessor) seines rechten und seines linken Nachfolgers. Ein Weg (path) von Knoten a
nach Knoten b ist eine Folge von Knoten a = v0 , v1 , · · · , vl−1 , vl = b mit l ≥ 1, für die
stets vi+1 (linker oder rechter) Nachfolger von vi (i = 0, . . . , l − 1) ist. l ist die Zahl der
Übergänge, die Weglänge (path length), und muß mindestens 1 sein.1
Definition 9.1 Ein Binärbaum ist eine endliche nichtleere Menge V von Knoten mit den
folgenden Eigenschaften:
1
Ein wesentlich allgemeiner Wegbegriff wird in Abschnitt 14.1, Seite 373, eingeführt.
9.1. BINÄRE SUCHBÄUME
QUARK
..........................
..........
..........
..........
..........
..........
..........
.
.
.
.
.
.
.
.
.
..........
...
..........
..........
..........
..........
.
.
.
.
.
.
.
..........
.
.
......
.
.
.......... ...
.
.
.
.
.
.
.
.
.
..............
.............
.
.
.
.
.
.
.
.
.
.
. ....
...........
PFAU
RUHE
......
.......
.......
........
.......
.
.
.
.
.
.
.......
.......
. ........
..........
................
............
....... ..............
.......
.......
.......
........
.......
.......
.
.
.
.
.
.
........
.
.......
.......
....... .
.......
.
.
.
.
.
.
..........
.
.
..........
.
.
.
.
..................
............
BESUCH
.......
ROT
...... .............
.......
.......
.......
........
.......
.......
.
.
.
.
.
.
........
........
.......
.......
.......
.
.
.
.
.
.
....... ..
.
..........
.
..........
.
.
.
.
..
..............
...............
ANTON
TANNE
.......
...... .............
.......
.......
.......
........
.......
.......
.
.
.
.
.
.
........
........
.......
.......
.......
.
.
.
.
.
.
....... ..
.
..............
.........
.
.
..............
................
LAGE
SEHEN
....................
.......
.......
.......
........
.......
.
.......
.
.
.
.
.
.
.......
.......
.......
.......
.
........
.
.
.
.
.
.
.
....... ..
................
.........
.
.
................
.................
HUT
......
.......
.......
.......
.......
.
.
.
.
.
.
.
.
.......
.......
. .......
...........
...............
HAT
........
.......
........
.......
.
.
.
.
.
.
.
...
.......
.......
. ........
............
...............
DER
ZAHN
....
........
.......
.......
.......
........
.......
.......
.......
....... ...
..
...................
KNABE
ZANK
............
....... .............
.......
.......
.......
.......
........
.......
.
.
.
.
.
.
.
.......
..
.......
.......
....... .
.......
.
.
.
.
.
....... ..
.
.
............
.
.
.
.
.
...................
...........
LUST
NOCH
........
.......
........
.......
.
.
.
.
.
.
.
...
.......
.......
. ........
............
................
NEIN
Abbildung 9.2: Binärbaum binbaum1
259
260
KAPITEL 9. SUCHBÄUME
1. Jeder Knoten hat höchstens einen linken Nachfolger und höchstens einen rechten
Nachfolger.
2. Jeder Knoten hat höchstens einen Vorgänger.
3. Es gibt genau einen ausgezeichneten Knoten w, die Wurzel (root), der keinen Vorgänger hat.
4. Es gibt genau einen Weg von der Wurzel zu jedem anderen Knoten.
Ein Weg a = v0 , v1 , · · · , vl−1 , vl = b in einem Binärbaum heißt einfach (simple), wenn
alle Knoten paarweise verschieden sind2 . Er heißt offen (open), wenn a 6= b, anderenfalls
geschlossen (closed). Gibt es einen Weg von einem Knoten u zu einem Knoten v, so heißt v
von u erreichbar (reachable). Wichtige Eigenschaften von Binärbäumen sind im folgenden
Satz zusammengefaßt.
Satz 9.1 Es sei B ein Binärbaum.
a. Außer der Wurzel hat jeder Knoten genau einen Vorgänger.
b. In B sind alle Wege einfach.
c. In B sind alle Wege offen.
d. Ist Knoten v von Knoten u erreichbar, so gibt es genau einen Weg von u nach v.
e. Jeder Knoten v aus B bildet zusammen mit allen von ihm erreichbarem Knoten
einen Binärbaum, dessen Wurzel er ist.
Beweis: a. Jeder Knoten außer der Wurzel ist von der Wurzel erreichbar, hat also mindestens einen Vorgänger. Nach Punkt 2 der Definition hat er also genau einen Vorgänger.
b. Wir nehmen an, es gäbe einen nicht-einfachen Weg. v sei der erste Knoten, der auf ihm
mehrfach vorkommt. v kann nicht die Wurzel sein, denn diese hat keinen Vorgänger. Es
sei v 0 das erste Vorkommen von v auf dem Weg und v 00 das zweite. Der eindeutig bstimmte
Vorgänger y von v 0 (und v 00 ) kann nicht auf dem Wege liegen. Dann wäre nämlich v 0 nicht
das erste Auftreten eines mehrfachen Knoten auf dem Weg. Da y jedoch auch Vorgänger
von v 00 , muß es andererseits auf dem Weg liegen, denn v 00 ist nicht der Anfangsknoten des
Weges. Dieser Widerspruch zeigt, daß es nicht-einfache Wege nicht geben kann.
c. In allen Wegen, die zwei oder mehr Knoten durchlaufen, ist nach b. der erste vom
letzten Knoten verschieden. Sie sind offen. Es durchlaufe der Weg nur einen Knoten.
Dieser ist Vorgänger und Nachfolger von sich selbst. Der Knoten kann also nicht die
Wurzel sein. Es gibt dann einen Weg von der Wurzel zu dem Knoten und auf diesem
2
Diese Bedingung ist etwas stärker als die in Definition 14.1, Seite 374.
9.1. BINÄRE SUCHBÄUME
261
einen vom Knoten verschiedenen Vorgänger. Der Knoten hätte dann zwei verschiedene
Vorgänger im Widerspruch zu Punkt 2 der Definition.
d. Gäbe es zwei verschiedene Wege von u nach v, so gäbe es auch zwei verschiedene Wege
von der Wurzel nach v. Das wäre ein Widerspruch zu Punkt 4 der Definition.
e. Beweis als Übung. Der neue Binärbaum kann aus nur einem Knoten bestehen.
2
Folgerung: Der rechte Nachfolger eines Knotens v in einem Binärbaum existiert entweder nicht oder bildet zusammen mit allen von ihm erreichbaren Knoten einen Unterbaum,
den rechten Unterbaum von v. Entsprechend wird der linke Unterbaum von v definiert. 2
Anmerkung 9.1 Linke und rechte Unterbäume kann man benutzen, um Binärbäume
anders zu definieren. Ein Binärbaum ist entweder leer oder besteht aus einer Wurzel und
einem rechten und einem linken Binärbaum als Unterbäumen. Es läßt sich zeigen, daß
diese Definition und Definition 9.1 gleichwertig sind.
2
Größen in einem Binärbaum
Die Anzahl Knoten eines Binärbaumes wird häufig mit n bezeichnet. Es gibt bei n Knoten
die doppelte Anzahl von Feldern, die auf einen Nachfolger verweisen. Die Anzahl Knoten,
die als Nachfolger infrage kommen, ist n−1. Die Anzahl Nachfolgerfelder mit NIL-Verweis
ist demnach 2n − (n − 1) = n + 1. Ein Knoten eines Binärbaumes heißt Blatt (leaf ), wenn
er keine Nachfolger hat. In einem Binärbaum gibt es mindestens
1 Blatt. Da jedes Blatt
Blätter.
zwei leere Nachfolgerfelder aufweist, gibt es höchstens n+1
2
In einem Binärbaum versteht man unter der Stufe (level) eines Knotens die Länge des
Weges von der Wurzel zu dem Knoten +1. Eine sehr wichtige Größe in Binärbäumen ist
die Höhe (height) h. Darunter versteht man die größte Länge eines Weges von der Wurzel
zu einem Blatt +1. Statt von Höhe spricht man gelegentlich auch von Tiefe (depth). Die
größte mögliche Höhe bei n Knoten ist h = n und ergibt sich bei einem linearen Baum
ohne Verzweigungen. Wenn man in einem Binärbaum ein Blatt so umsetzt, daß es danach
näher an der Wurzel liegt, kann die Höhe des Baumes nicht wachsen. Siehe Abbildung 9.3
Aus diesem Grund kann bei einem Binärbaum, der aus n = 2k − 1 Knoten besteht, die
kleinste Höhe hmin (n) nur auftreten kann, wenn ein vollständiger ausgewogener Binärbaum
(complete balanced binary tree) vorliegt. Das ist ein Binärbaum, der auf der ersten Stufe
einen Knoten, auf der zweiten zwei usw. und auf der letzten 2k−1 Knoten aufweist. In
ihm liegen alle Blätter auf der Stufe k und alle anderen Knoten haben zwei Nachfolger.
Abbildung 9.4 zeigt eine solchen Binärbaum aus 15 Knoten. Für solche Binärbäume gilt
hmin (n) = k, also hmin (n) = ld (n + 1). Gilt 2k − 1 < n < 2k+1 − 1, so kommt man
mit k Stufen nicht mehr aus. Man kann jedoch stets Binärbäume angeben, in denen
hmin (n) = k + 1 , also hmin (n) = dld (n + 1)e gilt. Dazu bildet man mit 2k − 1 Knoten
einen vollständigen ausgewogenen Baum und verteilt die n − 2k + 1 verbleibenden Knoten
262
KAPITEL 9. SUCHBÄUME
..........
.... ......
..
.....
..... .......
.
.... ......... ......
....
....
.
.
.
.... ..
.. ....
..........
.................. .........
..... ....
.......................
....
...
..
...
...
....
.
.
...
.... ......
...
.......... ...
.................
.
....
.
.
..
....
.
.
.
.... ..
. ....
..............
................ ........
.
.
.
.
.
.
.
.
...... .....
.... .........
.
.
..
..
.....
....
.
.
.
..
...
...
.
.
.
..................
.....................
.
.
.
.
.
.
....
..
..
.
.
.
.
.
.
.
.
.... .
. ...
. ....
..
......
........
................. ........
...... ............
......................
....... .....
..... ..........
.... .....
.
..
.....
..
....
.
.....
.
.
...
..
.
.... ....
..
...................
...........
..................
....
....
....
....
.... .
. ......
.
..
..........
.
................. .........
.
.
....... ....
....................
.
.
..
...
....
.....
.
.
...
..
.
.
.
.................
......................
.
.
.
.
.
.
.
....
.
..
.
.
.
.
.
.
....
.
... ..
. .....
. .....
.....
........
................. .........
....... ...........
........................
..... ......
..... .........
..
....
..
...
.
.....
...
....
...
.
..
...
...
.
.................
................
..............
w
b
..........
.... ......
....
.
....
..
.
.
. ......
.... ........ .......
...
.
....
.
.
.... ..
.. ....
.........
................. .........
..... ....
........................
...
....
...
...
...
....
.
..
.... ......
...
........... ....
.................
.
.
....
.
.
....
....
.... ..
. ....
..............
................ ........
.
.
.
.
.
.
.
.
...... ....
.... .........
.
..
.
..
....
....
.
..
..
...
.
.
..
.
.
....................
..................
.
.
.
.
.
.
....
..
.
...
.
.
.
.
.
.
.
.... .
. ...
. ...
.. .
........
........
................. .........
.......................
.......................
...... ....
...
.... .....
.
.... .....
..
..
.....
.....
.....
.
.
.... ....
..
..
...
..........
..................
.......................
....
....
....
.....
....
....
. .....
.......
.. ..
.
.
.
.................. .........
............
............
......................
.....................
.
...... .....
.
...
...
...
..
....
....
...
.... .....
.... .....
..
................
...........
................
.
.
.
....
...
.
....
.
.
.... ..
. ....
.....
................. .........
....... ...........
..... ......
..... .........
....
...
..
.
.
.
..
...
...
.................
................
w
b
Abbildung 9.3: Höhe in Binärbäumen
.........
.... ......
.
...
....
...
......................... ................
.
.
.
.
.
.
.
.
........
......
.
.
........
.
.
.
.
.
........
.......
.........
........
........
........
........
........
.
.
.
.
.
.........
.
.
.....
.
........
.
.
.
.
.
.
........
......
.
.
.
.
........
.
.
.
.........
......
.
.
.
.
.
.
.
........
.....
.
.
.
.
........ ... ........
.
.
.
.
... . .. .
...........................................
.
.
...................... ......
.
..
....
.
...
..
.
.
.
.
....
...
.................... .........
.
.
.
...... .............. ............
.
.
......
....
......
......
.
.
.
.
.
.
.
.
.
.
.
.
.
......
......
...
....
......
......
......
......
......
......
......
......
......
......
......
......
.
.
.
.
.
.
......
.
.
.
.
.
.
.
.
...... .
...
...... ..
.
...........
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
........ .........
..
.......... ...................
..............................
.
.
.
.
.
.
.
.
...................................
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
.
...
...
...
.
.
...
.
.
.
.....
.
.
.....
...
.
...
..
.
.
...
.
...
.
.
.
.... .....
..... .....
.. .
....... ......
...... .......
.... ......... ......
.... ......... .......
... ....... .......
... ...... ......
.
.
.
.
.
.
.
.
.
.
.
.
.
.
....
....
....
....
.
..
.
.
...
.
.
.
.
.
.
.
.... ..
.
.
.
.
.
.
.
.
.
.
.
... ..
... ..
... ..
..
..
..
........
........
........
.......
................. .........
................. .........
................. .........
.................. .........
...... ...........
......... ............
.......... ............
.......... ............
.. .
.... .....
..... .....
..... ..........
..... ......
.... .......
.... ......
... ......
.... ......
.
.
.
.
..
.
.
.
..
.
.
.
.
.
.....
.
.
.
.
...
...
...
...
...
...
.
.
..
.
..
.....
.
...
.
.
.
.
.
..
...
.
.
.
.
.
.
.
.... ...
.... ...
.... ...
.... ...
.... ...
.... ...
...... .......
...............
...........
...........
...........
..........
..........
..........
....
Abbildung 9.4: Vollständiger ausgeglichener Binärbaum
so auf die 2 · 2k−1 = 2k Verweisfelder der Stufe k, daß alle Blätter höchstens Stufe k + 1
haben. Es gilt also allgemein
hmin (n) = dld (n + 1)e = Θ(ln(n))
(9.1)
Gleichung 9.1 fließt in die folgende Proposition ein, die für spätere Anwendungen gebraucht wird.
Proposition 9.1 Die maximale und die mittlere Tiefe eines Binärbaumes mit m Blättern
ist wenigstens ld m.
Beweis: Das folgenden ist [OttmW1996] entnommen. Für die maximale Tiefe ergibt
sich das aus Gleichung 9.1.
Für die mittelere Tiefe soll das durch Widerspruch bewiesen werden. Es sei m die kleinste
Anzahl von Blättern, für die es einen Binärbaum T gibt, der der Behauptung nicht genügt.
9.1. BINÄRE SUCHBÄUME
263
Dann ist m ≥ 2 und T hat einen linken Unterbaum T1 mit m1 Blättern und einen rechten
Unterbaum T2 mit m2 Blättern. Es ist m1 + m2 = m. Da m1 und m2 kleiner als m sind,
gilt
mittlere Tiefe(T1 ) ≥ ld m1
mittlere Tiefe(T2 ) ≥ ld m2 .
In T ist die Tiefe eines Blattes genau um 1 größer als in dem Unterbaum T1 oder T2 , in
dem es liegt. Daraus folgt
mittlere Tiefe(T ) =
≥
=
m1
(mittlere Tiefe(T1 ) + 1) + mm2 (mittlere
m
m1
((ld m1 ) + 1) + mm2 (ld m2 + 1)
m
1
(m1 · ld (2m1 ) + m2 · ld (2m2 )).
m
Tiefe(T2 ) + 1)
Die mittlere Tiefe von T ist somit eine Funktion f (m1 , m2 ) und wegen der Nebenbedinung
m1 + m2 = m eine Funktion von m, nämlich f (m1 , m − m2 ). Mit einigem Rechenaufwand
stellt man fest, daß diese Funktion für m1 = m2 = m2 ein Maximum aufweist. Also
mittlerte Tiefe(T ) ≥ ld m, was ein Widerspruch zur obigen Voraussetzung ist,
2.
Eine Klasse von Binärbäumen nennt man buschig (bushy), wenn h(n) = hmin (n) =
Θ(ln(n)). Sie heißt dürr (sparse), wenn h(n) = Θ(n). In etwas ungenauer Sprechweise
heißt ein Binärbaum buschig bzw. dürr, wenn er zu einer solchen Klasse gehört. Buschige
Binärbäume werden häufig auch ausgewogen (balanced) genannt.
Darstellungen von Binarbäumen
Meistens werden Binärbäume wie in Abbildung 9.2 als gerichtete Graphen dargestellt.
Linke Nachfolger werden durch einen nach links gehenden Pfeil, rechte durch einen nach
rechts gehenden Pfeil gekennzeichnet. Manchmal werden die Pfeilspitzen weggelassen und
die Nachfolgerrelation durch die Lage – darüber und darunter – ausgedrückt. Man kann
rechts und links auch anders charakterisieren, z. B. durch Farben.
Gerichtete Bäume sind hierarchische Strukturen und alle Formen der Darstellung solcher
Strukturen können auf solche Bäume angewandt werden. Ein wichtiges Beipiel ist die aus
dem Bibliothekswesen stammende Dezimalklassifikation3 . Tabelle 9.1 zeigt die Struktur
des Binärbaumes aus Abbildung 9.6 in Dezimalklassifikation.
Mit Hilfe von Klammern ist auch eine rein lineare Darstellung von Binärbäumen möglich.
Hierauf soll jedoch nicht weiter eingegangen werden.
Ursprünglich von Leibniz (siehe Seite 93) erdacht. Von Melvil Dewey 4 erweitert und in der zweiten
Hälfte des 19. Jahrhunderts in den USA eingeführt.
3
Dewey, Melvil (eigentlich Melville Louis Kossuth Dewey) ∗10. Dezember 1851, Adams Center, N.Y.
†26. Dezember 1931, Lake Placid, N.Y. Amerikanischer Bibliothekar. Mitbegründer der Amercian Library
Association. Betätigte sich auch als Rechtschreibreformer der englischen Sprache.
4
264
KAPITEL 9. SUCHBÄUME
1
1.1
1.1.1
1.1.1.1
1.1.1.2
1.1.1.2.1.
1.1.1.2.1.1
1.1.1.2.1.1.1
1.1.1.2.2.
1.1.1.2.2.1
1.1.1.2.2.2
1.1.1.2.2.2.1
1.2
1.2.1
1.2.2
1.2.2.1
1.2.2.2
1.2.2.2.2
QUARK
PFAU
BESUCH
ANTON
LAGE
HUT
HAT
DER
KNABE
LUST
NOCH
NEIN
RUHE
ROT
TANNE
SEHEN
ZAHN
ZANK
Tabelle 9.1: Binärbaumstruktur in Dezimaldarstellung
Realisierung von Binarbäumen
Ein naheliegende Realisierung von Binärbäumen benutzt Sätze mit Verweisfeldern. In
Abbbildung 2.9, Seite 72, wurde je ein Verweisfeld für den linken Nachfolger und ein
Verweisfeld für den rechten Nachfolger benutzt. Damit lassen sich Einfüge- und Suchoperationen gut realisieren. Im allgemeinen Fall wird jedoch noch ein weiteres Feld, nämlich
ein Verweis auf den Vatersatz, d. h. den unmittelbaren Vorgänger im Baum, benötigt.
Abbildung 9.5 zeigt schematisch einen Ausschnitt aus einer solchen Realisierung. Man
sieht, daß sie einer doppelten Verkettung entspricht. Wir wollen im folgenden bei Binärbäumen stets eine Realisierung durch Verweise voraussetzen. Um bei der Formulierung
von Algorithmen in C-Pseudocode präziser werden zu können, wollen wir in den Sätzen
der Binärbäume jeweils ein Feld left (linker Nachfolger), ein Feld right (rechter Nachfolger), eine Feld par (Vorgänger, parent) und ein Feld color (für Rot-Schwarz-Bäume)
voraussetzen. Außerdem soll ein Feld key (Schlüsselfeld) vorhanden sein. Für dieses sei
eine lineare Ordnung gegeben.
Es sei erwähnt, daß Binärbäume auch anders als über Zeiger realisiert werden können.
Ein Beispiel liefert die Binärsuche in Abschnitt 8.2. Die Reihung, in der gesucht wird,
trägt implizit die Struktur eines Binärbaumes. Auch bei den Sortierverfahren Quicksort
(Abschnitt 11.2) und Heapsort (Abschnitt 11.3) werden Binärbäume als Reihungen ge-
9.1. BINÄRE SUCHBÄUME
265
.......
............
.. ......
....
....
.....
.....
....
....
.....
.....
....
....
.....
.....
....
....
.....
.....
....
....
.....
.....
....
....
.....
...
◦
Satz1
◦
◦
...
..
...
...
.. ..
...
...
................. ..................
...
..
..
.. ....
...
....
..... .
...
.
.
.
.
.
...
.
.....
...
..
.
.
...
.
.
.
.
.
.
....
...
...
.
.
.
...
.
.
.
.
.
....
...
.
...
.
.
.
.
.
.
.
.
...
.....
..
....
.
.
.
...
.
.
.
.
.....
..
..
...
.
.
.
.
.
.
.
.
.....
...
...
..
.
.
.
.
.
.
.
...
.....
..
....
.
.
.
...
.
.
.
.
.
.....
...
...
.
...
.
.
.
.
.
....
...
..
.
.
.
.
.
.
.
.
.
.
...
.....
..
....
.
.
.
...
.
.
.
.
.....
.. .......
.
.
.
.
..... .....
.. .......
.
.
.
.... ....
. ..
..... .. .
........ .........
..... ..........
..............
..... ........
............
..... .
..
.....
◦
◦
Satz2
◦
....
....
....
....
...
.
.
....
...
....
.......
.............
.
.
..
satz3
◦............
....
...
....
....
....
.... .
..
.................
...
•
◦...........
...
....
....
....
....
.... ..
..................
...
Satz1 ist ein rechter Nachfolger.
• ist ein NIL-Verweis.
Abbildung 9.5: Realisierung von Binärbäumen durch Verweise
speichert.
Durchläufe durch einen Binärbaum
Wie für Listen ist es auch für Binärbäume notwendig, Methoden zur Verfügung zu haben,
mit denen der Datenbestand komplett durchlaufen werden kann. Man sagt, daß die Knoten
nacheinander besucht (visit) werden. Von besonderer Wichtigkeit sind Methoden, die dabei
die Struktur des Binärbaumes berücksichtigen. Wir definieren:
W Besuche die Wurzel
L Besuche die Knoten des linken Unterbaumes. Tue nichts, falls dieser leer.
R Besuche die Knoten des rechten Unterbaumes. Tue nichts, falls dieser
leer.
Das ergibt 6 Methoden des Durchlaufs, nämlich WLR, WRL, LWR, RWL, LRW und
RLW. Dabei wird vorausgesetzt, daß auch die Unterbäume rekursiv auf die gleiche Art
durchlaufen werden. Tabelle 9.2 zeigt ein Programm für LWR. v ist darin eine Knotenva-
266
'
&
KAPITEL 9. SUCHBÄUME
void LWR (VERTEX ∗v)
{
1 if (v == NULL) return;
2 LWR (v→lef t);
3 bearbeite (v);
4 LWR (v ← right);
}
$
%
Tabelle 9.2: LWR-Durchlauf durch einen Binärbaum
riable, die beim ersten Aufruf auf die Wurzel zeigt. Die Bearbeitung in Zeile 3 kann zum
Beispiel in der Ausgabe des Schlüssels bestehen. Es ist unmittelbar klar, wie Programme
für die anderen Durchlaufsarten aussehen.
Drei Methoden haben eigene Namen. Preorder-Durchlauf steht für WLR, PostorderDurchlauf steht für LRW und Inorder-Durchlauf steht für LWR. Die Namen bezeichnen
die Stellung der Wurzel in bezug auf linken und rechten Unterbaum. Die Durchlaufsmethoden, bei denen der rechte Unterbaum vor dem linken Unterbaum bearbeitet wird,
haben keine besonderen Namen, aber im Allgemeinen durchaus eine andere Wirkung.
Als Beispiel soll der Binärbaum von Abbildung 9.2 mit LWR durchlaufen werden. Es ergibt
sich: ANTON, BESUCH, DER, HAT, HUT, LAGE, LUST, KNABE, NEIN, NOCH, PFAU, QUARK,
ROT, RUHE, SEHEN, TANNE, ZAHN, ZANK.
Mehr zu Durchläufen ist auf Seite 268 zu finden.
9.1.3
Binäre Suchbäume und Operationen auf ihnen
Bei Aufbau und Benutzung von Binärbäumen ist die entscheidende Frage, wie festgelegt
wird, welcher Satz linker bzw. rechter Nachfolger eines Satzes ist. Abbildung 6.2, Seite
180, zeigt, wie sich eine Binärbaumstruktur beim Mischsortieren durch die Zerlegung einer
Liste in zwei Teillisten ergibt. Es gibt eine Vielzahl ähnlicher Beispiele. Die wichtigste
Anwendung von Binärbäumen sind jedoch binäre Suchbäume und auf diese wollen wir
uns im folgenden beschränken. Sie sind charakterisiert durch:
Für jeden Knoten eines binären Suchbaumes gilt: Alle Knoten seines linken Unterbaumes haben einen kleineren Schlüsselwert. Alle Knoten seines rechten Unterbaumes haben einen größeren Schlüsselwert.
Beispiel 9.1 Abbildung 9.2 zeigt die Schlüsselwerte von Beispiel 8.1, Seite 236, als Binärbaum, aber nicht als binären Suchbaum. Schlüsselwert KNABE müßte nämlich im linken
Unterbaum von Schlüsselwert LAGE liegen. Abbildung 9.6 zeigt einen korrekten binären
9.1. BINÄRE SUCHBÄUME
QUARK
..........................
..........
..........
..........
..........
..........
..........
.
.
.
.
.
.
.
.
.
..........
...
..........
..........
..........
..........
.
.
.
.
.
.
.
..........
.
.
......
.
.
.......... ...
.
.
.
.
.
.
.
.
.
..............
.............
.
.
.
.
.
.
.
.
.
.
. ....
...........
PFAU
RUHE
......
.......
.......
........
.......
.
.
.
.
.
.
.......
.......
. ........
..........
................
............
....... ..............
.......
.......
.......
........
.......
.......
.
.
.
.
.
.
........
.
.......
.......
....... .
.......
.
.
.
.
.
.
..........
.
.
..........
.
.
.
.
..................
............
BESUCH
.......
ROT
...... .............
.......
.......
.......
........
.......
.......
.
.
.
.
.
.
........
........
.......
.......
.......
.
.
.
.
.
.
....... ..
.
..........
.
..........
.
.
.
.
..
..............
...............
ANTON
TANNE
.......
...... .............
.......
.......
.......
........
.......
.......
.
.
.
.
.
.
........
........
.......
.......
.......
.
.
.
.
.
.
....... ..
.
..............
.........
.
.
..............
................
KNABE
.......
SEHEN
........ ..............
.......
.......
.......
.......
.......
.......
........
.......
.
.
.
.
.
.
.......
....
.
.
.
.
.......
.
.
.
.
....... ...
................
.
...
.
..................
.................
HUT
......
.......
.......
.......
.......
.
.
.
.
.
.
.
.
.......
.......
. .......
...........
...............
HAT
........
.......
........
.......
.
.
.
.
.
.
.
...
.......
.......
. ........
............
...............
DER
ZAHN
....
........
.......
.......
.......
........
.......
.......
.......
....... ...
..
...................
LUST
ZANK
............
....... .............
.......
.......
.......
.......
........
.......
.
.
.
.
.
.
.
.......
..
.......
.......
....... .
.......
.
.
.
.
.
....... ..
.
.
............
.
.
.
.
.
...................
...........
LAGE
NOCH
........
.......
........
.......
.
.
.
.
.
.
.
...
.......
.......
. ........
............
................
NEIN
Abbildung 9.6: Binärer Suchbaum binbaum2
267
268
KAPITEL 9. SUCHBÄUME
Suchbaum aus den gleichen Schlüsselwerten. Hierzu eine Anmerkung: Abbildung 9.6 ist
keineswegs die einzige Möglichleit, die gegebene Schlüsselwertmenge als binären Suchbaum zu organisieren. Man kann z. B. leicht einen binären Suchbaum, in dem nur rechte
Nachfolger vorkommen, daraus bilden. Es ist sogar möglich, in jeder Binärbaumstruktur
mit 18 Knoten die Werte von Abbildung 9.6 so einzusetzen, daß sich ein binärer Suchbaum
ergibt. Dieser ist eindeutig bestimmt. Siehe Aufgabe 9.1.
2
Operationen auf binären Suchbäumen
Binäre Suchbäume benutzen die Binärbaumstruktur, um die Operationen auf einer abstrakten linear geordneten Liste von Schlüsselwerten effizient realisieren zu können. Sie
dienen nicht der Darstellung hierarchischer Sachverhalte und deren Bearbeitung. Wir
sollten also zunächst einmal die Möglichkeit haben, die Liste in aufsteigender bzw. absteigender Reihenfolge der Schlüsselwerte zu bearbeiten. Außerdem sollten die gleichen
schlüsselwertabhängigen Einzeloperationen zur Verfügung stehen wie bei Listen. Das sind
(siehe Seite 234): SIZE, SEARCH, FIND, INSERT, DELETE, MIN, MAX, NEXT, PREVIOUS. Wir
wollen im folgenden zwischen Operationen, die den Suchbaum unverändert lassen, und
solchen, die ihn verändern, unterscheiden.
Durchläufe durch einen binären Suchbaum
Die auf den Seiten 265ff eingeführten Durchlaufsformen WLR, WRL, LWR, RWL, LRW
und RLW lassen sich natürlich auch auf binare Suchbäume anwenden. Als Beispiel sollen
sie auf den Binärbaum von Abbildung 9.6 angewendet werden. Tabelle 9.3 zeigt, in welcher
Reihenfolge die Knoten besucht werden. Es wird dabei jeder Knoten genau einmal besucht.
D. h. wir haben z. B. W OCLW R = Θ(n) bzw. W OCRW L = Θ(n).
Es fällt auf, daß LWR die Sätze in aufsteigender Reihenfolge der Schlüsselwerte liefert
und RWL in absteigender. Das ist kein Zufall. Es gilt nämlich die folgende Proposition.
Proposition 9.2 1. Ein Binärbaum ist genau dann ein binärer Suchbaum, wenn die
Durchlaufreihenfolge LWR die Schlüsselwerte in aufsteigender Reihenfolge liefert.
2. Ein Binärbaum ist genau dann ein binärer Suchbaum, wenn die Durchlaufreihenfolge
RWL die Schlüsselwerte in absteigender Reihenfolge liefert.
Beweis: Behauptung 1. Es sei ein Binärbaum gegeben und k0 , k1 , . . . , kn , kn+1 , . . . die
Reihenfolge in der LWR die Schlüsselwerte liefert (siehe Tabelle 9.2). Ist der Binärbaum
ein binärer Suchbaum, so haben alle Sätze, die vor kn auftreten, einen kleineren Schlüsselwert als kn und alle, die danach auftreten, einen größeren. Die Sätze erscheinen in
aufsteigender Schlüsselwertreihenfolge.
Es liefere LWR für den Binärbaum eine andere Reihenfolge als aufsteigende Schlüsselwerte. Dann gibt es ein i und ein j > i mit ki > kj . Es liegt dann entweder Satz i im linken
Unterbaum von Satz j oder es gibt einen Satz n, in dessen linken Unterbaum Satz i und
in dessen rechten Unterbaum Satz j liegt. Der Binärbaum ist kein binärer Suchbaum.
9.1. BINÄRE SUCHBÄUME
WLR
QUARK
PFAU
BESUCH
ANTON
KNABE
HUT
HAT
DER
LUST
LAGE
NOCH
NEIN
RUHE
ROT
TANNE
SEHEN
ZAHN
ZANK
WRL
QUARK
RUHE
TANNE
ZAHN
ZANK
SEHEN
ROT
PFAU
BESUCH
KNABE
LUST
NOCH
NEIN
LAGE
HUT
HAT
DER
ANTON
269
LWR
ANTON
BESUCH
DER
HAT
HUT
LAGE
LUST
KNABE
NEIN
NOCH
PFAU
QUARK
ROT
RUHE
SEHEN
TANNE
ZAHN
ZANK
RWL
ZANK
ZAHN
TANNE
SEHEN
RUHE
ROT
QUARK
PFAU
NOCH
NEIN
KNABE
LUST
LAGE
HUT
HAT
DER
BESUCH
ANTON
LRW
ANTON
DER
HAT
HUT
LAGE
NEIN
NOCH
LUST
KNABE
BESUCH
PFAU
ROT
SEHEN
ZANK
ZAHN
TANNE
RUHE
QUARK
RLW
ZANK
ZAHN
SEHEN
TANNE
ROT
RUHE
NEIN
NOCH
LAGE
LUST
DER
HAT
HUT
KNABE
ANTON
BESUCH
PFAU
QUARK
Tabelle 9.3: Durchläufe durch einen binären Suchbaum
Der Beweis für die Behauptung 2. verläuft analog.
2
Anmerkung 9.2 Liegen die Schlüsselwerte in einer anderen binären Suchbaumstruktur
vor, z. B. so wie es die Ersetzung der Abbildung 9.8 zeigt, so ergibt LWR (RWL) natürlich
auch wieder die aufsteigende (absteigende) Reihenfolge der Schlüsselwerte. Die Reihefolge
bei anderen Durchlaufsformen ändert sich durchaus. Z. B. liefert WRL die Reihenfolge
QUARK, RUHE, TANNE, ZAHN, ZANK, SEHEN, ROT, PFAU, BESUCH, KNABE, NEIN, NOCH,
LUST, LAGE, HUT, HAT, DER, ANTON.
2
Operationen, die die Struktur unverändert lassen
SIZE: Als Ergebnis wird die Anzahl Sätze im Baum zurückgeliefert, Null für einen leeren
Baum.
Diesen Werte kann man wie bei Listen in einer Gesamtbeschreibung des Baumes führen.
Es ist jedoch manchmal zweckmäßig, eine Prozedur zu haben, die die Größe eines jeden
Unterbaumes liefert. Tabelle 9.4 zeigt eine solche Prozedur. Der zu Knoten v gehörende
Unterbaum wird in RLW Reihenfolge durchlaufen. Die Prozedur ist für alle Binärbäume,
nicht nur für binäre Suchbäume sinnvoll. Der Aufwand ist W OCSIZE = Θ(n).
270
'
&
KAPITEL 9. SUCHBÄUME
int SIZE (VERTEX ∗v)
{
1 if (v == NULL) return 0;
2 return (SIZE(v→right) + SIZE(v→lef t) + 1);
}
$
%
Tabelle 9.4: Operation SIZE in einem
MIN und MAX : Es wird die Adresse des Satzes mit dem kleinsten (größten) Schlüsselwert
zurückgeliefert.
In Tabelle 9.5 ist eine Realisierung für MIN zu sehen. Eine Realisierung für MAX sieht ganz
entsprechend aus. Es wird ein Weg von der Wurzel zu einem Blatt zurückgelegt und alle
'
&
VERTEX ∗MIN (VERTEX ∗v)
{
1 if (v == NULL) return NULL;
2 if (v→lef t == NULL)
3
{return v;}
4 else
5
{return MIN(v→lef t);}
}
$
%
Tabelle 9.5: Minimum in einem Binärbaum
Knoten dieses Weges besucht. Das bedeutet W OCM IN = W OCM AX = h, für buschige
Bäume also W OCM IN = W OCM AX = Θ(ln(n)).
Anmerkung 9.3 MIN und MAX liefern nur die Adresse des „linkesten“ („rechtesten“) Knoten im Baum. Das kann unter Umständen auch für Binärbaume, die keine Suchbäume
sind, von Interesse sein. Wird in einem binären Suchbaum der kleinste (größte) Schlüsselwert gesucht, so muß dieser dem gefundenen Knoten explizit entnommen werden. Die
Prozeduren sind so vom Datentyp der Schlüsselwerte unabhängig.
2
NEXT und PREVIOUS: Es wird zu einem Knoten die Adresse des Satzes mit nächstgrößerem
(nächstkleinerem) Schlüsselwert zurückgeliefert. NULL, falls es diesen Satz nicht gibt.
Im folgenden wird nur NEXT erläutert. Die Betrachtungten für PREVIOUS können leicht
sinngemäß ergänzt werden.
9.1. BINÄRE SUCHBÄUME
271
Der nächstgrößere Schlüsselwert, wenn es ihn gibt, ist der kleinste Schlüsselwert unter den
größeren. Wie können wir diesen Schlüsselwert finden? Dazu betrachten wir den Weg von
der Wurzel zum gegebenen Knoten v. Abbildung 9.7 zeigt ein Beispiel. Wir wollen einen
................
...
..
....
.
.... .......
......... ...
...
....
... ..
.................. .........
...... .....
.
..
....
.
.... 1.....
.......... ...
....
....
.... ..
................. .........
...... ....
...
....
.. 2....
................
.
.
.
..
.
.
.
..
. ....
.....
....... .............
..... .........
..
.....
... 3...
.................
....
....
.... .
..
................. .........
..... ....
..
.....
.
... 4....
.................
....
....
.... .
..
.................. .........
. .
.... .....
..
.....
.... 5......
........... ....
....
....
.... ..
................. .........
.... ....
...
.....
..
...
.................
w
u
u
u
u
u
v
Abbildung 9.7: Linksvorgänger unf Rechtsvorgänger
Knoten auf diesem Wege einen Linksvorgänger nennen, wenn sein Nachfolger auf dem
Weg sein linker Nachfolger ist. Entsprechend ist Rechtsvorgänger definiert. Im Beispiel
sind w, u1 , u3 , u4 und u5 Rechtsvorgänger. u2 ist Linksvorgänger. Die Schlüsselwerte
eines Rechtsvorgängers ebenso wie die seines linken Unterbaumes sind kleiner als der
Schlüsselwert von v und brauchen nicht berücksichtigt zu werden.
Die Schlüsselwerte eines Linksvorgängers sowie die seines rechten Unterbaumes sind größer
als die Schlüsselwerte aller Knoten, die ihnen auf dem Weg folgen, sowie deren Unterbäume. Insbesondere sind sie größer als der Schlüsselwert von v und als die Schlüsselwerte
des rechten Unterbaumes von v.
Das bedeutet: Der Knoten mit nachstgrößerem Schlüsselwert ist der mit kleintstem Schlüsselwert im rechten Unterbaum. Ist der rechte Unterbaum leer, so ist der Knoten mit
nächstgrößerem Schlüsselwert der erste Linksvorgänger auf dem Weg vom Knoten zur
Wurzel. Gibt es keinen solchen Linksvorgänger, so hat der Knoten den größten Schlüsselwert im Baum und es gibt keinen mit nächstgrößerem Schlüsselwert. Damit ergibt sich
zur Bestimmung des Knotens mit nächstgrößerem Schlüssel die in Tabelle 9.6 angegebene
Prozedur.
Zur Bestimmung der Komplexität überlegt man sich, daß NEXT (und auch PREVIOUS)
entweder einen Weg im rechten (linken) Unterbaum des Knotens oder höchstens in Rückwärtsrichtung den Weg vom Knoten zur Wurzel durchläuft. Das bedeutet W OCN EXT =
W OCP REV IOU S = h und für buschige Bäume W OCN EXT = W OCP REV IOU S = Θ(ln(n)).
272
'
KAPITEL 9. SUCHBÄUME
VERTEX ∗NEXT (VERTEX ∗v)
{
1 VERTEX ∗u;
2
3
4
5
6
&
7
8
9
}
if (v == NULL) return NULL;
if (v→right != NULL) return MIN(v→right);
u = v;
while (u→par != NULL)
{ if (u == (u→par)→lef t)
return u→par;
u = u→par;
}
return NULL;
$
%
Tabelle 9.6: Knoten mit nächstgrößerem Schlüssel
Anmerkung 9.4 Ähnlich wie in Anmerkung 9.3 wird bei NEXT und PREVIOUS nur die
Struktur des Binärbaumes benutzt und der Datentyp des Schlüsselfeldes nicht gebraucht.
Die beiden Anweisungen können auch auf Binärbäume, die keine binären Suchbäume sind,
angewandt werden.
2
SEARCH: Es wird ein Satz des Baumes nach seinem Schlüsselwert gesucht und bereitgestellt.
Wird der Satz nicht gefunden, so wird das zurückgemeldet.
Bei der Suche wird in der Wurzel des Baumes begonnen. Ist deren Schlüsselwert gleich
dem Suchwert, so hat man den Knoten gefunden. Ist der Suchwert kleiner, so fährt man
mit dem linken Unterbaum fort. Ist er größer, so wird mit dem rechten fortgesetzt. Auf
diese Weise findet man entweder den Satz mit dem gegebenen Suchwert oder man stößt
zum ersten Mal auf einen leeren Unterbaum. Ist das der Fall, so weiß man, daß es einen
Knoten mit dem gegebenen Suchwert im Baum nicht gibt.
Diese Vorgehen läßt sich leicht rekursiv programmieren, wie die Prozedur in Tabelle 9.7
zeigt. Sie wird mit der Wurzel des Baumes und dem Suchwert als Parametern aufgerufen.
Darin wird angenommen, das die Schlüsselwerte vom Typ int sind. Sind sie von einem anderen Datentyp, z. B. Zeichenreihen, so müssen die entsprechenden Vergleichsoperationen
genommen werden.
Da für SEARCH maximal ein Weg von der Wurzel zu einem Blatt durchlaufen wird, gilt für
die Komplexität auch hier W OCSEARCH = h und für buschige Bäume W OCSEARCH =
Θ(ln(n)).
9.1. BINÄRE SUCHBÄUME
'
&
273
VERTEX ∗SEARCH(VERTEX ∗v, int schluessel)
{
1 if (v == NULL) return NULL;
2 if (v→key == schluessel) return v;
3 if (schluessel < v→key) return SEARCH(v→lef t, schluessel);
4 if (schluessel > v→key) return SEARCH(v→right, schluessel);
}
$
%
Tabelle 9.7: Prozedur SEARCH
FIND Es wird ein Satz des Baumes nach seinem Schlüsselwert gesucht und bereitgestellt.
Ist ein Satz mit dem gegebenen Schlüsselwert nicht in der Liste und dieser kleiner als der
größte und größer als der kleinste existierende Schlüsselwert, so wird der Satz mit dem
nächstkleineren Schlüsselwert bereitgestellt. Anderenfalls wird ein Nullverweis zurückgeliefert.
Zu FIND siehe auch Seite 234, insbesondere die Fußnote. Tabelle 9.8 zeigt eine Realisie'
&
VERTEX ∗FIND(VERTEX ∗v, int schluessel)
{
1 if (v == NULL) return NULL;
2 if (v→key == schluessel) return v;
3 if (schluessel < v→key)
4
{ if {(v→lef t != NULL) return FIND(v→lef t, schluessel);}
5
else {return PREVIOUS(v);}
6
}
7 if (schluesse > v→key)
8
{ if {(v→right != NULL) return FIND(v→right, schluessel);}
9
else
10
{ if {(NEXT(v) == NULL) return NULL;}
11
else {return v;}
12
}
}
$
%
Tabelle 9.8: Prozedur FIND
rung von FIND. Anhand dieser Prozedur soll das Vorgehen erläutert werden. Wenn es
einen Satz mit dem Suchwert gibt, wird er mit den Zeilen 2, 4 und 8 genau so gefunden
274
KAPITEL 9. SUCHBÄUME
wie bei SEARCH. Wenn es ihn nicht gibt, tritt entweder die Abbruchbedingung in Zeile 5
oder die Abbruchbedingung in Zeile 9 ein.
Zeile 5 wird erreicht, wenn die Suche im aktuellen linken Unterbaum fortgesetzt werden müßte, aber dieser leer ist. Da alle Rechtsvorgänger des aktuellen Knoten kleinere
Schlüsselwerte haben als die Werte, die wie der Suchwert in den linken Unterbaum fallen
würden, ist der größte unter ihnen der unmittelbare Vorgänger des aktuellen Knotens
und auch des hypotetischen Knotens mit dem Suchwert als Schlüsselwert. Wenn es diesen
Vorgänger nicht gibt, ist der Suchwert kleiner als der kleinste existierende Schlüsselwert
und es wird korrekt NULL zurückgeliefert.
Zeile 9 wird erreicht, wenn die Suche im aktuellen rechten Unterbaum fortgesetzt werden
müßte, aber dieser leer ist. Dann kann es sein, daß der aktuelle Knoten v den größten
Schlüsselwert im Baum hat (Zeile 10). Wenn das nicht der Fall ist, gibt es Knoten mit
größerem Schlüsselwert. Der kleinste unter ihnen ist NEXT(v) und dessen Schlüsselwert ist
größer als der Suchwert. v ist demnach der Knoten mit nächstkleinerem Schlüsselwert.
Auch hier gilt wieder W OCF IN D = h und für buschige Bäume W OCF IN D = Θ(ln(n)).
Beispiel 9.2 Wir betrachten die Werte aus Tabelle 8.1, Seite 236. Sie mögen als binärer
Suchbaum vorliegen. Es soll mit FIND der Schlüsselwert NAHT gefunden werden. Er liegt in
dem Intervall, das von den Schlüsselwerten LUST und NEIN begrenzt wird. Unabängig von
der Struktur des vorliegenden binären Suchbaumes muß FIND den Satz mit dem Schlüsselwert LUST zurückliefern. In dem binären Suchbaum darf LUST keinen rechten Unterbaum
oder NEIN keinen linken Unterbaum haben. In der Binärbaumstruktur von Abbildung 9.6
hat NEIN keinen linken Unterbaum und Zeile 5 liefert des Satz mit Schlüsselwert LUST als
Ergebnis.
Ersetzt man in Abbildung 9.6 den rechten Unterbaum von KNABE so, wie es Abbildung
9.8 angibt, so ergibt sich wieder ein binärer Suchbaum für die Schlüsselwerte. Jetzt hat
LUST
.....
.... .........
......
......
......
......
......
.....
.
.
.
.
.
......
.....
.
......
.
.
.
...
......
.
.
.
.
.
......
.....
.
.
......
.
.
...
......
.
.
.
.
.
......
.....
.
.
.........
.
.
.
.............
.
.
.
..................
......
LAGE
NOCH
...
......
......
......
......
.....
.
.
.
.
.
..
......
.....
.....
......
......
.
.
.
.
.
. ..
.........
.............
NEIN
NEIN
.....
.... .........
......
......
......
......
......
.....
.
.
.
.
.
......
.....
.
......
.
.
.
...
......
.
.
.
.
.
......
.....
.
.
......
.
.
...
......
.
.
.
.
.
......
.....
.
.
.........
.
.
.
.............
.
.
.
..................
.......
=⇒
LUST
...
NOCH
......
......
......
......
.....
.
.
.
.
.
..
......
.....
.....
......
......
.
.
.
.
.
. ..
.........
.............
LAGE
Abbildung 9.8: Ersetzung eines Unterbaumes
jedoch LUST keinen rechten Unterbaum und der Satz mit dem Schlüsselwert LUST wird
von Zeile 11 geliefert.
2
9.1. BINÄRE SUCHBÄUME
275
Operationen, die die Struktur verändern
Mit diesen Operationen wird ein neuer Satz in einen binären Suchbaum eingefügt bzw. es
wird ein Satz aus dem Baum entfernt. Bedingung ist, daß auch nach der Operation ein binärer Suchbaum vorliegt. Diese Bedingung kann auf ganz unterschiedliche Art und Weise
erfüllt werden, z. B. dadurch, daß mit der neuen Schlüsselwertmenge eine ganz neue Binärbaumstruktur aufgebaut wird. In diesem Kapitel wollen wir „naheliegende“ Realisierungen
der Operationen INSERT und DELETE betrachten. Darunter wollen wir Realisierungen verstehen, die die Struktur des gegebenen Binärbaumes weitestgehend erhalten. Man spricht
auch von „naiven“ Realisierungen (naives Einfügen, naives Löschen).
INSERT: Es wird ein weiterer Satz so eingefügt, daß die Eigenschaft eines binären Suchbaumes erhalten bleibt. Ist ein Satz mit dem gleichen Schlüsselwert in dem Baum schon
vorhanden, so erfolgt eine Fehlermeldung.
Es wird wie bei SEARCH im Baum die Stelle gesucht, an der Satz stehen müßte. Steht
dort schon ein Satz, so wird fälschlicherweise, versucht einen Schlüsselwert zum zweiten
Mal einzufügen. Andernfalls findet man ein leeres rechtes oder linkes Nachfolgerfeld und
kann den neuen Satz dort einfügen. Tabelle 9.9 zeigt eine Realisierung der Prozedur. Diese
liefert TRUE zurück, wenn der Satz eingefügt wurde, und FALSE, wenn der entsprechende
Schlüsselwert schon vorhanden ist. Die Wurzel des Baumes ist t, der einzufügende Satz
ist v. Beide sind vom gleichen Typ. Von v wird vorausgesetzt, daß es nicht NULL ist.
Wie leicht zu sehen, gilt W OCIN SERT = h und für buschige Bäume W OCIN SERT =
Θ(ln(n)).
DELETE: Es wird der Satz mit dem gegebenen Schlüsselwert so aus dem Baum entfernt, daß
die Eigenschaft eines binären Suchbaumes erhalten bleibt. Existiert kein Satz mit diesem
Schlüsselwert, so erfolgt eine Fehlermeldung.
Es sei v der Knoten, der aus der dem Baum entfernt werden soll. Wir nehmen zunächst
einmal an, daß v die Wurzel des Baumes ist, und unterscheiden drei Fälle.
v hat keinen Nachfolger. Dann bleibt nach dem Löschen ein leerer Baum zurück.
v hat genau einen Nachfolger. Dann wird dieser Nachfolger Wurzel des neuen Baumes.
v hat zwei Nachfolger. Der in der Schlüsselwertreihenfolge auf v folgende Knoten sei
v 0 Dieser Knoten gehört zum rechten Unterbaum von v und ist dort Wurzel oder linker
Nachfolger. In jedem Fall muß der linke Unterbaum von v 0 leer sein. Der rechte Unterbaum
braucht nicht leer zu sein. Wenn v 0 nicht Wurzel des rechten Unterbaumes von v ist, wird
es zunächst einmal zur Wurzel dieses Unterbaumes gemacht. Dazu wird v 0 herausgelöst
und sein rechter Unterbaum wird zum linken Unterbaum seines Vorgängers.
Nach dieser Umformung ist es nun leicht, v aus dem ursprünglichen Binärbaum zu entfernen. v 0 wird die Wurzel des neuen Binärbaumes. Der linke Unterbaum von v wird zum
linken Unterbaum von v 0 , der rechte Unterbaum von v 0 bleibt so, wie er sich nach der
Umformung ergab.
Es bleibt noch der Fall, daß der zu entfernende Knoten v nicht Wurzel des gegebenen
276
'
KAPITEL 9. SUCHBÄUME
BOOLEAN INSERT(VERTEX ∗ ∗ t, VERTEX ∗v)
1 { if (∗t == NULL) //v wird Wurzel
2
{ v→par = NULL;
3
v→right = NULL;
4
v→lef t = NULL;
5
∗t = v;
6
return TRUE;
7
}
8 else
9
{ if (∗t→key == v→key) return FALSE;
// Schlüsselwert existiert schon
10
if (v→key < ∗t→key)
11
{ if (∗t→lef t != NULL)
12
{ return INSERT(&(∗t→lef t), v); }
13
else
14
{ v→par = ∗t
15
v→lef t = NULL;
16
v→right = NULL;
17
∗t→lef t = v; // v wird linker Nachfolger von ∗t;
18
return TRUE;
19
} }
20
if (v→key > ∗t→key)
21
{ if (∗t→right != NULL)
22
{ return INSERT(∗t→right, v); }
23
else
24
{ v→par = ∗t
25
v→lef t = NULL;
26
v→right = NULL;
27
∗t→right = v; // v wird rechter Nachfolger von ∗t;
28
return TRUE;
29 } } } }
&
$
%
Tabelle 9.9: Prozedur INSERT
Binärbaumes ist. v ist dann aber Wurzel eines Unterbaumes und diese kann wie oben beschrieben gelöscht werden. Es ergibt sich ein neuer Unterbaum, der an den Vorgänger von
v angefügt werden kann. Im ursprünglichen Binärbaum weisen v und beide Unterbäume
9.2. ROT-SCHWARZ-BÄUME
277
von v nämlich sämtlich größere oder sämtlich kleinere Schlüsselwerte als der Vorgänger
von v auf.
Anmerkung 9.5 Im Fall, daß v zwei Nachfolger hat, kann man auch den Knoten mit
nächstkleinerem Schlüsselwert suchen. Der liegt im linken Unterbaum von v. Das weitere
Vorgehen ist dann anlog zu dem oben beschriebenen.
2
Tabelle 9.10 zeigt eine Realisierung einer Prozedur für DELETE. Sie ruft die Prozedur
'
BOOLEAN DELETE(VERTEX ∗∗t, int key)
1 VERTEX
∗v, ∗w, ∗p;
// v wird gelöscht
// w ersetzt v
// p ist der Vorgänger von v, nach dem Löschen von w
2 BOOLEAN
brechts;
3
4
5
6
7
8
9
10
11
}
$
v = SEARCH(∗t, key);
if ( v == NULL) return FALSE; // Schlüsselwert nicht im Baum
p = v→par;
w = ROOTDELETE(v);
if (p == NULL) { ∗t = w; if (w != NULL) w→par = p; return TRUE;}
brechts = (v == p→right);
if (brechts) {p→right = w;}
else {p→lef t = w;}
return TRUE;
&
%
Tabelle 9.10: Prozedur DELETE
ROOTDELETE (siehe Tabelle 9.11) auf, in der die eigentliche Löschung passiert.
9.2
9.2.1
Rot-Schwarz-Bäume
Definition und Eigenschaften von Rot-Schwarz-Bäumen
Bie der Behandlung der Operationen in binären Suchbäumen (Unterabschnitt 9.1.3) haben wir gesehen, daß deren Komplexität Θ(ln(n)) ist, wenn es sich um buschige Bäume
handelt. Ein buschiger Baum bleibt buschig, wenn Operationen, die ihn nicht verändern,
auf ihn angewandt werden. Wir haben also sehr günstige binäre Suchbäume, wenn wir
278
'
&
KAPITEL 9. SUCHBÄUME
VERTEX ROOTDELETE(VERTEX ∗v)
{
1 VERTEX
∗w;
// v wird gelöscht
// w ersetzt v
//
2
//
3
4
//
5
6
//
7
8
9
10
11
12
13
14
15
}
$
Keine Nachfolger
if (v→lef t == NULL && v→right == NULL) return NULL;
Nur rechter Nachfolger
if (v→lef t == NULL && v→right != NULL)
return v→right;
Nur linker Nachfolger
if (v→lef t != NULL && v→right == NULL)
return v→lef tf ;
Zwei Nachfolger
w = NEXT(v);
w→lef t = v→lef t;
w→lef t→par = w;
if (w == v→right) return w;
(w→par)→lef t = w→right;
if ((w→right != NULL) w→right→par = w→par;
w→right = v→rightf ;
v→right→par = w;
return w;
%
Tabelle 9.11: Prozedur ROOTDELETE
mit einem buschigen Baum beginnen und alle verändernden Operationen wieder einen
buschigen Baum liefern. Rot-Schwarz-Bäume sind eine Datenstruktur, die das gewährleistet.
Ein Rot-Schwarz-Baum ist ein binärer Suchbaum, in dem jeder Knoten genau eine der
Farben SCHWARZ oder ROT aufweist. Die Farben werden benutzt, um zu erzwingen, daß
alle Wege von der Wurzel zu einem Blatt Längen in der gleichen Größenordnung haben.
Das allein reicht für Buschigkeit jedoch nicht. Es muß weiter garantiert werden, daß auch
alle Wege von der Wurzel zu einer Stelle, an der ein „Ast abgeschnitten“ wurde, Längen
in der gleichen Größenordung haben. Dazu lassen wir als Endknoten eines Weges auch
NIL-Verweise zu und führen rb-Wege ein. Ein rb-Weg in einem binären Suchbaum ist ein
9.2. ROT-SCHWARZ-BÄUME
279
Weg, der in einem (echten) Knoten beginnt und in einem NIL-Verweis endet. Ein Beispiel
soll das verdeutlichen.
Beispiel 9.3 In Abbildung 9.9 ist der linke Suchbaum aus Abbildung 9.8, Seite 274, zu
sehen. Die NIL-Verweise wurden hinzugefügt. Beispiele für rb-Wege sind: NOCH-NEINLUST
..........
..... ..........
.....
......
......
......
......
......
.
.
.
.
.
......
......
......
......
......
.
.
.
.
......
...
.
.
.
.
.
......
.....
.
......
.
.
.
...... ..
...........
.
..
..........
.
.
....................
.
.
.
...
LAGE
..
..
...
...
...
...
..
...
.
•
NOCH
..
...
....
......
......
......
.
.
.
.
.
..
......
.....
.....
......
......
.
.
.
.
..
.. .......
.........
.............
...
...
...
...
..
...
.
•
...
...
...
...
..
...
.
•
NEIN.
...
...
...
...
..
...
..
•
...
..
...
...
..
...
..
•
Abbildung 9.9: rb-Wege
rechtsNEIN oder LUST-LAGE-linksLAGE oder NEIN-rechtsNEIN oder LUST-NOCHrechtsNOCH.
2
Definition 9.2 Ein binärer Suchbaum ist ein Rot-Schwarz-Baum, wenn er die folgenden
Eigenschaften hat:
1. Jeder Knoten ist entweder rot oder schwarz.
2. Alle Nachfolger eines roten Knotens sind schwarz.
3. Alle rb-Wege, die von einem Knoten ausgehen, weisen die gleiche Anzahl schwarzer
Knoten auf.
2
Der folgende, recht nützliche Hilfssatz ergibt sich unmittelbar aus der Definition
Hilfssatz 9.1 Hat eine Knoten eines Rot-Schwarz-Baumes nur einen Nachfolger, so besteht der entsprechende Unterbaum aus nur einem roten Knoten und der Knoten selbst
ist schwarz.
Rot-Schwarz-Bäume sind in der Tat buschig, wie sich aus dem folgenden Satz eribt.
Satz 9.2 Für die Höhe h(n) eines Rot-Schwarz-Baumes mit n Knoten gilt h(n) = Θ(ln(n)).
280
KAPITEL 9. SUCHBÄUME
Beweis: h(n) kann nicht kleiner als die minimale Höhe in einem Binärbaum sein. Nach
Gleichung 9.1, Seite 262, gilt also h(n) = Ω(ln(n)). Bleibt zu zeigen, daß auch h(n) =
O(ln(n)) gilt.
Es seien also B ein Rot-Schwarz-Baum mit n Knoten und bh(B) die Anzahl schwarzer
Knoten auf einem rb-Weg, der in der Wurzel beginnt. n = 0 sei zugelassen. In diesem
Fall ist bh(B) = 0. Behauptung: Der Baum hat mindestens n ≥ 2bh(x) − 1 Knoten. Beweis
durch vollstandige Induktion:
n = 0. Für einen leeren Baum gilt bh(x) = 0, also n = 20 − 1 = 0.
Die Behauptung sei richtig für v = 0, 1, . . . , n − 1. Es sei B ein Rot-Schwarz-Baum mit
n ≥ 1 Knoten. B1 sei der linke Unterbaum und B2 der rechte der Wurzel von B. Die
Anzahl der Knoten des Baumes ist n = n1 + n2 + 1, wobei n1 die Anzahl der Knoten
von B1 und n2 die Anzahl der Knoten von B2 ist. Ist die Wurzel von B schwarz, gilt
bh(B1 ) = bh(B) − 1, anderenfalls bh(B1 ) = bh(B). Entsprechendes gilt für B2 . In jedem
Fall haben wir n = n1 +n2 +1 ≥ 2bh(B)−1 −1+2bh(B)−1 −1+1 = 22bh(B)−1 −2+1 = 2bh(B) −1.
Die Höhe h eines Rot-Schwarzbaumes ist die Anzahl Knoten in einem längsten Wege von
der Wurzel zu einem Blatt. Mindestens die Hälfte der Knoten muß schwarz sein. Genauer
. Für gerades
bh(B) ≥ b h2 c. Ist h gerade, so ist b h2 c = h2 . Ist h ungerade, so ist b h2 c = h−1
2
h
bh(B)
h ergibt sich n ≥ 2
− 1 ≥ 2 2 − 1, also 2ld (n + 1) ≥ h. Für ungerades h finden wir
2ld (n + 1) + 1 ≥ h. In beiden Fällen ist h(n) = O(ln(n)).
2
9.2.2
Operationen auf Rot-Schwarz-Bäumen
Alle Operationen aus Unterabschnitt 9.1.3, die den Baum nicht verändern, können direkt
auf Rot-Schwarz-Bäume angewandt werden. Das sind vollständige Durchläufe, SIZE, MIN,
MAX, NEXT, PREVIOUS, SEARCH, FIND. Für Durchläufe und SIZE gilt W OC = Θ(n), für
alle übrigen W OC = Θ(ln(n)). Wenn die Operationen Einfügen und Löschen eines Satzes
mit Auwand Θ(ln(n)) ausgeführt werden und nach ihnen der Baum weiterhin ein RotSchwarz-Baum bleibt, dann haben wir einen vollständigen Satz effizienter Operationen für
Rot-Schwarz-Bäume. Man kann in der Tat Einfügungen und Löschungen so ausführen,
daß beide Bedingungen erfüllt sind. Beim Einfügen erreichen wir das, indem wir einen
neuen Satz auf naive Art einfügen und danach Veränderungen, die unabhängig von der
Größe des Baumes nur eine feste Anzahl von Operationen benutzen, sogenannten lokale
Veränderungen (local changes), anwenden und in höchstens O(ln(n)) Schritten den Baum
zu einem Rot-Schwarz-Baum umformen. Beim Löschen kann man analog vorgehen. Allerdings werden wir einen etwas abweichenden Lösungsweg verfolgen. Außerdem werden wir
die beiden Operationen so implementieren, daß die Wurzel stets schwarz ist.
Rotationen
Rotationen (rotation) sind Operationen, die lokale Veränderungen auf einem binären Suchbäum so bewirken, daß danach weiterhin ein binärer Suchbaum vorliegt. Es gibt die Ope-
9.2. ROT-SCHWARZ-BÄUME
281
rationen Rechtsrotation (right rotation) und Linksrotation (left rotation). Sie sind invers
zueinander. Abbildung 9.10 zeigt schematisch die Wirkung von Links-und Rechtsrotation.
...
...
...
...
...
.........
.........
..
Rechtsrotation
=⇒
Y
X
....
....
...
....
.
.
.
....
.. ....
...........
.......
....
...
....
....
...
.
.
. ...
.. ...
...........
......
....
....
....
....
....
.... .
.. .
.................
..
w
....
...
....
....
....
....
.......
...............
..
...
...
...
...
...
.........
.........
..
⇐=
X
.....
......
......
......
......
.
.
.
.
.....
.. ......
.........
............
u
..
....
....
...
.
.
.
.
....
.. ....
...........
......
Linksrotation
v
u
....
....
....
....
....
.... .
.. .
.................
..
Y
....
...
....
....
....
....
... ..
.................
..
v
w
Abbildung 9.10: Rotationen (nach [CormLR1990])
Man kann erkennen, daß die Eigenschaft, ein binärer Suchbaum zu sein, erhalten bleibt.
Rotationen verändern keine Farben. Pseudocode für die Rotationen ist in den Tabellen
9.12 und 9.13 angegeben.
'
$
&
%
void LEFTRORATE (VERTEX ∗ ∗ t, VERTEX ∗Y )
1 { VERTEX
∗X;
2 X = Y →par;
3 if (X == NULL) { fehlermeldung; return; }// Kein Vater
4 if (X→right) != Y) { fehlermeldung; return; } // Nicht rechter Nachfolger
5 Y →par = X→par;
6 if (Y →par != NULL) // Y an Vorgänger von X anhängen
7
{ if (isleft (X)) { Y →par→lef t = Y ; } else { Y →par→right = Y ; }}
8 else // Y wird Wurzel
9
{ ∗t = Y ; }
10 X→right = Y →lef t;
11 Y →lef t = X;
12 X→par = Y ;
13 if (X→right != NULL) X→right→par = X;
14 return;
Tabelle 9.12: Prozedur LEFTROTATE
282
'
KAPITEL 9. SUCHBÄUME
void RIGHTRORATE (VERTEX ∗ ∗ t, VERTEX ∗X)
1 { VERTEX
∗Y ;
2 Y = X→par;
3 if (Y == NULL) { fehlermeldung; return; } // Kein Vater
4 if (Y →lef t) != X) { fehlermeldung; return; } // Nicht linker Nachfolger
5 X→par = Y →par;
6 if (X→par != NULL) // X an Vorgänger von Y anhängen
7
{ if (isleft (Y )) { X→par→lef t = X; } else { X→par→right = X; }}
8 else // X wird Wurzel
9
{ ∗t = X; }
10 Y →lef t = X→right;
11 X→right = Y ;
12 Y →par = X;
13 if (Y →lef t != NULL) Y →lef t→par = Y ;
14 return;
&
$
%
Tabelle 9.13: Prozedur RIGHTROTATE
RBINSERT
Hier soll eine Lösung fur das Einfügen vorgestellt werden. Es sei ein Rot-Schwarz-Baum
gegeben und es soll ein neuer Satz eingefügt werden. Dazu wird er zunächst naiv, also mit
INSERT eingefügt. Er wird dann rot gefärbt. Wenn der Satz, an den er angehängt wurde,
schwarz ist, bleibt der erweiterte Baum ein Rot-Schwarzbaum. Anderenfalls ist von den
Bedingungen der Definition 9.2 nur Punkt 2. verletzt. Das weitere Vorgehen läßt sich am
besten anhand eines Beispiels erklären.
Beispiel 9.4 Abbildung 9.11 zeigt im oberen Teil einen binären Suchbaum mit roten
Knoten (kursiv) und schwarzen Knoten (Fettdruck). Ohne den Knoten HUT ist es ein
Rot-Schwarz-Baum. Wird HUT als roter Knoten an der entsprechenden Stelle eingefügt,
so folgen die roten Knoten KNABE und HUT aufeinander. Im übrigen bleibt es ein
korrekter Rot-Schwarz-Baum.
Um die aufeinander folgenden beiden roten Knoten aufzulösen, sehen wir uns den Vorgänger von KNABE, nämlich den Knoten LAGE an. Er ist, wie es sein muß, schwarz.
Sein zweiter Nachfolger, der Knoten QUARK, muß rot sein (warum?) und ist das auch.
Man kann nun, ohne die Struktur des Baumes zu ändern, durch einfaches Umfärben weiterkommen: LAGE wird rot und KNABE und QUARK werden schwarz. Im unteren Teil
von Abbildung 9.11 ist das Ergebnis dargestellt.
Auf der untersten Stufe ist damit die Farbkollision beseitigt. Leider ist in unserem Beispiel
eine Stufe darüber (also näher an der Wurzel) jedoch eine neue Farbkollision aufgetreten:
9.2. ROT-SCHWARZ-BÄUME
283
SEHEN
......
.....
.............
.............
.............
.............
............
.
.
.
.
.
.
.
.
.
.
.
.
.........
.............
..... .............
......................
.
......
.......
.......
......
.
.
.
.
.
..
......
. ........
.........
..............
DER ....
.............
.............
.............
.............
.............
.............
.............
............. ..
...................
.............
TANNE
....
......
.......
.......
.......
.......
.......
....... ..
..
...................
BESUCH
......
.......
.......
.......
.......
.......
....... ..
..
....................
LAGE...
...
.....
.....
.....
.....
.
.
.
.
....
........
...........
........
KNABE
ZANK
.....
.....
.....
.....
.....
.....
..... ...
...................
.
QUARK
...
.....
.....
.....
.
.
.
.
..
.....
.. .....
............
.......
HUT
Fall 1
SEHEN
.......
.
............
.............
.............
.............
.
.
.
.
.
.
.
.
.
.
.
............
.............
.............
..... ..............
......................
....
.......
......
......
......
.
.
.
.
.
.
......
. ........
.........
..............
DER ...
.......
.......
.......
.......
.......
.......
..........
...................
BESUCH
KNABE
TANNE
...
.......
.......
.......
.......
.......
.......
..........
....................
LAGE...
....
.....
.....
.....
.....
.
.
.
.
..
.. ......
............
........
.............
.............
.............
.............
.............
.............
.............
............. ...
..................
............
ZANK
.....
.....
.....
.....
.....
.....
..... ...
...................
.
QUARK
.
.....
....
.....
....
.
.
.
.
....
.....
...........
...........
HUT
Abbildung 9.11: Beispiel für RBINSERT – Teil I (nach [CormLR1990])
DER und LAGE sind beide rot. Jetzt kommt man mit einfachem Umfärben nicht mehr
weiter. Wird einer der Knoten DER oder LAGE schwarz, so ist auf jeden Fall Bedingung
3 der Definition 9.2 verletzt. Um weiterzukommen, wollen wir Rotationen anwenden und
versuchen, damit die beiden Unterbäume der Wurzel auszugleichen. Dazu muß SEHEN
in den rechten Unterbaum wandern und ein Knoten aus dem linken Unterbaum Wurzel
werden. Das läßt sich mit einer Rechtsrotation um SEHEN erreichen. Würde man diese
direkt anwenden, so wird, wie leicht zu sehen, das Ungleichgewicht auf die andere Seite der
(neuen) Wurzel verlagert. Deswegen wird voher auf DER eine Linksrotation angwendet.
284
KAPITEL 9. SUCHBÄUME
Fall 2
SEHEN
.......
...
...........
...........
...........
...........
...........
.
.
.
.
.
.
.
.
.
.
........
...........
.
.... ............
....................
LAGE...
......
.......
.......
.......
.
.
.
.
.
...
.......
.. .......
........
..............
...
...
..
...
.......
..........
......
.............
.............
.............
.............
.............
.............
.............
............. ...
..................
............
TANNE
...
.......
.......
.......
.......
.......
.......
..........
...................
.......
.......
.......
.......
.......
.......
..........
....................
QUARK
DER ....
ZANK
.......
.......
.......
.......
.......
....... .
..........
...............
BESUCH
KNABE
...
.....
.....
.....
....
.
.
.
.
...
.........
............
.......
HUT
Fall 3
LAGE.......
...........
.............
.............
.............
.
.
.
.
.
.
.
.
.
.
.
.
.
.............
.............
.............
..... .............
......................
..
.......
......
......
......
.
.
.
.
.
.
...
......
.. ......
..........
.............
BESUCH
DER ...
......
.......
.......
.......
.......
.......
....... ..
.. .
..................
KNABE
...
..
.....
.....
.....
.....
.
.
.
.
..
.. ......
..............
........
HUT
.............
.............
.............
.............
.............
.............
.............
............. ...
..................
............
SEHEN...
..
.......
......
......
......
.
.
.
.
.
.
...
......
.. ......
..........
..............
QUARK
......
.......
.......
.......
.......
.......
....... ..
.. .
...................
TANNE
...
.....
.....
.....
.....
.....
.....
..... ...
.................
.
ZANK
Abbildung 9.12: Beispiel für RBINSERT – Teil II (nach [CormLR1990])
Das Ergebnis ist im oberen Teil der Abbildung 9.12 zu sehen. Die Kollision zweier roter
Knoten existiert weiterhin und sie ist auch nicht näher an die Wurzel gerückt. Die Lage
der beiden roten Knoten im Baum hat sich jedoch verändert. Nun kann die Rechtsrotation
auf SEHEN angewandt werden. Sie macht LAGE zur neuen Wurzel. Um die Farbkollision
aufzuheben, wird LAGE schwarz und als Folge davon SEHEN rot. Das Ergebnis ist im
unteren Teil von Abbildung 9.12 zu sehen. Man kann leicht nachprüfen, daß es sich um
einen korrekten Rot-Schwarz-Baum handelt.
2
Dem Beispiel folgend ist etwas mühsam, aber nicht wirklich schwer den Pseudocode für
RBINSERT zu entwickeln. Er ist in den Tabellen 9.14 und 9.15 wiedergegeben. Abbil-
9.2. ROT-SCHWARZ-BÄUME
'
BOOLEAN RBINSERT(VERTEX ∗ ∗ t, VERTEX ∗v)
1 { VERTEX
∗x, ∗f , ∗g, ∗o; actual node, father, grandfather, oncle
2 BOOLEAN
Bo = FALSE, Bred = FALSE, B3 = FALSE;
3
4
5
6
7
8
9
10
11
12
//
13
14
15
16
17
18
if (!INSERT(∗t, v)) return FALSE; //Schlüsselwert schon vorhanden
x = v;
x→color = RED;
while (x→par) != NULL)
{ f = x→par; // Vater von x
if (f→color == BLACK) break; // Fertig, wenn Vater schwarz
g = f→par; // Grossvater von x; muss schwarz sein
o = otherson(g, f ) // Onkel von x
if (o != NULL) Bo = TRUE; // Onkel existiert
if (Bo) Bred = (o→color == RED); Onkel existiert und ist rot
Fall 1: Onkel existiert und ist rot – Umfaerben Vater, Grossvater und Onkel
if (Bred)
{ f→color = BLACK;
o→color = BLACK;
g→color = RED;
x = g; // Grossvater wird akltueller Knoten
}
&
285
$
%
Tabelle 9.14: Prozedur RBINSERT – Teil I
dung 9.13 zeigt den Rot-Schwarz-Baum, der sich ergibt, wenn die Knoten SEHEN, DER,
RUHEN, ANTON, ZAHN, TANNE, BESUCH, HUT, KNABE, LAGE, ROT, QUARK, PFAU, NOCH,
NEIN, LUST, ZANK, HAT in dieser Reihenfolge mit RBINSERT eingefügt werden.
Zum Aufwand für RBINSERT: Das naive Einfügen eines Satzes kostet Θ(ln(n)) Schritte.
Der Aufwand für das anschließende Umformen, um wieder einen Rot-Schwarz-Baum zu
erhalten, wird durch die while-Schleife (Tabellen 9.14 und 9.15) bestimmt. In der Schleife
werden bei jedem Durchlauf die beiden aufeinanderfolgende roten Knoten näher an die
Wurzel heran geschoben. D. h. auch das Umformen, und damit die gesamte Operation
kosten Θ(ln(n)) Schritte.
RBDELETE
Auch das Löschen eines Satzes in einem Rot-Schwarz-Baum läßt sich mit einem Zeitaufwand Θ(ln(n)) durchführen, und zwar so, daß hinterher wieder ein Rot-Schwarz-Baum
entsteht. Um das zu sehen, betrachten wir die Fälle: Der zu löschende Knoten hat zwei
286
HUT
....................................
.............
.................
.............
................
.............
................
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.............
.
...
.............
................
.............
.................
.
.
.
.
.
.
.
.
.
.
.
.............
.
.
.
.
..........
.
.
............. ...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
..................
.......................
.
.
.
.
.
.
.
.
. ......
............
QUARK
.
BESUCH
......
..............
....
..........
..........
....
..........
....
..........
.
.
.
.
.
....
.
.
.
.
.......
.
....
.
.
.
.
.
.
.
.
.... ..
.......
.
.
.
.
.
.
.
.
.
...............
.................
.
.
.
.
.
.
.
...
.............
... ..
....... ......
.......
....
........
....
.......
.
.
.
....
.
.
.
..
....
.......
.... ..
.......
.
.
.
.
.
.
.
.........
.................
.
.
.
.
.
...
.... ......
ANTON
DER
..
...
...
...
...
...
...
..............
......
HAT
LAGE
RUHEN
.....
...
....... ...
....... ....
........
...
.......
.
...
.
.
.
.
.
...
........
..
.......
.
.
.
.
.
.
.
................
..............
.
.
.....
.
................
KNABE
NOCH
....
.. ............
....
.......
.......
....
.......
...
.
.
........
.
.......
....
.......
...
.
.
....... ..
.
.
.........
................
.
...............
...
ROT
..... ...
...... .......
......
....
......
....
......
.
.
....
.
.
.
.
.... .
......
.. .
............
.
.
.................
....
..
..............
NEIN
..
PFAU
..
...
...
...
.
.
.. ....
.. .
.........
....
Abbildung 9.13: Rot-Schwarz-Baum rbbaum
..... ...
...... ......
......
...
....
......
......
....
.
.
.
.
.
....
...
......
.......
..............
.
...............
.
..
..............
SEHEN
ZAHN
..
....
....
....
....
....
....
.... ..
.................
...
ZANK
KAPITEL 9. SUCHBÄUME
LUST
TANNE
....
9.2. ROT-SCHWARZ-BÄUME
'
// Fall 2: Onkel ist schwarz oder existiert nicht – Vater ist linker Nachfolger
19
if (!Bred && isleft(f ))
20
{ if (isright(x)) {leftrotate(t, x); } else { x = f ; }
21
rightrotate(t, x);
22
x→color = BLACK;
23
g→color = RED;
24
break;
25
}
// Fall 3: Onkel ist schwarz oder existiert nicht – Vater ist rechter Nachfolger
26
if (!Bred && isright(f ))
27
{ if (isleft(x)) {rightrotate(t, x); } else { x = f ; }
28
lefttrotate(t, x);
29
x→color = BLACK;
30
g→color = RED;
31
break;
32
}
33
}
34
};
35 ∗t→color = BLACK; // Wurzel ist immer schwarz
36 return TRUE;
37}
&
287
$
%
Tabelle 9.15: Prozedur RBINSERT – Teil II
Nachfolger und der zu löschende Knoten hat höchstens einen Nachfolger. Die Prozeduren,
mit denen man das Löschen durchführen kann, sind in den Tabellen 9.16 (Seite 288),
9.17 (Seite 290), 9.18 (Seite 291) und 9.19 (Seite 292) angegeben. Anhand dieser Prozeduren wird erläutert, wie man vorgeht.
Der zu löschende Knoten hat zwei Nachfolger. Die Hauptrozedur RBDELETE
(Tabelle 9.16) behandelt diesen Fall. In dieser Prozedur wird zunächst geprüft, ob es den
zu löschenden Satz überhaupt gibt (Zeilen 5 und 6), und dann, ob ein Satz mit höchstens
einem Nachfolger gelöscht werden soll (Zeilen 7 und 8).
In diesem Fall gibt es im Baum einen nächstgrößeren Schlüsselwert. Wenn man einfach
im zu löschenden Satz den Schlüsselwert mit dem nächstgrößeren überschriebe, bliebe der
Rot-Schwarz-Baum korrekt, allerdings müßte der Satz mit diesem Schlüssel gelöscht werden. Mit dieser Idee kommt man weiter; es sind jedoch einige zusätzliche Überlegungen
notwendig. Zunächst einmal wollen wir einen Satz aus dem Baum nur ausketten und nicht
288
'
KAPITEL 9. SUCHBÄUME
BOOLEAN RBDELETE(VERTEX ∗ ∗ t, int key)
1 { VERTEX *v; // v wird gelöscht
2 VERTEX *w; // w ersetzt v
3 VERTEX *w1; // w1 Platzhalter für w
4 BOOLEAN bsucc; // w1 ist Nachfolger von v
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 }
$
v = SEARCH(∗t, key);
if ( v == NULL) return FALSE; // Schlüsselwert nicht im Baum
// v hat höchstens einen Nachfolger
if (v→right == NULL || v→lef t == NULL)
{onedel(tree, v); return TRUE; }
// v hat zwei Nachfolger
w = min(v→right);
bsucc = (w→par == v); // w ist rechter Nachfolger von v
/* Platzhalter für w konstruieren */
w1 = allocate();
w1→color = w→color; w1→lef t = w→lef t; w1→right = w→right;
if (bsucc) {w1→par = w; w→right = w1; }
else {w1→par = w→par; w→par→lef t = w1; }
/* v durch w ersetzen */
w→lef t = v→lef t; w→lef t→par = w;
if (!bsucc) {w→right = v→right; w→right→par = w; }
w→color = v→color; w→par = v→par;
if (v->par != NULL)
{ if (v→par→lef t == v) v→par→lef t = w; else v→par→right = w; }
else { *t = w; }
onedel(t, w1); // Platzhalter entfernen
deallocate(w1); //
return TRUE;
&
%
Tabelle 9.16: Prozedur RBDELETE – Teil I
wirklich löschen. Außerdem dürfen Schlüsselwerte in Sätzen nicht überschrieben werden,
denn sie sind ein Identifikationsmerkmal. Das führt zu der Lösung, die im Rest der Prozedur zu sehen ist. In Zeile 9 wird der Satz w mit dem nächsthöheren Schlüsselwert bestimmt
9.2. ROT-SCHWARZ-BÄUME
289
und in Zeile 10 festgehalten, ob dieser unmittelbarer Nachfolger des zu löschenden Satzes
ist oder nicht. In den Zeilen 11 bis 14 wird ein Platzhalter für w konstruiert und an der
entsprechenden Stelle in den Baum eingefügt. Die Anweisungen in den Zeilen 15 bis 20
ketten den zu löschenden Satz aus dem Baum aus und ersetzen ihn durch w. Schließlich
wird in Zeilen 21 und 22 der Platzhalter gelöscht – er hat höchtens einen Nachfolger! –
und sein Speicherplatz freigegeben.
Der zu löschende Knoten hat höchsten einen Nachfolger. Es gibt dann die folgenden Möglichkeiten.
1. Der Knoten ist rot und hat keine Nachfolger.
2. Der Knoten ist schwarz und hat keine Nachfolger.
3. Der Knoten ist schwarz und hat einen linken roten Nachfolger.
4. Der Knoten ist schwarz und hat einen rechten roten Nachfolger.
Dabei kann Fall 3 nicht auftreten, wenn der zu löschenden Satz der Platzhalter aus RBDELETE ist. Die Bearbeitung der vier Fälle beginnt mit der Prozedur ONEDEL,
Tabelle 9.17 (Seite 290). Zeilen 3 bis 5 bestimmen den Nachfolger, der auch NULL sein
kann. Zeile 6 findet den Vorgänger. Ist der zu löschende Knoten die Wurzel des Baumes,
so wird er gelöscht und, falls ein Nachfolger vorhanden ist, wird dieser die neue Wurzel
(Zeilen 7 bis 10). Ist er nicht die Wurzel und hat er einen von NULL verschiedenen Nachfolger, so wird er durch diesen ersetzt, wobei der Nachfolger schwarz gefärbt wird (Zeilen
16 bis 17) und sich somit ein korrekter Rot-Schwarz-Baum ergibt.
Es bleibt der Fall, daß der zu löschende Knoten keine Nachfolger hat und nicht Wurzel ist.
Dieser Fall wird in der Prozedur RBDELREBALANCE (Tabellen 9.18, Seite 291 und
9.19, Seite 292) behandelt. Diese Prozedur ruft sich rekursiv auf (Zeilen 11 und 19). Um
zu verstehen, wieso die Löschung auf einen korrekten Rot-Schwarz-Baum führt, wollen
wir den Anfangsaufruf und eventuelle Folgeaufrufe der Prozedur getrennt ansehen. Beim
ersten Aufruf ist x der zu löschende Satz, nicht die Wurzel und ohne Nachfolger. Ist x
rot, so wird mit der Anweisung 3 eine (in diesem Fall überflüssige) Umfärbung vorgenommen und die Prozedur verlassen. Der verbleibende Rot-Schwarz-Baum ist korrekt. Ist x
schwarz, so muß es einen Bruder s geben. Vom mitgelieferten Parameterwert blef t hängt
es ab, ob es der linke oder rechte Bruder ist. Die weitere Bearbeitung hängt von diesem
Bruder ab.
Fall A: Der Bruder ist rot. Der Vater, der schwarz war, wird rot gefärbt, der Bruder
schwarz (Zeile 9). Danach wird durch eine entsprechende Rotation (Zeile 10) der Bruder
zum Großvater. Da x schwarz ist, der Bruder rot war, mußte der Bruder vor der Rotation zwei scharze Söhne gehabt haben. Einer davon wird nach der Rotation neuer,jetzt
schwarzer Bruder von x, der andere bleibt Sohn von s. Mit einigen Überlegungen erkennt
290
'
&
KAPITEL 9. SUCHBÄUME
void ONEDEL(VERTEX ∗ ∗ t, VERTEX ∗v)
1 { VERTEX
∗w, ∗p, ∗g, ∗o; // successor, predeccessor
2 BOOLEAN
blef t;
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$
if (v→right != NULL) w = v→right; // Nachfolger bestimmen
if (v→lef t != NULL) w = v→lef t;
if (v→right == NULL && v→lef t == NULL) w = NULL;
p = v→par; // Vorgänger bestimmen
if (p == NULL) // Wurzel löschen
{ *t = w;
if (w != NULL) { w→color = BLACK; w→par = NULL;};
return;
}
else // Nichtwurzel löschen
{ if (v == p→lef t) { blef t = TRUE; p→lef t = w; }
else { blef t = FALSE; p→right = w; }
}
if (w != NULL)
{ w→par = p; w→color = BLACK; return; }
rbdelrebalance(t, v, blef t);
return;
}
%
Tabelle 9.17: Prozedur ONEDEL
man, daß auch der neu gebildete Baum ein korrekter Rot-Scharz-Baum ist, wenn man
x hinzurechnet. Mit diesem umgeformten Baum wird RBDELREBALANCE rekursiv
aufgerufen (Zeile 11).
Fall B.1: Der Bruder ist schwarz. Beide Neffen sind schwarz oder NULL.
Fall B.2: Der Bruder ist schwarz. Es gib einen roten Neffen.
9.2. ROT-SCHWARZ-BÄUME
291
'
void RBDELREBALANCE(VERTEX ∗ ∗ t, VERTEX ∗x, BOOLEAN blef t)
1 { VERTEX
∗s, ∗p; // Bruder, Vater
2 BOOLEAN
b, bblack1, bblack2, blef tred, brightred;
3
4
5
//
6
7
//
8
9
10
11
12
//
13
14
15
16
17
//
18
19
if (x→color == RED) { x→color = BLACK; return; }
if (x→par == NULL) return; // Wurzel erreicht
p = x→par;
x→color ist schwarz; nicht Wurzel
if (blef t) s = p→right; // rechter Bruder von v; stets vorhanden
if (!bleft) s = p→lef t; // linker Bruder von v; stets vorhanden
Fall A: Bruder ist rot
if (s→color == RED)
{ p→color = RED; s→color = BLACK;
if (blef t) leftrotate(t, s); else rightrotate (tree, s);
rbdelrebalance(t, x, blef t); return;
}
Fall B: Bruder ist schwarz
else
{ bblack1 = FALSE; bblack2 = FALSE;
bblack1 = (s→right == NULL) && (s→lef t == NULL);
if (s→right != NULL && s→lef t != NULL)
bblack2 = (s→right→color == BLACK && s→lef t→color == BLACK);
Fall B.1: Beide Neffen sind schwarz oder NULL
if (bblack1 || bblack2)
{ s→color = RED; rbdelrebalance(t, p, (p→par→lef t == p)); return; }
&
Tabelle 9.18: Prozedur RBDELREBALANCE – Teil I
$
%
292
KAPITEL 9. SUCHBÄUME
'
$
&
%
// Fall B.2: Mindestens ein Neffe ist rot
20
else
21
{ blef tred = FALSE; brightred = FALSE;
22
if (blef t) // x ist linker Nachfolger von p
23
{ blef tred = (s→right == NULL);
24
if (!blef tred) blef tred = (s→right→color == BLACK);
25
if (blef tred) { s→lef t→color = BLACK; s→color = RED;
26
rightrotate(t, si→lef t); s = p→right; }
27
s->color = x->par->color; x->par->color = BLACK;
28
s→right→color = BLACK; leftrotate(t, s);
29
}
30
else // x ist rechter Nachfolger von p
31
{ brightred = (s→lef t == NULL);
32
if (!brightred) brightred = (s→lef t→color == BLACK);
33
if (brightred) { s→right→color = BLACK; s→color = RED;
34
leftrotate(t, s→right); s = p→lef t; }
35
s→color=→color; x→par→color = BLACK;
36
s→lef t→color = BLACK; rightrotate(t, s);
37
}
38
}
39
}
40 return;
41 }
Tabelle 9.19: Prozedur RBDELREBALANCE – Teil II
9.3. ZUFÄLLIGE BINÄRE SUCHBÄUME
9.3
293
Zufällige binäre Suchbäume
Wenn ein binärer Suchbaum mit naivem Einfügen aufgbaut wird, kann er buschig, dürr
oder irgend etwas dazwischen werden. Die Form des Baumes ergibt sich aus der Reihenfolge, in der die zu speichernden Schlüsselwerte eintreffen. Wenn man, was meistens
der Fall ist, diese Reihenfolge nicht kennt, kann man annehmen, daß die Reihenfolge der
Schlüsselwerte „zufällig“ ist. Das bedeutet, daß alle Reihenfolgen mit der gleichen Wahrscheinlichkeit P (p) = n!1 auftreten. Einige Suchbaumstrukturen werden durch genau eine
Reihenfolge erzeugt, z. B. der Baum mit ausschließlich Rechtsnachfolgern durch die Reihenfolge der aufsteigend sortierten Schlüsselwerte. Für andere Strukturen gibt es mehrere
Reihenfolgen, die sie erzeugen. Insgesamt ist es so, daß „günstige“ Suchbaumstrukturen
mit größerer Wahrscheinlichkeit erzeugt werden als „ungünstige“. Um das zu präzisieren,
wollen wir vereinfachend annehmen, daß ein binärer Suchbaum aus n nacheinander eintreffenden Sätzen einer zufälligen Ankunftsreihenfolge mit naivem Einfügen aufgebaut
wird. Danach finden in ihm nur noch Operationen statt, die ihn nicht verändern. Dabei
wiederum bechränken wir uns auf die Suchoperation SEARCH
Für diese Suchoperationen müssen wir zusätztlich etwas darüber aussagen, mit welcher
Wahrscheinlichkeit einer der vorhandenen Schlüssel gesucht wird. Mangels besseren Wissens setzen wir auch hier wieder voraus, daß alle Schlüssel mit gleicher Wahrscheinlichkeit
auftreten. Dazu führen wir für einen binären Suchbaum B die die folgenden Größen ein:
Mit i(v) bezeichnen wir die Länge des einfachen Weges von der Wurzel zu einem Knoten
v plus 1. Das ist also die Höhe (oder Tiefe) des Knotens v im Baum. Die Summe der
Höhen aller Knoten Baumes ergibt die interne Pfadlänge (internal path lerngth)
X
I(B) :=
i(v)
v∈B
Man müßte eigentlich von der kummulierten internen Pfadlänge sprechen. Die mittlere
Suchpfadlänge (average search path length) wird dann sinnvollerweise definiert durch
I(B)
¯
I(B)
:=
n
Dabei ist n die Anzahl der Knoten des Baumes. Für den leeren Baum und den Baum, der
nur aus einer Wurzel besteht. gilt offenbar:
I(0) = 0 und I(1) = 1
(9.2)
Die mittlere Suchpfadlänge gibt an, wie viele Vergleiche im Mittel bei einer Suche auszuführen sind. Die interne Pfadlänge und die mittlere Suchpfadlänge beziehen sich auf
einen gegebenen Baum B. Dieser aber ergibt sich aus einer zufälligen Ankunftsreihen¯
folge in der Aufbauphase. I(B) und I(B)
sind ist also Zufallsvariable. Wir wollen ihre
Erwartungswerte berechnen und beginnen mit dem Erwartungswert EI(n) der internen
294
KAPITEL 9. SUCHBÄUME
Pfadlänge. Dieser Wert soll in Abhängigkeit von n bestimmt werden. Dazu müssten wir für
jeden Baum die interne Pfadlänge kennen und wissen, mit welcher Wahrscheinlichlkeit der
Baum auftritt. Das ist schwierig. Weiter kommen wir mit einer Rekursionsbetrachtung.
Ausgangspunkt ist die Feststellung, daß sich die interne Pfadlänge I(B) eines Baumes B
aus den internen Pfadlängen des linken Unterbaums Bl und des rechten Unterbaums Br
der Wurzel folgendermaßen ergibt
I(B) = I(Bl ) + I(Br ) + Anzahl Knoten von B
Die Anzahl k der Knoten des linken Unterbaumes variiert zwischen 0 und n−1. Die Anzahl
Knoten des rechten Unterbaumes ist (n − 1) − k Damit ergibt sich für den Erwartungwert
EI(n) = E(I) = E(I(Bl )) + E(I(Br )) + n
n X
P(a(Bl ) = k − 1)EI(k − 1) + P(a(Br ) = n − k)EI(n − k) + n
=
k=1
n X
P(a(Bl ) = k − 1)(EI(k − 1) + EI(n − k)
= n+
k=1
Dabei ist a(B) die Anzahl Knoten des Baumes B und P(a(B) = m) die Wahrscheinlichkeit, daß diese Anzahl gleich m ist. Die Wahrscheinlichkeiten findet man mit folgender
Überlegung: Die Füllung des Baumes Bl hängt bei einem binären Suchbaum B nur vom
Schlüsselwert der Wurzel von B ab. Nach unseren Voraussetzungen sind alle Schlüsselwerte gleich wahrscheinlich. Daher sind auch alle Werte von a(B) gleich wahrscheinlich.
Wir haben also
n
EI(n) = n +
n−1
2X
1X
EI(k − 1) + E(n − k) = n +
EI(k)
n k=1
n k=0
Indem wir etwas umformen und zusätzlich n durch n + 1 ersetzen, erhalten wir
2
(n + 1)EI(n + 1) = (n + 1) + 2 ·
(n)EI(n) = (n)2 + 2 ·
n−1
X
n
X
EI(k)
k=0
EI(k)
k=0
Wir ziehen die zweite Gleichung von der ersten ab und erhalten nach weiterem Umformen
EI(n + 1) =
2n + 1 n + 2
+
EI(n)
n+1
n+1
(9.3)
Die Rekursionsgleichung 9.3 läßt sich geschlossen lösen, und zwar läßt sich (mit etwas
Rechnerei) durch vollständige Induktion zeigen
EI(n) = 2(n + 1)Hn − 3n
(9.4)
9.3. ZUFÄLLIGE BINÄRE SUCHBÄUME
295
Hn ist dabei der n-te Abschnitt der harmonischen Reihe
Hn := 1 +
1
1 1
+ +···+
2 3
n
Man spricht auch von der n-ten harmonischen Zahl (harmonic number). Für harmonische
Zahlen gilt
Hn = ln(n) + γ +
1
1
1
+
+
−
2
2n 12n
120n4
mit
0<<
1
252n6
γ ist die Eulerkonstante γ = 0.57721 . . . .
Es gilt also
1
EI(n) = 2n ln n − (3 − 2γ)n + 2 ln n + 1 + 2γ + O( )
n
und für den gesuchten Erwartungswert der mittleren Suchpfadlänge
EI(n) :=
EI(n)
2 ln n
= 2 ln n − (3 − 2γ) +
+···
n
n
(9.5)
(9.6)
Um zu sehen, was das bedeutet, vergleichen wir diesen Erwartungswert mit der mittleren
Suchpfadlänge in einem optimalen Baum. Der Einfachheit halber setzen wir zunächst
n = 2k voraus. Der optimale Baum ist dann ein vollständiger ausgewogener Binärbaum. In
ihm liegen alle Blätter auf der Stufe h = hmin = ld (n+1) und seine mittlere Suchpfadlänge
ist gegeben durch
h−1
1
1X
(i + 1) · 2i = h
[(h − 1) · 2h + 1]
I¯min (n) =
n i=0
2 −1
Dabei ergibt sich der rechte Teil der Gleichung aus n = 2h − 1 und Anmerkung 6.1, Seite
192. D. h. wir haben
I¯min (n) =
2h
1
ld (n + 1)
[(h − 1)(2h − 1) + h] = ld (n + 1) +
−1
−1
n
Daraus ergibt sich
EI(n)
≈ 1.39
I¯min (n)
(9.7)
Wählt man einen zufälligen Aufbau des binären Suchbaumes statt eines ausgewogenen
Aufbaus, so wird man im Mittel einen nur 40% schlechteren Baum erhalten. Das bleibt
auch so, wenn nur naive Einfügungen und rein lesende Operationen im Wechsel ausgeführt
werden. Das bleibt i. A. nicht so, wenn zwischendurch auch gelöscht wird. Siehe auch die
Literaturangaben.
296
9.4
9.4.1
KAPITEL 9. SUCHBÄUME
B-Bäume und externe Datenspeicherung
Mehrweg-Suchbäume
Bei einem Binärbäumen ist es nicht nur wichtig, daß ein Knoten maximal zwei Nachfolger
haben kann, sondern es ist auch ganz entscheidend, daß zwischen rechtem und linkem
Nachfolger unterschieden wird. Es gibt eine Ordnung auf der Menge der Nachfolger. Es
ist naheliegend, zu fragen, ob eine Ordnung auch nützlich ist, wenn ein Knoten mehr als
zwei Nachfolger haben darf. Ja, das ist der Fall und für manche Suchaufgaben eignen
sich solche Bäume in besonderem Maße. Wir wollen daher Mehrweg-Suchbäume (multiway search tree) einführen. Abbildung 9.14 zeigt einen solchen. Er hat 7 Knoten: Einen
Wurzelknoten auf Stufe 0, fünf Knoten auf Stufe 1 und einen Knoten auf Stufe 2. Jeder
Knoten ist mit mindestens einem und höchstens vier Schlüsselwerten gefüllt. Diese sind
im Knoten in aufsteigender Reihenfolge gespeichert. Vor dem ersten Schlüsseleintrag eines Knotens, zwischen zwei aufeinanderfolgenden Schlüsseleinträgen und nach dem letzten
Schlüsseleintrag gibt es ein Verweisfeld. Dieses enthält einen Verweis auf einen Knoten der
nächsten Stufe oder einen NIL-Verweis. NIL-Verweise sind schwarz.
Allgemein ist ein Mehrweg-Suchbaum zunächst einmal ein Baum, d. h. es gibt eine eindeutig bestimmte Wurzel ohne Vorgänger, jeder andere Knoten hat genau einen Vorgänger
und es gibt einen eindeutig bestimmten Weg von der Wurzel zu jedem anderen Knoten.
Vergleiche Unterabschnitt 9.1.2, Seite 258. Jeder Knoten enthält abwechselnd Verweisfelder und Schlüsselwerte. Die Schlüsselwerte sind in einem Knoten in aufsteigender Ordnung
gespeichert. Knoten, bei denen alle Verweisfelder NIL aufweisen, heißen Blätter (leaf ).
Charakteristisch für Mehrwegsuchbäume ist die folgende Regel:
Alle Knoten eines Unterbaumes, auf den ein Verweiseintrag eines Knotens zeigt,
haben Schlüsseleinträge, die größer sind als der Schlüsselwert vor dem Verweis
und
kleiner als der Schlüsselwert hinter dem Verweis.
Für den ersten und den letzten Verweis in einem Knoten gilt eine entsprechende einseitige
Festlegung. Ein Suche nach einem Schlüsselwert beginnt im Wurzelknoten und durchsucht
einen jeden besuchten Knoten in aufsteigender Schlüsselwertreihenfolge.
• Wird der Schlüsselwert gefunden, ist man fertig.
• Anderenfalls gibt es entweder einen ersten Schlüsselwert, der größer als der Suchwert
ist, und man folgt dem Verweis vor dem diesem Schlüssewlwert
• oder es gibt im Knoten keinen Schlüsselwert, der größer als der Suchwert ist, und
man folgt dem letzten Verweis im Knoten.
• Ist ein zu folgender Verweis NIL, so gibt es im Baum den gesuchten Schüsselwert
nicht.
• ANTON • BESUCH ◦
.
•
...
...
...
...
...
...
...
...
...
...
...
...
..
....... ..
.......
...
DER
•
HUT
•
HAT
•
LAGE
•
...
...
...
...
...
...
...
...
...
...
...
...
..
....... ..
.........
..
LUST
NOCH
◦ QUARK ◦ TANNE ◦
...
.
..
...
...
...
...
...
........
.........
...
•
PFAU
•
•
NEIN
•
.............
......
.............
.......
............
......
.............
.......
.............
.......
.............
......
.............
.......
.............
.......
.............
......
....................
.......
.......
................
......
.......
.......
.......
......
.......
.......
.........
...................
•
•
ROT
•
•
RUHE
ZAHN
•
ZANK
• SEHEN •
•
9.4. B-BÄUME UND EXTERNE DATENSPEICHERUNG
◦ KNABE ◦
.
.
...........
..........
..........
...........
..........
.
.
.
.
.
.
.
.
.
...........
..........
...........
.
.... ...........
....................
Abbildung 9.14: Mehrweg-Suchbaum
297
298
KAPITEL 9. SUCHBÄUME
Einige Beispiele in Abbildung 9.14:
QUARK: Wird im Wurzelknoten gefunden.
HAT: Wird im Knoten 2. Stufe gefunden.
ZOFF: Es wird im letzten Knoten erster Stufe festgestellt, daß es den Schlüsseleintrag
nicht gibt.
ERNST: Es wird im Knoten 2. Stufe festgestellt, daß es den Schlüsseleintrag nicht
gibt.
Wozu braucht man Mehrweg-Suchbäume? Sind sie besser als binäre Suchbäume? Für Datenstrukturen, die nur innerhalb eines Programmlaufs existieren und deswegen im Hauptspeicher liegen, ist das in der Regel nicht der Fall. Für Datenstrukturen in Dateien und
Datenbanken hingegen bieten Mehrweg-Suchbäume deutliche Vorteile.
9.4.2
Speicherung bei Dateien und Datenbanken
Wir wollen uns daher Datenstrukturen, die speziell für die längerfristige Speicherung von
Daten gedacht sind, einmal genauer ansehen. In Dateien und mehr noch in Datenbanken
(siehe Abschnitt 2.2, Seite 33) sind Sätze die Transport- und Speichereinheiten. Gesucht
und bereitgestellt werden sie entweder sequentiell oder direkt über einen Schlüsselwert.
Sätze des gleichen Typs haben oft die gleiche Länge. Die Längen unterschiedlicher Satztypen differieren. Es gibt aber auch Satztypen mit variabler Satzlänge. Der Aufbau aus
Sätzen unterschiedlicher Typen wird logische Schicht (logical level) genannt.
Sätze werden im Allgemeinen auf Geräten und Dateträgern mit direktem Zugriff gespeichert. In der Mehrzahl der Fälle sind das Magnetplatten. Frühere Magnetplattengeräte
hatten auswechselbare Träger. Die gespeicherten Blöcke hatten variable Längen und wurden über Zylinder- und Spurnummern addressiert. Neuere Geräte sind Festsplatten und
sind in Sektoren gleicher Länge eingeteilt. Es hat sich als vorteilhaft erwiesen und ist heute
üblich, über die verschiedenen Speichergeräte eine einheitliche physikalische Schicht (physical level) zu legen. Diese besteht aus Blöcken gleicher Länge, die zu größeren Bereichen,
Partitionen (partition), zusammengfaßt sind. Innerhalb einer Partition sind die Blöcke
durchnumeriert. Es ist nun Aufgabe der Dateiverwaltung bzw. des Datenbanksystems, die
Abbildung zwischen den beiden Schichten vorzunehmen. Dabei sind Mehrweg-Suchbäume
von großem Nutzen.
Wir wollen dafür den folgenden Fall genauer untersuchen. Es sei ein Datenbestand mit
Sätzen einer festen Klasse gegeben. Ihre Anzahl sei so groß, daß es nicht möglich oder
zumindest nicht zweckmäßig ist, alle Schlüsselwerte im Hauptspeicher zu halten. Für die
Schlüsselwerte wird ein Mehrweg-Suchbaum angelegt. Dabei ist ein Knoten ein Block der
physdikalischen Schicht und wir gehen davon aus, daß ein Zugriff zu einem Knoten einem
9.4. B-BÄUME UND EXTERNE DATENSPEICHERUNG
299
Blocktransport vom Plattenspeicher entspricht. Dagegen finden Vergleiche von Schlüsselwerten mittels Maschinenbefehlen im Hauptspeicher statt und sind größenordnungsmäßig
um den Faktor 1000 schneller. Es ist also sehr wichtig, einen Schlüsselwert mit wenigen
Blocktransporten, d. h. Knotenzugriffen zu finden. Die Suchzeit innerhalb eines Knotens
fällt nicht so stark ins Gewicht. Oft verwendet man dafür sequentielle Suche. Eine geringe
Zahl von Knotenzugriffen erreicht man durch einen ausgewogenen Mehrweg-Suchbaum
und durch eine große Zahl von Schlüsselwerten in jedem Knoten. Diese Anforderungen
lasssen sich mit B-Bäumen und ihren Varianten erfüllen.
9.4.3
B-Bäume
B-Bäume wurden von Bayer und McCreight eingeführt [BayeMCr1972]. Die Autoren gaben keine Erklärung für die Namenswahl. Die Grundidee ist, eine maximal mögliche Füllung eines Knotens vorzugeben und zu verlangen, daß alle Knoten außer der Wurzel mindestens halb voll sind. Außerdem müssen alle Blätter auf der gleichen Stufe liegen. Der
Mehrweg-Suchbaum von Abbildung 9.14 ist demnach kein B-Baum. Das folgende Beispiel
soll zeigen, wie ein B-Baum entstehen kann.
Beispiel 9.5 Es soll ein B-Baum mit den Schlüsselwerten QUARK, PFAU, BESUCH, ANTON,
LAGE, HUT, HAT, DER, KNABE, LUST, NOCH, NEIN, RUHE, ROT, TANNE, SEHEN, ZAHN,
ZANK in dieser Reihenfolge aufgebaut werden. Ein Knoten soll maximal 4 und minimal 2
Knoten enthalten. Wir legen also einen leeren Wurzelknoten an und füllen ihn mit den
ersten vier Schlüsselwerten, Abbildung 9.15, Teil A. Beim fünften Knoten, LAGE, läuft
die Wurzel über und muß geteilt werden. Das wird so gemacht, daß zwei neue Blöcke
mit je zwei Schlüsselwerten gebildet werden. Der mittlere Schlüsselwert wird einziger Eintrag einer neu zu bildenden Wurzel, Abbildung 9.15, Teil B. Danach können können alle
Schlüsselwerte bis einschließlich ZAHN in Blöcke der ersten Stufe eingefügt werden. dabei
kommt es zu drei weiteren Teilungen und dementsprechend zu drei weiteren Einträgen in
der Wurzel, Abbildung 9.15, Teil C. Beim Einfügen von ZANK kommt es zu einer weiteren
Blockteilung auf Stufe 1. Der Schlüsselwert TANNE müßte in die Wurzel kommen. Dabei
läuft diese erneut über. Sie muß geteilt und ein neuer Wurzelknoten eingerichtet werden.
Der Schlüsselwert NOCH wird in diesen verschoben, Abbildung 9.16, Teil D.
2
Definition 9.3 Ein Mehrweg-Suchbaum heist B-Baum (B-tree) der Ordnung m, wenn
er die folgenden Eigenschaften hat:
1. Jeder Knoten enthält höchstens m − 1 Schlüsselwerte.
2. Jeder Knoten außer der Wurzel enthält mindestens b m−1
c Schlüsselwerte.
2
3. Der Baum ist entweder leer oder die Wurzel enthält mindestens einen Schlüsselwert.
4. Alle NIL-Verweise gehören zu Knoten der gleichen Stufe.
300
•
ANTON
•
BESUCH
•
PFAU
•
QUARK
•
Teil A
◦
LAGE
...
........
.........
.........
.........
.
.
.
.
.
.
.
......
.........
.........
. ..........
............
..................
◦
.........
.........
.........
.........
.........
.........
.........
.........
......... .
.............
................
PFAU
QUARK
ANTON
BESUCH
Teil B
◦
ANTON
BESUCH
DER
◦
......
.....
......
......
.....
.
.
.
.
..
......
.. ......
.........
.............
HAT
HUT
KNABE
LAGE
◦
...
...
...
...
...
..........
.........
..
LUST
NEIN
NOCH
◦
ROT
......
......
......
......
......
......
......
...... ..
.. .
....................
PFAU
QUARK
Teil C
Abbildung 9.15: Aufbau eines B-Baumes
◦
............
............
............
...........
............
............
............
...........
............
.................
.. .
................
RUHE
SEHEN
TANNE
ZAHN
KAPITEL 9. SUCHBÄUME
............
............
............
...........
............
.
.
.
.
.
.
.
.
.
.
.
.
............
...........
.
............
.... ...........
....................
◦
...
...
...
...
...
..........
........
...
ANTON
BESUCH
NOCH
DER
◦
...
...
...
...
...
..........
........
...
HAT
HUT
KNABE
LAGE
◦
.......
.......
.......
.......
.......
.......
.......
.......
.......
.......
.......
.......
.......
....... .
..........
.................
◦
◦
...
...
...
...
...
..........
.........
..
...
...
...
...
...
..........
........
...
PFAU
QUARK
LUST
NEIN
ROT
◦
TANNE
...
...
...
...
...
..........
.........
..
RUHE
SEHEN
Teil D
◦
...
...
...
...
...
..........
.........
..
ZAHN
ZANK
9.4. B-BÄUME UND EXTERNE DATENSPEICHERUNG
◦
.......
......
.......
.......
.......
.
.
.
.
.
..
.......
......
.......
.......
......
.
.
.
.
.
.
.......
.. .......
..........
..............
Abbildung 9.16: Aufbau eines B-Baumes (Fortsetzung)
301
302
KAPITEL 9. SUCHBÄUME
Die folgende Proposition ist unmittelbar als richtig zu erkennen.
Proposition 9.3 Punkt 4. von Definition 9.3 ist gleichwertig zu den beiden Aussagen
4a. Alle Blatter liegen auf der gleichen Stufe.
4b. Ein Knoten mit t − 1 Schlüsselwerten ist entweder ein Blatt oder hat t Nachfolger.
Die Definition von B-Bäumen kann somit auch etwas anders gefaßt werden und ist in der
entsprechenden Version in vielen Büchern formuliert. In einem B-Baum bestimmt jeder
Weg von der Wurzel zu irgendeinem Blatt durch seine Länge + 1 die Höhe des Baumes.
Es läßt sich leicht zeigen, daß B-Bäume ausgewogen sind. Hierfür ist es zweckmäßig, die
c + 1. einzuführen. t ist der Minimalgrad eines Nichtblatt-Knotens im
Größe t := b m−1
2
B-Baum.
Satz 9.3 Für die Höhe h(n) eines B-Baumes der Ordnung m mit n Schlüsselwerten gilt
h(n) ≤ logt
n+1
2
Beweis: Die Wurzel enthält mindestens einen Schlüsselwert und alle anderen Knoten
mindestens t − 1. Das bedeutet, es gibt auf Stufe 1 mindestens 2 Knoten, auf Stufe 2
mindestens 2t und auf der letzten Stufe h mindestens 2th−1 Knoten. Damit läßt sich die
Anzahl n der Schlüsselwerte im Baum abschätzen zu
h
h
X
t −1
τ −1
n ≥ 1 + (t − 1)
2t
= 1 + 2(t − 1)
= 2th − 1
t
−
1
τ =1
Daraus folgt die Behauptung des Satzes.
2
Operationen auf B-Bäumen
Für B-Bäume soll kein kompletter Satz von Operationen besprochen werden. Die wichtigsten Operationen sind SEARCH, INSERT und DELETE. Auf Seite 296 wurde besprochen, wie
gesucht werden kann. In Beispiel 9.5 wurde erläutert, wie B-B äume aufgebaut werden
können. Einen Eintrag zu löschen, ist etwas schwieriger. Es sei auf die Literurangaben
verwiesen.
Varianten und Verfeinerungen
Zu B-Bäumen gibt es eine Reihe von Varianten. Die wichtigsten sind wohl:
a. Die Knoten sind mindestens zu 23 Dritteln gefüllt. Man spricht auch von B*-Bäumen.
b. In der Praxis sind oft die Sätze einer Datei/Datenbak sehr viel länger als die Schlüssel.
Dann ist es zweckmäßig, einen „Index“, der nur aus einem B-Baum von Schlüsseln besteht,
9.5. DIGITALE BÄUME
303
aufzubauen und von den Schlüsseln einen direkten Verweis auf die Sätze zu benutzen. Die
eigentlichen Sätze sind für einen schnelleren direkten Geamtdurchlauf in einer doppelten
Kette mit aufsteigenden Schlüsselwerten gespeichert. Allerdings muß auch diese Datenstufe B-Baumeigenschaft haben, die Blöcke also eine Mindestfüllung aufeinander folgender
Sätze aufweisen. In diesem Fall spricht man manchmal von B+ Bäumen.
9.5
Digitale Bäume
Sie werden auch Tries 5 (von retrieval) genannt. Man geht von Schlüsselwerten aus, die
Zeichenreihen (vergleiche hierzu Abschnitt 3.6, Seite 112) sind. Daher auch der Name
digit (Ziffer). Wir wollen jedoch die Elemente der Zeichenreihen weiterhin Zeichen nennen. Abbildung 9.17 zeigt einen Digitalbaum für die Wörter HER bis LAGE.
Im unteren Teil der Zeichnung ist die vollständige Liste der Schlüsselwerte zu sehen, im
oberen Teil der zugehörige Digitalbaum, genauer ein digitaler Wald (digital forest). Jedes
Blatt des Waldes zeigt über eine Linie ohne Pfeilspitzen auf den eindeutig bestimmten
Schlüsselwert. Dabei gilt:
a. Der Weg im Baum umfaßt nicht notwendigerweise alle Zeichen des Schlüssels. Er bricht
ab, sobald der Schlüsselwert eindeutig bestimmt ist.
b. Ist ein Schlüsselwert Anfangsstück eines anderen Schlüsellwertes, so wird das durch ein
angehängtes Leerzeichen (t) im Baum kenntlich gemacht.
Implementierumgs-und Effizienzfragen sind in Aufgabe 9.3 zu bearbeiten.
Aufgaben
Aufgabe 9.1 Es ist ein Verfahren anzugeben, mit dem aus n Sätzen jede vorgegebene
Binärbaumstruktur als binärer Suchbaum mit n Knoten gebildet werden kann.
Aufgabe 9.2 Geben Sie ein Beispiel für einen Binärbaum mit n Knoten, dessen Höhe
dld (n + 1)e ist und in dem die Knoten bis zur vorletzten Stufe einschließlich keinen
vollständigen ausgewogenen Binärbaum bilden.
Aufgabe 9.3 Skizzieren Sie ein Verfahren mit dem Digitalbäume der Art, wie sie in
Abbildung 9.17 dargestellt ist, aufgebaut werden können. Wie kann man in diesen Bäumen
suchen und einfügen? Welcher Aufwand fällt für Einfügen und Suchen an ? Wird eine
Ordnung der Zeichen des benutzten Alphabets gebraucht?
Aufgabe 9.4 Wie kann man mit Rot-Schwarz-Bäumen Prioritätswarteschlangen (siehe
Seite 254) implementieren?
5
Gesprochen wie “try“.
304
H....
Q
...
...
...
...
...... ..
.........
...
E....
A
.........
............. .... ........................................
........ ......................
.......................
...
......... ..........................
.........................
...
...................................
........ ............................
.
.
.
.
.
.
.
.
.
.
.
...
.
.........
. ..
...
..
......... .................................................
.............. ...........
...
...
....
...................................
.........
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
......... ... ................................................... ..
........
........ .... .................
.
.
.
.
.
.
.
.
.
.
................. ...................
.
... .
.
.
.
.
.
.
.
.
....
.
. ..
...................
......................
....................
............. ..............
A
...
...
...
...
........
.........
...
B....
...
...
...
...
........
.........
...
E
R
....
........
.. ... ...
.. .... .....
.
.
.. .. ..
... .. ...
. ... .. ... . ... .
............ ........... .................
..
...
......
..
......
... ....
.... ....
.. ..
.. ...... ..... ..
....... ........
....
..
.
...
t R M
N R
L
N
........
......... ....
...
........
.........
..
.........
.
.
...
.
.
.
.
.
.
.
.........
.........
........
.
.
.
.
.
.
.
.
........
............
.
.
.
.
.
.
.
..
.... ......
.
......
... ...
... ..
... ....
.
.
.
... . ...
...... . ...........
....
..........
...
.
L
G T.......
B
......
....... ..
....... .....
.......
.
.
...
.
.
.
.
.....
...
.......
.......
.........
.............
.
.
.
..........
.....
.
...............
t
L
Z....
...
...
...
...
...... ..
........
...
R Z
A
..
.....
.
... ...
... ...
.. ..
.. ....
.
.
.. .. . ..... ..
......... ........
..
.....
...
H N
... ...
... ....
... ...
.. ....
......... .... ..
........ ............
.....
...
O R
E
.....
..... ..........
..... ... .......
..... .... ..............
.....
.
... ... .......
.
.
.
.
.. ... ......
.....
.....
........ ..... .. ......... ..
.. .....
.....
......... .............
.................
.
.... ................
...
....
t
S
N R S
.
......
... ..
.. ...
... ....
.
.
.
.
... .. .... ..
...
.....
.......... ..........
.
......
t S
Q
U
A
R
K
A A A A A A A A A
A B B L L L L L L
L E E B L L L L L
N R E
E E E E
D
D
N N R
O
S
T
E
I
N
A
L
L
E
S
A
N
G
S
T
A
N
T
O
N
A
N
T
R
I
E
B
Abbildung 9.17: Digitaler Baum
A
R
Z
T
A
Z
U
R
S
E
H
E
N
Z
A
H
N
Z
A
N
K
L
A
G
E
KAPITEL 9. SUCHBÄUME
H H H
E E E
R R R
R M
A
N
N
9.5. DIGITALE BÄUME
305
Literatur
Suchbäume werden in allen Büchern über Datenstrukturen behandelt z. B. [AppeL1995],
[CormLR1990] [AhoU1995], [SedgF1996], [Bras2008] [KrusTL1997],[AhoU1995], [Knut1997],
[OttmW1996].
Für die Berechnung der mittleren Höhe eines Binärbaumes haben wir in Abschnitt 9.3
angenommen, daß alle Reihenfolgen, aus denen jeweils ein Baum aufgebaut wird, gleichwahrscheinlich sind. Dabei kommte es vor, daß unterschiedliche Reihenfolgen, den gleichen
Baum ergeben. Wenn man nicht die Reihenfolgen, sondern die Bäume als gleichwahrscheinlich ansieht, man sagt die möglichen „Gestalten“ seien gleichwahrscheinlich, ergibt
sich im Mittel auch ein buschiger Baum. Siehe hierzu [OttmW1996].
In einen Binärbaum gibt es n + 1 Verweisfelder mit NULL-Verweisen (siehe Seite 261).
Man kann den enstprechenden Speicherplatz nutzen und darin zusätzliche Verweise, sogenannte Fädelungszeiger(threading pointer), unterbringen, mit denen sich in bestimmten Fällen rekursive Durchläufe und Keller vermeiden lassen. Zu Einzelheiten siehe Ottmann/Widmayer [OttmW1996], Abschnitt 5.1.2, oder Oberschelp/Wille [OberW1976],
Abschnitt 6.4.
Zur rekursiven Definiton von Binärbäumen siehe [Knut1997]
306
KAPITEL 9. SUCHBÄUME
Kapitel 10
Suchen mit Schlüsseltransformation
In der Einleitung zu Kapitel 9 wurden Suchverfahren in Verfahren mit Schlüsseltransformation und Verfahren mit Schlüsselvergleich eingeteilt. Das Schema für Schlüsseltransformation ist in Abbildung 9.1, Seite 257, zu sehen. Wir wollen Sätze mit eindeutigen
Schlüsselwerten in einem Datenbestand gleichartiger Sätze suchen und beschränken uns
auf den Fall, daß der Datenbestand nur innerhalb eines Programmlaufs existiert. Ähnlich wie bei Suchbäumen sind Suchverfahren mit Schlüsseltransformation jedoch auch für
das Suchen in permanenten Datenbeständen (Dateien und Datenbanken) wichtig. Wir
benutzen die folgenden Bezeichnungen: K ist die Menge der möglichen Schlüsselwerte
und k := |K|. R ist die Menge der zu einem Zeitpunkt im Datenbestand existierenden
Schlüsselwerte, d. h. Sätze. Anders ausgedrückt, wir haben einen sich dynamisch verändernden Datenbestand R und r := |R| ist sein Umfang. S ist der zur Verfügung stehende
Speicherplatz und s := |S|. In S werden die Sätze, also die Zielinformation des Verfahrens
gespeichert. s ändert sich während eines Programmlaufes nicht.
Das weitere hängt von den Größenverhältnissen zwischen diesen Mengen ab. Zunächst
einmal ist k ≥ r, denn jeder existierende Satz hat einen Schlüsselwert. Weiterhin gilt
k ≥ s. denn es macht keinen Sinn, mehr Speicherplätze zu haben, als es Schlüsselwerte
gibt. Schließlich ist es nicht sinnvoll, in einer Menge zu suchen, die man nicht speichern
kann. Es ist also s ≥ r. Hier ist jedoch Vorsicht geboten. Die Suchverfahren mit Schlüsseltransformation – zumindestens die von uns untersuchten – arbeiten mit einer Adressierungstabelle (adressing table) H vom Umfang m := |H|. Die Elemente der Tabelle H
können die Sätze selber sein oder Verweise auf sie. Es sind aber auch Verweise auf Mengen
von Sätzen möglich, z. B. verkettete Sätze. Im letzteren Fall kann s > m sein, und somit
ist auch r > m möglich. D. h. es können mehr Sätze vorhanden sein, als die Adressierungstabelle Einträge hat. Wir wollen die Fälle k = s = m und k > m unterscheiden. Im
ersten Fall spricht man von direkter Speicherung, im zweiten Fall von Hashing.
307
308
10.1
KAPITEL 10. SUCHEN MIT SCHLÜSSELTRANSFORMATION
Direkte Speicherung
Direkte Speicherung (direct strorage) 1 kann angewandt werden. wenn k = s, wenn also
für jeden Schlüsselwert ein Speicherplatz existiert. Wir haben dann eine bijektive Adressierungsfunktion h : K 7→ S. Am günstigsten läßt sich das realisieren, wenn man S als
Reihung von s Elementen anlegt und h(k) ein Indexwert dieser Reihung ist. Die Reihungselemente sind die Sätze des Datebestandes oder auch Verweise auf diese. Außerdem
muß eine Kennung vorhanden sein, die angibt, ob der entsprechende Schlüsselwert im
Datenbestand vorhanden ist oder nicht.
10.2
Grundlagen des Hashings
Beim Hashing (hashing) 2 ist der Schlüsselwertraum K größer, häufig sehr viel größer als
der Speicheraum S. Er ist in jedem Fall größer als die Hashtabelle (hashing table) H.
Die Adressierungsfunkion h : K 7→ H wird Hashfunction (hashing function) genannt.
Sie bildet die möglichen Schlüsselwerte auf die Einträge der Tabelle, die Hashadressen
(hashing address) ab. Sie kann nicht injektiv sein. Das bedeutet aber nur, daß man zu
jedem möglichen Schlüsselwert eine Hashadresse berechnen kann. Es bedeutet nicht, daß
auch jeder Eintrag in der Tabelle belegt ist. In der Hashtabelle wird nämlich nur dann
ein Eintrag belegt, wenn der Schlüssel eines in R existierenden Satzes dorthin führt. Es
wird also mit Sicherheit einen mehrfach belegten Eintrag in H geben, wenn im Datenbestand R mehr Sätze existieren als die Tabelle H Eintragsplätze hat. Man spricht von
einer Kollision (collision) Bei einer sorgfältigen Planung wird man jedoch die Tabelle H
hinreichend dimensionieren und Kollisionen wegen eines zu großen Datenbestandes werden im Allgemeinen nicht auftreten. Aber selbst, wenn im Prinzip für jeden existierenden
Satz ein eigener Tabellenplatz vorhanden ist, kommt es immer dann zu Kollisionen, wenn
die Hashfunktion h die Schlüssel zweier Sätze aus R auf die gleiche Hashadresse abbildet.
Ob das passiert, hängt nicht nur von der Hashfunktion h ab, sondern auch von der Menge
R der vorhandenen Schlüssel. In den meisten Anwendungen werden die Schlüssel aus R
nicht gleichverteilt in K liegen. So sind in einem Telefonverzeichnis bestimmte Namen
sehr viel häufiger als andere. Die symbolischen Namen eines C-Programms weisen auch
Häufungen für bestimmte Werte auf.
Im folgenden Unterabschnitt wollen wir untersuchen, wie geeignete Hashfunktionen aussehen und in zwei weiteren Unterabschnitten werden wir Methoden zu Kollisionauflösung
kennenlernen.
Direkte Speicherung wird manchmal auch direkte gestreute Speicherung und im Englischen direct
addressing oder auch direct access genannt.
2
Hashing wird manchmal auch gestreute Speicherung oder indirekte gestreute Speicherung genannt.
To hash bedeutet im Deutschen kleinhacken, zerhacken.
1
10.2. GRUNDLAGEN DES HASHINGS
10.2.1
309
Hashingalgorithmen
Was muß ein Hashverfahren können? Sätze müssen im Datenbestand nach ihrem Schlüsselwert gesucht werden können. Außerdem ist es notwendig, Sätze in den Datenbestand
einzufügen oder aus ihm zu entfernen. Das geschieht so, daß aus dem Schlüsselwert k
zunächst einmal die Hashadresse brechnet wird. Wenn an dieser Position der Tabelle kein
Eintrag steht, wird kein Satz gefunden und es kann auch keiner entfernt werden. Ein neuer
Satz kann ohne Schwierigkeiten eingefügt werden. Steht dort genau ein Eintrag, so hat
man den gesuchten Satz gefunden und kann ihn gegebenfalls löschen. Will man einfügen,
so muß man prüfen, ob der Schlüsselwert des Tabelleneintrags gleich dem des einzufügenden Satzes ist. Ist er es, kann wegen mehrfachen Schlüsselwertes der Satz nicht gespeichert
werden. Ist er es nicht, so tritt für diese Tabellenpostion, also für diese Hashadresse, eine
erste Kollision auf und die Kollisionsbehebung muß aufgerufen werden. In allen Fällen
ohne Kollisionsbehebung ist der Aufand für die auszuführende Operation unabhängig von
der Größe des Datenbestandes R und kann als konstant angesehen werden, also durch
O(1) abgeschätzt werden.
Anders sieht es aus, wenn an der Hashadresse h(k) eine Kollision vorliegt, wenn also
zu dieser Stelle der Tabelle eine Menge von mindestens zwei Sätzen gehört. Dann muß
festgestellt werden, ob der gesuchte Schlüsselwert zu einem dieser Sätze gehört oder nicht.
Falls ja, hat man den gesuchten Satz gefunden und kann gegebenenfalls auch löschen.
Man kann einen neuen Satz nicht einfügen. Falls nein, endet das Suchen bzw. Löschen
mit Fehlermeldung, aber ein neuer Satz kann eingefügt werde. Die Kollisionsmenge wird
um einen Satz größer. In diesen Fällen wird der Aufwand für eine Operation vom Umfang
der Kollisionsmenge abhängen und damit auch von der Gesamtgröße des Datenbestandes,
Er kann nicht mehr durch O(1) abgeschätzt werden.
Wie sollte nun die Hashfunktion h beschaffen sein und wie kann man h(k) ausrechnen?
Zunächst einmal wird aus jedem Schlüssel k eine natürliche Zahl gewonnen, Dabei ist
jedoch einiges zu beachten.
Beispiel 10.1 Wir wollen uns das am Beispiel der Schlüsselwertmenge {QUARK, PFAU,
BESUCH, ANTON, LAGE, HUT, HAT, DER, KNABE, LUST, NOCH, NEIN, RUHE, ROT, TANNE, SEHEN,
ZAHN, ZANK} verdeutlichen. Als natürliche Zahlenwerte wählen wir die Darstellung zu
Basis q = 26 mit den Zifferwerten der Tabelle 10.1. Diese Darstellung ist naheliegend.
A
0
K 10
U 20
B
1 C
L 11 M
V 21 W
2 D
12 N
22 X
3 E
4 F
5 G
13 O 14 P 15 Q
23 Y 24 Z 25
6 H
16 R
7 I
8
17 S 18
J
9
T 19
Tabelle 10.1: Ziffernwerte der Großbuchstaben
Es ergeben sich jedoch zwei Schwierigkeiten. Zum einen ergäben die Zeichenreihen AR,
310
KAPITEL 10. SUCHEN MIT SCHLÜSSELTRANSFORMATION
AAR und R wegen führender Nullen die gleiche Zahl und damit auf jeden Fall Kollisionen.
Das könnte man beheben, indem man die Ziffernwerte der Tabelle um 1 erhöht und zur
Basis q = 27 übergeht. Die Abbildung ist dann nicht mehr surjektiv und es ergeben sich
„Löcher“ im Bildbereich. Wie weit das die Güte der Hashfunktion beeinträchtigt, ist nicht
unmittelbar klar.
Bedeutsamer ist wahrscheinlich der folgende Einwand. Wenn man, wie moderne Compilerversionen das tun, Namen mit bis zu 30 Zeichen (oder mehr) zuläßt und ein Grundalphabet aus Großbuchstaben, Kleinbuchstaben, Dezimalziffern und einigen Sonderzeichen
hat, wird der zu berücksichtigende Bereich natürlicher Zahlen zu groß für rechnerinterne
Darstellungen ganzer Zahlen in 32 Bits (oder auch 64, ja sogar 128 Bits). Man muß zu
Gleitpunktzahlen übergehen, was Auswirkungen auf die Hashfunktion haben kann, oder
symbolisch rechnen, was umständlich und langsamer ist. Hilfe kann man durch moduloRechung (Unterabschnitt 6.1.4, Seite 193) bekommen. Danach ist
0
(an q n + an−1 q n−1 + · · ·+ a1 q 1 + a0 q 0 ) mod m = (an qn0 + an−1 qn−1
+ · · · + a1 q10 + a0 q00 ) mod m
(10.1)
0
i
0
mit qi := q mod m. Die qi berechnet man bei festgelegter Tabellengröße m nur einmal
und hat danach Zahlen in vernünftigen Größen. Siehe Aufgabe 10.1.
In unserem Beispiel wollen wir der Einfachheit halber führenden Nullen zulassen und die
Tabelle 10.1 sowie q = 26 benutzen. Wir nehmen sogar die Zeichenreihen L und AAL
hinzu, um absichtlich eine Kollision durch führende Nullen zu bewirken. Die Länge der
Zeichenreihen wollen wir auf 6 begrenzen und haben somit keine Schwierigkeiten mit dem
Zahlenbereich.
2
Liegt die Transformation der ursprünglichen Schlüsselwerte in natürliche Zahlen vor, so
muß danach eine Funktion angewendet werden, die die Hashadresse ausrechnet. Wichtig
sind die beiden folgenden Methoden.
Divisionsmethode. Bei dieser naheliegenden Methode findet man zu einem Schlüsselwert die Hashadresse, indem man den Schlüsselwert k durch die Größe m der Hashtabelle
teilt und den Rest als Hashwert nimmt.
h(k) := k mod m
Dabei muß man allerdings einen geeigneten Wert für m wählen. m sollte z. B. keine gerade
Zahl sein, weil sonst gerade Schlüsselwerte auf gerade Adressen und ungerade auf ungerade
Adressen abgebildet werden. Aus ähnlichen Gründen sollte man keine Zahl m wählen, die
gleich q i ± j mit kleinem i, j ist. q ist hierbei die Basis der Zahlendarstellung – in unserem
Beispiel 26. Empfohlen wird, eine Primzahl zu nehmen, die diesen Bedingungen genügt.
Hält man sich an diese Regel, so liefert die Divisionsmethode in der Praxis gute Ergebnisse.
10.2. GRUNDLAGEN DES HASHINGS
311
Multiplikationsmethode. Bei dieser Methode wird der Schlüsselwert k mit einer irrationalen Zahl 0 < α < 1 multipliziert und davon der ganzzahlige Anteil abgezogen:
β := kα − bkαc. Nach einem Satz von Vera Turan Sós3 ist β in [0, 1] gut verteilt
und
√
5−1
h(k) := bkβc ist es in [0, m − 1]. besonders gute Werte erhält man für α := 2 ≈
0.6180334. Der Wert von m ist bei dieser Methode nicht kritisch.
Fortsetzung 1 von Beispiel 10.1: Unser Beispiel soll hier fortgesetzt werden. Bei
der Divisionsmethode ist zuerst einmal die Größe m der Tabelle festzulegen. Dabei ist
zunächst zu beachten, daß alle vorkommenden Schlüsselwerte hinein passen sollten. Wir
brauchen also mindestens 20 Einträge. Dann kommen für m z. B. die Primzahlen 23, 29,
31, 37 infrage. Sie genügen allesamt den obigen Anforderungen nicht gut, am wenigsten
23, am besten 37. Tabelle 10.2 zeigt die Ergebnisse für m = 23 und m = 37. Für m = 23
ergeben sich die folgenden Kollisionsmengen {QUARK, ZANK, L, AAL}, {PFAU, DER}, {LUST,
SEHEN} und {NOCH, ROT}. Für m = 37 ergben sich immer noch 3 Kollisionsmengen, an
denen insgesamt 7 Schlüsselwerte beteiligt sind.
Bei der Berechnung der Hashadressen durch die Multiplikationsmethode – letzte Spalte
der Tabelle – wurden m = 23 und α = 0.618033 benutzt5 . Es ergeben sich die Kollisionsmengen {QUARK, HAT}, {ANTON, DER, KNABE, ROT} sowie {LUST, L, AAL}.
2
10.2.2
Kollisionsauflösung durch Verkettung
Eine naheliegende Lösung des Kollisionsproblems liegt darin, alle existierenden Schlüsselwerte, die auf die gleiche Hashadresse führen (Synonyme), in einer linearen Liste zu
verketten. Die Hashtabelle enhält entweder einen NIL-Verweis oder zeigt auf den Anfang
der Kette. Solange keine langen Listen auftreten, haben wir ein sehr schnelles Suchverfahren. Etwas genauer: Wir nehmen einmal an, daß jeder zu bearbeitende Schlüsselwert
(Suchen, Löschen, Einfügen) eine Hashadresse liefert, die mit gleicher Wahrscheinlichkeit
auf jeden möglichen Tabellenplatz zeigt. Dann werden im Mittel mr Schlüsselwerte auf jede
Adresse fallen, d. h. das ist die Länge der Kette. Bei jedem Eintrag müssen wir im Mittel
die Hälfte der Kette durchsuchen, bis wir einen Schlüsselwert finden und gegebenenfalls
löschen können. Wir müssen die gesamte Überlaufkette durchsuchen, bevor wir wissen,
daß der Schlüsselwert nicht vorhanden ist und gegebenenfalls eine neuen Wert einfügen
können. Unter unseren Annahmen haben wir bei hinreichend großer Hashtabelle im Mittel
nur einen Eintrag in der Kette. Da unsere Annahmen die Realität im allgemeinen nicht
Turan Sós, Vera. ∗September,11 1930 Budapest, Ungarn. Ungarische Mathematikerin (Graphentheorie, Zahlentheorie). Bewies den von H. Steinhaus vermuteten Drei-Abstands-Satz.
4
b geschriebeng und hängt mit dem goldenen Schnitt zusammen. Zum ZuDieser Wert wird auch −Φ
sammenhang mit den Fibonaccizahlen siehe Seite 12.
5
Hier wird teilweise mit recht großen Zahlen multipliziert. Um die für die Multiplikationsmethode
wichtigen Nachkommastellen nicht zu verlieren, sollte mit doppelter Genauigkeit gerechnet werden.
3
312
KAPITEL 10. SUCHEN MIT SCHLÜSSELTRANSFORMATION
Schlüssel
Num. Wert
QUARK
PFAU
BESUCH
ANTON
LAGE
HUT
HAT
DER
KNABE
LUST
NOCH
NEIN
RUHE
ROT
TANNE
SEHEN
ZAHN
ZANK
L
AAL
7663588
267040
14039227
241709
193496
5271
4751
2149
4798278
207343
238011
231413
312498
11875
8691674
8300721
439595
439748
11
11
DM
DM
MM
(m = 23) (m = 37)
11
0
0
10
11
12
4
21
0
2
25
3
20
23
21
4
17
14
13
15
6
10
3
3
18
7
0
21
32
18
7
27
15
10
15
20
20
33
1
7
35
3
20
4
11
21
30
11
19
35
5
11
3
17
11
11
18
11
11
18
Tabelle 10.2: Divisions- und Multiplikatiosnmethode
treffen, werden längere Ketten durchaus vorkommen. Wenn wir die Hashtabelle nicht zu
groß dimensionieren wollen und bewußt eine Überladung in Kauf nehmen, wird die Wahrscheinlichkeit für längere Ketten größer, aber je nach Anwendung kann das Verhahren
immer noch akzeptabel sein. Man könnte eine schellere Bearbeitung der Überlauflisten
durch zusätzliche Strukturen – z. B. eine Baumstruktur für jede Kette – erreichen. Das
ist in den meisten Fällen jedoch wenig sinnvoll.
Abbildung 10.1 zeigt die Kollisionsauflösung durch Verkettung für die letzte Spalte der
Tabelle 10.2.
10.2.3
Kollisionsauflösung durch offenes Hashing
In der frühen Phase der Datenverarbeitung war es sehr wichtig, Programme mit geringem
Speicherplatzbedarf zu haben. In besonderem Maße galt das für Sprachübersetzer. In
diesen wiederum war es die Symboltabelle, die man klein zu halten versuchte. Da lag
10.2. GRUNDLAGEN DES HASHINGS
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
•
◦.................................................. RUHE
•
◦................................................ ANTON
◦................................................ ZAHN
•
◦................................................ QUARK
•
◦................................................ TANNE
•
•
◦................................................ SEHEN
◦................................................ PFAU
◦.................................................. BESUCH
◦................................................ HUT
◦................................................ NOCH
•
◦.................................................. ZANK
◦................................................ LUST
•
◦................................................ NEIN
◦................................................. LAGE
•
313
•
◦................................................. DER
•
◦................................................. KNABE ◦................................................... ROT
◦................................................. HAT
•
•
•
•
•
•
•
•
•
◦.................................................
L
◦................................................. AAL
•
•
•
Abbildung 10.1: Kollisionsauflösung durch Verkettung
es nahe, den Platz der Kette beim Hashen mit den unbelegten Plätzen in der Tabelle
auszugleichen. Wenn man das tut, spricht man von offenem Hashing (open hashing). Wir
wollen uns zunächst einmal klarmachen, wie ein Schlüsselwert gesucht wird. Wir berechnen
als erstes die Hashadresse h(k) und prüfen, ob an dieser Stelle der gesuchte Wert steht.
Wenn ja, hat man ihn gefunden. Wenn nein, muß man zwei Fälle unterscheiden. Ist der
Tabellenplatz unbelegt, so gibt es den Schlüssel im Datenbestand nicht. Falls gewünscht,
kann man ihn dort einfügen. Anderenfalls ist in dem Platz ein anderer Schlüsselwert
gespeichert und man muß weitersuchen.
Was heißt weitersuchen? Wir erweitern unsere Hashfunktion um eine Sondierfolge (probing
314
KAPITEL 10. SUCHEN MIT SCHLÜSSELTRANSFORMATION
sequence). Das ist eine Funktion, die für jeden Versuch i = 0, 1, . . . , m − 1 einen Abstand
p(i) liefert, um den man von h(k) ausgehend weiterzählen muß, um die nächste zu testende
Stelle in der Tabelle zu finden. Wenn man am Tabellenende angekommen ist, muß man
natürlich am Anfang weitermachen. Man muß modulo m zählen. Wir betrachten nur Fälle,
bei denen p(i) nicht von k abhängt. Außerdem muß p(i) so beschaffen sein, daß jeder
Tabellenplatz genau einmal drankommt. Man weiß also nach maximal m Sondierungen,
ob es den gesuchten Schlüsselwert gibt oder nicht. Bei jedem der Veruche macht man das
gleiche wie beim ersten. Wird der Schlüssel gefunden, ist man fertig. Falls gewünscht,
kann man löschen. Neu einfügen kann man nicht. Wird ein leerer Platz gefunden, gibt
es den Schlüssel nicht in der Tabelle und man kann ihn auch nicht löschen. Hingegen
kann man einen neuen Schlüssel einfügen. Es kann schließlich auch passieren, daß man
die ganze Tabelle durchsucht hat, ohne den Schlüssel zu finden. Dann kann man wegen
Platzmangels auch keinen neuen einfügen.
Was soll beim Löschen eines Satzes passieren? Das Satz ist aus dem Datenbestad zu
entfernen. Wir können aber nicht ohne weiteres den entsprechende Platz in der Tabelle
freigeben. Es könnte ja sein, das Schlüsselwerte mit dem gleichen Hashwert später eingefügt wurden und in der Sondierfolge später dran kamen. Diese Schlüssel stehen noch in
der Tabelle, sind aber wegen des Loches nicht mehr erreichbar. Man hilft sich, indem man
die Tabellenstelle als ungültig, aber nicht frei, kennzeichnet. Das ist ungünstig und bedeutet, daß offens Hashing bei Anwenungen mit häufigem Löschen nicht verwendet werden
sollten. Siehe auch Anmerkung 10.1.
Was für eine Sondierungsfolge soll man nehmen? Hier sollen zwei Techniken vorgestellt
werden.
Lineares Sondieren. Man setzt p(i) := i für (i = 0, 1, . . . , m − 1) und berechnet die
nächste zu testende Stelle der Tabelle durch j = (h(k) + p(i))mod m. Ein Nachteil dieses
Verfahrens ist leicht zu erkennen: Bei einer Kollision wird der nächst freie Platz belegt.
Bei einer weiteren Kollision zum gleichen Schlüssel wiederum der nächste. So wächst die
Kollisionsliste kompakt weiter. Verschlimmert wird das dadurch, das Schlüsselwerte mit
anderen Hashadressen, die in die belegte Liste fallen, zusätzlich zu deren Verlängerung
beitragen. Man spricht von primärer Häufung (primary clustering).
Quadratisches Sondieren. Bei quadratischem Sondieren wächst der Entfernungswert
zur Ursprungsadresse quadratisch und jeder Entfernungwert wird rückwärts und vorwärts
eingesetzt, Man prüft nacheinander die Plätze
h(k), h(k) − 1, h(k) + 1, h(k) − 4, h(k) + 4, · · · .
Es ist also p(i) = d 2i e2 (−1)i für i = 0, 1, · · · , m−1. Für m = 4i+3 und m Primzahl ist dabei
gewährleistet, daß die Sondierungsfolge eine Permutation der Hashadressen 0, 1, . . . , m−1
ergibt [OttmW1996].
10.2. GRUNDLAGEN DES HASHINGS
315
Fortsetzung 2 von Beispiel 10.1: Unser Beispiel soll hier mit Tabelle 10.3 fortgesetzt
werden. Es wird die Divisionsmethode mit m = 23 benutzt. Bei Kollisionen wird lineares
Schlüssel
QUARK
PFAU
BESUCH
ANTON
LAGE
HUT
HAT
DER
KNABE
LUST
NOCH
NEIN
RUHE
ROT
TANNE
SEHEN
ZAHN
ZANK
L
AAL
Num. Wert Hashadr. lin.Sond.DM quadr. Sond.
7663588
11
11
11
267040
10
10
10
14039227
4
4
4
241709
2
2
2
193496
20
20
20
5271
4
5
3
4751
13
13
13
2149
10
12
9
4798278
18
18
18
207343
21
21
21
238011
7
7
7
231413
10
14
6
312498
20
22
19
11875
7
8
8
8691674
20
0
16
8300721
21
1
22
439595
19
19
15
439748
11
15
12
11
11
16
1
11
11
17
14
Tabelle 10.3: Offenes Hashing
und quadratisches Sondieren eingesetzt. Es ist von Interesse, die Kollisionen zum Schlüssel
QUARK zu verfolgen. Die Kollisionsmenge ist {QUARK, ZANK, L, AAL}. Man erkennt, wie sich
bei linearer Sondierung durch primäre Häufung ab der Hashadresse 11 ein kompaktes
Feld von 7 belegten Adressen aufbaut. Es fallen dort die Adressen für die Kollisionsmenge
hinein, aber auch die Adressen für DER, HAT und NEIN.
Bei quadratischem Sondieren (letzte Spalte der Tabelle) ist das nicht der Fall. Man sieht
jedoch, daß beim Suchen des Schlüssels AAL mehr als die 4 kollisionsbedingten Zugriffe
zur Tabelle nötig sind. Dazu machen wir uns klar, welche Sondierungsfolge zum Hashwert
11 auftritt. Sie ist in Tabelle 10.4 zu sehen.
Um den zu AAL gehörenden Tabellnplatz zu finden sind 15 Zugriffe zur Tabelle nötig. 11
Zugriffe davon ergeben sich aus besetzten Plätzen anderer Schlüsselwerte. Dies Form der
Leisungsminderung wird sekundäre Häufung (secondary clustering) genannt.
2
316
0
11
KAPITEL 10. SUCHEN MIT SCHLÜSSELTRANSFORMATION
1
10
2
12
3
7
4
15
4
2
6
20
7
18
8
4
9
9
10
13
11
21
12
1
13
8
14
14
15
16
16
6
17
22
18
0
19
3
20
19
21
5
Tabelle 10.4: Quadratische Sondierungsfolge
Anmerkung 10.1 Bei Kollisionsauflösung durch Verkettung bestimmt auschließlich die
Anzahl der Kollisionen die Anzahl Zugriffe bis zum Finden des Schlüssels. Bei offenem
Hashing ist das keineswegs so. Besonders teuer werden die Verzögerungen duch Häufungen,
beim Suchen nach Schlüsseln, die nicht im Bestand sind. Andererseits sind heutzutage
hauptspeichersparende Techniken unnötig oder nachrangig. Daher ist im Normalfall vom
Einsatz von Kollisionsauflösung durch offenes Hashen abzuraten.
2
10.2.4
Universelles Hashing
Wenn man mit der Leistung eines Hashverfahrens nicht oder nicht mehr zufrieden ist,
liegt das in der Regel daran, daß die Hashfunktion zu viele Kollisionen bewirkt. Das
wiederum kann daran liegen, daß die Hashfunktion die zu bearbeitende Schlüsselmenge
ungleich verteilt. Es kann aber auch daran liegen, daß die Menge der zu bearbeitenden
Schlüssel für den Platz der Tabelle zu groß geworden ist. Dieser Fall wird in im folgenden
Unterabschnitt 10.2.5 behanderlt.
Hier widmen wir uns dem ersten Problem. Bei jeder fest gewählten Hashfunktion ist es
nöglich, daß die Menge der vorkommenden Schlüssel für diese Funktion ungüstig ist. D. h.
es ergeben sich große Kollisionsmengen. Dann kommt man im Mittel wesentlich besser davon, wenn man zu Anfang der Bearbeitung aus einer Menge gleichartiger Hashfunktionen
zufällig eine auswählt.
Die Menge dieser Hashfunktionen läßt sich so bestimmen, daß sie der folgenden Bedingung
genügt: Wird eine Funktion h zufällig ausgewählt und sind x und y zwei verschiedene
Schlüssel aus einer Mennge von m Schlüsseln, so gibt es im Mittel m1 Kollisionen h(x) =
h(y). Man spricht dann von einer universellen Menge von Hashfunktionen (universal set
of hashing functions). Für eine solche Menge kann man beweisen: Hat eine Tabelle die
Größe m und sind in ihr n < m Schlüssel gespeichert, so gibt es im Mittel weniger als
eine Kollision bei einer Neueinfügung. Zu den Einzelheiten siehe die Literaturangaben.
10.2.5
Dynamisches Hashing
Gegenüber Baumtechniken haben Hashhverfahren den Vorteil des schnelleren Zugriffs,
oft in der Größenordnung O(n). Es gibt jedoch auch zwei deutliche Nachteile. Zum einen
gibt es keine Hashverfahren, die die Bearbeitung in auf- oder absteigender Reihenfolge
der Schlüsselwerte erlauben. Zum anderen ist die Größe der Hashtabelle fest. Bei offenen Hashverfahren begrenzt das die Zahl der Schlüsselwerte. Bei Kollisionsauflösung
22
17
10.2. GRUNDLAGEN DES HASHINGS
317
durch Verkettung kann eine Überladung der Tabelle zu deutlichem Leistungsabfall führen. Wenn man nicht umhinkommt, dynamisch wachsende Schlüsselmengen mit unbekannter Endgröße zuzulassen und auf jeden Fall Hashverfahren beutzen will, so braucht
man Möglichkeiten zur dynamischen Vegrößerung der Hashtabelle. Das ist nicht einfach,
denn die Größe der Hashtabelle ist ein essentieller Parameter jeder Hashfunktion. Man
muß nicht nur den Tabellenplatz vergösßern, sondern auch die Hashfunktion ändern und
die erweiterte Tabelle neu einrichten. In den Modalitäten, wie man das macht und wie
man möglicherweise einen abrupten Übergang vermeidet und die Erweiterung gleitend
durchführt, unterscheiden sich die Lösungen. Zu Einzelheiten siehe die Literaturangaben.
Aufgaben
Aufgabe 10.1 Man leite Gleichung 10.1, Seite 310, her.
Literatur
Ähnlich wie das im nächsten Kapitel zu behandelnde Sortieren gehören Hashverfahren
zur Grundlage der Algorithmik. Alle Lehrbücher über Algorithmen und Datenstrukruren
behandeln den Stoff, siehe z. B. Brass [Bras2008], Kruse/Tondo/Leung [KrusTL1997],
Aho/Ullman [AhoU1995], Weiss [Weis1995].
Besonders sei hingewiesen auf die umfangreichen Darstellungen in Ottmann/Widmayer
[OttmW1996] und Cormen/Leiserson/Rivest [CormLR1990], in denen die Dinge, auf die
in den Unterabschnitten 10.2.4 und 10.2.5 hingewiesen wird, behandelt werden.
Wie immer bei Algorithmen und Datenstrukturen ist auch hier Knuth [Knut1998a] Pflichtlektüre.
318
KAPITEL 10. SUCHEN MIT SCHLÜSSELTRANSFORMATION
Kapitel 11
Sortieren
11.1
Allgemeines zum Sortieren
Allgemeines zum Sortieren wurde in Abschnitt 1.2, Seite 18, gesagt. Der Vollständigkeit
halber soll es hier wiederholt werden.
Sortieren: Die Elemente einer endlichen, nichtleeren Menge, der Sortiermenge, sollen der Reihe nach angeordnet werden!
Sortieren bedeutet nicht: Nach Sorten einteilen.
Die Elemente der Sortiermenge wollen wir als Sätze (siehe Abschnitt 7.1, Seite 227) auffassen. Sie besitzen einen Sortierwert und sind nach diesem anzuordnen, zu sortieren. Der
Wertebereich, aus dem die Sortierwerte sind, bildet das Sortierkriterium, auch Sortierwertemenge genannt.
Damit man sortieren kann, muß auf der Sortierwertemenge eine lineare Ordnung definiert
sein. Man kann nach dieser Ordnung aufsteigend oder absteigend sortieren. Der Sortierwert eines Satzes der Sortiermenge muß nicht eindeutig sein, d. h. unterschiedliche
Sätze der zu sortierenden Menge dürfen den gleichen Sortierwert haben. Der Sortierwert
ist nicht notwendigerweise ein Schlüsselwert.
Die wichtigsten Beispiele für Sortierwertemengen sind:
• Ganze Zahlen
• Reelle Zahlen
• Wörter mit lexikographischer Ordnung (siehe Seite 114)
Unter einem Sortierverfahren versteht man einen Algorithmus oder ein Programm, mit
dem eine Menge sortiert werden kann. In Abschnitt 1.2, Seite 18 wurde Sortieren durch
Einfügen vorgestellt. Es hat im schlechtesten Fall und auch im Mittel die Komplexität
319
320
KAPITEL 11. SORTIEREN
O(n2 ). Ein elegantes und sehr effizientes Sortierverfahren haben wir in Unterabschnitt
6.1.2, Seite 178, kennengelernt, das Mischortieren. Seine Komplexität ist Θ(n · ln(n) (Gleichung 6.15, Seite 198). Eine weitere Möglichkeit zu sortieren wäre, mit den Schlüsselwerten
einen binären Suchbaum – z. B. einen Rot-Schwarz-Baum – aufzubauen und diesen für
aufsteigende Sortierung mit LWR und für absteigende mir RWL zu durchlaufen (Proposition 9.2, Seite 268). Allerdings geht das ohne Zusatzüberlegungen nur für eindeutige
Sortierschlüssel. Siehe hierzu Aufgabe 11.1. Man spricht von Baumsortieren (tree sort).
Obwohl man in der Größenordnung nicht langsamer ist als die üblichen Sortierverfahren,
wird dieser Weg selten gewählt.
In diesem Kapitel wollen wir uns mit drei weiteren Sortierverfaheren beschäftigen und die
Mindestkomplexität beim Sortieren untersuchen.
11.2
11.2.1
Quicksort
Algorithmus und Programm
In den frühen Jahren der Datenverabreitung waren die Rechner deutlich langsamer; noch
viel schneller ist inzwischen das Verhältnis der Hauptspeichergrößen gewachsen. So war
es in jener Zeit besonders wichtig, eine Reihung von Sortierwerten möglichst schnell sortieren zu können und das, ohne zusätzlichen Speicherplatz zu belegen. 1962 stellte Tony
Hoare 1 das Sortierverfaheren Quicksort vor, das diese Anforderungen auf sehr elegante
Art erfüllte. Quicksort ist bis heute ein viel benutzter Sortieralgorithmus geblieben.
Quicksort geht folgendermaßen vor:
1. Es wird aus den Sortierwerten die abstrakte Struktur eines binären Suchbumes aufgebaut und damit die Sortierung bewirkt.
2. Durch geschickte Ausnutzung der Indextruktur der Reihung wird das sortierte Ergebnis
in der Reihung selbst aufgebaut.
Dabei geschehen die Schritte 1. und 2. nicht nacheinander, sondern sind ineinander verzahnt.
Um den Aufbau des binären Suchbaumes zu erläutern, nehmen wir zunächst an, daß eine
Menge von N eindeutigen Schlüsselwerten vorliegt, die aufsteigend sortiert werden soll.
Ein Element aus dieser Menge wird als Vergleichselement – Pivotelement – ausgewählt,
und der Rest der Menge in zwei Teilmengen zerlegt. Die erste Teilmenge enthält alle
Schlüsselwerte, die kleiner sind als der Pivot, die zweite die größeren Schlüsselwerte. Dann
wird das Verfahren rekursiv auf die Teilmengen angewandt. Es endet mit leeren oder
einelementigen Teilmengen. Tabelle 11.1 zeigt den Algorithmus in Pseudocode. Es ist
Hoare, Sir Charles Antony Richard ∗11.Januar 1934, Colombo, Sri Lanka. Britischer Informatiker.
Studierte in Oxford und Moskau. Bis zur Emeritierung Professor in Oxford. Die Prozeßalgebra Communicating Sequential Processes ist sein wichtigster Betrag zu Theoretischen Informatik und zur Theorie
der Programmiersptrachen.
1
11.2. QUICKSORT
'
&
321
SMENGE
∗QUICKSORT(SMENGE ∗M)
1 { SWERT
∗s, ∗p; // Laufender Schluesselwert, Pivotelement
2 SMENGE ∗M1, ∗M2 // Aufzubauende Teilmengen
3
4
5
6
7
8
9
10
waehle Pivotelement p aus M;
fuer alle s 6= p aus M
{ if (s < p) fuege S in M1 ein;
else fuege s in M2 ein;
}
if (M1 enthaelt mehr als 1 Element) QUICKSORT (M1);
if (M2 enthaelt mehr als 1 Element) QUICKSORT (M2);
}
%
Tabelle 11.1: Pseudocode für Quicksort
leicht einzusehen, daß sich so ein binärer Suchbaum ergibt und die Pivotelemente die
Wurzeln seiner Unterbäume sind.
Wie kann man nun vorgehen, damit die Teilmengen gebildet und alle Daten nur innerhalb
der gegebenen Reihung bewegt werden? Das ist in den Tabellen 11.2 und 11.3 zu sehen2 .
Tabelle 11.2 zeigt die Prozedur qcksort, die in C-Programmen aufgerufen werden kann.
Sie setzt eine global verfügbare Reihung A, in der die zu sortierenden Werte stehen, voraus
und wird auf eine Teilreihung von A rekursiv angewandt. Besteht diese Teilreihung aus
einem oder zwei Elementen, so ist sie schon sortiert oder durch ein einfaches Vertauschen
dazu zu bringen. Hat sie mehr als zwei Elemente, so wird ihr erstes Element, das ist
A[a], als Pivotelement genommen und sie mit der Routine partition so umgeordnet, daß
zunächst die kleineren Elemente, dann das Pivotelelement und schließlich die größeren
Elemente in der Teilreihung stehen. Das Pivotelement hat dann die richtige Position,
auch in der Ausgangreihung. Auf die Teilreihungen davor und danach, soweit nicht leer,
wird dann qcksort rekursiv angewandt. Schematisch wird das in Abbildung 11.1 gezeigt.
Im oberen Teil der Abbildung steht die Teilreihung, so wie sie in partition eingegeben
wird. Das erste Element ist das Pivotelement. Danach folgen in irgendeiner Reihefolge
Sortierwerte, die kleiner (KL) oder größer (GR) als der Pivotwert sind. Das umgeordnete
Ergebnis steht im unteren Teil der Abbildung.
Die Routine partition ist in Tabelle 11.3 zu sehen. Sie arbeitet nach dem folgenden Schema: Das erste Element wird als Pivotelement genommen. Dann wandern in der Reihung
vom zweiten Element ein linker Zeiger nach rechts (in Richtung höherer Indizes) und ein
rechter Zeiger vom Ende nach links. Der linke Zeiger wandert, solange er auf Sortierwerte
2
$
In Hinblick auf Beispiel 11.1 werden Sortierwertvergleiche mit der C-Funktion strcmp realisiert.
322
KAPITEL 11. SORTIEREN
/***************************************************************/
/*
Prozedur qcksort (fuer Zeichenreihen)
*/
/***************************************************************/
void
qcksrt(int p, int q)
{ int
s, i;
char
*y;
if (q < p)
{ printf("quicksort: Fehler! q < p\n");
printf(" p = %d
q = %d\n", p, q);
exit(0);
}
if (q == p ) return;
if (q - p == 1)
{ i = strcmp(A[p], A[q]);
if (i > 0)
{ y = A[p];
A[p] = A[q];
A[q] = y;
}
return;
}
s = partition (p, q); // Es ist p - q > 2
if (s == p) qcksrt(p+1, q);
else
{ if (s == q) qcksrt(p, q-1);
else
{ qcksrt(p, s-1);
qcksrt(s+1, q);
}
}
}
Tabelle 11.2: Prozedur qcksort (für Quicksort)
trifft, die kleiner als der Pivotwert sind, und der rechte, solange die angetroffenen Elemente größer als der Pivot sind. Die Zeiger heißen in partition i und j. Das Wandern
endet auch, sobald die Zeiger die gleiche Position erreicht haben.
Wenn beide Zeiger stehen bleiben, ohne die gleich Position erreicht zu haben, muß ein
Austausch der Sortierwerte vorgenommen werden. Wenn die Zeiger die gleiche Position
11.2. QUICKSORT
323
....................
....................
....................
....................
....................
....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................
.... ........
.... ........
.... .........
... .........
.... ........
.... ........
.... ........
... .........
.... .......
.... ........
.... .........
... .........
.... ........
....
......
... ..
... ..
... ..
...
......
......
......
......
......
......
......
......
......
......
...
.....
.....
.....
...
....
....
...
....
....
....
...
....
....
..
.
.
.
...
.
.
.
.
...
...
...
.
..
..
..
.
..
.
..
.
...
...
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
....
....
....
....
.....
.....
....
....
.....
.....
....
....
.....
..
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
...
...
. ....
. ...
. ....
. ....
. ....
. ....
. ....
...
. .....
. .....
.
.
.. .....
.. ......
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
......
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.......
.......
.......
.......
....... ......
....... ......
....... ......
.......
....... ......
.......
....... ......
........ ........
......... .........
....................
..................
..................
..................
..................
...........
...........
...........
.................
...........
.................
...........
........
.....
PIV GR GR KL KL KL GR GR KL KL GR GR KL GR
.....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................
.... ........
... ........
.... ........
.... ........
.... .......
.... ........
.... ........
... ........
.... ........
.... ........
.... ........
... ........
.... ........
....
......
... ..
... ..
... ..
...
......
... ..
... ..
......
......
......
......
......
......
......
...
.....
.....
.....
...
....
....
...
....
...
...
....
...
..
...
....
..
.
.
.
.
.
..
..
....
....
...
....
.
.
.
.
.
.
....
.
...
....
....
....
...
....
....
....
....
...
.
.
.
.
.
.
.
.
.
.
.
.
.
..
.
.
.
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.. ....
.. ....
.. ...
.. ....
.. ....
.. ....
.. ....
.. ....
.. .....
.. ....
.. ....
.. ....
.. ....
...
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. .
. .
. .
. ......
. ......
......
. .
. .
. .......
. ......
. .
. ......
. ......
. .......
.
...................... ...........................
......................
...................... ........................... ...........................
......................
......................
...................... ........................... ...........................
......................
...................... ...........................
KL KL KL KL KL KL PIV GR GR GR GR GR GR GR
Abbildung 11.1: Erläuterung zu Quicksort (Prozedur partition)
erreichen, ist entweder i = j = a und alle Sortierwerte rechts von PIV waren vom Typ
GR oder es gibt Sortierwerte vom Tip KL. Im ersten Fall bleibt das Pivotelement an
der gleichen Stelle. Im zweiten wird geprüft, ob ai vom Typ KL ist oder nicht. Ist es das,
so wird seine Position mit dem Pivotelemtent getauscht. Ist es das nicht, so tauscht sein
Vorgänger die Position mit dem Pivotelement.
Beispiel 11.1 (Quicksort) In Tabelle 11.4 ist eine Reihung von 18 Zeichenreihen zu
sehen, die sortiert werden soll. Sie steht in Spalte 0. Wie in C sinnvoll und üblich werden
nicht die Zeichnereihen selbst, sondern Verweise auf sie in der Reihung gehalten. Das erste
zu teilende Intervall ist [0, 17] und das erste Pivotelement ist QUARK. Spalte 1 der Tabelle
zeigt das Ergebnis nach der ersten Partition.
Für die zweite Partition ergeben sich die Teilintertvalle [0, 10] mit Pivotelement NEIN und
[12, 17] mit dem Pivotelement RUHE.
Die Teilintervalle der dritten Stufe sind [0, 7] mit Pivotelement LUST, Teilintervall [9, 10]
(zweielementig), Teilintervall [12, 12] (einelementig) und [14, 17] mit Pivotelement TANNE.
Für die vierte Stufe haben wir die Teilreihungen [0, 6] mit Pivotelement DER, [14, 14]
(einelementig) und [16, 17] (zweielementig).
Für die fünfte Stufe verbleiben die Teilreihungen [0, 1] (zweielementig) und [3, 6] mit
Pivotelemenmt LAGE.
In der sechsten Stufe wird die Teilreihung [3, 5] mit Pivotelement HAT bearbeitet, die in
der siebten und letzten Stufe zur Teilreihung [4, 5] (zweielementig) führt.
Deren Bearbeitung ergibt schließlich die sortierte Anordung in der letzten Spalte.
2
Anmerkung 11.1 Die Prozedur qcksrt ruft sich selbst nach der Partitionierung zunächst rekusrsiv für die Teilreihung mit den kleineren Indizes auf. Danach folgt der rekursive Aufruf für die Teilreihung mit den größeren Indizes. Wir haben einen rekursiven
Abstieg „links vor rechts“. Die Reihenfolge ist jedoch belanglos und beeinflußt weder Ergebnis noch Effizienz. Wie man in Tabelle 11.4 gut erkennen kann, könnte man die sich
ergebenden Teilintervalle in irgendeiner Reihenfolge bearbeiten. Insbesondere wäre es auch
möglich, sie gleichzeitig zu bearbeiten. Siehe Teil V, Aufgabe 23.5, Seite 608.
2
324
KAPITEL 11. SORTIEREN
/***************************************************************/
/*
Partition fuer QUICKSORT
*/
/***************************************************************/
int
partition(int a, int b)
{
int
i, j, k;
char
*x, *y;
i = a+1;
j = b;
x = A[a];
while (TRUE)
{ while (i < j && strcmp(A[i], x) < 0) i++;
while (i < j && strcmp(A[j], x) > 0) j--;
if (i == j) break;
y = A[i];
A[i] = A[j];
A[j] = y;
}
if (i == a + 1) return a;
k = strcmp(A[a], A[i]);
if (k < 0) i = i-1;
y = A[i];
A[i] = A[a];
A[a] = y;
return i;
}
Tabelle 11.3: Partition für Quicksort (C-Prozedur)
Quicksort mit nicht eindeutigen Sortierwerten
Bisher haben wir Quicksort nur mit eindeutigen Sortierwerten betrachtet. Natürlich muß
ein Sortierverfahren auch mit mehrfachen Sortierwerten zurechtkommen und Quicksort
kann das auch. Wie man sich leicht klarmachen kann, reicht es dafür aus, in der Routine
partition, Tabelle 11.3, die Vergleiche beim Verschieben der Zeiger von „kleiner“ in
„kleiner/gleich“ und von „größer“ in „größer/gleich“ abzuändern.
11.2. QUICKSORT
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
0
QUARK
PFAU
BESUCH
ANTON
KNABE
ZAHN
HAT
DER
LUST
LAGE
NOCH
NEIN
RUHE
ROT
TANNE
SEHEN
HUT
ZANK
325
1
NEIN
PFAU
BESUCH
ANTON
KNABE
HUT
HAT
DER
LUST
LAGE
NOCH
QUARK
RUHE
ROT
TANNE
SEHEN
ZAHN
ZANK
2
LUST
LAGE
BESUCH
ANTON
KNABE
HUT
HAT
DER
NEIN
PFAU
NOCH
QUARK
ROT
RUHE
TANNE
SEHEN
ZAHN
ZANK
3
DER
LAGE
BESUCH
ANTON
KNABE
HUT
HAT
LUST
NEIN
NOCH
PFAU
QUARK
ROT
RUHE
SEHEN
TANNE
ZAHN
ZANK
4
BESUCH
ANTON
DER
LAGE
KNABE
HUT
HAT
LUST
NEIN
NOCH
PFAU
QUARK
ROT
RUHE
SEHEN
TANNE
ZAHN
ZANK
5
ANTON
BESUCH
DER
HAT
KNABE
HUT
LAGE
LUST
NEIN
NOCH
PFAU
QUARK
ROT
RUHE
SEHEN
TANNE
ZAHN
ZANK
Tabelle 11.4: Beispiel für QUICKSORT
6
ANTON
BESUCH
DER
HAT
KNABE
HUT
LAGE
LUST
NEIN
NOCH
PFAU
QUARK
ROT
RUHE
SEHEN
TANNE
ZAHN
ZANK
7
ANTON
BESUCH
DER
HAT
HUT
KNABE
LAGE
LUST
NEIN
NOCH
PFAU
QUARK
ROT
RUHE
SEHEN
TANNE
ZAHN
ZANK
326
KAPITEL 11. SORTIEREN
11.2.2
Komplexität von Quicksort
Zur Bestimmung der Komplexität müßten die Anzahl der Vergleiche von zwei Sortierwerten und die Anzahl von Vertauschungen herangezogen werden. Man geht jedoch davon
aus, daß ein (im Mittel) fester Prozentsatz von Vergleichen zum Vertauschen der Sortierwerte führt, so daß man sich auf die Anzahl der Vergleiche beschränken kann. Mit diesem
Maß sind die Komplexitäten von Quicksort:
W OC(n) = Θ(n2 )
BEC(n) = Θ(ln (n) · n)
AV C(n) = Θ(ln (n) · n)
(11.1)
(11.2)
(11.3)
Mit den folgenden Überlegungen soll das hergeleitet werden. Dabei ist es zweckmäßig,
den binären Suchbaum zu betrachten, der sich aus einer zu sortierenden Folge von Werten
ergibt. Es sei also – analog zu Seite 198 – sn eine Folge von n Sortierwerten. Der Einfachheit
halber nehmen wir zunächst an, daß sie paarweise verschieden sind. Der Aufwand für die
Sortierung mit Quicksort, gemessen in der Anzahl Vergleiche, soll mit R(sn ) bezeichnet
werden. Abbildung 11.2 zeigt zwei Beispiele für Quicksort-Bäume. Die Knoten zeigen die
Sortierwerte, die als Pivotelemente ihr endgültige Position erreicht haben und entweder bei
Vergleichen, um Doppelzählungen zu vermeiden, nicht gezählt werden oder nicht weiter
in Vergleiche eingehen. Die Länge t(v) des Weges von der Wurzel zu einem Knoten v gibt
die Anzahl der Vergleiche an, die dieser Knoten durchläuft. Die Summe über alle Knoten
ergibt für die Folge sn den gesuchte Aufwand R(sn ). Hat der Quicksortbaum von sn die
Höhe t(sn ), so bedeutet das
t(sn )−1
X
R(ns ) =
ai · i
i=0
Dabei ist ai die Anzahl Knoten der Stufe i + 1 des Baumes3 .
Abschätzung des schlechtesten Falles (WOC).
Ist sn eine Folge aufsteigender Sortierwerte, so ergibt sich ein linearer Quicksortbaum und
n−1
P
es gilt R(sn ) =
i·1 = n·(n−1)
also W OC(n) = Ω(n2 ). Wenn die Folge sn keinen linearen
2
i=0
Quicksortbaum ergibt, müssen einige Summanden der Gesamtsumme kleiner werden. Wir
, d. h. W OC(n) = O(n2 ). Zusammen ergibt sich 11.1.
haben also R(sn ) ≤ n·(n−1)
2
Abschätzung des besten Falles (BEC).
Wir nehmen zunächst an, daß n = 2k eine Zweierpotenz ist. Wird daraus mit Quicksort ein
t(sP
n )−1
ai ·i. Wenn wir in dievollständiger ausgewogener Suchbaum gebildet, so ist R(sn ) =
i=0
sem nur die Blätter betrachten, so haben wir die Abschätzung R(sn ) ≥ 2k−1 · (k − 1), d. h.
3
Man beachte, daß die auf Seite 261 eingeführte Stufennummeriererung bei 1 beginnt.
11.2. QUICKSORT
327
Quicksort-Baum zu
(4,6,1,2,5,3,7)
Quicksort-Baum zu
(2,6,1,3,5,4,7)
..................
....
......
....
...
...
...
....
.
...
..
...
..
...
...
.
.
.
.
.
.
.
.
.
.. ....................... ......
....
....
....
....
...
....
....
....
.
.
.
....
..
.
.
.
....
..
.
.
....
.
....
....
.
.
.
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
....... ...
.... ........................
......
.
....
.
.
.
.
.
...
....
..
...
.
.
...
.
.
..
..
...
....
.
.
.
...
...
..
...
.
...
...
.
..
.
.
...
...
.
..
.
.
.
.
.
.....
.
.
......
...
.......................
.....................
...
...
...
...
...
...
...
...
...
..
.
...
.
...
.
...
.
...
.
.
...
.
...
...
..
...
.
...
.
.
..................
.
. ...........
......................
......
.
...
.
.
.
.
....... ..........
.
.
.
....
...
...
...
...
...
.
.
.
.
.
.
.
...
..
..
.
.
.
.
.
....
.
.
..
..
..
...
....
....
.
..
.
...
.
...
...
.
.
..
.
.
.
.
...
...
...
..
.
..
.
.
.
.
.
.
.
.
.....
.
......
......
..
...
.
.
.
.
.......................
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
........
........
...
...
...
...
...
...
...
..
.......................
.
.
.
.
...
...
...
.
...
.....
...
..
..
...
...
...
.
.
.....
.
.......................
4
6
1
2
7
5
3
..................
....
......
...
....
...
...
....
.
...
..
..
...
...
...
.
.
.
.
.
......... .........
... ......... .....
...
...
...
...
...
...
.
.
...
..
.
...
.
..
...
.
.
...
...
.
...
.
.
... ...................
.
.
.
.
.
.
.
.
.
.
.
.
.
.....
...... ...........
.
.
.
.....
...
...
.
...
...
..
....
..
...
..
...
.
...
..
..
...
.
...
.
..
...
.
.
.
...
.
...
....
.
.
.
.
.
.
.
.......
.
.......
..........
.
.
.
.
..................
.
.
.
.
.
.
.
.
.
.
.
.
...
..
...
...
...
...
...
...
...
...
.
.
...
..
...
.
.
...
..
.
.
...
.
.
.
... ..................
.
.....
..........................
.
.
.
......
...
...
..
...
.
...
.
.
...
..
.
....
..
.
..
....
...
.
.
...
..
.
...
.
.
...
...
...
.
..
.
.
.
.
.
.....
......
...
.
.
..........................
.
.
.
.
.
.
.
.
.
........
...
...
...
...
...
...
...
...
...
... ..............
......
. ..
...
.....
...
...
...
....
..
..
...
...
...
.
.
.
....
..
.
.
.
.
.
.
.
.
.
.
.
.
.. .............
...
...
...
...
.
.
..
...
...
...
....................
.
.
.
.
.
.
.
.....
.
....
...
..
...
...
....
..
..
...
..
...
.
.
.
......
.
.
.
.
.
..................
2
1
6
7
3
5
4
Abbildung 11.2: Beispiel für Quicksort-Bäume
R(sn ) = Ω(n·ln(n). Jedoch ist
t(sP
n )−1
i=0
ai · i ≤ (k − 1) ·
t(sP
n )−1
i=0
ai = (k − 1) · n = O(ln(n) · n).
Es folgt 11.2. Mit den auf Seite 198 vorgestellten Methoden sieht man leicht, daß das Ergebnis auch für Sortierfolgen beliebiger Länge gilt.
Abschätzung des mittleren Falles (AVC).
Es werden alle Sortierfolgen als gleichwahrscheinlich angesehen. Da die Reihenfolge, in der
der die Teilintervalle bearbeitet werden, beliebig ist, folgt daraus, daß auch alle Folgen
der Pivotelemente gleichwahrscheinlich sind. Für die interne Pfadlänge der zugehörigen
binären Suchbäume wurde in Abschnitt 9.3, Gleichung 9.5, Seite 295, hergeleitet
1
EI(n) = 2n ln n − (3 − 2γ)n + 2 ln n + 1 + 2γ + O( )
n
Nun ist aber E(R(sn )) = EI(n) − n, also AV C(n) = Θ(ln(n) · n). Es gilt 11.3.
Anmerkung 11.2 Es ist nicht schwer einzusehen, daß die Abschätzungen auch für den
Fall mehrfach auftretender Sortierwerte gelten.
328
11.2.3
KAPITEL 11. SORTIEREN
Randomisiertes Quicksort
Randomisiertes Quicksort ist Quicksort, bei dem des Pivotelement nicht fest aus dem zu
bearbeitenden Teilintervall ausgewählt wird, sondern zufällig, d. h. mit gleicher Wahrscheinlichkeit. Das ist einfach: In Tabelle 11.1 ist in Zeile 3 „wähle Pivotelement p aus
M“ zu ersetzen durch „wähle Pivotelement p zufällig aus M“. Der Ablauf ist dann ein
Zufallsereignis und die Anzahl Vergleiche, die bis zur endgültigen Sortierung benötigt
werden, eine Zufallsvariable. Der Erwartungswert der Anzahl Vergleiche, die Randomisiertes Quicksort braucht, ist mit unseren bisherigen Ergebnissen leicht zu finden. Die
zufällige Auswahl des Pivotelementes bewirkt nämlich, daß ein zufälliger Quicksortbaum
aufgebaut wird, und zwar unabhängig von der Reihenfolge der zu sortierenden Werte. Wir
haben also analog zum Mittelwert bei deterministischem Quicksort
AV Crnd (n) = Θ(ln(n) · n)
(11.4)
Randomisiertes Quicksort ist ein Las-Vegas-Algorithmus, siehe Unterabschnitt 6.3, Seite
220. Es handelt sich also um einen Algorithmus, der auf jeden Fall endet und das richtige
Ergebnis liefert. Unter Umständen braucht er jedoch dafür sehr lange. Letzteres ist eine
recht ungenaue Aussage und soll präzisiert werden. Daß der Algorithmus im Mittel effizient
ist, wissen wir schon. Das reicht aber nicht aus. Wir wollen uns eine Dauer, d. h eine
Anzahl von Vergleichen m, vorgeben und fragen nach der Wahrscheinlichkeit, daß ein
Ablauf länger dauert. Wir wollen P(R(sn ) > m) abschätzen. Dafür benutzen wir die
Ungleichung von Markov (Gleichung B.4, Seite 622)
P(R(sn ) > m) ≤
E(R(sn ))
m
(11.5)
1
Setzen wir m := 10 · E(R(sn )), so ist P(R(sn ) > m) ≤ 10
. Nach den Ausführungen
im vorigen Unterabschnitt ist E(R(sn )) etwas kleiner, aber in der Größenordnung von
2 ln(n)n. Für n = 10000 bräuchte man im Mittel ungefähr 195.000 Vergleiche. Die Wahr1
.
scheinlichkeit, daß die Sortierung länger als 1.950.000 Vergleiche dauert ist kleiner als 10
Die Ungleichung von Markov ist kein scharfes Instrument. Mit größerem mathematischen
Aufwand kann man bessere Abschätzungen erzielen.
Las-Vegas-Algorithmen können auch anders betrachtet werden. Man gibt sich eine „Ungeduldsgrenze“ vor, in unserem Beispiel könnten das 1.500.000 Vergleiche sein. Wird diese
Grenze bei einem Lauf überschritten, bricht man den Lauf ab, so als wäre das Ergebnis
falsch. Man startet eine neuen Lauf, bei dem die Pivotelement wieder zufälling und außerdem unabhängig vom ersten Lauf gewählt werden. Die Wahrscheinlichkeit, daß beide
1
Läufe jeweils mehr 1.500.000 Vergleiche brauchen, ist dann kleiner als 100
.
Allgemein: Ist P(R(sn ) ≥ m) < α mit 0 < α < 1, so gilt für k ≥ 1
P(k aufeinanderfolgende Quicksortläufe brauchen jeweils mehr als m Schritte) < αk .
Randomisiertes Quicksort verlagert den Zufall auf die Wahl des Pivotelementes und hat
für alle Sortierfolgen im Mittel eine gute Effizienz.
11.3. HALDEN, HEAPSORT UND PRIORITÄTSWARTESCLANGEN
11.2.4
329
Qicksort und Mischsortieren
Quicksort, insbesondere Randomisiertes Quicksort, ist im Mittel ein sehr effizientes Verfahren. Daher sieht man im allgemeinen in der Komplexität keinen deutlichen Vorteil von
Mischsortieren gegenüber Quicksort. Im übrigen wird im Vergleich von Quicksort und
Mischsortieren von vielen Autoren Quicksort ein leichter Vorteil eingeräumt4 . Meistens
wird das damit begründet, daß Quicksort ohne zusätztlichen Speicherplatz auskommt,
in situ arbeitet, wir man das nennt. Es ist richtig, daß Mischsortieren ein zusätzliches
Verweisfeld für jeden Schlüsselwert der zu sortierenen Daten braucht. Mit diesem einen
Zusatzfeld und einem Rekursionskeller, der auch nicht größer als der von Quicksort ist,
läßt sich Misortieren jedoch sehr elegant programmieren und läuft sehr effiezient ab (Unterabschnitt 6.1.2, Seite 178). Die Aussage “MergeSorte requires tons of extra memory”
(siehe Fußnote 4) geht weit an der Realität vorbei. Sie ist Unsinn.
Es ist übrigens bemerkenswert, das die Routine qsort der GNU-C-Bibliothek, die Schlüsselwerte in Reihungen sortiert, Mischsortieren benutzt, falls der Rechner meint, genügend
Platz zu haben. Außerdem ist erwähnenswert, daß ein wirklich böser Gegenspieler Quicksort immer zu einem n2 -Verhalten zwingen kann [McIl1999].
11.3
11.3.1
Halden, Heapsort und Prioritätswartesclangen
Halden (als Datenstruktur)
Die hier einzuführenden Halden sind von denen in Unterabschnitt 8.4.4, Seite 252 zu
unterscheiden.
Eine Halde (heap) von n Elementen ist ein vollständig gefüllter Binärbaum von
Sortierwerten, in dem jeder Knoten mindestens so groß ist wie seine Nachfolger.
Die Werte müssen nicht notwendigerweise paarweise verschieden sein. Die Wurzel ist stets
mindestens so groß wie jedes andere Element des Baumes. Das bzw. ein größtes Element
ist also leicht zu finden. Hingegen ist kein Verfahren bekannt, mit dem effizient zu einem
gegebenen Wert das zugehörige Element bzw. die zugehörigen Elemente gefunden werden
können.
Abbildung 11.3 zeigt eine Halde aus den Werten QUARK, PFAU, BESUCH, HUT, ZANK,
LUST, DER, LAGE.. Der Binärbaum der Halde ist nicht eindeutig bestimmt. Zum Beispiel
könnten HUT und DER in der Reihenfolge vertauscht sein sein. Ähnlich wie bei Quicksort
wird die Baumstruktur in einer Reihung gespeichert. Die Knoten sind in der Reihung
von links nach rechts und dann von oben nach unten nummeriert. Tabelle 11.5 zeigt die
4
Siehe z. B.http://stackoverflow.com/questions/70402/why-is-quicksort-better-than-mergesort
330
KAPITEL 11. SORTIEREN
ZANK
......
.......... ...................
..........
..........
..........
..........
..........
..........
..........
..........
.
.
.
.
.
.
.
.
..........
.
.......
.
.
..........
.
.
.
.
.
.
.
.......... .
......
.
.
.
.
.
.
.
.
..............
.
.
.
...............
.
.
.
.
.
.
.
. ........
.................
QUARK
.
PFAU
........
...... ...........
......
......
......
......
......
......
.
.
.
.
.
......
.
......
......
...... ..
......
.
.
.
.
.
.
... .
..............
.
.
..................
.
.
.... .
........
...... ...........
......
......
......
......
......
......
.
.
.
.
.
......
..
......
......
...... ..
......
.
.
.
.
.
.
... .
...............
.
..................
.
......
LAGE
..
LUST
HUT
DER
....
....
....
...
....
.
.
. ......
............
.......
BESUCH
Abbildung 11.3: Beispiel für eine Halde
0
ZANK
1
QUARK
2
PFAU
3
LAGE
4
LUST
5
HUT
6
DER
7
BESUCH
Tabelle 11.5: Beispiel: Halde als Reihung
Reihung zu Abbildung 11.3. Wie bei dem Beispiel für Quicksort sind in der Reihung
Verweise und nicht die Werte selbst gespeichert.
In den Fällen, in denen wir Halden anwenden wollen, brauchen wir Halden variabler Größe. Allerdings nicht beliebige Halden, sondern nur solche, die Anfangsstücke einer festen,
gegeben Reihung A der Länge N sind. Solche Reihungen sind durch die Länge l des Anfangsstückes charakterisiert. Für l = 0 existiert keine Halde5 . Für l > 0 besteht die Halde
aus den Elementen A[0], A[1], . . . , A[l − 1]. Der Vorgänger eines Nichtwurzelelementes A[i]
ist A [b(i − 1)/2c]. Für die Nachfolger eines Elementes A[i], soweit vorhanden, gilt: Der
linke ist A[2i + 1], der rechte A[2i + 2]. Ein Nachfolger ist vorhanden, wenn 2i + 1 < l,
bzw. 2i + 2 < l.
Wir wollen Halden für zwei Zwecke benutzen. Zum einen soll mit ihnen ein Sortierverfahren gebildet werden und zum anderen wollen wir ihre Eignung als Prioritätswarteschlangen
untersuchen. In jedem Fall müssen wir in der Lage sein, eine Halde dynamisch aufzubauen. Die wichtigste Operation dafür ist VERSICKERN6. Eingabe für diese Operation
ist eine Länge 1 ≤ l ≤ N und ein Element A[i] in dem durch l gegebenen Anfangsstück
der Reihung A. Weiter wird angenommen, daß sowohl linker als auch rechter Unterbaum
von A[i], soweit nicht leer, Halden sind. Nach dem Versickern ist der gesamte durch i
oder, wenn man so will, eine leer Halde.
Der englische Ausdruck ist sift down. Cormen/Leiserson/Rivest [CormLR1990] benutzen das einprägsame Wort heapify. Die Übersetzung haldisieren hätte alledings einen eigenartigen Klang.
5
6
11.3. HALDEN, HEAPSORT UND PRIORITÄTSWARTESCLANGEN
331
gegebene Unterbaum eine Halde. Das Beispiel in den Abbildungen 11.4 und 11.5 soll das
ZANK
.
..............................
..........
..........
..........
..........
.........
..........
.
.
.
.
.
.
.
.
.
..........
.
..........
..........
..........
..........
.
.
.
.
.
.
.
.
.
..........
.......
.
.
.
.
.......... ....
.
.
.
.
.
.
.
.
... .
.. .....
...................
.................
BALD
PFAU
...........
...... ...........
......
......
......
......
......
......
.
.
.
.
.
......
..
......
......
...... ..
......
.
.
.
.
.
.
..
.............
.
...................
.
.
.
.
..
...........
...... ...........
......
......
......
......
......
......
.
.
.
.
.
......
..
......
......
...... ..
......
.
.
.
.
.
.
..
.............
.
...................
.
.
.
.
..
LAGE
..
LUST
HUT
DER
..
....
...
....
.
.
.
..
....
........
...........
......
BESUCH
Abbildung 11.4: Beispiel für das Versickern in einer Halde (Teil 1)
ZANK
.....................
..........
..........
..........
..........
..........
..........
..........
..........
.
.
.
.
.
.
.
.
.
..........
.......
.
.
.
..........
.
.
.
.
.
.
..........
......
.
.
.
.
.
.
.
.......... ..
.
.
..................
..............
.
.
.
.................
...............
LUST
.....
..... ..........
......
......
......
......
......
......
......
......
.
.
.
.
.
......
....
.
...... .
.
.
.
.
........
...........
.
.
....
...................
.............
LAGE
..
BALD
PFAU
.....
..... ..........
......
......
......
......
......
......
......
......
.
.
.
.
.
......
....
.
...... .
.
.
.
.
........
...........
.
.
...
..................
.............
HUT
DER
..
...
....
....
...
.
.
.
....
.. ....
...........
......
BESUCH
Abbildung 11.5: Beispiel für das Versickern in einer Halde (Teil 2)
erläutern. Im Binärbaum der Abbildung 11.4 verletzt Knoten BALD an der Stelle A[1] die
Haldenbedingung. Er ist kleiner als beide Nachfolger. Versickern wählt den größeren der
beiden Werte – also LUST – und vertauscht ihn mit BALD (Abbildung 11.5). Der Wert BALD
steht jetzt an der Stelle A[4] und der ganze Baum erfüllt die Haldenbedingung.
Tabelle 11.6 zeigt den Pseudocode für VERSICKERN. Die Baumstruktur – z. B. „hat
A[i] einen rechten Nachfolger?“ – wird über Indexrechnung behandelt, wobei l die Größe
des Baumes angibt, d. h. die Länge eines Anfangsstücks der Reihung A.
Was kostet VERSICKERN? Vergleichen und Vertauschen haben unabhängig von der
Größe der Halde einen festen Aufwand. Sie werden in jeder Stufe der Halde höchstens
332
KAPITEL 11. SORTIEREN
'
void
VERSICKERN(char **A, int l, int i)
1 if (linker Nachfolger von A[i] == NULL) return;
2 if (rechter Nachfolger von A[i] == NULL) j = 2i + 1;
3 if (rechter Nachfolger von A[i] != NULL)
4 { if (linker Nachfolger von A[i] >= rechter Nachfolger von A[i]) j = 2i + 1;
5
else j = 2i + 2;
6 }
7 if (A[i] < A[j])
8
{ vertausche Werte von A[i] und A[j];
9
VERSICKERN(A, l, j);
10
}
11 return;
&
$
%
Tabelle 11.6: Pseudocode für VERSICKERN
einmal aufgerufen. Da die Halde ein vollständiger Binärbaum von höchstens N Elementen
ist, kann ein Aufruf von VERSICKERN durch O(ln N) abgeschätzt werden.
11.3.2
Sortieren mit Halden (Heapsort)
Um mit einer Halde zu sortieren, muß eine Reihung von Sortierwerten zunächst zu einer
Halde gemacht werden. Die Grundidee ist einfach. Eine einelementige Reihung ist eine
Halde. Andernfalls duchläuft man die Knoten in Rückwärtsreihenfolge und erzwingt mit
VERSICKERN, daß jeder Wurzel eines Unterbaums wird, der der Haldenbedingung genügt.
Wir wollen das Verfahren BAUHALDE nennen. Pseudocode dafür ist in Tabelle 11.7 zu
'
&
void
BAUHALDE(char **A, int N)
1 int
j;
2 if (N == 1) return;
3 for (j = N − 1; j >= 0; j − −) VERSICKERN(A, N, j)
4 return;
Tabelle 11.7: Pseudocode für BAUHALDE
sehen.
Die Komplexität von BAUHALDE ist leicht abzuschätzen. Wir haben N − 1 Knoten und für
jeden wird genau einmal VERSICKERN aufgerufen. D. h. für die Komplexität gilt O(N ln N).
$
%
11.3. HALDEN, HEAPSORT UND PRIORITÄTSWARTESCLANGEN
333
Man kann übrigens die Zahl der Aufrufe von VERSICKERN halbieren, wenn man beachtet,
daß nur die Knoten versickern müssen, die Nachfolger haben. In Aufgabe 11.2 ist das
näher zu untersuchen. In Aufgabe 11.3 ist zu zeigen, daß eine Halde auch mit Aufwand
O(n) aufgebaut werden kann.
Wenn einmal die zu sortierende Menge zur Halde geworden ist, läuft der Sortiervorgang
folgendermaßen ab: Es wird das erster Element der Halde genommen und mit dem letzten
vertauscht. Damit ist gewährleistet, daß das größte Element an der richtigen Stelle steht.
Dann wird die Reihung um den letzten Platz gekürzt. Das neue Anfangsstück ist keine
Halde, kann jedoch durch Versickern des ersten Elementes dazu gemacht werden. Man
iteriert das Verfahren mit jeweils um 1 verkürzten Anfangsstücken, bis schließlich die
ganze Ausgangsreihung sortiert ist. In Tabelle 11.8 ist der Pseudocode zu sehen.
'
&
void
HEAPSORT(char **A, int N
1 void BAUHALDE(char **A, int N);
2 void VERSICKERN(char **A, int l, int i);
3 int
j;
4 if (N == 1) return;
5 BAUHALDE(A, N);
6 for (j = N − 1; j > 0; j − −)
7 { xc = A[j];
8
A[j] = A[0];
9
A[0] = xc;
10
VERSICKERN(A, j − 1, 0)i;
11 }
12 return;
Tabelle 11.8: Pseudocode für HEAPSORT
Zur Abschätzung der Komplexität von HEAPSORT ist zunächst der Aufwand O(n ln(n))
für den Aufbau der Halde zu beachten. Danach wird in jedem Schritt eine Element durch
Vertauschen an die richtige Stelle gebracht und durch VERSICKERN die Haldenbedingung wieder hergestellt. Wir haben auch dafür den Aufwand O(n ln(n)) und damit diesen
Aufwand auch für HEAPSORT insgesamt.
11.3.3
Prioritätswarteschlangen (nach Knuth)
Die für das Sortieren eingeführte Datenstruktur Halde bietet sich an, eine dynamisch
wachsende und schrumpfende Menge von Werten (Prioritätswerten), so zu organisieren,
$
%
334
KAPITEL 11. SORTIEREN
daß stets das erste Element einen Wert hat, der nicht kleiner als die übrigen Werte ist.
Von Knuth stammt dafür die Bezeichnung Prioritätswarteschlange (priority queue)7 . Für
eine Prioritätswarteschlange dieser Art werden die folgenden Operationen geforder:: 1.
FIRSTPQ: Übergib den ersten Satz der Prioritätswarteschlange, ohne diese zu verändern.
2. INSERTPQ: Füge einen neuen Satz in die Prioritätswarteschlange ein. Der Satz kann eine
neue Priorität haben oder auch eine schon gespeicherte Priorität aufweisen. 3. REMOVEPQ:
Entferne den ersten Satz der Prioritätswarteschlange.
Wir wollen nun diese Operationen durch eine Halde realisieren und dann anhand eines
Beispiels illustrieren, aber auch gleichzeitig eine Schwäche aufdecken.
FIRSTPQ: Liefert den erten Satz der Halde. Die Implementierung ist unmittelbar klar.
Der Aufwand ist O(1).
INSERTPQ: Man fügt den neuen Satz zunächst so ein, daß die Halde weiterhin ein
vollständig gefüllter Binärbaum bleibt. Danach muß man den neuen Satz soweit nach oben
steigen lassen, bis sich eine korrekte Halde ergibt. Code für die Prozedur INSERTPQ
ist in Tabelle 11.9 zu sehen. C ist die Reihung, in der die Halde aufgebaut wird, value
'
&
void
INSERTPQ(char **C, char *value, int l)
1 { int
n, parent;
2 char
xc;
3 C[l] = value;
4 if (l == 0) return;
5 n = l;
6 while (n > 0)
7
{ parent = (n-1)/2;
8
j = strcmp(C[parent], C[n]);
9
if (j < 0)
10
{ xc = C[parent];
11
C[parent] = C[n];
12
C[n] = xc;
13
n = parent;
14
}
15
else n = 0;
16
}
17 return;
18 }
Tabelle 11.9: Prozedur INSERTPQ
7
Man beachte, daß diese Definition nicht der Definition von Seite 254 entspricht.
$
%
11.3. HALDEN, HEAPSORT UND PRIORITÄTSWARTESCLANGEN
335
ist der einzufügende Prioritätswert, l gibt die nächste freie Stelle der Halde an. Da der
einzufügende Wert mit jedem Schritt eine Position näher an die Wurzel geschoben wird,
ist der Aufwand O(ln(l)). Er ist also auch O(ln(N)), wobei N die Größe der Reihung C
ist.
REMOVEPQ: Der erste Satz wird aus der Halde entfernt. Der letzte Satz der Halde
wird zum ersten gemacht und die Halde um eine Position gekürzt. Danach erfüllen linker
und rechter Unterbaum des neuen ersten Elementes die Haldeneigenschaft und man läßt
es versickern. Tabelle 11.10 enthält Code für die Prozedur REMOVEPQ. Der Aufwand
'
&
void
*REMOVEPQ(char **C, int l)
1 char
xc;
2 xc = C[0];
3 if (l == 0) return xc;
4 C[0] = C[l];
5 VERSICKERN(C, l − 1, 0);
6 return xc;
Tabelle 11.10: Prozedur REMOVEPQ
wird durch den Aufruf von VERSICKERN bestimmt und ist O(ln(N)). N ist wieder
die Größe der Reihung C.
Beispiel 11.2 Wir wollen mittels der Prozedur INSERTPQ aus den Werten
ZANK, AAR, ZAHN1 , BALD, LAGE, LUST, BERN, HUT, PFAU, ZAHN2
in dieser Reihenfolge ein Prioritätswarteschlange bilden. Danach soll mit REMOVEPQ
das erste Element entfernt werden. Der Wert ZAHN tritt zweimal auf; der Index dient zur
Verfolgung des Wertes im Baum. Die Graphen der Abbildung 11.6 zeigen wie sich die
Prioritätswarteschlanger auf- und abbaut. PSW A zeigt die Prioritätswarteschlange, die
sich nach 6 Einfügungen ergibt, PSW B die nach 8 Einfügungen, PSW C die nach allen
10 Einfügen und PSW D den Stand nach der Entfernung des ersten Elementes.
2
Anmerkung 11.3 In Beispiel fällt auf, daß der zuletzt eingefügte Wert von ZAHN und
nicht der zuerst eingefügte gelöscht wurde. D. h. bei mehrfachen Prioritätswerten ist unbestimmt, in welcher Reihenfolge sie beim Abbau der Schlange auftreten. Das steht im
Gegensatz zu Prioritätswarteschlangen, wie sie auf Seite 254 eingeführt wurden, und ist für
Auftragsbearbeitungen aller Art ein entscheidendes Manko. Ein Scheduler eines Betriebssystems muß jedenfalls Aufträge gleicher Priorität in der Reihenfolge ihres Eintreffens
bearbeiten. Merke:
$
%
336
ZANK
ZANK
.......
.......... ................
.........
........
..........
........
..........
........
.
.
.
.
.
.
.
.
........
..
........
..........
........ ..
..........
.
.
.
.
.
.
.
.
.
.
............
.. .....
.............
..................
ZAHN1
LAGE
........
....... .............
.......
.......
.......
.......
.......
.......
.
.
.
.
.
.
.......
.....
.
.
....... ..
.
.
.
.
..........
.............
.
.
..............
.............
AAR
.......
.......... ................
.........
........
..........
........
..........
........
.
.
.
.
.
.
.
.
........
..
........
..........
........ ..
..........
.
.
.
.
.
.
.
.
.
.
............
.. .....
.............
..................
....
.....
.....
.....
.....
.
.
.
.
..
.........
............
.......
BALD
LAGE
ZAHN1
........
....... .............
.......
.......
.......
.......
.......
.......
.
.
.
.
.
.
.......
.....
.
.
....... ..
.
.
.
.
..........
.............
.
.
..............
.............
..........
..... ..........
.....
.....
.....
.....
.....
.....
.
.
.
.
..... .
..
... .
. ......
..................
...................
.
.
...
HUT
.
LUST
LUST
BALD
BERN
...
...
...
....
...
.
.
..
......
...........
....
AAR
PWS A
PWS B
ZAHN2
ZANK
...
.......... ...............
.........
........
..........
........
........
..........
........
..........
.
.
.
.
.
.
.
.
.
........
......
.
.
.
........ ..
.
.
.
.
.
.
...........
...............
.
.
.
.
.
................
................
ZAHN2
ZAHN1
PFAU
ZAHN1
.
..................
.......
.......
.......
.......
.......
.......
.
.
.
.
.
.
.......
.......
.......
..............
..........
.
.
.
...............
.
..................
.
.
.
.
..........
..... .........
.....
.....
.....
.....
.
.
.
.
.....
...
.....
.....
........
...........
.
.
..................
............
.
.
..................
.......
.......
.......
.......
.......
.......
.
.
.
.
.
.......
....
.......
.......
..........
...............
.
.
..........
.
...................
.
.
.
.
.
..........
..... .........
.....
.....
.....
.....
.
.
.
.
.....
...
.....
.....
........
...........
.
.
..................
............
.
..
... ....
.... ......
...
....
...
.
....
.
... ..
. .....
...............
...............
.
....
...
HUT
PFAU
.
.
...
...
...
...
.
.
. .....
.............
.......
BALD
PWS C
LUST
BERN
LAGE
..
BALD
LUST
..
.. ....
.... .....
...
....
...
....
.
.
... .
...
.
.............
................
.
.
.
....
.......
AAR
HUT
PWS D
Abbildung 11.6: Dynamischer Auf- und Abbau einer Prioritätswarteschlange
BERN
KAPITEL 11. SORTIEREN
LAGE
..
AAR
...
.......... ...............
.........
........
..........
........
........
..........
........
..........
.
.
.
.
.
.
.
.
.
........
......
.
.
.
........ ..
.
.
.
.
.
.
...........
.................
.
.
.
.
................
................
11.4. MINDESTKOMPLEXITÄT BEIM SORTIEREN
Prioritätswarteschklangen auf der Basis von Halden sind für die Pozeßplanung
in Betriebssystemen nicht geeignet.
337
Zusammenfassend kann man sagen: Prioritätswarteschlangen auf der Basis von Halden
sind eine interessante Datenstruktur. Sie liefern stets ein Element höchster Priorität zurück. Wir wenden sie in Abschnitt 19.2, Seite 513, bei der Bestimmung kürzester Wege in
Graphen an. Allgemein werden sie in der Informatikund ihren Anwemdumgem nicht sehr
häufig eingesetzt.
2
11.4
Mindestkomplexität beim Sortieren
Ein Algorithmus zum Sortieren ode auch zur Lösung eines anderen Problems soll zunächst
einmal das Problem vollständig und richtig lösen. Das soll er, wie wir mehrfach fesgestellt
haben, möglichts effizient tun. Wir haben das definiert als „die Anzahl Operationen soll bei
gegebenem Umfang der Eingabe möglichst klein sein“. Wir haben uns bemüht, so genau
wie möglich abzuschätzen, wie groß der Aufwand im schlechtesten Fall sein wird, wie groß
im Mittel und wie groß im besten Fall. In all diesen Fällen haben wir einen fest gegebenen
Algorithmus betrachtet und den Umfang und andere Eigenschaften der Eingabe variert.
Zwei oder mehr Algorithmen haben wir verglichen, indem wir die algorithmusspezifischen
Einzelergebnisse miteinander veglichen haben. Die Frage nach dem Mindestaufwand, den
alle Algorithmen, die das Problem lösen, verursachen haben wir bisher nicht gestellt.
Das wollen wir jetzt tun. Um vom Mindestaufwand für die Lösung eines Problems sprechen
zu können, müssen wir festlegen,
1. welches Problem gelöst werden soll,
2. welche Algorithmen betrachtet werden und
3. was es heißt, ein Algorithmus verursacht mindestens diesen Aufwand.
Um 1. und 2. exakt zu behandeln, müßte man das das Problem als formale Sprache und
die Lösungalgorithmen als Turingmaschinen ansehen, ähnlich wie in Unterabschnitt 6.2.4,
Seite 210. Die Turingmaschinen wären allerdings deterministisch. Zum Glück kommt man
manchmal auch ohne so viel formalen Aufwand weiter. Im Falle des Sortierens betrachten
wir alle Algorithmen, die zum Finden des Ergebnisses nur Schlüsselvergleiche und sonst
keine Eigenschaften der zu sortierenden Menge benutzen.
Es bleibt Punkt 3. Dazu legen wir ein Komplexitätsmaß
√ a(n) für das Problem fest. n ist
der Umfang der Eingabe. Zum Beispiel a(n) = O(n2 n) oder a(n) = O(n ln n). Es sei
A die betrachtete Menge von Algorithmen, die das Problem P lösen. Wir sagen: a(n) ist
eine untere Schranke für die Algorithmen aus A, wenn
1. WOC(A) = Ω(a(n)) für alle A ∈ A (untere Schranke im schlechtesten Fall)
338
KAPITEL 11. SORTIEREN
2. AVC(A) = Ω(a(n)) für alle A ∈ A (untere Schranke im Mittel)
Das bedeutet, keiner der Algorithmen aus A ist im schlechtesten Fall (im Mittel) in der
Größenordnun besser als a(n).
Die untere Schranke läßt sich vielleicht verbessern. D. h. es kann ein a0 (n) > a(n) geben,
das auch noch untere Schranke ist. Wie man leicht sieht, ist das jedoch nicht möglich,
wenn es einen Algorithmus A ∈ A gibt, der die untere Schranke annimmt: WOC(A) =
O(a(n)) bzw. AVC(A) = O(a(n)). Wir haben dann nicht nur eine untere Schranke für
die Komplexität, sondern die untere Grenze. Wir haben dann die Mindetskomplexität
(minimal complexity).
Zurück zur Mindestkomplexität beim Sortieren. A ist wie gesagt die Menge der Sortieralgorithmen, die ausschließlich Vergleiche zwischen den Sortierwerten anwenden, um das
sortierte Ergebnis zu gewinnen. Wir wollen zeigen, daß a(n) := n ln n sowohl eine untere
Schranke im schlechtesten Fall als auch eine untere Schranke im Mittel für die betrachteten Sortieralgorithmen ist. Da wir schon wissen, daß eine Reihe von Sortierverfahren der
betrachteten Klasse im schlechtesten Fall durch O(n ln n) nach oben begrenzt sind, würde
das bedeuten, daß wir die Mindeskomplexität dieser Sortieralgorithmen betimmt hätten.
Dazu wählen wir einen Algorithmus A0 aus und lassen ihn eine Folge von n Eingabewerten
sortieren. Wir bilden den zum Sortiervorgang gehörenden Entscheidungsbaum (decision
tree) Was das bedeutet, macht man sich am besten anhand eines Beipiels klar. Der zu
untersuchende Algorithmus sei das in Unterabschnitt 6.1.2, Seite 178, behandelte Mischsortieren. Eingabe sie eine beliebige Folge a1 , a2 , . . . , an−1 , an von n Sortierwerten. Wir
wollen voraussetzen, daß die Werte paarweise verschieden sind und können dann o.B.d.A.
annehmen, daß sie eine Permutation der natürlichen Zahlen {1, 2, . . . , n−1, n} bilden. Der
Algorithmus zerlegt diese Eingabe in einem ersten Schritt zunächst in Teillisten, die dann
im zweiten Schritt gemischt werden. Im ersten Schritt gibt es keine Vergleiche. Nach dem
Muster von Abbildung 6.2, Seite 180, ergibt sich die in Abbildung 11.7 gezeigte Aufteilung bei n = 10. Man sieht, es werden bei fester Sortieranzahl n = 10 stets die Vergleiche
a1 : a9 , a3 : a7 , a2 : a10 und a4 : a8 . ausgeführt. Beim Mischen von sortierten Teillisten der
Länge größer als 1 hängt die Zahl der durchgeführten Vergleiche von den Teillisten ab. Es
seien z. B. (a02 , a06 , a010 ) und (a04 , a08 ) sortierte Teillisten der Stufe 2, die zu eine sortierten
Teilliste der Stufe 1 zusammengemischt werden sollen. Dann hängt die Zahl der ausgeführten Vergleiche von den Eingabelisten ab und ist z. B. für (1, 3, 4), (5, 8) anders als für
(1, 4, 8), (3, 5). Bei einer Eingabe vom festen Umfang n läuft also das Mischsortieren so
ab, daß in einem gegebenen Binärbaum von Vergleichen ein Weg von der Wurzel zu einem
Blatt durchlaufen wird und das Blatt das sortierte Ergebnis ist. Die Zahl der Vergleiche
längs dieses Weges, also die Länge des Weges, gibt den Aufwand an, der zur Gewinnung
des Ergebnisses nötig ist.
Wir gehen nun davon aus, daß das ein solcher Binärbaum von Vergleichen, ein solcher Entscheidungbsaum, zu jedem der Algorithmen der von uns betrachteten Klasse A existiert
11.5. LINEARES SORTIEREN
339
...u
a1 a2 a3 a4 a5 a6 a7 a8 a9 a...10
........ ..............
......
........
.
.......
.......
.......
........
.......
.
.
.
.
.
.
.
.
........
.......
........
.......
........
.
.
.
.
.
.
.
.......
........
1 3 5 7 9...........................
.....
.
.
.
.
.
.....
...
.....
.....
.....
.....
.....
.....
.
.
.....
.
...
.....
.
.
.
.
.....
...
.
.
.
.....
.
...
.....
.
.
.
.
.....
...
.
.
.
.....
.
...
.
.....
.
.
.
....
...
.
.
.
.
1 5 9 .............
3 7 ..............
.
.
.
.
...
.. ....
..
.
.
.
.
.
.
...
...
...
..
...
...
..
...
.
.
...
.
.
.
.
...
...
...
..
...
...
..
...
...
.
.
.
.
...
.
...
..
.
.
.
...
.
.
...
.
..
.
...
.
.
.
.
.
...
.
..
...
.
.
.
.
.
...
.
...
.
...
.
.
.
1 9.............
5 .
3 .
7 .
.. .....
.
...
...
...
...
...
...
...
..
...
.
.
...
.
.
...
...
.
...
..
.
...
.
...
a a a a a u
a a a u
a a u
a1 u
a a u
a u
a u
a9 u
........
........
.......
........
........
........
.......
........
........
.......
........
........
........
......
2 4 6 8 10....................
.
...
.
.
.
....
.
....
....
...
....
....
....
.
.
.
....
....
.
.
....
...
....
.
.
....
...
.
.
....
..
.
.
.
....
..
.
.
.
....
.....
.
2 6 10 ..........
4 8 ..............
...
...
.
.
...
...
...
...
...
...
..
...
...
...
..
..
.
.
.
.
...
...
.
..
.
.
.
...
...
.
.
.
.
...
.
.
.
...
..
...
...
.
.
.
.
...
...
.
..
.
.
.
.
...
...
.
..
.
.
.
...
...
.
.
.
....
..
.
2 10 ..... .....
6
4 .
8 .
.. .....
.
.
...
...
...
...
...
...
...
...
..
.
.
...
.
.
...
...
.
...
..
....
a a a a a
a a a
a u a a
a2 u
u
u
a u
u
a a u
a u
a u
a10 u
Abbildung 11.7: Entscheidungsbaum für Mischsortieren
und die Sortierung steuert. Da jede der n! Permutiationen der Eingabefolge als Ergebnis
herauskommen kann, haben wir in dem Baum n! Blätter. Nach Proposition 9.1, Seite 262,
gilt hmin (n!)≥ ld (n!). Aus der Stirlingschen
Formel (Gleichung 6.16. Seite 201) ergibt
n n
= Ω(n ln n), Wir wollen das Ergebnis als Satz
sich n! > ne . D. h. hhmin = Ω ln ne
formulieren.
Satz 11.1 Für Algorithmen, die zum Sortieren ausschließlich Vergleichsoperationen benutzen, ist die Mindestskomlexität im schlechtesten Fall und im mittleren Fall Ω(n ln n).
11.5
Lineares Sortieren
Wir wollen von linearem Sortieren (linear sort) sprechen, wenn ein Sortierverfahren vorliegt mit W OC = O(n) oder wenigstens AV C = O(n). Nach dem Ergebnis des Abschnitts
11.4 müssen solche Verfahren, wenn sie überhaupt existieren, Eigenschaften ausnutzen,
die über den Vergleich von Sortierwerten hinausgehen. Ja, es gibt solche Verfahren und
eines soll im folgenden dargestellt werden.
Zählsortieren: Beim Zählsortieren (counting sort) müssen die Sortierwerte natürliche
Zahlen aus einem bekannten Intervall [0, K] sein. Wir nehmen an, daß die zu sortierenden
Werte in einer Reihung A der Länge n gegeben sind. Der Grundgedanke ist nun, den
Sortierwert aus A als Index in einer Hilfsreihung B der Länge K + 1 aufzufassen. Die
Elemente von B werden mit 0 vorbesetzt und jedes Mal, wenn in A der Wert s auftritt,
wird B[s] um 1 erhöht. Danach kann man B aufsteigend durchlaufen und den aktuellen
340
KAPITEL 11. SORTIEREN
Index s so oft ausgeben oder in eine Ergebnisreihung C schreiben, wie die Häufigkeit in
B[s] angibt. D. h. für B(s) = 0 wird nichts ausgegeben. Es ist einfach, dafür ein Programm
anzugeben.
In vielen Fällen ist damit das Problem jedoch noch nicht gelöst. Die numerischen Sortierwerte sind häufig Felder von Sätzen mit weiteren Datenfeldern. Angaben dazu stehen oft
in der Reihung A, ohne Teil des Sortierwerts zu sein. Im einfachsten Fall ist die Zusatzinformation eine Satzadresse. Das geht beim Übergang zur Datei B und Rückgewinnung
des Sortierwertes aus dem Index s verloren, insbesondere ist das bei mehrfachen Sortierwerten der Fall. Deren Reihefolge in A ist nicht mehr erkennbar. Man braucht also ein
Verfahren, mit dem man nach der Bestimmung der Sortierposition bei der Bildung des
Ergebnisses auf die ursprüngliche Stelle in A zugreifen kann.
Das gelingt mit dem folgenden eleganten Kunstgriff. Man hält in B[j] nicht nur fest, wie
häufig der Wert j in A auftritt, sondern auch, wie viele Werte kleiner als j es in A gibt.
Danach kann man, indem man A rückwärts durchläuft die auftretenden Werte an der
richtigen Stelle einer Ergebnisreihung C einfügen.
In Tabelle 11.11 ist ein Programm COUNTSORT für das Zählsortieren zu sehen. Die ganzzah-
'
$
&
%
void
COUNTSORT(void)
// A, B und C sind ganzzahlige Reihungen; sie sind globale Variable
// N ist Länge von A und von C; K ist die Länge von B
// N und K sind auch globale Variable
1 int
j, actpos;
2 for (j = 0; j < K; j++) B[j] = 0; // Vorbesetzung
3 for (j = 0; j < N; j++) B[A[j]]++; // Berechnung der Häufigkeit des Sortierwertes
4 for (j = 1; j < K; j++) B[j] = B[j] + B[j-1];
5 for (j = 0; j < K; j++) B[j]−−; // höchste Position in C für Wert A[j]
6 for (j = N-1; j>=0; j−−) // A rückwärts durchlaufen
7
{ actpos = B[A[j]];
8
C[actpos] = A[j];
9
B[A[j]]−−; // Eine Position näher zum Anfang der Reihung
10
}
11 return
Tabelle 11.11: COUNTSORT
ligen Reihungen A, B und C sind globale parameter für das Programm. Die zu sortierende
Zahkenfolge steht in Reihung A; das Ergebnis in Reihung C. Das folgende Beispiel soll
die Arbeitsweise von COUNTSORT erläutern.
11.5. LINEARES SORTIEREN
341
Beispiel 11.3 Abbildung 11.8 besteht aus den Teilen I, II, III und IV. Sie zeigen ver0
1
2
3
4
5
6
7
8
9 10 11
A: 3 7 7 6 1 3 4 8 1 3 3 1
0
1
2
3
4
5
6
7
8
9
B: -1 2 2 6 7 7 8 10 11 11
0
1
2
3
5
5
6
7
8
9 10 11
0
1
2
3
4
5
6
7
8
9 10 11
A: 3 7 7 6 1 3 4 8 1 3 3 1
0
1
2
3
4
5
6
7
8
9
B: -1 1 2 4 7 7 8 10 11 11
0
1
2
3
5
5
6
7
8
9 10 11
C: * * * * * * * * * * * *
C: * * 1 * * 3 3 * * * * *
I. Zustand nach den Vorbereitungen
II. Zustand nach Durchlauf j = 9
0
1
2
3
4
5
6
7
8
9 10 11
A: 3 7 7 6 1 3 4 8 1 3 3 1
0
1
2
3
4
5
6
7
8
9
B: -1 -1 2 3 6 7 7 10 10 11
0
1
2
3
4
5
6
7
8
9 10 11
0
1
2
3
4
5
6
7
8
9 10 11
A: 3 7 7 6 1 3 4 8 1 3 3 1
0
1
2
3
4
5
6
7
8
9
B: -1 -1 2 3 6 7 7 8 10 11
0
1
2
3
5
5
6
7
8
9 10 11
C: 1 1 1 * 3 3 3 4 6 * * 8
C: 1 1 1 3 3 3 3 4 6 7 7 8
III. Zustand nach Durchlauf j = 3
IV. Endzustand
Abbildung 11.8: Ablauf von COUNTSORT
schiedene Zustände beim Ablauf von COUNTSORT. Es soll die Zahlenfolge 3,7,7,6,1,3,4,
8,1,3,3,1 sortiert werden. Diese Folge steht in allen vier Teilen in der Reihung A und
wird dort nicht verändert. Teil I zeigt den Zustand nach der Vorbereitung der Reihung B.
Als Intervall für die möglichen Sortierwerte wählen wir [0, 9], so daß B die Länge 10 hat. B
wird in Zeile 2 zunächst mit 0 vorbesetzt. Dann wird in Zeile 3 für jeden Sortierwert A[j]
seine Häufigkeit eingetragen und in Zeile 4 alle Anzahlen kleinerer Sortierwerte addiert.
Schließlich wird in Zeile 5 duch Verminderung dieses Wertes um 1 die Ausgabepostion in
der Reihung C berechnet, in die das letzte Vorkommen des Sortierwertes A[j] zu speichern ist. Man erkennt in B: Die Sortierwerte 0, 2, 5 und 9 treten nicht auf. Die Sterne
in Reihung C besagen, daß dort noch nichts eingetragen ist.
Danach wird die Reihung A rückwärts durchlaufen (Zeile 6). Über den Sortierwert A[j]
wird in B die Zielposition des Sortierwertes in C gefunden und der Wert dort eingetragen
(Zeilen 6 und 8). Damit ein eventuelles weiteres Vorkommen des Sortierwertes an die
richtige Stelle in C gebracht werden kann, vermindert man schließlich die Zielposition
um 1 (Zeile 9).
Teil II der Abbildung zeigt den Zustand nach Bearbeitung der ersten drei Sortierwerte
in Rückwärtsreihenfolge, das sind die Werte 1,3,3. Sie sind an die richtigen Stellen der
Reihung C gebracht worden. Die übrigen Stellen in C wurden noch nicht angesprochen.
342
KAPITEL 11. SORTIEREN
In Reihung B wurden die entsprechenden Positionen einmal um 1 und einmal um 2
vermindert.
Den Zustand nach Bearbeitung von 9 Sortierwerten zeigt Teil III der Abbildung. Es
fehlen noch zwei Siebenen und eine Drei. Die entsprechenden Positionen in der Reihung
C sind noch mit Sternen besetzt. Reihung B zeigt die Zielpositionen der noch fehlenden
Sortierwerte.
In Teil IV der Abbildung ist der Endzustand zu sehen. In Reihung C sind die Sortierwerte
in aufsteigender Reihenfolge gespeichert. Reihung B ist zu entnehmen, daß im Vergleich
zu Bild I drei Einsen, vier Dreien, eine Vier, eine Sechs, zwei Siebenen und eine Acht
umgespeichert wurden.
2
Komplexität von COUNTSORT: Für jede Folge von Sortierwerten wird die Reihung A
zweimal und die Reihung B drei Mal durchlaufen. Es ist also W OC = BEC = AV C =
Θ(n + K). Wenn K = O(n), d. h.die Zahl der Lückenstellen zwischen den Sortierwerten
wächst ungefähr so wie die Zahl der Sortierwerte einschließlich der Mehrfachzählungen,
dann haben wir W OC = BEC = AV C = O(n).
Anmerkung 11.4 Ein Sortierverfahren heißt stabil (stable), wenn mehrfach auftretende
Sortierwerte im Ergebnis in der gleichen Reihenfolge erscheinen wie bei der Eingabe.
COUNTSORT ist stabil. Siehe auch Aufgabe 11.4.
2
Aufgaben
Aufgabe 11.1 Geben Sie einen Weg an, wie man mit Rot-Schwarz-Bäumen auch Sortierwertmengen mit mehrfachen Schlüsseln effizient sortieren kann.
Aufgabe 11.2 Der Algorithmus von Tabelle 11.7 ist so abzuändern, daß VERSICKERN nur
für die Knoten aufgerufen wird, die Nachfolger haben.
Aufgabe 11.3 Zeigen Sie, daß eine Halde auch mit Aufwand O(n) (statt mit O(n ln(n))
aufgebaut werden kann.
Aufgabe 11.4 Geben Sie für die in diesem Buch behandelten Sortierverfahren an, ob sie
stabil sind oder nicht.
Literatur
Sortieralgorithmen sind eines der am besten bekannten und erforschten Gebiete der Algorithmik. Alle Bücher über Algorithmen und Datenstrukturen widmen ihm (im allgemeinen
umfangreichen) Platz, so zum Beispiel Cormen/Leiserson/Rivest [CormLR1990], Brass
11.5. LINEARES SORTIEREN
343
[Bras2008], Kruse/Tondo/Leung [KrusTL1997]i, Ottmann/Widmayer [OttmW1996] und
Aho/Ullman [AhoU1995]. Knuth [Knut1998a] ist das Standardwerk über Sortieralgorithmen.
Die in Abschnitt 11.3 eingeführten Halden werden auch binäre Halden genannt. Es gibt
auch Binomialhalden und Fibonacci-Halden. Diese können im Gegensatz zu jenen mit Aufwand O(ln(n)) verschmolzen werden werden. Auf sie wird in diesem Buch nicht eingegangen. Siehe Cormen/Leiserson/Rivest [CormLR1990], Ottmann/Widmayer [OttmW1996]
oder Brass [Bras2008]. Für das Suchne nach einem Schlüsselwert sind alle Halden ungeeignet.
Auch Datenstrukturen für Mengen und Relationen werden in diesem Buch nicht behandelt. Es wird auf Ottmann/Widmayer [OttmW1996] und Cormen/Leiserson/Rivest
[CormLR1990] verwiesen.
344
KAPITEL 11. SORTIEREN
Teil IV
Allgemeine Graphen
345
Kapitel 12
Grundlagen allgemeiner Graphen
12.1
Definitionen und Beispiele
Wir wollen einen allgemeinen Graphbegriff einführen. Das soll zunächst intuitiv und auf
graphischem Wege erfolgen. Dazu betrachten wir Abbildung 12.1 Wir sehen dort sechs
Zeichnungen von Graphen: G1, G2, G3, G4, G5, G6. Die kleinen Kreise mit den Bezeichnungen A, B, C, D, E, F sind die Knoten. Zwei Knoten können durch eine Linie
verbunden sein, einige Linien verbinden einen Knoten mit sich selbst. Auch die Linien
tragen Bezeichnungen. Im Graphen G1 gibt es die Linien e, f, g, h, i, j, k. Zum Graphen
G6 gehören die Linien e, f, g, h, i, j, k, l, m, n. Einige Linien tragen auf einer Seite
Pfeilspitzen. Das soll eine Richtung angeben. Es ist erlaubt, daß zwei verschiedene Linien
die gleichen Knoten verbinden. Z. B. sind die Knoten A und B in Graph G4 durch die
Linien n und h verbunden. Das gleiche gilt auch für die Graphen G5 und G6. Damit sind
alle Elemente, die in allgemeinen Graphen auftreten, eingeführt und wir können zu einer
formalen Definition übergehen.
Es seien V und L disjunkte endliche Mengen und V 6= ∅.
Definition 12.1 Eine Graphinzidenzstruktur (graph incidence structure) über V und L
ist eine Abbildung
ϕ : L 7→ P(V ) mit 1 ≤ |ϕ(l)| ≤ 2.1
Eine Graphinzidenzstruktur soll auch einfach Inzidenzstruktur genannt werden. V ist die
Menge der Knoten (vertex), L die Menge der Linien (line), ϕ ist die Inzidenzabbildung
(incidence mapping). Für Knoten werden wir auch Punkt (point) sagen2 . Die Knoten
ϕ(l) heißen Inzidenzpunkte (incidence points) der Linie l. Eine Linie, die nur einen Inzidenzpunkt hat, heißt Schlinge (loop). Ein Knoten, der mit keiner Linie inzidiert, heißt
P(M ) ist die Potenzmenge der Menge M . |M | bezeichnet die Anzahl Elemente der Menge M (siehe
Anhang A, Seite 611).
2
Im Englischen ist auch die Bezeichnung node üblich, im Deutschen werden Knoten oft Ecken genannt.
Wir wollen diese Bezeichnungen jedoch nicht benutzen.
1
347
348
KAPITEL 12. GRUNDLAGEN ALLGEMEINER GRAPHEN
D
..........................
.........
.....
...
..... ......
...
...
.....
..
.
.
.
...
.
.
....
..
...
... ....
...
... ........
.
.
.
...
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.....
.
... ......
..
.
.
.
.
.....
.
.
.
.
.
.
.
.
...
......................
..
...
...
...
....
..
...
...
...
....
....... .................
...
.......
....
....
.......
.
... ......
.
.
.
.
...
.
...... .
............................
..... .........
...
...
..
....
.
....
...
..
...
.... ............
...
................
.......... ........
...
.
.......
.......
....
....
....... ..............
.
...
...
......
...
...
.
....
..
...
.
.
...
.....
....
.
....... .................
........................
.......
.
.
.
.
.
.
....
........... ........
.... ............
....
..
...
................
G1: Schlichter Graph
G2: Ungerichteter Graph mit Schlingen
...............
...
..
....
.
.... .....
............
.
.
...
.
.
...
.
.
.
................
...
....
...
...
....
.... .....
...
.
.
.
.
.
.
.
.
.
.. ...........
.......
....
.......
...
.
.
.
.
.
.
.
.
.
....... .
...........................
..... .........
...
...
.
.....
....
..
...
...
..
................
............................
.......
.......
....... ............
..........
...
..
.
...
..
.......................
.
.
.
.
.
.
.....
.
.
.
.
.
.
....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.... ............
....
..
.... .....
...........
B
h
A
e
f
k
C
j
g
i
h
E
m
A
e
f
k
C
j
g
i
l
A
e
f
k
C
j
g
i
E
F
D
G5: Digraph mit mehrfachen
gerichteten Schlingen
i
F
e
f
k
C
j
i
E
l
m
F
D
G3: Allgemeiner Graph mit Schlingen
h
h
g
D
n
j
B
A
F
B
C
E
.....
.....................
...... ........
.......
....
..
..
....
...
.........................
..
...
..
........
.
.....
.....
.................
.
.
.
.
.
...
.
...
...
.
...
.
.
.
.
.
.
.
...
...
.
..............
.
.
..
.
.
.
.
.
.
.
.
.
.
.
.
....................
..
.
.
.
.
.
.
.
....
.
...
..
....
....
...
...
........... .............................
...
....
...
....... .............
...
.. .....
.......
..
..
...
..... ...
.......
.
.
.
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.........
....
...
.
..
..
...
........................................
...
.....
..
.
.
.
.
.
.
.
.
.
......
...
...
........................
.........................
...............
........
...
........
...
........ ...........
........... ...
...
..
.
...
....
..
..
...
.....
....... ................
.
.
...
.
.
.
.
.....
.
.
.
.
.
.
.
.
....
.....
...... .................
..
.....
.... .....
...........
n
E
....
.................
.......... ..............
...
...
....
...
..
.
...........................
...
....... .......
... .
.......
.
.
.
.
.
.
.
.
.
.
......
..
............... .........
...
......
...
..
....
.
.
.... .. ..........
...
.
.
.
.
.
.
.
.
.
.
.... ............................
..
..
...
.
.
.
.
.
.
..
...
..
...
.
.....
..
....
.................... ............................
..
..........
....
....
.......... ...............
........ ......
...
...
....... .
...
.
... ...
.
.
.
.
.
.
.
.
.
.
.
.
..
...
......
... .. .
. ... .
.
..... ................
..... .........
...
..
................................................
.....
.
.
....
...
.
.
.
..... ....
.
.... .....
.
.
.
.
.
.
......................
.......................
...........
.
........
.
........ ..
....
............ .............
...
...
..................
..
.
....
...
...
...... .
........................
.......
.
.......
.
.
.
.
.
..
. ...
.......... ...........
.... ......................
.....
.
... .....
............
e
k
l
D
.....................
....
......
...........
....
.... .....
...
..
....
...
..
...
...
.
...
.
.
.
...
................
.
.
.
.
...
.
.
.
..
.
.
.
.
.
.
.
.
..
..
.............
........................
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
..
...
.
..
...
.
.
.
.
.
...
.
.
.
.
.
.
.
.
.
.
.
.
...
..
...
.
...............
..
...
....
....
..
.........
...
....
....
...
....... .............
.. .........
.......
...
...
... ............
.......
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
.
. ....
.. ..........
...
........................................
..
...
....
.
...
..
...
.... .....
....... ..............
...
...........
....
........
...
........
...
.
...
.......
........
.
.
...
.
........ ................
. .. .
....
...
....
.
...
.
...
..
...
.
.
.
...
....
.
.
..
.....
.
.
.......
....... ...............
.
.
....
.
..................
.
.
.
.....
.
.
.
.
.
.
.
.
.
.
.......... .........
.... ....................
.....
.
.... .....
...........
h
A
f
g
F
B
m
B
l
m
G4: Ungerichteter Graph mit Mehrfachkanten
...................
..................
.......
....
...
..
....
...
.
.............................
...
...
..
.
.
.......
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
........
................ ...........
...
...
.
.
.
.
.
..
......
.. . ..........
.
.
...
.
.
.
.
.
.
.
.
.
.
.... ............................
..
.
.
.
.
....
.
.
.
..
.
...
.
....
.
....
..
...
...................... ............................
...
..........
...
....
.......... ...........
....... ......
...
...
....... .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
..
...
......
....... .
....
..... ................
..... .........
...
..
...
..
.
.....
.
....
...
....
.
...
.
.
.... .....
.
.
.
.
.
.
......................
............... ........
...........
.
.
.
.......
........
....
....... .............
...
........
...
...
..
.....
...
.
.......................
.
.
....... .
.
.
.
.....
.
........
.
.
.
.
.
..........
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
... ......................
...
..
....
.... .....
..........
B
n
h
A
i
f
k
C
j
g
e
E
l
m
F
D
G6: Allgemeiner Graph mit Mehrfachlinien
Abbildung 12.1: Beispiele für Graphen
isoliert (isolated). Zwei verschiedene Knoten, die mit der gleichen Linie inzidieren, heißen benachbart (Nachbar, neighbor, adjazent, adjacent). Ein Knoten ist Nachbar von sich
selbst, wenn er mit einer Schlinge inzidiert. Auch zwei verschiedene Linien, die einen
12.1. DEFINITIONEN UND BEISPIELE
349
gemeinsamen Inzidenzpunkt haben, werden benachbart genannt. Die Linien l1 und l2
(l1 6= l2 ) sind Mehrfachlinien (multiple lines), wenn ϕ(l1 ) = ϕ(l2 ) gilt.
Wir wollen nun die Definition erweitern und auch die Pfeilspitzen, d. h. die Richtung
berücksichtigen. Die Linien, die eine Richtung aufweisen, werden gesondert gekenzeichnet.
Die Richtung wird durch Angabe eines ersten und eines zweiten Knotens ausgedrückt.
Definition 12.2 Eine allgemeine Graphstruktur (general graph structure) auf V und L
ist definiert durch
• eine Inzidenzabbildung ϕ, die auf V und L eine Graphinzidenzstruktur erzeugt,
• eine Teilmenge A ⊆ L und
• eine Abbildung
ψ : A 7→ V × V mit ψ(a) = (u, v) ⇒ u, v ∈ ϕ(a).
A ist die Menge der Bögen (arc). ψ ist die Orientierungsabbildung (orientation mapping)
für Bögen. Ist ψ(a) = (u, v), dann heißt u Startpunkt (head) des Bogens a und v Zielpunkt
(tail) des Bogens a. Es ist zweckmäßig, den nichtorientierten Linien einer allgemeinen
Graphstruktur einen eigenen Namen zu geben: Die Menge E := L \ A soll Menge der
Kanten (edge) heißen. Für Kanten, aber nicht für Bögen, werden die Inzidenzpunkte auch
Endpunkte genannt. Eine allgemeine Graphstruktur auf V und L wollen wir auch
G(V, E, A, ϕ, ψ)
schreiben und einen allgemeinen Graphen (general graph) nennen. Dabei ist L implizit
durch E ∪ A gegeben. Man beachte, daß ϕ stets für ganz L definiert ist. Ein allgemeiner
Graph mit A = ∅ heißt ungerichteter Graph (undirected Graph), ein allgemeiner Graph
mit E = ∅, d. h. A = L, heißt gerichteter Graph (directed graph). Statt gerichteter Graph
werden wir meistens Digraph (digraph) sagen.
Eine Kante, die eine Schlinge ist, heißt ungerichtete Schlinge (undirected loop), ein Bogen,
der eine Schlinge ist, heißt gerichtete Schlinge (directed loop). Ein Bogen ist Ausgangsbogen (outgoing arc) für seinen Startpunkt und Eingangsbogen (incoming arc) für seinen
Zielpunkt. Der Startpunkt eines Bogens ist Vorgänger (predecessor) des Zielpunktes, der
Zielpunkt Nachfolger (successor) des Startpunktes. Die Kanten e1 und e2 heißen Mehrfachkanten (multiple edge), wenn sie Mehrfachlinien sind. Die Bögen a1 und a2 heißen Mehrfachbögen (multiple arc), wenn sie Mehrfachlinien sind und ψ(a1 ) = ψ(a2 ) gilt. Abbildung
12.2 zeigt drei Mehrfachlinien, von denen keine Mehrfachkante oder Mehrfachbogen ist.
Ungerichtete Graphen mit Mehrfachkanten werden auch Multigraphen (multigraph) genannt. Ein ungerichteter Graph ohne Mehrfachkanten und ohne Schlingen heißt schlicht
(einfach, simple, strict).
350
KAPITEL 12. GRUNDLAGEN ALLGEMEINER GRAPHEN
..................................................................................................................
...
.. .
..
..
....
.....................................................................................
.
.. .
..
.. ........
...
......................................................................................................................
Abbildung 12.2: Mehrfachlinien
Die Anzahl |V | der Knoten eines Graphen heißt seine Ordnung (order) und wird oft mit
n bezeichnet. Die Anzahl |L| der Linien eines allgemeinen Graphen wird i. a. mit m
bezeichnet. Für schlichte Graphen gilt offenbar
m≤
n · (n − 1)
2
(12.1)
Ein schlichter Graph heißt vollständig (complete), wenn je zwei Knoten benachbart sind.
Ein vollständiger schlichter Graph der Ordnung n wird mit Kn bezeichnet. Für ihn gilt
. In Erweiterung der Bezeichnung wollen wir auch einen allgemeinen Graphen
m = n·(n−1)
2
vollständig nennen, wenn je zwei Knoten benachbart sind.
Abbildung 12.1 soll die obigen Definitionen erläutern. Für Graph G3 gilt z. B. VC =
{A, B, C, D, E, F }, LC = {e, f, g, h, i, j, k, l, m}, EC = {e, f, g, j, k, l} und AC = {h, i, m}
sowie

e 7→ {A, C}




f 7→ {B, C}




g 7→ {C, D}





 h 7→ (B, A)
 h 7→ {A, B}
i 7→ {D, F } und ψ :
i 7→ (F, D)
ϕ :



j 7→ {C, F }
m 7→ (A, A)




k 7→ {C, E}




l 7→ {E}



m 7→ {A}
Die Graphen G2 und G3 haben die gleiche Inzidenzstruktur, ebenso die Graphen G4 und
G5. G5 und G6 haben nicht die gleiche Inzidenzstruktur. Es gilt zwar LG5 = LG6 , aber
ϕG5 (e) = {A, C} =
6 {D, F } = ϕG6 (e).
Bezeichnung: Aus Zweckmäßigkeitsgründen werden zusätzlich die folgenden Bezeichnungen eingeführt: Es sei G ein allgemeiner Graph. Mit G(V ) wird die Menge seiner Knoten
bezeichnet, mit G(L), G(E), G(A) die Mengen seiner Linien, Kanten, Bögen.
Dichte und dünne Graphen
Es ist oft wichtig zu wissen, wie die Anzahl Linien in einer Klasse von Graphen mit der
Anzahl Knoten wächst. Da bei gegebener Knotenzahl die Linienzahl beliebig wachsen
kann, wenn Mehrfachlinien zugelassen sind, bezieht sich die folgende Definition nur auf
Graphen ohne Mehrfachkanten und ohne Mehrfachbögen.
12.2. ORIENTIERUNGSKLASSEN UND UNTERGRAPHEN
351
Definition 12.3 Eine Klasse endlicher Graphen heißt dicht (dense), wenn |E| + |A| =
Ω(|V |2 ). Sie heißt dünn (sparse), wenn |E| + |A| = O(|V |) . 3
In Erweiterung der Definition nennt man einen Graph dicht bzw. dünn, wenn er zu einer entsprechenden Klasse gehört. Vollständige Graphen sind demnach dicht. Bei dünnen Graphen wächst die Anzahl Kanten/Bögen höchstens linear mit der Anzahl Knoten.
|−1)
+ 2|V |, also
Schätzt man |E| und |A| getrennt ab, so erhält man |E| + |A| ≤ 3|V |(|V
2
2
|E| + |A| = Θ(V ). Bei dichten Graphen wächst die Anzahl Linien quadratisch mit der
Anzahl Knoten.
12.2
Orientierungsklassen und Untergraphen
Orientierungsklassen
Alle allgemeinen Graphen mit gemeinsamer Inzidenzstruktur bilden eine Orientierungsklasse (orientation class). Zwei Graphen der gleichen Orientierungsklasse heißen Umorientierungen (reorientation) voneinander. Bei der Überführung eines allgemeinen Graphen
in eine Umorientierung werden Bögen zu Kanten, Kanten zu Bögen und es finden Richtungsänderungen auf Bögen statt. Eine Umorientierung, bei der nur Kanten zu Bögen
werden, ist eine Orientierung (orientation). Man spricht von einer vollständigen Orientierung (complete orientation), wenn die Menge der Kanten leer ist, also wenn sich ein
Digraph ergibt. Eine Umorientierung, bei der nur Bögen zu Kanten werden, heißt Desorientierung (disorientation). Die vollständige Desorientierung (complete disorientation) ist
der eindeutig bestimmte ungerichtete Graph, der zur Inzidenzstruktur gehört. In Abbildung 12.1 sind also die Graphen G2 und G3 Umorientierungen voneinander, ebenso die
Graphen G4 und G5. Insbesondere ist Graph G3 eine (nicht vollständige) Orientierung
von Graph G2. Graph G5 ist eine (vollständige) Orientierung von Graph G4. Graph G6
gehört zu einer anderen Orientierungsklasse.
Im folgenden werden wir sehen, daß einige wichtige Eigenschaften allgemeiner Graphen nur
von der Inzidenzstruktur und andere auch von der Orientierung abhängen. Aus Gründen,
die Abschnitt 14.1 klar werden, nennen wir Eigenschaften, die nur von der Inzidenzstruktur abhängen, a-Eigenschaften und solche, bei denen auch die Orientierung wesentlich ist,
f-Eigenschaften.
Untergraphen
Aus einem allgemeinen Graphen gewinnt man Untergraphen, wenn man Linien wegläßt.
Man kann auch Knoten entfernen, muß dann aber auch alle Linien löschen, die mit den
entfernten Knoten inzidieren.
3
Zur Bedeutung von Ω, O und Θ siehe Seite Unterabschnitt 6.1.5, Seite 195.
352
KAPITEL 12. GRUNDLAGEN ALLGEMEINER GRAPHEN
Definition 12.4 Ein allgemeiner Graph H = (VH , EH , AH , ϕH , ψH ) heißt Untergraph
(subgraph) eines allgemeinen Graphen G(V, E, A, ϕ, ψ), wenn VH ⊆ V , EH ⊆ E und
AH ⊆ A sowie ϕH (l) = ϕ(l) und ψH (l) = ψ(l) für alle l, für die ϕH (l) bzw. ψH (l)
definiert ist.
H ist ein echter Untergraph (eigentlicher Untergraph, proper subgraph), wenn VH ⊂ V
oder EH ⊂ E oder AH ⊂ A. Ist VH = V , so nennt man H einen aufspannenden Untergraphen (erzeugenden Untergraphen, spanning subgraph) von G. Ein maximaler, d. h nicht
mehr erweiterbarer, vollständiger Untergraph eines allgemeinen Graphen wird als Clique
(clique) bezeichnet.
Ein Untergraph eines Untergraphen ist auch Untergraph des Ausgangsgraphen. Die Untergraphbeziehung ist eine partielle Ordnung (siehe Seite 616) auf der Menge der Untergraphen eines Graphen. Sie soll mit den gleichen Symbolen wie die die Teilmengenbeziehung
bezeichnet werden: ⊆, ⊇.
Wichtig sind Untergraphen eines Graphen, die durch Teilmengen von Knoten oder Teilmengen von Linien erzeugt werden, bzw. die man durch Entfernen von Knoten oder Linien
gewinnt. Dazu die folgende Definition.
Definition 12.5 Es sei G(V, E, A, ϕ, ψ) ein allgemeiner Graph.
1. Eine Teilmenge von Knoten ∅ =
6 U ⊆ V erzeugt (generate) den Untergraph G[U] :=
(U, EU , AU , ϕU , ψU ). Dabei gilt EU := {l ∈ E | ϕ(l) ⊆ U} und AU := {l ∈ A | ϕ(l) ⊆
U}. ϕU ist die Einschränkung von ϕ auf EU ∪ AU . ψU ist die Einschränkung von ψ
auf AU .
2. Eine Menge X 6= ∅ aus Kanten und Bögen von G erzeugt den Untergraph G[X] :=
(VX , EX , AX , ϕX , ψX ). Dabei gilt VX := ϕ(X), EX := E ∩ X und AX := A ∩ X. ϕX
ist die Einschränkung von ϕ auf EX ∪ AX . ψX ist die Einschränkung von ψ auf AX .
3. Die Löschung (deletion) einer Teilmenge U 6= V von Knoten ergibt den durch V \ U
erzeugten Untergraphen.
4. Die Löschung einer Teilmenge X von Kanten und Bögen ergibt den durch Y :=
(E ∪ A) \ X erzeugten Untergraphen.
Man sagt auch, ein Untergraph werde durch eine Menge von Knoten bzw. Kanten induziert
(induce) oder generiert. Die Knotenmenge eines aufspannenden Untergraphen erzeugt den
Ausgangsgraph. Für jeden Untergraphen H = (V 0 , E 0 , A0 , ϕ0 , ψ)) von G(V, E, A, ϕ, ψ) gilt
G[E 0 ∪ A0 ] ⊆ H ⊆ G[V 0 ]
Anmerkung 12.1 Ist x ein Knoten des allgeneinen Graphen G, so wird mit G − x der
durch V \ {x} erzeugte Untergraph von G bezeichnet.
Ist l eine Linie des allgeneinen Graphen G, so wird mit G − l der durch L \ {l} erzeugte
Untergraph von G bezeichnet.
2
12.3. BIPARTITE GRAPHEN
353
Anmerkung 12.2 1. Es werden in der Literatur auch Graphstrukturen betrachtet, bei
denen eine Linie mit mehr als zwei Knoten inzidiert. Man spricht dann von Hyperkanten
(hyperedge) und nennt die Graphen Hypergraphen (hypergraph). Siehe hierzu das Buch
von Berge [Berg1976].
2. Einige Autoren lassen auch einen Nullgraph (null graph oder empty graph), der weder Knoten noch Kanten hat, zu. Einige Betrachtungen werden dann glatter. Da mit
Nullgraphen aber mehr Sonderfälle eingeführt als vermieden werden, wollen wir sie nicht
benutzen. Der einfachste Graph ist also ein Graph, der nur aus einem isolierten Knoten
besteht.
3. Man kann die Definitionen 12.1 und 12.2 so erweitern, daß für V und/oder L auch
unendliche Mengen zugelassen werden. Man spricht dann von unendlichen Graphen. In
diesem Buch werden ausschließlich endliche Graphen betrachtet.
12.3
Bipartite Graphen
Ein allgemeiner Graph, dessen Knotenmenge so in zwei Teilmengen V1 und V2 partitioniert
werden kann, daß jede Linie mit einem Knoten in V1 und mit einem Knoten in V2 inzidiert,
heißt bipartiter Graph (paarer Graph, bipartite graph). Ein schlichter bipartiter Graph mit
|V1 | = i und |V2 | = j wird mit Ki,j bezeichnet.
Die Partitionsmengen sind i. a. nicht eindeutig bestimmt, z. B. kann man isolierte Knoten
beliebig auf die beiden Partitionsmengen verteilen. In einem bipartiten Graphen gibt es
keine Schlingen, Mehrfachlinien können auftreten. Ein schlichter bipartiter Graph mit
n = |V1 | + |V2 | Knoten hat höchstens |V1 | · |V2| Kanten.
Um festzustellen, ob ein allgemeiner Graph bipartit ist, und Partitionsmengen V1 und V2
zu finden, startet man mit leeren Mengen V1 und V2 und einem beliebigen Knoten, den
man in V1 einfügt. Alle Nachbarn dieses Knoten müssen in V2 liegen, deren Nachbarn
wieder in V1 usw.
Sollten Knoten übrigbleiben, die auf diese Art und Weise nicht erreicht werden, so beginnt
man mit einem von ihnen wieder von vorn. In Algorithmus BIPART, Tabelle 12.1, ist das
Verfahren detailliert angegeben. Es ist zu erkennen, daß der Algorithmus genau dann
ohne Fehlermeldung endet, wenn jeder Knoten markiert, d. h. einer der Mengen V1 und
V2 zugeordnet ist und alle seine Nachbarn in der anderen Menge sind (wieso?). Daher gilt
die folgenden Proposition.
Proposition 12.1 Der Algorithmus BIPART liefert eine gültige Knotenpartition (V1 , V2 ),
falls der Graph bipartit ist, und eine Meldung, falls er es nicht ist.
Der Algorithmus benutzt Tiefensuche. Diese wird in Kapitel 15, Abschnitt 15.1 ausführlich
behandelt.
354
'
KAPITEL 12. GRUNDLAGEN ALLGEMEINER GRAPHEN
BIPART
/* Anfangs sind alle Knoten und Linien unmarkiert.
/* Die Knotenmengen V1 und V2 sind leer.
/* Für einen markierten Knoten v ist elem(v)
/* diejenige der Mengen V1 und V2 , die v enthält.
/* nelem(v) ist die Menge, die v nicht enthält.
1 for (alle Knoten v aus V ) /* Knotenliste */
2
{ if (v unmarkiert)
3
{ markiere v;
4
v in V1 einfügen;
5
for (alle Linien l, die mit v inzidieren)
6
{ if (l nicht markiert)
7
{ markiere l;
8
BP T (otherend(l, v), v);
9
} } } }
BPT(u, v)
1 if (u nicht markiert)
2
{ u in nelem(v) einfügen; /* v ∈
/ nelem(v) */
3
u markieren;
4
for (alle Linien l, die mit u inzidieren)
5
{ if (l nicht markiert)
6
{ l markieren;
7
BP T (otherend(l, u), u);
8
} } }
9 else
10
{ if (u in elem(v)) /* v ∈ elem(v) */
11
{ printf(“Der Graph ist nicht bipartit\n”);
12
exit(0);
13
} };
14 return;
&
$
*/
*/
*/
*/
*/
%
Tabelle 12.1: Algorithmus zum Testen auf Bipartitheit und Bestimmung einer gültigen
Knotenpartition
Anmerkung 12.3 Im Algorithmus wird die Funktion otherend(l, v) aufgerufen. Diese
wird auch in Algorithmen, die an späterer Stelle behandelt werden, benutzt. Die Funktion liefert zu einer Linie und einem Knoten, der mit der Linie inzidiert, den anderen
12.4. DER GRAD EINES KNOTENS
355
Inzidenzpunkt der Linie.
2
Anmerkung 12.4 Bipartitheit ist eine Periodizitätseigenschaft in Graphen. Zu Einzelheiten siehe Kapitel 17, Seite 465.
12.4
Der Grad eines Knotens
Es sei G(V, E, A, ϕ, ψ) ein allgemeiner Graph. Die Anzahl Linien, mit denen ein Knoten
v, inzidiert wird Gesamtgrad (total degree) des Knotens genannt und mit td(v) bezeichnet. Dabei werden Schlingen doppelt gezählt. Die Anzahl Kanten, mit denen ein Knoten v
inzidiert, heißt Grad (degree, valence) des Knoten und wird mit dg(v) bezeichnet. Ungerichtete Schlingen werden doppelt gezählt. Die Anzahl der von ihm ausgehenden Bögen
heißt sein Ausgangsgrad (outdegree) und wird mit od(v) bezeichnet. Die Anzahl der bei
ihm ankommenden Bögen heißt Eingangsgrad (indegree) und wird mit id(v) bezeichnet.
Schlingen werden nur einmal gezählt, treten aber für den Knoten als Eingangs- und als
Ausgangsbogen auf. Es gilt offenbar td(v) = d(v) + od(v) + id(v).
Ein schlichter Graph heißt k-regulär (k-regular), wenn alle Knoten den gleichen Grad k
haben. Abbildung 12.3 zeigt einen 3-regulären schlichten Graphen.
...............
................
....
...
...
..
.....
....
.
.... .......
...... .........
.
.. .. .
.
.
.
.
.
.
.
.
.
.
.
.
....
.. ....... ......
...
.
.
.
.
.
.
.
....
...
.
...
....
....
....
....
...
....
....
....
....
.... .
...
...
.............
................
....................
....................
.
.
.
.
.
.
...
.
.
....
.
...
..
..
..
.
.
.
.
....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.....................
.
.
... ...
...
...
.
.
.
.
..
.
...
....
.
.
.
.
.
.
.
.
.
.
.
.
................
................
..................
.. ......
.
.
.
...
....
.
.
....
.
.
.
....
..
...
....
.
...
...
.
.
.
.
.
....
....
..
..
.
...
...
.
.
.
.
.
.
.
...
....
..
...
....
.
.... .. ......
.
....
.
...
......................
......... ..........
...
...
..
...
...
.....
..
.....
..
...
.
.
...
.
...
.
.
.... ...
..
...............
...
...........
...
..
...
...
..
.
.
....
.
.
.
................................................................................................................................................................................................................................................................
s
w
u
r
y
v
x
t
Abbildung 12.3: 3-regulärer Graph
Da jede Linie zwei Inzidenzpunkte hat bzw. als Schlinge mit 2 zum Gesamtgrad ihres
Knotens beiträgt, gilt für einen allgemeinen Graphen G(V, E, A, ϕ, ψ)
X
2|E ∪ A| =
td(v).
(12.2)
v∈V
Daraus folgt
Hilfssatz 12.1 (Handschlaglemma) Für jeden allgemeinen Graphen ist die Anzahl
der Knoten ungeraden Gesamtgrades gerade.
Als einfache Anwendung von Gleichung 12.2 soll der folgende Satz bewiesen werden.
2
Satz 12.1 Jeder schlichte Graph mit |V | = n Knoten und |E| = m > b n4 c Kanten
enthält ein Dreieck.
356
KAPITEL 12. GRUNDLAGEN ALLGEMEINER GRAPHEN
Beweis: Es sei ein schlichter, dreiecksfreier Graph gegeben. e sei eine Kante, x und y
ihre Endpunkte. Wegen der Dreiecksfreiheit gibt es keine gemeinsamen Nachbarn von x
und y. Also gilt dg(x) + dg(y) ≤ n. Das gilt für alle Kanten und der Summand dg(x) tritt
in dg(x) Ungleichungen (Kanten) auf. Zur Erläuterung siehe Abbildung 12.4. Summation
über alle Kanten und dann Zusammenfassung nach Knoten ergibt
X
(dg(v))2 ≤ m · n
(12.3)
v∈V
Zusammen mit Gleichung 12.2 und der Ungleichung von Cauchy-Schwarz-Bunjakowski
...........
... ......
....
..
......................................
.
.
.
.
.
.
.
........
.
........
........
........
........
.
.
.
.
.
.
.
........
..
........
........
........
........
.
.
.
.
.
........
.
.
.....
.
........
.
.
.
.
.
.
........
.....
.
.
.
.
........
.
.
.
.....
........
.
.
.
.
.
.
.
........
.....
.
.
.
........
.
.
.
.
........
......
.
.
.
.
.
.
........
.
.....
.
.
........
.
.
.
.
.
........
.....
.
.
.
.
.
.
........
.
......
.
........
.
.
.
.
.
.
........
.....
.
.
.
.
........
.
.
.
.....
........
.
.
.
.
.
.
.
........ .............
...............................
................
................
................
...
.........
.
.
.
.
.
.
.
.
.
.
..
...
..
.................................................................................................................................
..
.
....
....
.
.....
.
... 3 ..
.. 1 ..
... 2....
.... 1......
..... 2.....
.
.
.
....... ......
..............
..............
.......... ....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
..
.
....
.
..
.
.
.
.
.
.
.
.
.
.
.
...
..
..
.
...
....
.
.
....
.
.
.
..
....
..
...
....
....
....
...
....
...
....
....
....
...
....
...
...
...
....
....
...
.
. 2
.
.
.
1
3
.
.
.
.
.
....
..
....
....
....
....
....
...
...
...
...
....
....
....
...
...
....
....
....
...
...
.
.
.
.
.
.
.
.
.
.
....
.
...
...
....
....
....
...
....
...
....
...
....
....
...
.. .......
.... .... .......
.
.
.....................
.
.
.
.
.
.
.
.
.
...
.
..... ........
....
.........................................................................................................................................................................................................................
...
...
...
..
................
................
z
x
x
e
y
x
y
e
e
e
x
y
Abbildung 12.4: Dreiecksfreier Graph
(siehe Abschnitt C.2, Seite 631) folgt daraus
X
X
(2m)2 = (
1 · dg(v))2 ≤ n ·
(dg(v))2 ≤ n2 · m,
v∈V
also m ≤
n2
,
4
2
d. h. m ≤ b n4 c.
v∈V
2
Anmerkung 12.5 Satz 12.1 wurde ursprünglich von Mantel [Mant1907] als Aufgabe
formuliert. Siehe auch Bollobás [Boll1998]. Die Ungleichung des Satzes ist scharf, d. h. zu
2
jeder Knotenzahl n ≥ 1 gibt es einen dreiecksfreien Graphen mit m = b n4 c Kanten (siehe
Aufgabe 12.4).
2
12.5
Anmerkung: Ersetzung von Kanten durch Bögen
Man könnte versucht sein, Kanten durch zwei Bögen in entgegengesetzter Richtung zu
ersetzen. Manche Autoren tun das auch. Das ist jedoch falsch. Wie die nachfolgenden
12.6. GLEICHHEIT UND ISOMORPHIE VON GRAPHEN*
357
Kapitel zeigen, ist in den meisten Fällen eine Kante etwas anderes als zwei Bögen, z. B.
bei der Definition von Brücken.
Es gibt jedoch Fälle, wo mit zwei Bögen in entgegengesetzter Richtung besser gearbeitet
werden kann, als mit Kanten. Das ist dann so zu verstehen, daß man einen allgemeinen
Graphen durch einen anderen ersetzt und für die zu untersuchende Fragestellung – und
nur dür diese – die Ergebnisse im zweiten Graphen auf den ersten übertragen werden
können. Ein Beispiel hierfür findet sich im Kapitel 19 „Kürzeste Wege in Netzwerken“.
12.6
Gleichheit und Isomorphie von Graphen*
Die allgemeinen Graphen G(V, E, A, ϕ, ψ) und G0 = (V 0 , E 0 , A0 , ϕ0 , ψ 0 ) sind gleich (equal),
wenn V = V 0 , E = E 0 , A = A0 , ϕ = ϕ0 und ψ = ψ 0 .
Definition 12.6 Es seien G(V, E, A, ϕ, ψ) und G0 = (V 0 , E 0 , A0 , ϕ0 , ψ 0 ) allgemeine Graphen. (Iv , Il ) ist ein Inzidenzisomorphismus (incidence isomorphism) von G auf G0 , wenn
gilt
1. Iv : V 7→ V 0 und Il : E ∪ A 7→ E 0 ∪ A0 sind Bijektionen.
2. für alle l ∈ E ∪ A gilt : Ist ϕ(l) = {v1 , v2 }, so folgt ϕ0 (Il (l)) = {Iv (v1 ), Iv (v2 )}
(Iv , Il ) ist ein Isomorphismus (Graphisomorphismus, graph isomorphism) von G auf G0 ,
wenn gilt
1. (Iv , Il ) ist ein Inzidenzisomorphismus von G auf G0 .
2. Il (A) = A0
3. Für alle a ∈ A gilt: Ist ψ(a) = (v1 , v2 ) so folgt ψ 0 (Il (a)) = (Iv (v1 ), Iv (v2 ))
Man nennt G und G0 inzidenzisomorph (incidence isomorphic), wenn es einen Inzidenzisomorphismus von G auf G0 gibt. Man nennt G und G0 isomorph (isomorphic), wenn
es einen Isomorphismus von G auf G0 gibt. Ist G = G0 , so spricht man von einem Inzidenzautomorphismus (incidence automorphism) bzw. von einem Graphautomorphismus
(graph automorphism).
Ist (Iv , Il ) ein Isomorphismus von G auf G0 , so ist (Iv−1 , Il−1 ) ein Isomorphismus von G0
auf G (Aufgabe 12.5).
Ist ein Isomorphismus von G auf G0 bekannt, so kennt man alle Isomorphismen von G auf
G0 , wenn die Automorphismen von G bekannt sind. Es seien die Graphen G(V, E, A, ϕ, ψ)
und G0 = (V 0 , E 0 , A0 , ϕ0 , ψ 0 ) gegeben und g = (Iv , Il ) ein Isomorphismus von G auf G0 .
Siehe Abbildung 12.5. f = (fv , fl ) bilde die Knoten und Linien G bijektiv auf die von G0
ab und es sei fl (A) = A0 . Dann gilt der folgende Satz.
358
KAPITEL 12. GRUNDLAGEN ALLGEMEINER GRAPHEN
G
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...... ..
.........
...
G
h
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
.....
..... ..
.
...................
.
f
g
..
.....................................................................................................................
.
G0
Abbildung 12.5: Zusammenhang zwischen Graphisomorphismen und Graphautomorphismen
Satz 12.2 Ist g ein Graphisomorphismus von G auf G0 , so ist f genau dann auch ein
Graphisomorphismus von G auf G0 , wenn h := g −1 ◦ f ein Graphautomorphismus auf G
ist.
Beweis: Siehe Aufgabe 12.6.
2
Graphisomorphismen und Graphautomorphismen sind ein gutes Beispiel für die Zusammenhänge, die generell für Isomorphismen und Automorphismen gelten, zum Beispiel
bei algebraischen Strukturen. Siehe hierzu van der Waerden4 [Waer1960], Paragraph 12.
Vergleiche auch Bourbaki5 [Bour1970],[Bour1970a].
Anmerkung 12.6 Sind zwei Graphen G(V, E, A, ϕ, ψ) und G0 = (V 0 , E 0 , A0 , ϕ0 , ψ 0 ) gegeben, so können sie nur isomorph sein, wenn |V | = |V 0 |, |E| = |E 0 | und |A| = |A0 |. Ist das
van der Waerden, Bartel Leendert, ∗2.Februar 1902 Amsterdam, †12.Januar 1996. Niederländischer
Mathematiker. War unter anderem Professor in Leipzig und Zürich. Wurde durch sein zweibändiges
Lehrbuch über Algebra [Waer1959], [Waer1960] bekannt, das entscheidend zum Durchbruch der modernen
Algebra beitrug. War auch in anderen Gebieten der Mathematik, z. B der Statistik, aktiv. Gilt als einer
der letzten Generalisten der Mathematik.
5
Bourbaki, Nicolas, ∗1934 Paris, † ?. Pseudonym einer Gruppe französischer Mathematiker. wechselnder Zusammensetzung, benannt nach einem französischen General der zweiten Hälfte des 19. Jahrhunderts.. Die Gruppe hatte sich zum Ziel gesetzt, die wesentlichen Teile der reinen Mathematik in axiomatischer, einheitlicher und moderner Form als Lehrbuch darzustellen. Bis 1998 sind 10 Bände erschienen:
1. Mengenlehre, 2. Algebra, 3. Allgemeine Topologie, 4. Funktionen einer reellen Veränderliochen, 5.
Topologische Vektorräume, 6. Integration, 7. Kommutative Algebra, 8. Lie-Gruppen und Lie-Algebren,
9.Differetial- und analytische Mannigfaltigkeiten, 10. Spektraltheorie. Der anfängliche Schwung der Veröffentlichungem hielt bis in die zweite Hälfte des vorigen Jahrhunderts an. Zur Zeit ist ungewiß, ob Bourbaki
noch lebt. Die Entwicklung der Mathematik, insbesondere die Darstellung, ist von Bourbaki nachhaltig
beeinflußt worden. Eine ausführliche Bschreibung des Wirkens von Bourbaki ist in Mashaal [Mash2006]
zu finden.
4
12.6. GLEICHHEIT UND ISOMORPHIE VON GRAPHEN*
359
der Fall, so kann man im Prinzip Isomorphie feststellen, indem man Definition 12.6 für alle
Bijektionen überprüft. Das ist natürlich von exponentieller Komplexität und nur für sehr
kleine Graphen praktisch durchführbar. Bis jetzt (2006) hat man noch kein effizientes,
d. h. polynomielles Verfahren zur Feststellung der Isomorphie zweier Graphen gefunden.
Allerdings ist es bis jetzt auch nicht gelungen, die NP-Vollständigkeit des Problems zu
zeigen. Möglicherweise gehört das Graphisomorphie-Problem zu einer zwischen P und NP
liegenden Komplexitätsklasse. Vergleiche auch Unterabschnitt 6.2.4, speziell Seite 215. Zu
mehr Einzelheiten siehe Garey/Johnson [GareJ1979], Kapitel 7. Siehe auch Abschnitt 6.3,
Seite 221.
2
Aufgaben
Siehe auch Aufgabe 13.2.
Aufgabe 12.1 Es sei G(V, E, A, ϕ, ψ) ein allgemeiner Graph. Was bedeutet:
• ϕ ist surjektiv? ϕ ist injektiv? ϕ ist bijektiv?
• ϕ ist surjektiv, aber nicht injektiv? ϕ ist injektiv, aber nicht surjektiv?
Es ist die Surjektivität, Injektivität und Bijektivität von ψ zu untersuchen. Wie hängen
diese Eigenschaften mit ϕ zusammen? Es werde A 6= ∅ angenommen.
Aufgabe 12.2 Es sei (V, L, ϕ) eine Graphinzidenzstruktur. Wieviele allgemeine Graphen
enthält die zugehörige Orientierungsklasse?
Aufgabe 12.3 Sind die Partitionsklassen eines schlichten regulären bipartiten Graphen
stets gleich groß? ([Dies2000], S.29)
Aufgabe 12.4 Zeigen Sie, daß es zu jedem n ≥ 1 einen dreiecksfreien schlichten Graphen
2
mit n Knoten und m = b n4 c Kanten gibt und daß dieser bis auf Isomorphie eindeutig
bestimmt ist.
Aufgabe 12.5 Es sei (Iv , Il ) ein Isomorphismus von G(V, E, A, ϕ, ψ) auf G0 (V 0 , E 0 , A0 , ϕ0 , ψ 0 ).
Zeigen Sie, daß (Iv−1 , Il−1 ) ein Isomorphismus von G0 auf G ist.
Aufgabe 12.6 Man beweise Satz 12.2.
360
KAPITEL 12. GRUNDLAGEN ALLGEMEINER GRAPHEN
Literatur
Die Begriffe „Graphinzidenzstruktur“, „allgemeiner Graph“, „Orientierungsklasse“ sind in
der Literatur über Graphen nicht oder nur implizit vorhanden. Sie werden hier neu eingeführt. Die üblichen Grundbegriffe werden von allen Büchern über Graphentheorie dargestellt. Als Beispiele seien Diestel [Dies2000], Bollobás [Boll1998], Chartrand/Oellermann
[CharO1993], Balakrishnan [Bala1997] und Harary [Hara1969] genannt. Einen ungewöhnlichen Zugang zur Graphentheorie, nämlich über das Vier-Farben-Problem, wählt Aigner
[Aign1984]. Eine gut lesbare Einführung in die Graphen- und Netzwerktheorie, wie wie
sie in Teil IV dieses Buches behandeln, ist Büsing [Busi2010]. Zu nennen ist auch Tittman
[Titt2011].
Für weiterführende Literatur zu Graphisomorphie siehe Leeuwen [Leeu1990], Garey/Johnson
[GareJ1979] und Köbler/Schöning/Torán [KoblST1993].
Geschichtliches: Moderne Graphentheorie ist als Zweig der Diskreten Mathematik aufzufassen. Sie ist im Vergleich zu anderen Teilen der Mathematik recht jung. Vorläufer sind
Probleme wie die, einen Eulerweg oder einen Hamiltonweg zu finden oder auch eine Landkarte mit vier Farbe zu färben. Sie wurden zunächst so ähnlich wie Denksportaufgaben
angesehen. Den Übergang zur modernen Graphentheorie markiert Königs 6 Buch „Theorie
der endlichen und unendlichen Graphen“ [Koni1990], das auch heute noch sehr lesenswert
ist. Von den weiteren Gründern der modernen Graphentheorie sollen insbesondere Whitney 7 und Tutte 8 genannt werden.
König, Dénes ∗21.September 1884 in Budapest, †19.Oktober 1944 ebenda. Ungarischer Graphentheoretiker. Satz von König über unendliche Graphen. Professor an der Technischen Universität Budapest.
Um der Judenverfolgung zu entgehen, beging er Selbstmord.
7
Whitney, Hassler ∗23. März 1907 in New York City, †10. Mai 1989 in Dents Blanches, Schweiz.
Amerikanischer Mathematiker. Havard University, später Institute of Advanced Study, Princeton. War
auf dem Gebiet der Differentialtopologie erfolgreich. Davor war er als eine Folge der Arbeit an seiner
Dissertation eine Zeitlang auf dem Gebiet der Graphentheorie tätig, wo er in rascher Folge wichtige und
grundlegende Ergebnisse erzielte. Frustriert über die Hartnäckigkeit des Vierfarben-Problems wandte er
sich endgültig von der Graphentheorie ab.
8
Tutte, William Thomas ∗14.Mai 1917 in Newmarket, Suffolk, England, †2. Mai 2002 in Kitchener,
Ontario, Canada. Britisch-kanadischer Mathematiker. Sohn eines Gärtners. Studierte in England erst
Chemie, dann Matematik. Während des 2. Weltkriegs gelang ihm ein spektakulärer Erfolg bei der Entzifferung militärischer deutscher Verschlüsselungen. Übersiedelte 1962 nach Kanada. Professor an der
University of Toronto und später an der University of Waterloo, Ontario. Tutte leistete eine Vielzahl
von bedeutenden Beiträgen zu Graphentheorie und Kombinatorik. Er galt über 3 Jahrzehnte als der führende Kopf der Graphentheorie. Er hat nur wenige Lehrbücher verfaßt. Zu nennen sind [Tutt1966] und
[Tutt1984].
6
Kapitel 13
Darstellungen von Graphen
Darstellungen (representation) von Graphen werden aus verschiedenen Gründen gebraucht.
Es ist wichtig, einzelne Graphen explizit als Beispiele (insbesondere auch Gegenbeispiele)
angegeben zu können. Außerdem muß man einzelne Graphen explizit zur Bearbeitung
durch Menschen, häufiger jedoch durch Rechner, angeben können. Schließlich helfen Darstellungen bei der allgemeinen Untersuchung von Graphen, indem man Eigenschaften der
Graphen auf Eigenschaften ihrer Darstellungen zurückführt.
Bei der Darstellung von Graphen gehen wir davon aus, daß es für jeden Knoten ein eindeutiges Identifikationsmerkmal, einen Knotennamen (vertex name) gibt. Häufig sind die
Knotennamen linear geordnet. Außerdem mag es weitere Knotenattribute geben, die für
die Bearbeitung im Graphen wichtig sind und deshalb auch in der Darstellung berücksichtigt werden. Kanten und Bögen werden eindeutig durch Liniennamen (line name)
identifiziert. Bei Graphen ohne Mehrfachkanten kann eine Kante auch durch Angabe der
Endpunkte bestimmt werden. Entsprechend ist bei Graphen ohne Mehrfachbögen ein Bogen durch Angabe des geordneten Paares (Startpunkt, Zielpunkt) festgelegt. Kanten und
Bögen können auch weitere Attribute (z. B. Gewichte) aufweisen.
13.1
Graphische Darstellung
Bei einer graphischen Darstellung (sic!) eines Graphen (graphical representation) werden
die Knoten als Kreise oder Punkte in der Ebene dargestellt. Kanten werden als Linien,
meistens Geradenstücke, zwischen den Endpunkten gezeichnet. Bögen werden durch Linien mit Pfeilspitzen dargestellt. Die Abbildungen 12.1, Seite 348, und 12.3, Seite 355,
zeigen Beispiele.
Ein und derselbe Graph kann sehr unterschiedliche Darstellungen als Zeichnung haben.
Andererseits können die graphischen Darstellungen verschiedener Graphen sehr ähnlich
aussehen. In Abbildung 13.1 sind die Graphen G und H gleich, die Graphen H und I
ungleich. Letzteres mag auf den ersten Blick etwas verwundern, da nach dem Entfernen
361
362
KAPITEL 13. DARSTELLUNGEN VON GRAPHEN
......
..... .......
.....
...
......
. ....
.. ........ ........ .......
.
......
...
...
...
...
..
....
...
...
..
...
..
.... .........................
..
..
... ... ...
.......
...
.... ... ...
.
..
.. ......................... ......... ...
...... ..
..
.
.
..
..... ...
....
..
.. .. .
...
.. .. ..
..
.. ... ..
... .............
.. .. ..
... .... ....
.......
.. .... ... ....
.
.
.
.
. .
... ...... ......
.
... .. ..
........
.... ... ....
..
....
..
...
.. ... ...
....
.... ... ...
....
. ..
..
............ .. . .
... ...... ..... ..... .... ....
...
...
.. ...
....
..... ......
... ..
...
........
..
......
.....
..
...
.
.
..
....
....
...
....
... ................ .......
... ......
... ...
........
.....
.
...
................
C
B
A
E
D
Graph G
.......
..... .....
...............
...........
... .....
... .....
..
....
........................................
.
.... ..............
..... .......
..
........... ... ..............
................. ............ ...............
... .....
........
. ...............
.
....
.
.
...... ..............
...
.......
... .....
...
...
.... ....................... ......
.......
......
.
..
...
...
..
..........
.
..
.
...
.
.
....
....................
.
.
.
...
.
.
.
.
.
.
.
.
...
.
.
...
......
.
.
.... ...
. ......
.
.
.
.
.
...........................
.................
...
..
...
.
.
........................................
.....
..
...
..... ......
...............
........
A
B
C
E
D
Graph H
.......
..... ......
...................
................
..
...
.........................................
..
....
.
.
.
..
...
..... ....................
........ ... ............. ................................
........
............... ..
.. ....
........ ....
. ...............
....
.
.
...
.... .... ...
...
.... .....
...
.... ....................... ...... ......
.......
......
.
...
.
.
.
.
..
.. .....
.
.
..
.
...
.
.
....
.
..
....................
.
.
.
.
.
.
.
.
.
.
...
.
.
....
....
.
.
.
.
.
. ......
.
.
.
.
.
.
.
.
.... .......... .......
...............
. ......
...
... .....
...
........................................
.....
..
...
..... ......
................
........
A
C
B
E
D
Graph I
Abbildung 13.1: Die Graphen G und H sind gleich, die Graphen H und I ungleich
der Knotenbezeichnungen identische Zeichnungen übrigbleiben. Die beiden Graphen sind
isomorph. Siehe Abschnitt 12.6 und Aufgabe 13.2.
Graphische Darstellungen von Graphen sind, solange die Größe der Graphen nicht zu
Unübersichtlichkeit führt, für Menschen gut geeignet. Sie erlauben die Darstellung von
Mehrfachlinien und die Mischung von Kanten und Bögen. Auch Attribute von Knoten
und Linien können hineingeschrieben werden. Für Rechner sind graphische Darstellungen
ungeeignet.
13.2
Darstellung durch Matrizen
Sowohl bei den Matrixdarstellungen, die in diesem Unterabschnitt betrachtet werden,
als auch bei den in in Abschnitt 13.3 zu behandelnden Listendarstellungen können Kanten/Bögen implizit durch Adjazenzen (Knotennachbarschaften) oder explizit durch selbständige Objekte und Angabe ihrer Begrenzungsknoten (Inzidenzen) dargestellt werden.
Nur mit Inzidenzen können Mehrfachkanten/-bögen dargestellt werden.
Adjazenzmatrizen
Adjazenzmatrizen werden nur für ungerichtete Graphen ohne Mehrfachkanten und Digraphen ohne Mehrfachbögen definiert.
Adjazenzmatrizen für ungerichtete Graphen ohne Mehrfachkanten: Ein ungerichteter Graph ohne Mehrfachkanten ist völlig bestimmt, wenn man zu je zwei Knoten
weiß, ob sie benachbart sind. Das läßt sich durch eine quadratische Matrix (Auv )(u,v)∈V ×V
angeben, deren Zeilen und deren Spalten durch die Knoten indiziert sind und die an der
Matrixposition (u, v) eine 1 aufweist, wenn u und v benachbart sind, und andernfalls
dort den Wert 0 enthält. Eine solche Matrix wird Adjazenzmatrix (adjacency matrix) des
13.2. DARSTELLUNG DURCH MATRIZEN
363
Graphen genannt. Adjazenzmatrizen von ungerichteten Graphen ohne Mehrfachkanten
sind symmetrisch (Nachbarschaft ist symmetrisch). Schlichte Graphen enthalten Nullen
in der Diagonale (keine Schlingen). Ein Graph ist genau dann regulär, wenn alle Zeilen
(und alle Spalten) die gleiche Anzahl von Einsen enthalten. Tabelle 13.1 enthält links die
Adjazenzmatrix zu Graph A. der Abbildung 12.1, Seite 348, und rechts die zum Graphen
der Abbildung 12.3, Seite 355.
A
B
C
D
E
F
A
0
1
1
0
0
0
B
1
0
1
0
0
0
C D E
1 0 0
1 0 0
0 1 1
1 0 0
1 0 0
1 1 0
F
0
0
1
1
0
0
r
s
t
u
v
w
x
y
r
0
1
1
0
0
0
0
1
s
1
0
1
1
0
0
0
0
t u
1 0
1 1
0 1
1 0
0 1
0 0
0 0
0 0
v w x y
0 0 0 1
0 0 0 0
0 0 0 0
1 0 0 0
0 1 1 0
1 0 1 1
1 1 0 y
0 1 1 0
Tabelle 13.1: Beispiele für Adjazenzmatrizen
In Programmen wird man Adjazenzmatrizen meistens durch ein zweidimensionales Feld
(array) realisieren, eventuell unter Ausnutzung der Eigenschaften der Matrix in Dreiecksgestalt. Der Grad der Ausnutzung des Speichers wird durch das Verhältnis der Anzahl
Einsen der Matrix zur Gesamtanzahl von Matrixpositionen bestimmt. Es gilt
0≤
Anzahl Einsen
2|E| − |LP |
=
≤1
Anzahl Matrixpositionen
|V |2
Dabei ist LP die Menge der Schlingen.
Adjazenzmatrizen für Digraphen ohne Mehrfachbögen: Für jeden Bogen bestimmt
der Startpunkt die Zeile und der Zielpunkt die Spalte in der Adjazenzmatrix. An der
entsprechenden Position wird eine Eins eingetragen. Alle anderen Positionen werden Null
gesetzt. Die Adjazenzmatrix ist i. A. nicht symmetrisch. Die obigen Betrachtungen zu
Speicherung und Speicherausnutzung gelten analog für Adjazenzmatrizen von Digraphen.
Abbildung 13.2 zeigt einen Digraphen und seine Adjazenzmatrix.
Anmerkung 13.1 Adjazenzmatrizen sind zur Bearbeitung ungerichteter und gerichteter
Graphen ohne Mehrfachkanten bzw. Mehrfachbögen durch Rechner gut geeignet. Außerdem läßt sich ein Reihe von graphentheoretischen Resultaten und Graphalgorithmen recht
einfach durch Operationen mit Adjazenzmatrizen gewinnen. Für allgemeine Graphen, in
denen Kanten und Bögen gemischt vorkommen, sind Adjazenzmatrizen selbst dann nicht
geeignet, wenn keine Mehrfachkanten und keine Mehrfachbögen vorkommen. Es lassen
364
KAPITEL 13. DARSTELLUNGEN VON GRAPHEN
.....................................................................
.........................................
.....................
.....................
...............
...............
............
...........
..........
.
.
.
.
.
.
.
.
.
.........
......
.
.
.
.
.......
.
.
.
......
.....
.
.
.
.
.
.....
....
.
....
.
.
...
..
.
.
...
.....
..
..............
.
.
.
...
........
.
........
... ...........
...
.
.
.
.
.
.
.
.
.
.
.
...................
.................
.................
.................
.
.
.
.
.
.
.
....................
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
...
...
..
.
...
..
.
...
.
.
.
.
..
.
.
.
.
.
.
.
.
.
.
.
.
.
..
..
..
...
..
........ ...
........ ..
........ ...
......................................................
..
....
.
.
.
.
.
.
.
.
.
.
.
...
...... ....
1 ................................................... ....... 2 .................................................. ....... 3 ................................................... .......
...
......... .
...
.
.
.
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
......
......
....... ......
....... ......
....... ...... ...........
................
..................................
.
.
.........
.
.
.
.
.
.
.
.. .........
...........
......
.......
.
...
...
........
..
...
...
........
..
.......
..
..
...
........
...
..
...
.......
...
........
..
.
...
...
........
..
.
.......
.
.
....
..... ..
.
........
.
.
.
.
...
......
.......
..
.
.
........
.
.
.
..
........ ...........................
..
...
..........
..
..
.....
..
...
.....
...
...
4 .......
..
.
.
...
......... ........
.
.
.
.
.
.
.
.
.
.
.
.
...
........ .....
..
.... .
..
....
..
...
....
...
...
.
.
...
....
....
...
....
....
.....
....
......
...
.
.......
.
........
........ ...
......... .... ........ .............
...............
...
.............
.
....
5 ......
...
.
......
.
.
.
.
.
.
...........
11
9
a
1
v
2
v
3
4
v
5
6
v
8
7
v
10
b
a
v1
v2
v3
v4
v5
b
a
0
0
0
0
1
0
1
v1
1
0
0
0
0
0
0
v2
0
1
0
0
1
0
0
v3
0
0
1
1
0
0
0
v4
0
0
1
0
0
1
0
v5
1
0
0
0
0
0
0
b
0
0
0
1
0
0
0
Abbildung 13.2: Beispiel für Adjazenzmatrix
sich nämlich Kanten nicht von einem Paar Bögen in entgegengesetzter Richtung unterscheiden.
2
Anmerkung 13.2 Nur in Ausnahmefällen werden die Knotennamen im Programm direkt als Indizes zur Adjazenzmatrix genommen werden können. Man wird in aller Regel
im Programm durch eine geeignete Hilfstruktur, z. B. Binärbäume, Kapitel 9, oder Hashtabellen, Kapitel 10, eine effiziente Umwandlung von Knotennamen in natürliche Zahlen
bereitstellen und diese als Matrixindizes nehmen. Da es n! Möglichkeiten gibt, die n Knoten eines Graphen auf {1, 2, . . . , n} abzubilden, ergeben sich somit n! Darstellungen des
Graphen als n-stellige quadratische Matrix.
2
Inzidenzmatrizen
Wir wollen Inzidenzmatrizen nur für ungerichtete Graphen definieren. Ein ungerichteter
Graph ist völlig bestimmt, wenn man zu jedem Knoten alle Kanten kennt, mit denen
er inzidiert. Das läßt sich durch eine Rechtecksmatrix I(v,e) (v,e)∈V ×E festlegen, deren
Zeilen durch die Knoten und deren Spalten durch die Kanten indiziert sind und die an
der Matrixposition (v, e) eine 1 aufweist, wenn v Endpunkt von e ist, und anderenfalls
dort den Wert 0 enthält. Dabei wird E 6= ∅ angenommen1 . Eine solche Matrix wird
Inzidenzmatrix (incidence matrix) genannt.
Tabelle 13.2 zeigt die Inzidenzmatrix zu Graph A. in Abbildung 12.1, Seite 348.
1
Falls E leer ist, wird eine Pseudokante, die mit keinem Knoten inzidiert, benutzt.
13.3. DARSTELLUNG DURCH LISTEN
A
B
C
D
E
F
h
1
1
0
0
0
0
f k
0 0
1 0
1 1
0 0
0 1
0 0
365
e
1
0
1
0
0
0
j
0
0
1
0
0
1
g
0
0
1
1
0
0
i
0
0
0
1
0
1
Tabelle 13.2: Beispiel einer Inzidenzmatrix
Wie die Adjazenzmatrix kann man in Programmen auch die Inzidenzmatrix eines Graphen
durch ein zweidimensionales Feld realisieren. Für den Ausnutzungsgrad des Speichers gilt
2 · |E| − |LP |
2
Anzahl Einsen
=
≤
Anzahl Matrixpositionen
|V | · |E|
|V |
und geht mit wachsender Knotenzahl gegen Null. Ebenso wird man auch bei Inzidenzmatrizen im allgemeinen Umsetzungsmechanismen von Namen (Knotennamen und Liniennamen) in Matrixindizes bereitstellen und die Anordnung der Knoten (Zeilen) sowie die
Anordnung der Kanten (Spalten) berücksichtigen müssen (vergleiche Anmerkung 13.2).
Mehrfachkanten und Schlingen lassen sich durch Inzidenzmatrizen ausdrücken. Man kann
im Prinzip Inzidenzmatrizen auch für Digraphen definieren; das ist jedoch nicht zweckmäßig.
13.3
Darstellung durch Listen
Adjazenzlisten.
Zur Darstellung eines ungerichteten Graphen ohne Mehrfachkanten durch eine Adjazenzliste (adjacency list) benutzt man eine Liste aller Knoten, die Knotenliste (vertex list).
Zu jedem Knoten gibt es eine Unterliste von Verweisen auf seine Nachbarknoten. Abbildung 13.3 zeigt als Beispiel die Adjazenzliste zu Graph A in Abbildung 12.1, Seite 348.
Digraphen ohne Mehrfachbögen lassen sich darstellen, indem man zu jedem Knoten eine
Unterliste der Nachfolger und eine Unterliste der Vorgänger aufbaut. Abbildung 13.4 zeigt
eine Adjazenzlistendarstellung des Digraphen von Abbildung Abbildung 13.2, Seite 364.
Mit drei Unterlisten (Nachbarn, Nachfolger, Vorgänger) lassen sich auch allgemeine Graphen ohne Mehrfachkanten/Mehrfachbögen darstellen.
Ob der Knotenname und gegebenenfalls weitere zum Knoten gehörende Daten im Knoteneintrag direkt gehalten werden oder ob vom Eintrag ein Zeiger zu diesen Daten führt
(Vertretersätze), ist implementierungsabhängig.
366
KAPITEL 13. DARSTELLUNGEN VON GRAPHEN
...
...
B .........r ..r..................................... ........r ..r.................................. ........r r
..........
.........
..
...
...
...
...
..
..........
........
..
E .........r r
...
...
...
...
..
.........
.......
...
A
rr
..........
.......
...
C
..
...................................... ..
...
..
...
........
.........
..
C
...
...
A .........r ..r..................................... ........r ..r.................................. ........r r
.........
.........
..
...
...
...
...
..
..........
.......
...
C .........r r
...
...
...
...
..
..........
........
..
rr
C
rr
rr
rr
rr
..
..
..
..
..
....................................... .. .................................... .. .................................... .. .................................... .. .................................... ..
...
...
...
...
...
.
.
.
.
.
...
...
...
...
..
.
.
.
.
.
........
.........
.........
.........
..........
........
........
........
........
........
...
...
...
...
..
F .........r r
...
...
...
...
..
....... .
........
...
B
.........
........
..
F
rr
D
rr
A
E
B
..
..
...................................... .. ................................... ..
...
...
..
..
...
..
.
........
.........
.........
.........
..
..
C
D
.........
.........
..
.........
........
..
...
...
D r ..r..................................... ........r ..r.................................. ........r r
C
F
Abbildung 13.3: Adjazenzliste zu Graph A
Inzidenzlisten.
Die allgemeinste Form von Graphdarstellungen sind Inzidenzlisten (incidence list). Sie
sind die am besten geeignete Darstellung für allgemeine Graphen. Bei Inzidenzlisten gibt
es außer der Knotenliste auch eine Liste der Linien (Kanten/Bögen). Zu jedem Knoten
gibt es eine Unterliste der Linien, mit denen er inzidiert, und zu jeder Linie gibt es zwei
Verweise auf ihre Inzidenzpunkte. Außerdem gibt es eine Kennzeichnung der Linie als
Kante oder Bogen und in letzterem Fall eine Angabe, welcher Knoten Startknoten ist.
Abbildung 13.5 zeigt als Beispiel die Inzidenzliste zu Graph A in Abbildung 12.1, Seite
348.
Bis auf explizit genannte Ausnahmen sollen im folgenden stets Inzidenzlisten als Datenstruktur zur Darstellung von Graphen genommen werden. Aus diesem Grund wollen wir
eine konkrete Festlegung für diese Listen treffen und und ausführlicher auf sie eingehen.
Siehe hierzu Abbildung 13.6. Darin bedeuten fett gezeichnete Pfeile Verweise von einem
Satz auf eine Liste von Sätzen und Pfeile normaler Strichstärke Verweise von einem Satz
auf einen anderen. Es gibt vier Satztypen: GRAPH, VERTEX, EDGE, INC. Ein Satz vom
Typ GRAPH ist Ausgangspunkt der Darstellung. Er enthält zentrale Angaben wie z. B.
den Namen und den Typ des Graphen. Außerdem enthält er Verweise auf die Knotenliste
13.3. DARSTELLUNG DURCH LISTEN
Vorgängerliste
367
Nachfolgerliste
Knotenliste
• ◦....... .............................................. ◦ ◦....... ...............................................◦... a ◦....... ◦.................................................. ◦....... ◦............................................... ◦....... •
....
..
..........
.......
...
b
....
..
..........
......
...
v4
...
...
...
...
...
...
...
.
..........
........
..
....
..
..........
........
...
v1
....
..
..........
......
...
v5
• ◦....... ...............................................◦.. b ◦ ◦................................................ ◦....... •
..
..
...
..
....... ..
.......
...
v3
...
...
...
...
...
...
..
.........
........
..
...
..
....... ..
........
...
a
• ◦....... ................................................◦.. v1 ◦....... ◦................................................ ◦....... •
...
...
....... .
........
...
a
...
...
...
..
...
...
...
..
....... ..
.........
..
...
...
....... .
.........
..
v2
• ◦....... .............................................. ◦ ◦....... ..............................................◦.. v2 ◦....... ◦................................................ ◦....... ◦............................................. ◦....... •
...
...
.........
........
...
v4
...
...
.........
........
...
v1
...
...
...
...
...
...
...
.
.........
.........
..
...
...
.........
.........
..
v3
...
...
.........
.......
...
v4
• ◦....... ............................................... ◦ ◦....... ...............................................◦... v3 ◦....... ◦................................................ ◦....... ◦............................................. ◦....... •
...
...
..........
........
..
v3
...
...
..........
........
...
v2
...
...
...
...
..
...
...
..
..........
........
..
...
...
..........
.........
..
b
...
...
..........
......
...
v3
• ◦....... ............................................... ◦ ◦....... ...............................................◦.. v4 ◦....... ◦................................................ ◦....... ◦............................................. ◦....... •
....
..
.........
........
...
v2
....
..
.........
.......
...
v5
..
...
...
...
...
...
...
..
.........
.........
..
....
..
.........
........
...
a
....
..
.........
.......
...
v2
• ◦....... ...............................................◦... v5 • ◦.................................................. ◦....... •
....
..
..........
......
...
a
....
..
..........
........
...
v4
Abbildung 13.4: Adjazenliste eines Digraphen
(vertex list), auf die Kantenliste (edge list) und auf die Bogenliste (arc list). Die Knotenliste besteht aus Sätzen vom Typ VERTEX. Die Kantenliste und die Bogenliste bestehen
beide aus Sätzen vom Typ EDGE. Ein satzinterner Indikator gibt an, ob es sich um eine
ungerichtete Kante oder einen gerichteten Bogen handelt.
Von jedem Knotensatz gibt es drei Verweise auf Listen von Inzidenzsätzen (Satztyp INC).
Es gibt je eine Inzidenzliste (incidence list) für die Kanten, mit denen der Knoten inzidiert,
eine für die vom Knoten ausgehenden Bögen und eine für die im Knoten ankommenden
368
KAPITEL 13. DARSTELLUNGEN VON GRAPHEN
...
...
B .........r ..r..................................... ........r ..r.................................. ........r r
.........
......
...
...
...
...
...
..
..........
........
..
E .........r r
...
...
...
...
..
.........
.......
...
h
rr
...
...
...
...
..
..........
........
..
..........
......
....
F .........r r
...
...
...
...
..
....... .
........
...
rr
r r ....r
g
r r ....r
h
r r ....r
i
r r ....r
j
r r r
k
..
...
...
....
..
.
......... .........
......... .......
...
..
.........
.........
..
e
...
...
...
...
...
C .........r ..r..................................... .........r ..r.................................. .........r ..r.................................. .........r ..r.................................. .........r ..r.................................. .........r r
j
f
BC
...
...
A .........r ..r..................................... ........r ..r.................................. ........r r
h
r r ....r
...
.
...
...
...
..
.
.......... ..........
......... .......
...
..
k
.........
......
...
e
AC
f
..
...................................... ..
...
..
...
........
......
...
...
...
...
...
..
..........
.......
...
r r ....r
...
...
...
...
..
..
....... . ....... .
......... .......
...
..
..........
........
...
..........
........
...
g
rr
..
..
...................................... .. ................................... ..
...
...
..
..
...
..
.
........
.........
......
.........
...
..
..........
........
...
e
..........
.......
...
k
..........
.......
...
f
CD
...
...
...
...
..
..
......... .........
......... ........
...
..
AB
...
...
...
...
..
.
.......... .........
........ ........
...
...
j
i
DF
.........
......
...
.........
.........
..
..
....
....
..
..
.
......... .........
......... ......
...
..
...
...
D r ..r..................................... ........r ..r.................................. ........r r
g
i
CF
...
...
...
...
..
..
......... .........
......... ........
...
..
...
...
...
...
...
...
..........
........
..
...
...
...
...
...
...
..........
.........
..
...
...
...
...
...
...
..........
........
..
...
...
...
...
...
...
..........
........
..
...
...
...
...
...
...
..........
.........
..
...
...
...
...
...
...
.........
........
..
CE
Abbildung 13.5: Beispiel für eine Inzidenzliste
Bögen. Jeder Inzidenzsatz zeigt auf die zugeordnete Kante, bzw. den zugeordneten Bogen.
Sowohl die Liste der Kanten als auch die Liste der Bögen wird durch Sätze vom Typ EDGE
realisiert. In jedem Fall zeigt der Satz auf zwei (nicht notwendigerweise verschiedene)
Knoten. Bei Kanten sind das in irgendeiner Reihenfolge die beiden Endknoten. Bei Bögen
zeigt der erste Verweis auf den Start- und der zweite Verweis auf den Zielknoten.
Bei Inzidenzlisten ist der Aufwand für die Speicherung höherer als bei Adjazenzlisten, da
die Kanten- und Bogenliste hinzukommen. Auch der Bearbeitungsaufwand, um von einem
Knoten zu einem Nachbarn zu kommen, erhöht sich um den zusätzlichen Zugriff zum
Eintrag in der Kantenliste bzw. Bogenliste. Die Tatsache, daß bei Inzidenzlistendarstellung
die Kanten/Bögen eines Graphen explizit als eigene Objekte gegeben sind, ist jedoch
ein Vorteil, der oft den Zusatzaufwand aufwiegt. Es können nicht nur Mehrfachkanten
dargestellt werden, auch bei Graphen ohne Mehrfachkanten kann man die Kanten/Bögen
mit Namen und weiteren Attributen, z. B. Gewichten, versehen.
Wie bei der Adjazenzliste können Knotennamen und andere knotenspezifischen Daten
13.4. EXTERNE DARSTELLUNGEN UND BASISWERKZEUGE
369
GRAPH
Bögen
Kanten
?
VERTEX
?
?
?
INC
INC
INC
?
?
?
EDGE
EDGE
EDGE
Kanten
Ausgangsbögen
Eingangsbögen
?
?
EDGE
EDGE
?
?
VERTEX
VERTEX
Endpunkt
Endpunkt
?
VERTEX
?
VERTEX
Startpunkt Zielpunkt
Abbildung 13.6: Graphdarstellung durch Inzidenzlisten
in den Einträgen der Knotenliste direkt gespeichert sein oder es kann auf sie verwiesen
werden (Vertretersätze). Das gleiche gilt bei den Einträgen der Kantenliste/Bogenliste.
Anmerkung 13.3 (Listenimplementierung) Die in den Listendarstellungen gegebenen Graphelemente werden in den meistens Algorithmen als Mengen benutzt: Für alle
Knoten des Graphen tue .... oder Für alle Ausgangsbögen von Knoten v tue .... Für diesen Zweck kann jede Datenstruktur genommen werden, die zur Darstellung von Mengen
geeignet ist, z. B. verkettete Listen. Diese hat den Vorteil, daß sie auch eine gelegentlich
benötigte lineare Ordnung der Elemente wiedergibt. Es besteht jedoch des öfteren auch
die Notwendigkeit, auf ein Element anhand seines Namens direkt zuzugreifen. Das wäre
mit verketten Listen zu aufwendig. Als Datenstruktur, die allen Anforderungen genügt,
bieten sich Baumstrukturen an. Die in Abschnitt 9.2, Seite 277 besprochenen Rot-SchwarzBäume sind sehr gut geeignet.
2
13.4
Externe Darstellungen und Basiswerkzeuge
Externe Darstellungen
Graphdarstellungen müssen auch in Dateien gespeichert werden können. Auch hier sind
Listen sinnvoll. Als Beispiel soll wieder der Digraph in Abbildung 13.2, Seite 364, dienen.
Tabelle 13.3 zeigt zwei externe Darstellungen in Form sequentieller Listen, die jeweils die
Adjazenz- bzw. Inzidenzstruktur widerspiegeln. Diese Darstellungen sind in gedruckter
370
KAPITEL 13. DARSTELLUNGEN VON GRAPHEN
a: v1, v5
b: a
v1: v2
v2: v3, v4
v3: v3, b
v4: v2, a
v5: v4
*KNOTEN*
a, b, v1, v2, v3, v4, v5
*BOEGEN*
1: (a, v1);
2: (v1, v2)
3: (v2, v4)
4: (v4, a)
5: (a, v5)
6: (v5, v4)
7: (v4, v2)
8: (v2, v3)
9: (v3, v3)
10: (v3, b)
11: (b, a)
Tabelle 13.3: Beispiel für externe Darstellungen in Form sequentieller Listen
Form auch für Menschen sinnvoll, da nicht für alle Zwecke, z. B. bei sehr umfangreichen
Graphen, graphische Darstellungen ausreichen.
Basiswerkzeuge
Wenn Graphalgorithmen als Programme in einem Rechensystem ausgeführt werden sollen, sind einige Hilfsprogramme erforderlich, die oft nur wenig mit graphentheoretischen
Eigenschaften zu tun haben. Auf diese Programme soll nicht näher eingegangen werden.
Als Beispiele seien genannt:
• Es werden Programme gebraucht, mit denen eine externe Graphdarstellung eingelesen und eine programminterne Darstellung (z. B. Adjazenzmatrix oder Inzidenzlisten) aufgebaut wird. Auch Programme, die eine interne Darstellung in eine externe
Darstellung umwandeln und diese in einer Datei sichern, werden benötigt.
Anmerkung: Nach dem Aufbau einer programminternen Darstellung aus einer externen Darstellung ist der Typ des Graphen bekannt, d. h. man weiß, ob es sich
um einen ungerichteten Graphen, einen Digraphen oder um einen, in dem sowohl
Kanten als auch Bögen auftreten, handelt. Auch die Existenz von Schlingen und
Mehrfachkanten wird erkannt. Später angewandte Algorithmen können daher den
Typ des Graphen als bekannt voraussetzen.
• Programme, die elementare statistische Größen (Minimalgrad, Maximalgrad, mittlerer Grad u. ä) liefern, sind nützlich. Das gilt auch für Programme, mit denen sich
13.4. EXTERNE DARSTELLUNGEN UND BASISWERKZEUGE
371
Knoten- und Kantenmengen verwalten und Mengenoperationen darauf anwenden
lassen.
• Wichtig sind Programme, die aus einer Menge von Knoten oder Kanten den entsprechenden Untergraphen erzeugen. Auch Programme, die aus einem ungerichteten
Graphen einen gerichteten erzeugen oder umgekehrt, gehören dazu.
• Nützlich sind auch Programme, die Graphdarstellungen ineinander umwandeln.
Aufgaben
Aufgabe 13.1 Wie kann man anhand ihrer Adjazenzmatrizen feststellen, ob zwei schlichte Graphen isomorph sind?
Aufgabe 13.2 Es werden die Graphen G, H und I aus Abbildung 13.1 betrachtet. Die
Knotenmenge der Graphen ist {A,B,C,D,E}. Die Kanten sollen durch die Mengen ihrer
Endpunkte charakterisiert werden.
1. Bilden Sie die Kantenmengen zum Nachweis, daß G = H 6= I.
2. Geben Sie alle Automorphismen von H an.
3. Benutzen Sie Satz 12.2 zur Bestimmung aller Isomorphismen von H auf I.
Literatur
Wie findet man automatisch, d. h. mit Hilfe von Rechnern, „gute“ graphische Darstellungen von Graphen? Das ist ein wichtiges Teilgebiet der algorithmischen Graphentheorie.
Einen Einstieg bietet der Aufsatz von Brandenburg/Jünger/Mutzel [BranJM1997]. Weitere Einzelheiten kann man im Beitrag [EadeM1999] im Handbuch [Atal1999] finden.
Rechnergerechte Darstellungen von Graphen mit zum Teil etwas anderer Bedeutung der
Bezeichnungen Adjazenzliste und Inzidenzliste findet man zum Beispiel in Sedgewick
[Sedg2002], Jungnickel ([Jung1994], Seiten 60 ff), Ahuja/Magnanti/Orlin ([AhujMO1993],
Seiten 31 ff), Chartrand/Oellerman ([CharO1993], Seite 57 ff), Deo ([Deo1974], Seiten
270 ff), Evans/Minieka ([EvanM1992], Seiten 27 ff), Golumbic ([Golu1980], Seiten 31
ff), Noltemeier ([Nolt1976], Seite 38 ff), Nägler/Stopp ([NaglS1996], Seiten 32 ff), Skiena
([Skien1990], Seiten 81 ff). Es sei außerdem auf Aho/Hopcroft/Ullman [AhoHU1983] und
Tarjan [Tarj1983] hingewiesen.
Im System LEDA sind Datenstrukturen für Graphen sowie eine Reihe wichtiger Graphalgorithmen implmenentiert. Sie sind im dazugehörigen Handbuch [MehlN1999], das
durchaus auch Lehrbuchcharakter hat, beschrieben.
372
KAPITEL 13. DARSTELLUNGEN VON GRAPHEN
Eine interessante Sammlung von Graphen und Werkzeugen zu ihrer Beartbeitung ist in
Knuth „The Stanford GraphBase“ [Knut1993] beschrieben.
Kapitel 14
Wege und Zusammenhang
14.1
Wege: Definitionen und elementare Eigenschaften
Als Struktureigenschaft eines Graphen sind Kanten ungerichtet und Bögen gerichtet. Als
„normale“ Durchlaufsrichtung eines Bogens ist die vom Startpunkt zum Zielpunkt anzusehen. Es ist jedoch zweckmäßig als Bearbeitungseigenschaft eines Bogens auch Durchläufe
in Rückwärtsrichtung zuzulassen. Das führt zu der folgenden Festlegung.
Bearbeitungsmodi für Bögen eines Graphen:
1. vorwärts (forward)
Bögen dürfen nur in Vorwärtsrichtung durchlaufen werden.
2. rückwärts (backward)
Bögen dürfen nur in Rückwärtsrichtung durchlaufen werden.
3. beliebig (any)
Bögen dürfen in beiden Richtungen durchlaufen werden.
Kanten dürfen in jedem Modus in beiden Richtungen durchlaufen werden.
2
Wenn der Bearbeitungsmodus nicht explizit angegeben ist, ist ein beliebiger, aber fest
gewählter Bearbeitungsmodus gemeint. Für ungerichtete Graphen fallen alle drei Modi
zusammen. Der Bearbeitungsmodus rückwärts in einem Graphen mit Bögen entspricht
in dem Graphen, den man durch Invertieren der Bogenrichtungen gewinnt, dem Bearbeitungsmodus vorwärts. In dem ungerichteten Graphen, den man durch Umwandlung der
Bögen in Kanten gewinnt, hat man die gleichen Bearbeitungsmöglichkeiten wie im Originalgraphen mit dem Modus beliebig. Man beachte, daß der ungerichtete Graph auch
dann Mehrfachkanten enthalten kann, wenn der Ausgangsgraph keine Mehrfachkanten
und keine Mehrfachbögen enthält.
373
374
KAPITEL 14. WEGE UND ZUSAMMENHANG
Wege sind in der Graphentheorie von besonderer Bedeutung. Es sind Folgen aneinandergrenzender Kanten und Bögen, die von dem Anfangspunkt des Weges zu seinem Endpunkt
führen. Wir führen f-Wege (forward, d. h Bögen dürfen nur in Vorwärtsrichtung durchlaufen werden), b-Wege (backward) und a-Wege (any) ein.
Wege haben immer eine eindeutige Richtung. Der Typ, nämlich a-Weg, f-Weg oder b-Weg,
bestimmt dabei nur, wie Bögen auf einem Weg durchlaufen werden können.
Definition 14.1 In einem allgemeinen Graphen G(V, E, A, ϕ, ψ) ist ein f-Weg (f-Pfad,
f-path) eine endliche Folge v0 , l1 , v1 , l2 , v2 . . . , vn−1 , ln , vn mit (n ≥ 1). Die lν sind Kanten
oder Bögen. Ist lν eine Kante, so ist vν−1 ein Inzidenzpunkt und vν der andere Inzidenzpunkt. Ist lν ein Bogen, so ist vν−1 sein Startpunkt und vν sein Zielpunkt. Entsprechend
werden b-Wege und a-Wege definiert.
v0 heißt Anfangspunkt (Anfangsknoten, starting vertex) und vn heißt Endpunkt (Endknoten, end vertex) des Weges, alle anderen Knoten des Weges heißen innere Knoten
(internal vertex). n heißt Weglänge (path length).
Ein Weg heißt geschlossen (closed), wenn v0 = vn , anderenfalls heißt er offen (open). Ein
Weg heißt linieneinfach (line-simple), wenn seine Kanten und Bögen paarweise verschieden sind. Ein offener Weg heißt einfach (simple), wenn alle Knoten paarweise verschieden
sind. Ein geschlossener Weg heißt einfach, wenn nur Anfangs- und Endpunkt gleich sind.
Ein geschlossener einfacher und linieneinfacher Weg heißt Kreis (circuit). Ein Graph, in
dem es keinen Kreis gibt, heißt kreisfrei (acyclic).
Ein Lauf (run) ist eine unendliche Folge v0 , l1 , v1 , l2 , . . . von Knoten und Linien, für die
jedes endliche Anfangsstück v0 , l1 , v1 , l2 , . . . , vn−1 , ln , vn ein Weg ist.
Der Träger (support, carrier) eines Weges oder eines Laufes ist der Untergraph von G,
der aus den durchlaufenen Knoten und Linien besteht.
Im Graph G1 der Abbildung 12.1, Seite 348, ist D, g, C, j, F, j, C ein offener a-Weg, der
nicht linieneinfach ist. C, f, B, h, A, e, C, j, F ist ein offener, linienneinfacher a-Weg, der
nicht einfach ist. B, h, A, e, C, j, F, i, D ist ein einfacher offener a-Weg. Im gleichen Graph
ist D, g, C, f, B, f, C, g, D ein geschlossener, nicht linieneinfacher a-Weg. C,g,D,i,F,j,C,e,A,
h,B,f,C ist ein geschlossener, linieneinfacher a-Weg. A,e,C,f,B,h,A ist ein a-Kreis. Da G1
ungerichtet ist, sind alle a-Wege auch f-Wege.
Ein weiteres Beispeil ist Graph Graph1 der Abbildung 14.1, Seite 377. In der Abbildung
sind keine Kanten- und Bogennamen angegeben, um das Bild nicht zu überlasten. Diese
sollen nach den in der Abbildung aufgeführten Regeln systematisch gebildet werden. In
Graph1 ist K04, dK16K04, K16, dK17K16, K17, dK16K17a, K16, uK16K17a, K17,
dK16K17b, K16, uK16K17b, K17 ein offener, linieneinfacher b-Weg (und auch a-Weg)
von K04 nach K17. Der Weg ist nicht einfach und auch kein f-Weg.
14.1. WEGE: DEFINITIONEN UND ELEMENTARE EIGENSCHAFTEN
375
Anmerkung 14.1 In einem Graphen ohne Mehrfachlinien bestimmt die Folge der Knoten einen Weg eindeutig. Daher werden wir für solche Graphen Wege gelegentlich nur
durch Knotenfolgen spezifizieren.
2
Anmerkung 14.2 Im folgenden werden wir nicht immer zwischen Weg und Träger des
Weges unterscheiden. Wir werden z. B. von einem Untergraphen als Kreis sprechen. Aus
dem Zusammenhang geht dann hervor, was für ein Kreis auf dem als Träger anzusehenden
Untergraphen gemeint ist.
2
Wenn man einen offenen Weg von u nach v sucht, kann man sich auf linieneinfache oder
einfache Wege beschränken, wie sich aus dem folgenden Hilfssatz ergibt.
Hilfssatz 14.1
1. Jeder offene Weg von u nach v enthält einen linieneinfachen Weg von u nach v.
2. Jeder linieneinfache offene Weg von u nach v enthält einen einfachen Weg von u nach
v.
3. Jeder einfache offene Weg ist linieneinfach.
Beweis: Aufgabe 14.2.
2
Bei geschlossenen Wegen sind die Verhältnisse etwas komplizierter. Zunächst einmal gilt
der folgende Hilfssatz.
Hilfssatz 14.2 Jeder geschlossene Weg enthält einen geschlossenen einfachen Weg mit
gleichem Anfangs- und Endpunkt.
Beweis: Es sei v = v0 , l1 , v1 , . . . , vn−1 , ln , vn = v der geschlossene Weg. Kommt v als
innerer Punkt vor, so betrachte man den geschlossenen Weg v = v0 , l1 , v1 , . . . , lj , vj = v mit
dem kleinsten j. Dieser enthält v nicht als inneren Punkt. Enthält der neue Weg den von v
verschiedenen Knoten u mehrfach, so reduziere man den Weg zu v = v0 , l1 , v1 , . . . , lν , vν =
u, lµ , . . . , lj , vj = v, wobei ν der kleinste und µ der größte Index ist, bei dem u auftritt.
Diesen Schritt wiederholt man so lange, bis ein geschlossener Weg übrigbleibt, in dem alle
Knoten bis auf Anfangs- und Endpunkt paarweise verschieden sind.
2
Ein einfacher geschlossener Weg der Länge mindestens 2 kann keine Schlingen enthalten.
Gibt es einfache geschlossene Wege, die nicht linieneinfach, also keine Kreise sind? Das
beantwortet der nächste Hilfssatz.
Hilfssatz 14.3 Ein geschlossener einfacher Weg, in dem Linien mehrfach auftreten, ist
vom Typ v, l, u, l, v mit v 6= u.
376
KAPITEL 14. WEGE UND ZUSAMMENHANG
Beweis: I. Ein Weg des angegebenen Typs kann als a-Weg auftreten.
II: In jedem anderen geschlossen Weg, in dem eine Linie mehrfach auftritt, gibt es auch
innere Knoten, die mehrfach auftreten oder gleich dem Anfangsknoten sind. In der Tat: Sei
l eine mehrfach auftretende Linie in einem geschlossenen Weg. Die Linie muß mindestens
zweimal auftreten und der Weg muß mindestens die Länge 2 haben. Ist l eine Schlinge, so
tritt ihr Inzidenzpunkt auch als innerer Punkt auf. Es sei l keine Schlinge. Wenn der Weg
nicht vom angegeben Typ ist, muß er mindestens die Länge 3 haben. Läßt man die letzte
Linie des Weges weg, so erhält man einen verkürzten Weg der Länge mindestens 2. Dieser
Weg muß offen sein, da sonst im ursprünglichen Weg der Anfangsknoten auch ein innerer
Knoten wäre. Als einfacher offener Weg ist der verkürzte Weg auch linieneinfach. Eine
mehrfach auftretende Linie im Ausgangsweg muß demnach die letzte Linie sein. Einer
ihrer Inzidenzpunkte ist der Anfangsknoten des Weges. Der andere ist ein mindestens
zweimal auftretender innerer Knoten des ursprünglichen Weges.
2
Der folgende Satz klärt den Zusammenhang zwischen einfachen geschlossenen Wegen und
Kreisen.
Satz 14.1
1. Jeder einfache geschlossene Weg der Länge 1 oder größer 2 ist ein Kreis.
2. In schlichten Graphen hat jeder Kreis mindestens die Länge 3.
3. In Digraphen ist jeder einfache geschlossene f-Weg (b-Weg) ein Kreis.
Beweis: 1. Folgt aus Hilfssatz 14.3.
2. Folgt auch aus Hilfssatz 14.3, da schlichte Graphen keine Schlingen und keine Mehrfachkanten aufweisen.
3. In einem f-Weg (b-Weg) eines Digraphen kann eine Folge . . . , v, l, u, l, v, . . . mit v 6= u
nicht auftreten. Die Behauptung folgt dann wieder aus Hilfssatz 14.3.
2
14.1. WEGE: DEFINITIONEN UND ELEMENTARE EIGENSCHAFTEN
377
.........................
.........................
.....
.....
...
...
...
...
...
...
...
...
..
...
.
....
....
.
...............
.
................
.
.
...................
.
.
.
.
.
.
.
.
.
.
...
.
....
...
......
....
... .............................
.
.
.
.
....
...
.
................
.
.
.
.
.
.....
...
...
............ ..............
.
.
...
.
.
.
.
.
.
.
.
.
.
.
..
.
.
.
.
.
.
.....
............
.....
......... ...........
...
...
.
.
.
.
.
..
..
.
.
.
...
.....
...
...
... .
..........
.
.
.
...
.
.
.
.....
...
...
.
...
.
. .....
.
.
.
.
.
.
.
.
.
.
.
.....
..... ..
.....
....
...
.
..
............. ..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
...
.................. ...
.....
..
.... ..................
..................
.....
.........
.
...
...
.......
...........
.....
........... ..... ..... .........
....................
...
.
.....
.....
..........................
... ..
.....
......
....
. ...
.....
...
..
...
... .......
....
....
.....
...
...
.....
..
...
.
.
.... ..
.
.
.
.
.
.
...
.
.
.....
... ... ...
..
.....
...
..
......
.
.
.
.
.
.
.
.
.
.
.
.
.
.
..... ..
.....
......
...
...
...
.
.
..
.
.
...
.
.
.
.
.
.
.
.
.
.
.
.
...
..... ..
.....
...
...
.. ....
..
.
.
.
..
.
.
.
.
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.....
...
...
......
....
...
.
..
................... ......
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
... .....
...
...
..................... .............
.....
.
...............
.............. ..................... ................
.....
...
...
.......
. ... .................................
...
.. ...
.....
...
.......................................
...
...
....
.... ..
...
..
.....
.. ........................................
.
...
...
...
...
...
...
..... ....
... ....................................................................
...
....
...
...
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
.......
....
.....
...
...
............................................. ....
..
.
.
.
.
.
.
.
.
.
.
.
.......................
.
.
.
.
.
.
.
.
.
.
.
...
...
.....
...
........
...........................
.
.
.
.
........ .............. ..
.
.
.
.....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
....
.
.
.
.....
...
...
....
..
..........................................
...
. ........
...
.
...
.
.
.
.
.
.
.
.
.
.
.
.
.....
.....
... ....
...
...
...
..
.
..
.. ......
.
.
.
.
.
.
.
.
.
....
.
.
.
.
.
.
..
.
.
.
...
.....................................................................................................................................................................................................................................................................................................................................................................................................................................................................
.
.
...
...
.....
.. ..
.....
...
.
..
.
...
..
...
.
.
.
.
.
.
.
.
.
.
.
.....
...
....
...
...
.....
...
...
...
..
.
.
..
.
.
.
.
.
.
.
.
.
.
.....
.
.
.
.
.
.
.
...
.....
.....
...
...
......
...
..
.
.
.
.......................
.
.
.
.
.
....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
....... ..
.....
...
...
.....
...
...
..
...
...
.....
.....
...
...
....
.....
...
..... .... ....
...
...
...
...
.....
...
..... .. ..
...
...
...
...
.....
...
........ ..
...
...
...
.....
...
...
.
.
.
.
.
.
.
...
.
.
.
.
....
...
.....
...
...........
...
...
.
.
.
.
.
.
.
.
.....
...
...
...........
...
.
.
...
.
.
.
.
.
.
.
.
.
.
.
.... ....
.....
....
...
...
.. .......
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
......
...
.....
...
.....
....
........ ...........
.....
.....
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
...
.....
.....
...
.
...
....
. ...
..
.
.
.
.
...
.
.
...
.
.
.
.
.
.
.
.
.
.
.....
...
...
...
.....
.
..
. ...
.
...
.
..
.
....
.
.
.
.
.
.
.
.
.
.
.
...
.....
.....
...
...
..
...
...
..
..
.. ...
.....
....
...
....
...
...
....
.....
.. ....
.....
..
..
...
.
....
.
.
.
.
.
.
.
.
.
.
.
.....
...
.....
...
...
...
..
...
.
. ...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
......
.
.
.
.
.
.
.
.
.
.....
....... .......
...
....
.....
...
...
.
..
.................... .........
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
...
.....
.....
.
...
....
.....
.....
.........
...
.....
..... ......
...
...
...
...
.....
......
.....
...
...
...
...
........
...
.....
.....
...
...
...
...
.........
.....
...
..
...
.....
...
...
.....
... .........
..
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.....
...
...
.....
.....
... ..
...
...
.....
....
.....
.....
...
...
... ..
...
..
.....
...
.....
.
.
....
.
.
.
.
.
.
...
.
.
.
.
.
.....
.....
.....
...
...
.....
...
..
.
.
.
.
.
.
.
..
.
.
.
.
.
.
.
.
...
.......
.....
.....
...
...
.
.....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.................. ..................
.....
........
...
... .. ...
...
.....
.....
.
.
.
.
.
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
... .. ...
.....
....
.
...
...
...
...
.
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
...
....
.....
..
....
..
.
..
.. ..
..
....
..............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
.
...
.....
.
...
.
.
.
...
.....
...
...
...
..
. ....
.....
...
.
.
.
.
.
.
.
.
.
.
...
.....
...
.....
... ...
...
...
....
.
....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
....... ....... ...
...
.....
.....
... ..
...
....... ........................
.
.
.
.
.
.
...........
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.....
...
.....
...
...
... ...
...
..
..
.....
..
...
...
..... .......................
... ..
.........
...
.....
.... ....
.
...
...
......
...
.....
...... ..
..................
...
... ..
....
...
.....
..
............
...
.....
....
...
.....
..
.....
..
...........
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
... ......
.....
.....
..
..
...
........
.....
.....
... .. ...
..
..
... ....
...
............
.....
.....
... .. ...
...
.........
............
...
... .....
.....
.....
... .. ...
............
.......
..
....
.....
..
...
........
... ... ... .......................
...
.....
.............
..
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
......
.
.
.
.
.
.
...............
............... ..
...... ..
.....
...
...
....
.....
...
..
...
...
... ...
....
...
...
.....
...
.... .............................
... ..
...
...
...
.....
..
...
......
.. ...
........
...
.
.
.
.
.
..
.....
.
...
.
.
.
.
.
.
..
.. ..
.
.
.
.
...
...
... ...
...
.....
. ..................
...
.
. ...
.
.
.
.
.
.
.
.
.
...
.....
...
...
... ...
..
.
...
...
..............
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
...
.....
...
... ...
....
....
...
...... ...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.......
.
.
.
.
.....
...
........ ........
...
...
...
...
.
.......
.
.
.
....................
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.....
...
...
...
...
...
.. ...............
... ....
...
...
...
...
...
...
... ...
.............
...
...
...
...
...
... ..
...
............ ..........
...
...
..
.... ..
...........
...
.....
...
...
...
...
..
............
.
.
.
.
.
.
.
.
.
.
...
.
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.....
....
...
...
...
..
...
.....
............
... ...
.......
...
...
...
.....
.
..............
............
...
..
...
... ....
.....
..........
............
..... ............... ....
..
...
...
.....
........................
... ....
............
.........
....
...
...
..... ....
....
.......
...........
.
.
....
.
.
.
.
.
.
.
.
.
....
.
.
.
.
.
.
.
.
.
.
.
.
.
..... .
...
...
...
...
...
...
......
.
.
......
.
...
.
.
....
.
.. ...........................
.
...
...
...
...
..............
....
...
...
...
.........
.
...
...
.
.
...
.
..
.. .........
.. ..
.
.
.
.
.
.
.
...
...
...
...
...
.....
.
..
.
...
.
.
.
.
.
.
.
.
.
.
.
.
.
...
.....
...
....
...
....
...
.
.
.
..
.
.
.
........ ...........
.
.
.
.
.
.
.
.
.....
...
...... ....
...
...
...
........
.....
...
...
...
.... ...........
...
...
.....
...
...
...
....
...
.....
...
...
.......
...
...
.
.....
...
...
..... .......
...
... ............
.....
....
.
.
..
.
.
.
...
.
.
.
.
.
.
....
.
.
.
.....
......
.
...
...
..
.....
...
...
.
...
...
........................
.....
.... ......................................
....
...
...
......
.....
...
..............
... ....
..
.....
...
...
...
... ..
... .....
.................
.....
...
....
...
...
....
.....
.....
.
..
..
...
.....
.
.
...
...
..
...
.....
.. .. ...
...
...................
..
...
.....
...
......................
...
....
...
.....
....
...
........ .......
.......
...
.....
........ ...........
..
.................. ............
.
.
.
.
.
.
.
.
.
.
.
..... .. ...
......
...
...... .. .......................
................................................
.. . .
....
...
.
...
..............
.......................
..
...
...
... .....
..
..
..
.
....
.
...
..
...
.
.
..
.
..
.
.
.
.
.
.
.
.
...
...
..
. .... ....
..........................................................................................................
.
.
.
.
.
.
.
.
.
...
....
....
.......
...
...........
.
.
....
.
.
........................
.
.
.
.
.
.
.
.
.
....
.
.
.
.
.
.
.
.
.
.....
... .... ...........
..................................................
...
.
......... ..........
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.........
...........
..................................................
. ........ .......
...
.
.
...
...
.....
.....
..
.. ........
.
...
..... ........ ..
...
...................................................
........
....
........
....
...
.
.
.
.......
.
.
.
.
.
.
.
.
.
.
.
.................................................................................................
..
K06
K05
K04
K07
K03
K08
K02
K09
K01
K10
K00
K11
K21
K12
K20
K13
K19
K14
K18
K15
K16
K17
Die Kanten- und Bogennamen sollen systematisch vergeben sein, und zwar nach den folgen Regeln: Eine Kante beginnt mit u, ein Bogen mit d. Dann folgen die beiden Knotennamen, bei Bögen in der Richtungsreihenfolge. Mehrfachkanten/-bögen werden durch nachgestellte Buchstaben
a, b, c, . . . unterschieden. So inzidieren z. B. mit dem Knoten K16 die folgenden Kanten/Bögen:
dK16K04, dK16K17a, dK16K17b, dK17K16, uK16K17a, uK16K17b. Mit K14 inzidieren:
uK14K14a, uK14K14b, dK14K14, dK14K15.
Abbildung 14.1: Graph1
378
14.2
KAPITEL 14. WEGE UND ZUSAMMENHANG
Erreichbarkeit und Zusammenhang
Definition 14.2
1. In einem allgemeinen Graphen G(V, E, A, ϕ, ψ) heißt ein Knoten
v von einem Knoten u f-erreichbar (f-reachable), wenn es einen f-Weg mit Anfangspunkt u und Endpunkt v gibt.
Entsprechend wird b-Erreichbarkeit und a-Erreichbarkeit definiert.
2. In einem allgemeinen Graphen G(V, E, A, ϕ, ψ) heißen Knoten u und v gegenseitig
f-erreichbar (mutually f-reachable), wenn es einen f-Weg von u nach v und einen
f-Weg von v nach u gibt.
Entsprechend wird gegenseitige b-Erreichbarkeit und gegenseitige a-Erreichbarkeit
definiert.
Da Wege der Länge 0 nicht zugelassen sind, kann man von einem isolierten Knoten keinen
Knoten erreichen und er ist von keinem Knoten erreichbar. Betrachtet man nur f-Wege,
so gibt es auch nicht-isolierte Knoten, die von keinem Knoten erreichbar sind. Z. B. ist
Knoten K10 in Graph1, Seite 377, nicht f-erreichbar. Ein Knoten, der von keinem Knoten
auf einem f-Weg erreicht werden kann, soll f-Startknoten (f-starting vertex) genannt werden. Ein Knoten, von dem kein Knoten auf einem f-Weg erreichbar ist, soll f-Stoppknoten
(f-stopping vertex) heißen. Ein isolierter Knoten ist Start- und Stoppknoten zugleich. Ein
Knoten, der von sich selbst f-erreichbar ist, soll Knoten mit f-Rückkehr (vertex of return)
heißen. Alle anderen Knoten heißen Knoten ohne f-Rückkehr. Ein Knoten mit f-Rückkehr
kann weder Start- noch Stoppknoten sein. Für a-Wege gelten die entsprechenden Definitionen. Da von jedem nicht-isolierten Knoten ein a-Weg zum Knoten zurückführt, wollen
wir die Bezeichnungen Startknoten, Stoppknoten, Knoten mit Rückkehr und Knoten ohne
Rückkehr nur für f-Wege benutzen und den Zusatz f- fortlassen. Ein Knoten u ist genau
dann Knoten mit Rückkehr, wenn es einen Knoten v gibt, so daß u und v gegenseitig
f-erreichbar sind.
Erreichbarkeit ist offensichtlich transitiv. a-Erreichbarkeit ist eine Oberrelation von fErreichbarkeit und von b-Erreichbarkeit. b-Erreichbarkeit ist die inverse Relation zu fErreichbarkeit: Ein f-Weg von u nach v ist ein b-Weg von v nach u. Gegenseitige fErreichbarkeit ist der (eventuell leere) symmetrische Kern von f-Erreichbarkeit und auch
von b-Erreichbarkeit und damit gleich gegenseitiger b-Erreichbarkeit. In beliebigen Graphen fallen a-Erreichbarkeit und gegenseitige a-Erreichbarkeit zusammen und jeder nichtisolierte Knoten ist ein Knoten mit a-Rückkehr. In ungerichteten Graphen ist in allen Bearbeitungsmodi Erreichbarkeit gleich gegenseitiger Erreichbarkeit und jeder nicht-isolierte
Knoten ist ein Knoten mit Rückkehr. Zu Relationen siehe Abschnitt A.5, Seite 616, im
Anhang.
Damit ergibt sich der folgende Satz.
Satz 14.2
1. f-Erreichbarkeit (a-Erreichbarkeit) ist eine transitive Relation auf der
Menge der Knoten.
14.2. ERREICHBARKEIT UND ZUSAMMENHANG
379
2. Gegenseitige f-Erreichbarkeit (a-Erreichbarkeit) ist eine symmetrische Relation auf
der Menge der Knoten.
3. Gegenseitige f-Erreichbarkeit (a-Erreichbarkeit) ist eine Äquivalenzrelation auf der
Menge der Knoten mit Rückkehr (der Menge der nicht-isolierten Knoten).
Als Äquivalenzrelation zerlegt gegenseitige f-Erreichbarkeit (a-Erreichbarkeit) die Menge
der Knoten mit Rückkehr (die Menge der nicht-isolierten Knoten) in disjunkte Aquivalenzklassen. Das führt zu der folgenden Definition.
Definition 14.3 Es sei G(V, E, A, ϕ, ψ) ein allgemeiner Graph.
1. Die durch die Äquivalenzklassen gegenseitig f-erreichbarer Knoten erzeugten Untergraphen heißen starke Zusammenhangskomponenten (strongly connected components, strong components) von G.
2. Die durch die Äquivalenzklassen gegenseitig a-erreichbarer Knoten erzeugten Untergraphen heißen schwache Zusammenhangskomponenten (weakly connected components, weak components) von G.
3. Besteht ein Graph aus einer einzigen starken/schwachen Zusammenhangskomponente, so heißt er stark/schwach zusammenhängend (strongly/weakly connected).
Anmerkung 14.3 Bei ungerichteten Graphen fallen schwache und starke Zusammenhangskomponenten zusammen und man spricht nur von Zusammenhangskomponenten
(connected components).
2
Da gegenseitige b-Erreichbarkeit das gleiche wie gegenseitige f-Erreichbarkeit ist, erzeugt
auch gegenseitige b-Erreichbarkeit die starken Zusammenhangskomponenten eines Graphen. Jede starke Zusammenhangskomponente ist Untergraph einer schwachen Zusammenhangskomponente.
Isolierte Knoten gehören zu keiner schwachen Zusammenhangskomponente. Die nur aus
einem isolierten Knoten bestehenden Untergraphen sollen uneigentliche schwache Zusammenhangskomponenten (improper weakly connected component) heißen.
Jeder nicht-isolierte Knoten und jede Linie gehört zu einer eindeutig bestimmten schwachen Zusammenhangskomponente. Jede Kante und jede Schlinge gehört zu einer eindeutig bestimmten starken Zusammenhangskomponente. Wie sehen nun allgemeine Graphen
aus, die keine starken Zusammenhangskomponenten aufweisen? Eine Antwort gibt der
folgende Satz.
Satz 14.3 Ein allgemeiner Graph hat genau dann keine starken Zusammenhangskomponenten, wenn er ein f-kreisfreier Digraph ist.
380
KAPITEL 14. WEGE UND ZUSAMMENHANG
Beweis: Enthält der Graph keine starken Zusammenhangskomponenten, so hat er keine
Kanten und ist ein Digraph. Enthielte er einen f-Kreis, so gäbe es auch einen Knoten mit
Rückkehr und somit eine starke Zusammenhangskomponente.
Ist der Graph ein Digraph und enthält er keinen f-Kreis, so enthält er nach Satz 14.1,
Nummer 3, keinen einfachen geschlossenen f-Weg und somit nach Hilfssatz 14.2 keinen
geschlossen f-Weg. Dann enthält er auch keine starken Zusammenhangskomponenten. 2
f-Kreisfreiheit läßt sich durch b-Kreisfreiheit ersetzen. Ein Beispiel für einen Graphen
ohne starke Zusammenhangskomponenten ist der durch die Knoten K00, K06, K11, K20
und K21 erzeugte Untergraph von Graph1, Seite 377. Satz 14.3 rechtfertigt die folgende
Definition.
Definition 14.4 Ein allgemeiner Graph ohne starke Zusammenhangskomponenten heißt
Dag (directed acyclic graph).
Wegen der Bedeutung dieser Digraphen in der Planungstheorie werden sie auch Präzedenzgraphen (precedence graph) genannt.
In einer schwachen Zusammenhangskomponente, die starke Zusammenhangskomponenten
enthält, kann es Bögen geben, die zu keiner starken Zusammenhangskomponente gehören.
Der von diesen Bögen aufgespannte Untergraph ist ein Dag und soll externer Dag (external
dag) der schwachen Zusammenhangskomponenten heißen. Er ist nicht notwendigerweise
schwach zusammenhängend und kann ganz fehlen. Da in schwachen Zusammenhangskomponenten alle Knoten gegenseitig a-erreichbar sind, muß in jeder schwachen Zusammenhangskomponente mit mehr als einer starken Zusammenhangskomponente jede von diesen
Knoten aufweisen, die auch zum externen Dag gehören. Sie sollen schwache Verheftungspunkte (weak attachment point) heißen. Knoten einer starken Zusammenhangskomponente, die keine schwachen Verheftungspunkte sind, sollen innere Knoten (internal vertices)
genannt werden. Es ist möglich, daß im externen Dag beide Knoten, mit denen ein Bogen inzidiert, schwache Verheftungspunkte sind. Eine starke Zusammenhangskomponente
kann mehrere schwache Verheftungspunkte haben, ein Knoten kann jedoch höchstens für
eine starke Zusammenhangskomponente schwacher Verheftungspunkt sein.
In einem externen Dag treten nur Bögen auf. Bögen kann es aber natürlich auch in einer starken Zusammenhangskomponente geben. Dann enthält diese nach der folgenden
Proposition auch einen f-Kreis.
Proposition 14.1 Enthält eine starke Zusammenhangskomponente einen Bogen, so liegt
dieser auf einem f-Kreis.
Beweis: Aufgabe 14.3.
Aus Proposition 14.1 ergibt sich der folgende Satz.
2
Satz 14.4 Eine starke Zusammenhangskomponente ist genau dann f-kreisfrei, wenn sie
ungerichtet und a-kreisfrei ist.
14.3. BRÜCKEN UND SCHNITTPUNKTE
381
Beweis: Es liege eine f-kreisfreie starke Zusammenhangskomponente vor. Sie muß ungerichtet sein, denn andernfalls wäre sie nach Proposition 14.1 nicht f-kreisfrei. Jeder
f-kreisfreie ungerichtete Graph ist auch a-kreisfrei.
Ist die starke Zusammenhangskomponente ungerichtet und a-kreisfrei, so ist sie auch fkreisfrei.
2
Proposition 14.2 Eine Kante liegt genau dann auf einem f-Kreis, wenn sie auf einem
a-Kreis liegt, der ganz in der zugehörigen starken Zusammenhangskomponente verläuft.
Beweis: Ein f-Kreis verläuft stets innerhalb einer starken Zusammenhangskomponente.
Eine Kante auf einem f-Kreis liegt also auf einem a-Kreis, der innerhalb der zugehörigen
starken Zusammenhangskomponente verläuft.
Eine Kante auf einem a-Kreis, der ganz in der zugehörigen starken Zusammenhangskomponente verläuft, läßt sich nach Proposition 14.9, die in Abschnitt 14.5 bewiesen wird, zu
einem Bogen machen, ohne den starken Zusammenhang zu zerstören. Nach Proposition
14.1 geht dann auch ein f-Kreis durch die Kante.
2
Weitere Eigenschaften von schwachen und starken Zusammenhangskomponenten werden
in Abschnitt 14.6 behandelt.
14.3
Brücken und Schnittpunkte
Schwacher Zusammenhang liefert eine erste Charakterisierung bestimmter Linien und
Knoten. Eine Linie eines allgemeinen Graphen heißt Brücke (bridge), wenn ihre Entfernung die Zahl der schwachen (eigentlichen oder uneigentlichen) Zusammenhangskomponenten erhöht. Ein Knoten eines allgemeinen Graphen heißt Schnittpunkt (Artikulationspunkt, cut point, articulation point), wenn seine Entfernung zusammen mit allen
mit ihm inzidenten Linien die Zahl der schwachen (eigentlichen oder uneigentlichen) Zusammenhangskomponenten erhöht. Eine Brücke/ein Schnittpunkt trennt (separate) die
neu entstandenen schwachen Zusammenhangskomponenten. Es ist im Zusammenhang mit
Brücken nützlich, die Bezeichnung [x] einzuführen. Für einen Knoten x eines allgemeinen
Graphen wird mit [x] die eigentliche oder uneigentliche schwache Zusammenhangskomponente bezeichnet, zu der x gehört
Hilfssatz 14.4 Eine Linie l eines allgemeinen Graphen G ist genau dann eine Brücke,
wenn für ihre Inzidenzpunkte a und b in G − l gilt [a] 6= [b].
Beweis: Alle Knoten x, die in G weder von a noch von b erreichbar sind, erzeugen in
G die gleichen Zusammenhangskomponenten [x] wie in G − l. Ist l keine Brücke, also die
Zahl der schwachen Zusammenhangskomponenten in G und G − l gleich, so muß [a] = [b]
382
KAPITEL 14. WEGE UND ZUSAMMENHANG
gelten. Ist l eine Brücke, also die Zahl der Zusammenhangskomponenten in G kleiner als
in G − l, so muß [a] 6= [b] gelten.
2
Die folgende Propositionen charakterisieren Brücken durch a-Kreise und Schnittpunkte
durch a-Wege.
Proposition 14.3 Eine Linie l eines allgemeinen Graphen G ist genau dann eine Brücke,
wenn sie auf keinem a-Kreis liegt.
Beweis: Es seien a und b die Inzidenzpunkte von l. Liegt l auf einem a-Kreis, so ist
[a] = [b] in G − l. Nach Hilfssatz 14.4 ist l dann keine Brücke. Liegt l auf keinem a-Kreis,
so gibt es in G−l keinen a-Weg von [a] nach [b]. Es ist [a] 6= [b] und – wieder nach Hilfssatz
14.4 – l ist eine Brücke.
2
Proposition 14.4 Ein Knoten v eines allgemeinen Graphen ist genau dann ein Schnittpunkt, wenn es von v verschiedene Knoten u und w mit u 6= w gibt, so daß jeder a-Weg
von u nach w über v führt und es mindestens einen solchen Weg gibt.
Beweis: Es sei v ein Knoten des allgemeinen Graphen G und [v] die (eigentliche oder
uneigentliche) Zusammenhangskomponente, die v enthält. Beim Übergang von G zu G−v
bleiben alle schwachen Zusammenhangskomponenten, die v nicht enthalten, unverändert.
Ist v ein Schnittpunkt, so gibt es in G − v schwache Zusammenhangskomponenten C1
und C2 , die vorher nicht existierten und sich aus [v] ergeben haben müssen. Es sei u ∈ C1
und w ∈ C2 . Es gibt in G einen innerhalb von [v] verlaufenden a-Weg von u nach v. In
G − v gibt es keinen Weg von u nach w. D. h. in G muß jeder Weg von u nach w über v
verlaufen.
Sind in G Knoten u und w durch einen Weg verbunden und führt jeder Wege zwischen ihnen über v, so gibt es G − v keinen Weg von u nach w. Die Knoten gehören zu verschieden
schwachen Zusammenhangskomponenten. Die Zahl der schwachen Zusammenhangskomponenten hat sich erhöht.
2
In Kapitel 16 „Die Biblockzerlegung“ wird genauer auf Schnittpunkte und ihre Bedeutung
bei der Zerlegung von Graphen eingegangen.
14.4
14.4.1
Kreisfreiheit und Bäume
Kreisfreiheit und Zusammenhang
Wegen ihrer Wichtigkeit seien die beiden folgenden Aussagen, die unmittelbar aus den
Definitionen folgen, noch einmal besonders betont:
• Starker Zusammenhang impliziert schwachen Zusammenhang.
14.4. KREISFREIHEIT UND BÄUME
383
• a-Kreisfreiheit impliziert f-Kreisfreiheit.
Wir wollen nun a-Kreisfreiheit und schwachen Zusammenhang in allgemeinen Graphen betrachten. Ein a-kreisfreier allgemeiner Graph kann keine Mehrfachlinien enthalten. Wenn
einem Graphen bei fester Knotenmenge immer neue Linien hinzugefügt werden, wird er
irgendwann einmal einen a-Kreis enthalten. Nehmen wir einem Graphen immer mehr Linien weg, wird er irgendwann einmal nicht mehr schwach zusammenhängend sein. Die
beiden folgenden Propositionen geben zu diesen Überlegungen Abschätzungen.
Proposition 14.5 Jeder schwach zusammenhängende allgemeine Graph G(V, E, A, ϕ, ψ)
enthält mindestens |V | − 1 Linien.
Beweis: Durch Induktion. Für |V | = 1 und |V | = 2 ist die Behauptung richtig.
Es sei |V | ≥ 3. Weiter sei v ∈ V und U = V \{v}. H sei der von U erzeugte Untergraph von
G. H zerfällt in die (eigentlichen oder uneigentlichen) schwachen Zusammenhangskomponenten C1 , C2 , . . . , Ck . Die Anzahl Knoten in Ci sei ni . Es ist n1 + n2 + · · · + nk = |V | − 1.
Nach Induktionsvoraussetzung enthält Ci mindestens ni −1 Linien. Außerdem muß wegen
des schwachen Zusammenhangs von G der Knoten v mit jedem Ci durch mindestens eine
Linie verbunden sein. Also hat G mindestens
(n1 − 1) + (n2 − 1) + · · · + (nk − 1) + k = |V | − 1
Linien.
2
Proposition 14.6 Jeder a-kreisfreie allgemeine Graph G(V, E, A, ϕ, ψ) enthält höchstens
|V | − 1 Linien.
Beweis: Durch Induktion. Für |V | = 1 und |V | = 2 ist die Behauptung richtig.
Es sei G a-kreisfrei und |V | ≥ 3. Ist E ∪ A = ∅, so ist die Behauptung richtig. Andernfalls sei l ∈ E ∪ A und H der durch (E ∪ A) \ {l} erzeugte Untergraph von G. Wegen
der a-Kreisfreiheit von G kann l keine Schlinge sein und in H müssen die beiden Knoten, mit denen l inzidiert, zu verschiedenen (eigentlichen oder uneigentlichen) schwachen
Zusammenhangskomponenten gehören (Proposition 14.3). D.h. H zerfällt in mindestens
zwei schwache Zusammenhangskomponenten und jede hat weniger als |V | Knoten. Nach
Induktionsvoraussetzung gilt dann, daß die Anzahl Linien höchstens
(n1 − 1) + (n2 − 1) + · · · + (nk − 1) + 1 = n1 + n2 + · · · nk − k + 1 ≤ n − 1
beträgt.
2
384
14.4.2
KAPITEL 14. WEGE UND ZUSAMMENHANG
a-Bäume
Definition 14.5 Ein schwach zusammenhängender, a-kreisfreier allgemeiner Graph wird
a-Baum (a-tree) genannt. Ein allgemeiner Graph wird a-Wald (a-forest) genannt, wenn
seine schwachen Zusammenhangskomponenten a-Bäume sind.
Anmerkung 14.4 Für a-Bäume, die ungerichtete Graphen sind, ist in der Literatur auch
die Bezeichnung freier Baum (free tree) üblich. Auch wir wollen ungerichtete Graphen
dieser Art so nennen.
2
a-Bäume haben eine Reihe wichtiger Eigenschaften, die alle charakteristisch sind, also
auch zur Definition herangezogen werden könnten. Dazu der folgende Satz.
Satz 14.5 Es sei G(V, E, ϕ, ψ) ein allgemeiner Graph. Dann sind die folgenden Aussagen
gleichwertig.
1. G ist ein a-Baum.
2. G ist schlingenfrei und zwei beliebige verschiedene Knoten sind durch einen eindeutig
bestimmten einfachen a-Weg verbunden.
3. G ist schlingenfrei und schwach zusammenhängend. Die Entfernung einer Linie
führt jedoch zu einem unzusammenhängenden Graphen.
4. G ist schwach zusammenhängend und hat |V | − 1 Linien.
5. G ist a-kreisfrei und hat |V | − 1 Linien.
6. G ist a-kreisfrei. Das Hinzufügen einer Linie führt jedoch zu einem a-Kreis.
Beweis: 1. ⇒ 2. Die Aussage ist richtig, wenn G nur aus einem isolierten Knoten besteht. Es sei |V | ≥ 2. Schlingenfreiheit folgt aus der Kreisfreiheit und die Existenz eines
einfachen a-Weges zwischen je zwei verschiedenen Knoten aus dem schwachen Zusammenhang. Bleibt die Eindeutigkeit eines solchen Weges zu zeigen. Dazu soll angenommen
werden, daß es von u nach v mit u 6= v zwei verschiedene einfache a-Wege gibt.
u = v0 , l1 , v1 , . . . , li , vi , li+1 , vi+1 , . . . , vj−1 , lj , vj , . . . , vn = v
0
0
0
, lk0 , vk0 = vj , . . . , vn = v
, . . . , vk−1
, vi+1
u = v0 , l1 , v1 , . . . , li , vi , li+1
0
Bis einschließlich vi seien die Wege identisch, vi+1 und vi+1
seien die ersten verschiede0
nen Knoten. vj = vk sei der erste Knoten an dem die Wege sich wieder treffen. Dann
0
ist vi , li+1 , . . . , lj , vj = vk0 , lk0 , . . . , li+1
, vi ein a-Kreis im Widerspruch zu angenommenen
Kreisfreiheit.
14.4. KREISFREIHEIT UND BÄUME
385
2. ⇒ 3. G ist schlingenfrei. Wenn zwischen je zwei verschiedenen Knoten ein eindeutig
bestimmter einfacher a- Weg existiert, ist G schwach zusammenhängend. Es sei l eine
Linie von G. Es seien u und v die Knoten, mit denen l inzidiert. Da l keine Schlinge ist,
gilt u 6= v. Der einzige einfache a-Weg von u nach v ist u, l, v. Wird l entfernt, so gibt es
keinen einfach a-Weg mehr von u nach v und damit überhaupt keinen a-Weg. Der neue
Graph ist nicht mehr schwach zusammenhängend.
3. ⇒ 4. G ist schwach zusammenhängend und daher gilt nach Proposition 14.5 |E|+|A| ≥
|V | −1. Durch Induktion soll gezeigt werden, daß auch |E| + |A| ≤ |V | −1 gilt. Ist |V | = 1,
so gilt E = 0, da es keine Schlingen gibt. Es sei |V | = n > 1 und die Behauptung richtig
für alle Graphen G0 = (V 0 , E 0 , A0 ) mit |V 0 | < n. Wir entfernen eine Linie l. Der neue Graph
hat mehr als eine (eigentliche oder uneigentliche) schwache Zusammenhangskomponente
und jede schwache Zusammenhangskomponente weniger als n Knoten. Jede schwache
Zusammenhangskomponente erfüllt 3., denn sonst würde die Bedingung auch von G nicht
erfüllt werden. Nach Induktionsvoraussetzung gilt dann für die Summe der Linien der
schwachen Zusammenhangskomponenten |E1 | + |A1 | + |E2 | + |A2 | + · · · |Ek | + |Ak | ≤
n−k ≤ n−2. Fügt man die entfernte Linie wieder hinzu, so gilt für den Ausgangsgraphen
|E| + |A| ≤ n − 1.
4. ⇒ 5. Angenommen, es gäbe in G einen a-Kreis. Sein Träger ist ein Untergraph H0 mit
k Knoten und k Linien (k ≥ 1). Wegen |E| + |A| = |V | − 1 müssen noch Knoten übrig
sein und wegen des schwachen Zusammenhangs muß es darunter einen geben, der durch
eine Linie mit einem Knoten von H0 verbunden ist. Wir erweitern H0 zu H1 , indem wir
den Knoten und die Linie hinzufügen. Das wird so lange durchgeführt, bis alle Knoten
verbraucht sind. Schließlich ergibt sich ein Untergraph von G, der |V | Linien und damit
mehr als G hat, und das ist ein Widerspruch.
5. ⇒ 6. G bestehe aus k schwachen Zusammenhangskomponenten. Alle sind a-kreisfrei
und damit a-Bäume. Für jede schwache Zusammenhangskomponente gilt also auch 5.,
d. h. die Summe der Anzahl Linien muß |V | − k sein und das bedeutet k = 1, G muß
schwach zusammenhängend sein. Dann gibt es zwischen je zwei verschiedenen Knoten ein
einfachen a-Weg und das Hinzufügen einer Linie, die diese Knoten verbindet schließt einen
a-Kreis. Ist die hinzugefügte Linie eine Schlinge, geht die Kreisfreiheit auch verloren.
6. ⇒ 1. Es ist zu zeigen, daß G schwach zusammenhängend ist. Gäbe es mehr als eine
schwache Zusammenhangskomponente, so wähle man die Knoten u und v aus verschiedenen Komponenten und verbinde sie durch eine Linie. Es müßte jetzt a-Kreise in G geben
und die müßten natürlich durch die hinzugefügte Linie gehen. Da die neue Linie die einzige Verbindung zwischen den schwachen Zusammenhangskomponenten ist, müßte sie bei
jedem geschlossenen Weg mindestens zweimal durchlaufen werden und dieser könnte kein
a-Kreis sein.
2
386
14.4.3
KAPITEL 14. WEGE UND ZUSAMMENHANG
f-Bäume
In diesem Abschnitt soll der Begriff „Baum“ auf den Bearbeitungsmodus vorwärts übertragen werden. a-Bäume wurden als schwach zusammenhängende und a-kreisfreie allgemeine Graphen eingeführt. Was sollen nun f-Bäume sein? Sie sollten zugleich auch aBäume sein. Dann sind sie natürlich auch f-kreisfrei. Starken Zusammenhang kann man
jedoch nicht fordern, denn das würde bei Graphen mit Bögen die f-Kreisfreiheit zerstören. Das folgt aus Proposition 14.1. Wenn es schon nicht möglich ist, jeden Knoten eines
f-Baumes von jedem anderen Knoten über einen f-Weg zu erreichen, dann sollte es aber
mindestens einen Knoten geben, von dem jeder andere f-erreichbar ist. Daher wollen wir
einen allgemeinen Graphen einen f-Baum (f-tree) nennen, wenn er ein a-Baum ist und
es einen Knoten gibt, von dem alle anderen f-erreichbar sind. Diese Knoten sollen Wurzelknoten (root vertex) heißen. Ganz entsprechend werden für den Bearbeitungsmodus
rückwärts b-Bäume (b-tree) 1 definiert. Für f-Bäume (b-Bäume), die Digraphen sind, gilt
die folgende Proposition.
Proposition 14.7
1. Ein Digraph, der ein f-Baum ist, besitzt genau einen Knoten,
von dem alle anderen Knoten f-erreichbar sind. Dieser hat keinen Vorgänger, alle
anderen haben genau einen Vorgänger.
2. Ein Digraph, der ein b-Baum ist, besitzt genau einen Knoten, der von allen anderen
Knoten f-erreichbar ist. Dieser hat keinen Nachfolger, alle anderen haben genau
einen Nachfolger.
Beweis: Gäbe es zwei verschiedene Knoten, von denen jeweils alle anderen f-erreichbar
sind, so wären sie gegenseitig f-erreichbar. Damit wäre der Digraph nicht f-kreisfrei, also
auch nicht a-kreisfrei und somit kein f-Baum. Sei w der so eindeutig bestimmte Knoten.
Aus den gleichen Gründen kann w keinen Vorgänger haben. Alle anderen Knoten sind von
ihm f-erreichbar, müssen also Vorgänger aufweisen. Hätte ein Knoten zwei Vorgänger, so
gäbe es von w zu ihm zwei verschiedene einfache f-Wege, also auch zwei verschiedene
einfache a-Wege und der Graph wäre kein a-Baum. 2. ergibt sich aus 1., wenn man berücksichtigt, daß b-Erreichbarkeit die inverse Relation zu f-Erreichbarkeit ist.
2
2
Ein Digraph, der ein f-Baum ist, soll gerichteter Baum (Wurzelbaum , directed tree, rooted
tree) heißen. Der eindeutig bestimmte Knoten, von dem alle anderen Knoten f-erreichbar
sind, wird Wurzel (root) genannt. Ein Digraph, dessen schwache Zusammenhangskomponenten gerichtete Bäume sind, heißt gerichteter Wald (directed forest).
In ungerichteten Graphen fallen a-Bäume und f-Bäume zusammen. Von jedem Knoten
sind alle anderen f-erreichbar. Man kann auch leicht Graphen angeben, die sowohl Kanten
Nicht zu verwechseln mit B-Bäumen (siehe Unterabschnitt 9.4.3, Seite 299, oder Cormen/Leiserson/Rivest [CormLR1990], Kapitel 19).
2
Manchmal wird auch ein freier Baum, in dem ein Knoten besonders hervorgehoben wird, Wurzelbaum
genannt.
1
14.5. PARTIELLE ORDNUNG UND SCHICHTENNUMERIERUNG
387
als auch Bögen aufweisen und in denen es mehr als einen Knoten gibt, von dem alle
anderen f-erreichbar sind (Beispiel?). Auch den Begriff Wurzel kann man auf allgemeine
Graphen übertragen. Das soll in Abschnitt 14.5 geschehen.
Um zu entscheiden, ob ein gegebener Digraph ein f-Baum ist, kann man die Bedingungen
der Definition überprüfen oder den folgenden Satz benutzen.
Satz 14.6 Ein Digraph G(V, A, ϕ, ψ) ist genau dann ein gerichteter Baum, wenn die
folgenden Bedingungen gelten:
1. G ist f-kreisfrei und
2. jeder Knoten hat höchstens einen Vorgänger und
3. es gibt einen Knoten von dem jeder andere Knoten f-erreichbar ist.
Beweis: Es sei G ein gerichteter Baum. Dann hat er nach Definition und Proposition
14.7 die Eigenschaften 1., 2. und 3.
Es sei G ein Digraph, für den 1., 2. und 3. gelten. w sei ein Knoten, von dem allen anderen
f-erreichbar sind. Wegen der f-Kreisfreiheit ist w eindeutig bestimmt und kann keinen
Vorgänger haben. Alle anderen Knoten haben nach 2. dann genau einen Vorgänger. G ist
schwach zusammenhängend, denn es gibt von jedem Knoten u zu jedem verschiedenen
Knoten v einen a-Weg über w. Es werde angenommen, daß es in G einen a-Kreis gibt.
Wenn w auf diesem Kreis liegt, setzen wir y = w. Andernfalls wählen wir einen f-Weg
von w zu einem Knoten x des Kreises. Es sei y der erste Knoten des Kreises, den wir auf
diesem Wege treffen. y kann von x verschieden sein. Der eindeutig bestimmte Vorgänger
von y gehört nicht zum Kreis. Es sei v0 = y, l1 , v1 , . . . , vk−1, lk , vk = y ein a-Kreis auf
dem gleichen Träger. Da y entweder keinen Vorgänger hat oder dieser nicht zum Kreis
gehört, wird l1 in Vorwärtsrichtung durchlaufen. Da auch v1 nur einen Vorgänger hat, wird
auch l2 in Vorwärtsrichtung durchlaufen usw. Es müßten alle Bögen in Vorwärtsrichtung
durchlaufen werden und der Kreis wäre im Widerspruch zu 1. ein f-Kreis.
2
14.5
Partielle Ordnung und Schichtennumerierung
Ist in einem Dag v von u f-erreichbar, so ist u nicht von v f-erreichbar, denn es gibt keine
starken Zusammenhangskomponenten. Daher ist für Dags f-Erreichbarkeit eine strikte
partielle Ordnung auf der Menge der Knoten. Zu Ordnungsrelationen siehe Abschnitt
A.5, Seite 616, im Anhang.
In allgemeinen Graphen gibt es i. a. starke Zusammenhangskomponenten und für deren
Knoten ist f-Erreichbarkeit keine partielle Ordnung, sondern eine Äquivalenzrelation. Es
gibt aber durchaus eine partielle Ordnung zwischen den Knoten einer starken Zusammenhangskomponente und Knoten, die nicht zu ihr gehören. Das besagt der folgende Hilfssatz.
388
KAPITEL 14. WEGE UND ZUSAMMENHANG
Dazu sei C(u) = {u}, wenn u ein Knoten ohne Rückkehr ist, und die von u erzeugte starke
Zusammenhangskomponente sonst. Wir nennen C(u) die von u erzeugte Schichtenklasse
(level class).
Hilfssatz 14.5 Es sei G ein allgemeiner Graph und es gebe einen f-Weg von u nach v,
aber keinen von v nach u. Dann gibt es für alle u0 ∈ C(u) und für alle v 0 ∈ C(v) einen
f-Weg von u0 nach v 0 , aber keinen von v 0 nach u0 .
Beweis: Wenn man von u0 in C(u) nach u kommen kann, dann kann man auf einem
f-Wege über u auch von u0 nach v kommen und von dort innerhalb C(v) nach v 0 . Gäbe es
einen f-Weg von v 0 nach u0, so könnte man analog einen f-Weg von v nach u finden. 2
Wir haben damit eine strikte partielle Ordnung „≺“ auf der Menge der oben definierten
Klassen C(u). Diese wollen wir benutzen, um allen Knoten einer Klasse C(u) und damit
auch den Klassen selbst eine Schichtennummer (level number) lv(C(u)) zuzuordnen:
1. Hat C(u) bezüglich ≺ keinen Vorgänger, so setzen wir lv(C(u)) := 0.
2. Andernfalls nehmen wir das Maximum der Vorgänger plus 1:
lv(C(u)) :=
max lv(C(v)) + 1
C(v)≺C(u)
C(u) heißt Anfangselement (starting element), wenn es bezüglich ≺ minimal ist. Es heißt
Endelement (end element), wenn es bezüglich ≺ maximal ist. Alle Anfangselemente haben die Schichtennummer 0. Endelemente haben i. a. verschiedene Schichtennummern.
Die Schichtennumerierung eignet sich gut zur graphischen Darstellung von allgemeinen
Graphen, insbesondere zur Darstellung abgeleiteter Graphen. Beispiele sind in Unterabschnitt 14.7 angegeben.
Wird auf einem f-Weg (a-Weg) eine Kante durchlaufen, dann ändert sich die Schichtennummer der beiden so gegebenen Knoten des Weges nicht. Das gleiche gilt, wenn ein
Bogen, der zu einer starken Zusammenhangskomponente gehört, durchlaufen wird. Wird
ein Bogen des externen Dags in f-Richtung durchlaufen, so ist die Klasse des Startpunktes
des Bogens bezüglich ≺ Vorgänger der Klasse des Zielpunktes. D. h. die Schichtennummer
wird größer. Entsprechend wird die Schichtennummer kleiner, wenn ein Bogen des externen Dags in b-Richtung durchlaufen wird. Wir wollen das als Proposition formulieren.
Proposition 14.8
1. Beim Durchlaufen eines f-Weges kann die Schichtennummer
nicht fallen. Sie wächst genau dann, wenn ein Bogen des externen Dags durchlaufen
wird.
14.5. PARTIELLE ORDNUNG UND SCHICHTENNUMERIERUNG
389
2. Beim Durchlaufen eines a-Weges ändert sich die Schichtennummer genau dann,
wenn ein Bogen des externen Dags durchlaufen wird. Sie wächst, wenn der Bogen
in f-Richtung durchlaufen wird. Sie fällt, wenn der Bogen in b-Richtung durchlaufen
wird.
Anmerkung 14.5 Die Schichtenummer entspricht der Länge einer maximalen Vorgängerkette. Es ist möglich und manchmal zweckmäßig die Schichtennumerierung „von unten“
vorzunehmen, d. h. statt Vorgänger Nachfolger zu verwenden.
2
Die partielle Ordnung der Klassen C(u) hilft uns, eine Aussage über die Orientierbarkeit
von Kanten zu beweisen und damit den Beweis von Proposition 14.2 zu vervollständigen.
Eine Kante heißt orientierbar (orientable), wenn sie zu einem Bogen gemacht werden kann
und ihre zugehörige starke Zusammenhangskomponente unverändert bleibt.
Proposition 14.9 Eine Kante l eines allgemeinen Graphen ist genau dann orientierbar,
wenn sie auf einem a-Kreis liegt, der ganz in ihrer starken Zusammenhangskomponente
verläuft.
Beweis: Eine Schlinge ist ein a-Kreis und orientierbar. Sei l keine Schlinge. Wir betrachten die starke Zusammenhangskomponente von l als selbständigen Graphen H. Gibt
es darin keinen a-Kreis durch l, so ist l nach Proposition 14.3 eine Brücke und daher der
einzige einfache a-Weg zwischen ihren Inzidenzpunkten. Macht man aus l einen Bogen, so
gibt es einen Inzidenzpunkt, der vom anderen nicht mehr f-erreichbar ist. D. h. H ist nicht
mehr stark zusammenhängend. Daran ändert sich auch nichts, wenn l in ganz G keine
Brücke ist, denn kein a-Weg von einem Inzidenzpunkt zum anderen, der z. T. außerhalb
von H verläuft, kann ein f-Weg sein.
Es liege l auf einem a-Kreis. Wir betrachten den Graphen H − l, der aus H durch Entfernen der Kante l entsteht. Da l keine Brücke ist, ist er schwach zusammenhängend. Wir
zerlegen ihn in starke Zusammenhangskomponenten und den externen Dag. Ergibt sich
eine einzige starke Zusammenhangskomponenten, so hat die Entfernung von l den starken
Zusammenhang von H nicht beeinflußt und wir können l in jeder der beiden Richtungen
orientieren. Ergeben sich mehrere Klassen C(u), so müssen sie in der partiellen Ordnung
so angeordnet sein, daß sie nach Hinzufügen von l zu einer einzigen Klasse zusammenfallen. Das ist genau dann möglich, wenn es genau ein Anfangselement (Schichtennummer
0) und genau ein Endelement (maximale Schichtennummer) gibt und wenn weiter ein
Inzidenzpunkt von l im Anfangselement und der andere im Endelement liegt. Wenn wir
l nun so orientieren, daß der Bogen vom Inzidenzpunkt mit höchster Schichtennummer
zum Inzidenzpunkt mit Schichtennummer 0 zeigt, bleibt H stark zusammenhängend. Bei
entgegengesetzter Orientierung wird der starke Zusammenhang verletzt.
2
Als weitere Anwendung der Schichtennummern wollen wir die Überlegungen von Abschnitt 14.4 fortsetzen und Knoten mit „Wurzeleigenschaft“ definieren und bestimmen. In
390
KAPITEL 14. WEGE UND ZUSAMMENHANG
Erweiterung der Definition von Seite 386 wollen wir den Begriff Wurzelknoten auch für
allgemeine Graphen, die keine a-Bäume sind benutzen. In einem allgemeinen Graphen ist
also ein Wurzelknoten ein Knoten, von dem es zu jedem anderen Knoten einen f-Weg gibt.
Die Behauptungen der folgenden Proposition sind unmittelbar klar.
Proposition 14.10 Es sei G(V, E, A, ϕ, ψ) ein allgemeiner Graph.
1. Enthält G einen Wurzelknoten, so ist G schwach zusammenhängend.
2. Ist u ein Wurzelknoten von G, so sind auch alle u0 ∈ C(u) Wurzelknoten von G.
3. Ein Knoten u ist genau dann Wurzelknoten von G, wenn lv(C(u)) = 0 und lv(C(v)) >
0 für alle C(v) 6= C(u).
Allgemeine Graphen, die Wurzelknoten aufweisen, sollen nun näher untersucht werden.
Wir wollen der Frage nachgehen, ob es Knoten gibt, die von einem Wurzelknoten auf mehr
als einem einfachen oder linieneinfachen Weg erreicht werden können. Es sei G(V, E, A, ϕ, ψ)
ein allgemeiner Graph und u ∈ V ein Wurzelknoten. G ist schwach zusammenhängend.
Um Trivialfälle auszuschließen, wollen wir annehmen, daß G mindestens eine Linie enthält.
Wir wollen wir die folgenden Fälle unterscheiden:
1. G ist a-kreisfrei.
G ist ein a-Baum und alle einfachen f-Wege von einem Wurzelknoten zu einem
anderen Knoten sind eindeutig bestimmt. Nicht-einfache f-Wege gibt es nur, wenn
der Graph Kanten aufweist, und sie entstehen nur durch Mehrfachdurchlauf von
Kanten, sind also nicht linieneinfach.
2. G enthält keine f-Kreise, wohl aber einen a-Kreis.
Die starken Zusammenhangskomponenten von G, wenn es sie gibt, bestehen nach
Proposition 14.1 nur aus Kanten und sind nach Proposition 14.2 a-kreisfrei. Kein
a-Kreis in G ist vollständig in einer starken Zusammenhangskomponente enthalten.
In Proposition 14.11 wird bewiesen, daß dann in G ein Knoten u0 existiert, zu dem
von u zwei verschiedene einfache f-Wege führen.
3. G enthält einen f-Kreis.
In Proposition 14.11 wird bewiesen daß es einen Knoten u0 gibt, zu dem von u zwei
verschiedene linieneinfache f-Wege führen. Allerdings kann es sein, daß einer der
Wege die Länge 0 hat.
Proposition 14.11 Es sei G ein allgemeiner Graph und u in ihm ein Wurzelknoten.
1. Enthält G einen f-Kreis, so gibt es einen Knoten u0 , der von u auf zwei verschiedenen
linieneinfachen f-Wegen erreichbar ist. Einer der Wege kann die Länge 0 haben.
14.5. PARTIELLE ORDNUNG UND SCHICHTENNUMERIERUNG
391
2. Enthält G einen a-Kreis, der nicht vollständig in einer starken Zusammenhangskomponente enthalten ist, so gibt es einen von u verschiedenen Knoten u0 , der von
u auf zwei verschiedenen einfachen f-Wegen erreichbar ist.
Beweis: 1. Sei x0 , l1 , x1 , l2 , . . . , xn−1 , ln , xn = x0 ein f-Kreis. Liegt u auf dem Kreis, so
wählen wir xo = u. Andernfalls wird ein einfacher f-Weg von u zu einem Knoten des
Kreises gewählt. x0 soll der der erste Knoten des Kreises auf diesem Wege sein. Der f-Weg
von u bis x0 ist der erste, der um den f-Kreis verlängerte Weg ist der zweite f-Weg von u
nach x0 . Beide sind linieneinfach.
2. Ein a-Kreis, der nicht vollständig in einer starken Zusammenhangskomponente enthalten ist, kann keine Schlinge sein und hat mindestens die Länge 2. Alle seine Linien sind
paarweise verschieden. Er muß Knoten mit unterschiedlichen Schichtennummern durchlaufen. Es sei u0 ein Knoten mit höchster Schichtennummer. Diese ist mindestens 1. Unter
den Vorgängern von u0 auf dem a-Kreis gibt es einen ersten, der eine andere, also niedrigere Schichtennummer hat. Das sei w. Entsprechend gibt es unter den Nachfolgern auf
dem a-Kreis einen auch einen ersten, der eine niedrigere Schichtennummer hat. Das sei
w 0 . Der Fall w = w 0 kann auftreten. Der unmittelbare Nachfolger von w auf dem a-Kreis
sei w1 , die w und w1 verbindende Linie des a-Kreises sei l1 . Der unmittelbare Vorgänger
von w 0 auf dem a-Kreis sei w2 , die w 0 und w2 verbindende Linie des a-Kreises sei l2 . Die
Fälle u0 = w1 und u0 = w2 sind möglich.
u0 , w1 und w2 haben die gleiche (höchste) Schichtennummer und gehören, falls mindestens
zwei von ihnen verschieden sind, zur gleichen starken Zusammenhangskomponente. Wenn
alle drei Knoten zusammenfallen, kann es sein, daß u0 ein Knoten ohne Rückkehr ist. Wir
wählen nun einfache f-Wege (eventuell der Länge 0) von u nach w und von u nach w 0.
Auf ihnen haben wir nach Proposition 14.8 nicht-fallende Schichtennummern, so daß u0
auf keinem der Wege liegen kann. Die f-Wege können um l1 bzw. um l2 verlängert werden,
denn beides sind Bögen des externen Dags und zeigen zum Knoten mit höherer Schichtennummer. Wir erhalten einfache f-Wege von u nach w1 und von u nach w2 . Die Wege
sind mindestens in l1 und l2 verschieden. Ist w1 = u0 , so ist der erste Weg vollständig. Andernfalls verlängern wir ihn innerhalb der starken Zusammenhangskomponente um einen
einfachen f-Weg von w1 nach u0 . Im Fall u0 6= w2 führen wir eine entsprechende Verlängerung des zweiten f-Weges durch und erhalten schließlich zwei verschiedene einfache f-Wege
von u nach u0.
2
Zur Behauptung, daß auch Wege der Länge 0 zugelassen werden müssen und daß die Wege
manchmal keine einfachen Wege sein können, siehe das folgende Beispiel.
Beispiel 14.1 Abbildung 14.2 zeigt einen allgemeinen Graphen, der aus zwei schwachen
Zusammenhangskomponenten besteht. Jede enthält eine starke Zusammenhangskomponente mit f-Kreis. In der ersten schwachen Zusammenhangskomponente gibt es nur den
Wurzelknoten u. Der einzige Knoten, den man von ihm auf zwei verschiedenen linieneinfachen f-Wegen erreichen kann, ist u selber. Einer der Wege hat die Länge 0, der andere
392
KAPITEL 14. WEGE UND ZUSAMMENHANG
...........
.... ....
. ....... .
............
...... ........
........ ..... .....
.
.....
..............................................
...
...
...... ......
................
.....
u
..................
.
.....
..
...
................
..........
..........
.... ......
.... ...... ......
....
.......................................
.
...
.... .... ........
..
.
.
.
..........
.............
...
...
...
....
...
...
...
...
...
...... ..
.
.
...
.......
.
.
..
..............
..............
..............
.
.
.
................
.
.
.
.
.
.
........ ...
........ ...
...
.
........................................
.
.....
0............................................... .............................................. ......
...
.
.
...
.
..... ....
..... ....
...... .....
...............
........
........
.....
U
U
V
Abbildung 14.2: f-Wege vom Wurzelknoten
ist eine Schlinge. In der zweiten schwachen Zusammenhangskomponente gibt es die Wurzelknoten U und U 0 . Der einzige Knoten, den man von ihnen auf zwei verschiedenen
linieneinfachen f-Wegen erreichen kann, ist V . Für jeden Wurzelknoten ist einer der Wege
nicht einfach.
2
Anmerkung 14.6 Auch in einem allgemeinen Graphen, der f-Kreise aufweist, kann es
einen a-Kreis geben, der nicht vollständig in einer starken Zusammenhangskomponente
verläuft. Für ihn gilt, wenn u ein Wurzelknoten ist, die zweite Aussage von Proposition
14.11 natürlich auch.
2
14.6
Klassifizierung von Zusammenhangskomponenten
Wir wollen in diesem Abschnitt allgemeine Graphen nach ihren Zusammenhangseigenschaften zerlegen und klassifizieren. In Unterabschnitt 14.2 haben wir eine hierarchische
Zerlegung gefunden. Tabelle 14.1 zeigt die zugehörigen drei Ebenen: Allgemeiner Graph,
schwache Zusammenhangskomponenten, starke Zusammenhangskomponenten. Darüber
'
1
2
3
4
5
&
Ebene
Ebene
Ebene
Ebene
Ebene
0 allgemeiner Graph
1
uneigentliche schwache Zusammenhangskomponente
1
eigentliche schwache Zusammenhangskomponente
2
starke Zusammenhangskomponente
2
externer Dag
Tabelle 14.1: Zerlegung in starke und schwache Zusammenhangskomponenten
hinaus wollen wir die schwachen Zusammenhangskomponenten danach klassifizieren, ob
sie a-kreisfrei sind oder nicht und ob sie starke Zusammenhangskomponenten enthalten oder nicht. Starke Zusammenhangskomponenten wollen wir einteilen in solche, die
f-kreisfrei sind, und solche, die einen f-Kreis aufweisen. Diese Eigenschaften sind nicht
voneinander unabhängig. Für alle schwachen Zusammenhangskomponenten läßt sich nach
Proposition 14.10 bestimmen, ob es Wurzelknoten gibt oder nicht.
$
%
14.6. KLASSIFIZIERUNG VON ZUSAMMENHANGSKOMPONENTEN
393
A. a-kreisfreie schwache Zusammenhangskomponente: Es handelt sich um einen
a-Baum, d. h. zwischen je zwei verschiedenen Knoten gibt es genau einen einfachen a-Weg.
1. Ohne starke Zusammenhangskomponenten
Es handelt sich um einen Dag. Wenn es Wurzelknoten gibt, dann genau einen (siehe
Abschnitt 14.5) und die schwache Zusammenhangskomponente ist ein f-Baum.
2. Mit starken Zusammenhangskomponenten
Die starken Zusammenhangskomponenten bestehen nur aus Kanten. Als Untergraphen
sind sie freie Bäume. Wenn es Wurzelknoten gibt, ist jeder Wurzel eines f-Baumes.
B. Nicht a-kreisfreie schwache Zusammenhangskomponente:
1. Ohne starke Zusammenhangskomponenten
Es handelt sich um einen Dag, aber nicht um einen a-Baum. Wenn es Wurzelknoten gibt,
existiert genau einer. Mindestens ein anderer Knoten ist dann nach Proposition 14.11 auf
mehr als einem einfachen f-Weg erreichbar.
2. Mit starken Zusammenhangskomponenten
2.a. Jede f-kreisfreie starke Zusammenhangskomponente ist ungerichtet (Proposition 14.1)
und als Untergraph ein freier Baum. Enthält die schwache Zusammenhangskomponente
ausschließlich f-kreisfreie starke Zusammenhangskomponenten, so kann kein a-Kreis ein
f-Kreis sein. D. h. jeder a-Kreis muß zwei Bögen in entgegengesetzter Richtung enthalten.
Wenn es Wurzelknoten gibt, ist von jedem ein von ihm verschiedener Knoten auf zwei
verschiedenen einfachen Wegen erreichbar (Proposition 14.11).
2.b. Im allgemeinen Fall gibt es starke Zusammenhangskomponenten mit f-Kreisen. Für
diese ist eine weitergehende Zerlegung mit Hilfe der Biblockzerlegung möglich und sinnvoll
(siehe Abschnitt 16.7, Seite 458). Wenn es Wurzelknoten gibt, kann von jedem von ihnen
ein Knoten auf zwei verschiedenen linieneinfachen Wegen erreicht werden (Proposition
14.11).
Wir werden in Kapitel 15 Algorithmen zur Bestimmung der einzelnen Typen schwacher
und starker Zusammenhangskomponenten sowie der Schichtennummern angeben.
Beispiel 14.2 Die eingeführten Begriffe sollen am Beispiel von Graph1, Seite 377, erläutert werden. Siehe dazu Tabelle 14.2. Es gibt keine uneigentlichen Zusammenhangskomponenten. Die schwachen Zusammenhangskomponenten sind W1 , W2 und W3 . W1 enthält
auf Schicht Nummer 0 den Knoten ohne Rückkehr K12, auf Schicht Nummer 1 die starke
Zusammenhangskomponente S11 , auf Schicht Nummer 2 die starken Zusammenhangskomponenten S12 und S13 sowie auf Schicht Nummer 3 die starke Zusammenhangskomponente
S14 . Für jede starke Zusammenhangskomponente sind ihre Knoten und ihre schwachen
Verheftungspunkte zum externen Dag angegeben. E1 ist der externe Dag von W1 , festgelegt durch seine Bögen. In analoger Weise ist in der Tabelle die Struktur der schwachen
Zusammenhangskomponenten W2 und W3 dargestellt.
2
Ein weiteres Beispeil ist in Abschnitt E.3, Seite 660 im Anhang zu finden.
394
'
•
KAPITEL 14. WEGE UND ZUSAMMENHANG
W1
◦
◦
=
0
1
◦
2
◦
2
◦
3
◦
•
•
W2
◦
◦
=
0
1
◦
W3
◦
=
0
◦
1
&
◦
{K00, K01, K02, K03, K06, K07, K08, K09, K11, K12, K13, K19, K20, K21}
{K12}
Knoten ohne Rückkehr
S11 = {K00, K06, K11, K13}
starke Zusammenhangskomponente
Schwache Verheftungspunkte: K00, K11, K13
S12 = {K01, K02, K03, K09}
starke Zusammenhangskomponente
Schwache Verheftungspunkte: K01, K09
S13 = {K07, K19, K20, K21}
starke Zusammenhangskomponente
Schwache Verheftungspunkte: K19, K21
S14 = {K08}
starke Zusammenhangskomponente
Schwache Verheftungspunkte: K08,
E1 = {dK00K01, dK00K21, dK09K08, dK12K11, dK12K13,
dK19K08} externer Dag
{K04, K05, K10, K16, K17, K18}
{K10 }
Knoten ohne Rückkehr
S21 = {K04, K05, K16, K17, K18} starke Zusammenhangskomponente
Schwache Verheftungspunkte: K18
E2 = {dK10K18} externer Dag
{K14, K15}
S31 = {K14}
starke Zusammenhangskomponente
Schwache Verheftungspunkte: K14
S32 = {K15}
starke Zusammenhangskomponente
Schwache Verheftungspunkte: K15
E3 = {dK14K15} externer Dag
Tabelle 14.2: Zerlegung von Graph1 in schwache und starke Zusammenhangskomponenten
14.7
Abgeleitete Graphen
Es ist oft zweckmäßig, zusätzlich zu einem Graphen weitere Graphen, die von ihm abgeleitet (derived) sind, zu betrachten. Zum einen sind diese Graphen Vereinfachungen,
zum anderen heben sie Struktureigenschaften hervor. Zwei abgeleitete Graphen sollen im
folgenden eingeführt werden. Andere werden an späterer Stelle behandelt.
Kondensierter Digraph. Es sei G(V, E, A, ϕ, ψ) ein allgemeiner Graph. V wird zerlegt in die Mengen C1 , C2 , . . . , Ck . Dabei ist Ci die Menge der Knoten einer starken Zusammenhangskomponente von G oder eine durch einen Knoten ohne Rückkehr gebildete
einelementige Knotenmenge. Es wird ein Digraph CG gebildet, dessen Knoten die Ci sind.
Von Ci zu Cj wird genau dann ein Bogen eingeführt, wenn es in G Knoten vi ∈ Ci und
vj ∈ Cj und einen Bogen von vi zu vj gibt. CG soll der zu G gehörige kondensierte Digraph (reduzierter Digraph, condensed digraph, reduced digraph) heißen. CG ist f-kreisfrei
$
%
14.7. ABGELEITETE GRAPHEN
395
und es gibt genau dann in G einen f-Weg (b-Weg, a-Weg) von vi ∈ C1 nach vj ∈ Cj mit
i 6= j, wenn es in CG einen f-Weg (b-Weg, a-Weg) von Ci nach Cj gibt (warum?). CG
besitzt keine Mehrfachbögen.
Komponentengraph. Der kondensierte Digraph gibt wichtige Eigenschaften eines allgemeinen Graphen wieder. Wenn man nach wie vor die starken Zusammenhangskomponenten zu einem Knoten zusammenschrumpfen will, ihre Einbettung in die entsprechenden externen Dags aber detaillierter braucht (zum Beispiel will man die schwachen
Verheftungspunkte haben), dann ist der Komponentengraph (component graph) geeigneter. Er wird gebildet, indem man den externen Dag (also einschließlich der schwachen
Verheftungspunkte) unverändert läßt. Zusätzlich führt man für jede starke Zusammenhangskomponente einen neuen Knoten ein. Diesen verbindet man mit jedem der schwachen Verheftungspunkte der starken Zusammenhangskomponente durch eine ungerichtete
Kante. Dies ist keine Kante des Originalgraphen. Sie wird deshalb auch Metakante genannt.
Auch der Komponentengraph ist f-kreisfrei. Er kann Mehrfachbögen aufweisen, nämlich
die, die es im externen Dag gibt. Sind u und v Knoten im Originalgraphen, die nicht zur
gleichen starken Zusammenhangskomponente gehören, so gibt es genau dann einen f-Weg
(b-Weg, a-Weg) von u nach v, wenn es im Komponentengraphen einen entsprechenden
Weg gibt. Dabei werden innere Knoten einer starken Zusammenhangskomponente durch
deren Knoten im Komponentengraph repräsentiert3 .
Beispiel 14.3 Als Beispiele sollen der kondensierte Digraph und der Komponentengraph
von Graph2, Seite 396, erläutert werden. Graph2 ist schwach zusammenhängend. Die
Zerlegung in starke Zusammenhangskomponenten und externen Dag zeigt Tabelle 14.3.
Der zugehörige kondensierte Digraph ist in Abbildung 14.4, Seite 397, und der zugehörige
Komponentengraph ist in Abbildung 14.5, Seite 398, zu sehen. In beiden Abbildungen
wird die Schichtennumerierung durch horizontale punktierte Linien dargestellt. {v12 } hat
die Schichtennummer 0.
2
Manchmal, wie zum Beispiel im Komponentengraph auf Seite 560, ist es wünschenswert, alle Knoten
in der starken Zusammenhangskomponente aufzuführen und die schwachen Verheftungspunkte hervorzuheben.
3
396
KAPITEL 14. WEGE UND ZUSAMMENHANG
'
SC1 = {V 01, V 02, V 03, V 04, V 05, V 06, V 07, V 08, V 09, V 10, V 11, V 13, V 14, V 15,
V 16, V 17, V 20, V 25}
Schwache Verheftungspunkte: {V 02, V 09, V 13, V 17, V 20, V 25}
SC2 = {V 18, V 19, V 22, V 23}
Schwache Verheftungspunkte: {V 18, V 23}
ED = {dV 12V 09, dV 12V 17, dV 12V 21, dV 13V 18, dV 17V 18, dV 17V 21, dV 18V 21,
dV 23V 24, dV 20V 24, dV 02V 26, dV 25V 26}
&
Tabelle 14.3: Zerlegung von Graph2 in starke Zusammenhangskomponenten
.......................
........................
........................
......
......
....
....
......
....
....
....
...
...
...
...
..
..
...
...
...
...
..
..
.
..
...... ....
.
..
.
.
.....
.... .
..
...
............................................................................
.....................................................................................................................................................
..
.
.
.
...
.
.
.
...
..
...
.
.
..
.
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
........... ...
....
.
.
.....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
........
....
..
.
.
...
....................
....... . ......................................
..... ...................
.......
...
........
.............
.........
.......
........ ............. .............
.
...
..........
... ......
.....
...
...
...
....
...
...
...
.
...
.
.
.
...
.
...
.
.
.
.
.
...
.
.
.
.
...
..
...
...
...
....
...
..
..
..
.
.
.
.
.
...
.
.
.
...
...
.
.
.
.
.
...
.
...
.
.
.
.
.
...
............... .....
.
.
.
.
.
.
.
.
.
...
.
.
.
.
.
.
... ..
.... .. .............................
..
.
.
.
.
...
.
.
.
...
.
.
.
.
.........
....
.
.
.
.
.
.
.
.
.
.
.
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
......
......
......
.... ...............
.....
.
.....
.....
.
.
.
.
......
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
...
...
..........
..
..
..
..
.
.
.
.
...
.
.
.
.
.
.
.
...
...
...
.
.
.
.
..
.
.
.
....
.
.
..
.
.
..
..
..
.
.
...........................................................................
.
....
....
..
.
....
..
.
..
..
...... ....
.
.
...
.
.
.
...
...
...
..
.
.
.
.
.
.
...
.
.
.
.
.
.
.
...
...
.
...
.
.
....
.
..............
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
......
......
. .
......
..................... .............. ..................................
..........................
..........................
.............................. ............... .............................
.......
.... .............. ...
.........
....
.........
...
....
...
......
...
...
...
...
...
...
...
...
..
.
.
.
.
... ...
.
...
.
.
.
.........................................................................
... ...
... ...
.
...
.
.
...
. ...
.. ......
... ...
.
.
...
.
.
...
... ..
. ...
................
.
.
.
.
... ....
...
.
.
.
. ..
..... ......
.
.. ..............
.
.
... .....
.
.
.
.
.
.
.
...
.
.
.
.
.......
. ........
.. ..
....
.
.
... ..........................
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
.
.
.
.
.
.
..
....... .......
...
.
.
.
.
.
.
.
.
.
.
.
.
...
...
............
...............
.
.
.
.
.
.
.
.
.
.
.
........................
.......
....
.........................
......................
.....................
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.......
....
....
.
....
.... ..........
..
...
...
.
.
.
.
...
.
.
....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.......
...
...
..............
.
.
...
.
.
.
...
....
.
.
.
..
..
.
..
..
.
.. ...
..
..
.. .......
...
..
...
........................................................................
.
.........................................
.
.
.
.
.
.
..
...
...
...
. ...
.
.
. ..
.
.
.
..
.
.
...
.
.
.
.
...
...
...
..
.
..
.
...
...
.
.
.
.
.
.
.
.
.
.....
....
.....
.
.
..
......
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.......................
.......................
........... ...................... .....
........... .......................
...
...
....
.. .
.. ..
....
....
...
.... .
..
....
......
...
...
...
.. .....
...
...
....
....
.
....
.
.
.
.
...
.
.
.
.
.
.
....
.......
..
...
.
..
...
.
.
.
.
.
.
.
.
.
.
.... .
......
.
.
..
.
...
.
.
.
.
.
.
.
.
.
.
.
...... .................
.
..................
.................. .....
...
.
.
.................... .....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.....
.....
........
.......
.......
...
.
.
....
....
.
.
.
.
.
.
.
.
.
.
.
.....
.
.
.
.
.
...
...
...
.
.
...
..
...
.
.
...
...
....
...
....
....
.
.
...
...
..
...
...
.
..
...
....
....
....
.
.
.
...
...
....
...
...
......................................................................
........................................................................
...
.
.
.
.
.
...
...
.
.
.
.
.
.
.
.
...
...
...
.
.
.
..
...
..
.
.
.
...
.
.
.
.
.
.
.
.
.
.....
....
....
.
....
..
.
...
.....
.
.
.
.
.
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.......
.
.
.
.
.
.
.
.
.
........ .......
......... ......... ............
...
.. .......................... .....
...................... ......
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.. ....
.
....
.
...
...
...
.
.
.
.
.
.
.
....
...
...
....
.
.
.
.
.
.
.
.
.....
.
.
.
...
....
...
...
..
.
...
.
.
.
.
....
...
.
.
.
...
....
..
..........
...
.
.
...
.
.
.
.
.
...
....
.... .
......
..
.
...
.
.
.
.
.
.
.
.
.
.........
...
.......
....
.....
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.......
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
........
............
...
............
...
....
....
..
..
...
...
...
...
..............
...
...
...
...
......
..
...
...
..
.
...
.
.
.
...
...
.........................
...
.
..........................
..
.
.
.
.
.
.
.
.
.
.
....
....
...
...
..
......................
...
..
.
.
.
..
.
.
.
.
.
.
.
.
.......................
...
...
...
...
...
.
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...........................
...
.. ..
...
...
..
.
.
..
.
.
.
.
.
.
.
.
.
......
.
.
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.......
.. ..
.
..................................................................
.........................
...
......................
...
...
.. ........
...
.
...
...
...
...
....
....
...
...
.
...
...
....
...
...
...
.............
...
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
.....................
......................
.
....
....
.. .
......
..
...
......
....
....
...
........ ...
...
.....
...
...
...
...
........ ...
......
. ...
..
...
.
.....
.
.
.
.
.
.....
.
.
.
...
...
...
. ...
...
.
.
.
.
...
.
.
.
...
..
. ...
...
.
.
.
.
...
.....
.
.
...
.
.
...
. ....
.....
.
.
..
...
.
.
....
.
.
.
.
..
. . ... .
....
.
... . .. ... ..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...........
........
. .........
...
........... ........
.
.
.
.
.
.
.
.
....
...
..... ...
.............
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.......
.......
....... .......
.. ..............
......
.....
.......
......
....
....
....
..................
....
....
....
...
....
....
...
...
...
...
...
...
...
..
...
...
.
.
.
.
.
.
..
.
.
....
...
..
..
.. .
.. .
.
..
...
..
................................................................................................
.............................................................................
....
..
.
.. ..
.. ..
...
...
.
...
...
..
.
.
.
.
.
.
.
...
.
.
.
.
.
.
.
...
...
...
.
.
.
.
....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.....
......
......
......
.
...........................
..........................
..........................
.........................
V01
V26
V02
V03
V04
V09
V12
V07
V08
V10
V11
V13
V14
V16
V15
V17
V21
V25
V06
V05
V20
V18
V19
V22
V23
Abbildung 14.3: Graph2
V24
$
%
14.7. ABGELEITETE GRAPHEN
397
..................
....
.......
..
....
..
...
..
...
...
...
...
..
.
.
...
..
.
......
.
.
......................
...
..
.
. . . ..... . ...... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
...
....
...
...
...
...
...
...
...
...
...
...
...
...
...
.....
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
.....
...
..
...
....
.. .
.........................................
..
............... ...................
........
...........
.......
...
......
...
.
.
.
.
......
....
.
....
.
.....
.
.
...
.
....
...
.
...
.
...
..
.
...
...
.
...
...
...
.
.
...
...
.
.
...
...
...
.
...
...
..
.
...
.
...
.
..
.
...
...
...
.
..
....
...
...
..
..
...
.
.
...
...
..
....
1
...
..
...
.
.
.
...
.
...
.
.
...
...
...
...
...
...
...
.
...
..
....
..
...
.
...
.
...
.
...
...
...
...
...
....
...
...
...
...
...
.
.
...
.
.....
.
. ..
......
...... ......
...
......
....
......
.......
...
.......
....
..........
...
.........
....
.. ...................................................
...
.
....
...
...
..
....
....
.
...
...
....
...
..
.
...
....
...
...
..
.
.
....
...
...
...
.
.
....
.
...
..
.
.
....
.....
.
.
...
...
....
...
..
.
.
...
....
..
.....
....
.
....
...
..
..
....
.
....
.
.
..
...
....
..
...
.
.
...
....
.
..
...
.
.
.
....
...
....
..
...
.
....
...
...
....
..
....
.
...
...
....
...
..
.
.
...
....
...
...
..
.
...
....
...
...
..
....
.
.
...
..
..
....
.....
.
.
...
...
....
..
...
.
.
....
...
..
...
.....
.
....
...
...
...
..
.
.
.
. . . ... . . . . . . . . ... . . . . . . . . . . ... . . . . . . . .... . . . . . . . . . . . . . . . . . ....... . . . . . . . . . . . .
....
...
.........
..
.....
.
........
....
...
..
...
....
.
...
.
..
....
...................................
...
...
........
......
...
....
.
.
..
.
.
.
.
.
.....
..
....
...
...
.
.
..
.
.
.
.
.
.
....
....
...
...
...
..
.
.
.
....
.
.
...
..
..
....
...
.
.
.
.
.....
...
...
.
....
..
.
...
.
.
..
...
.. ..
..
.
..
...
.
.
.
.
................ ....................
...
..
..
...
..
........
.....
.
.
.
...
..
...
..
...
..
.
.
...
.
.
...
...
..
.
.
.
.
..
....
.
.
..
2
...
..
...
..
...
...
.
.
.
.
...
...
...
..
.
..
.
...
.
.
.
.
...
...
...
...
.
.
..
.
.
.
.
.
....
.
.....
...
...
..
.
..
.
.
.
..
.
.
.
.
.
.
.
.
.
.
...
.
.
.
.
.
.
.............
.
...
..
...... .......
...
...
...
......
......
......
...
...... ........
...
...
......
.......
....
.......
......
............
...
...
...
.....
.......................
......
.
.
..
.
.
..
.
.
.
.
....
..
...
.
..
....
......
.. .
.. ... . . ..
..
......
. . . ........... .............. . . ............ . . . . . . . . . . . . . . . . . . . . . . . ........ ... ............... . . . . . . . . . . . . . . . . . . . . . . . . .
....... .................
...................... ..............
.
.
.
.
.
.
......
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.........
.
.... .............
...
.
....
.
.
.
.
.
.
.
...
...
.
..
...
..
...
....
....
..
..
..
.
.
...
.
...
...
..
...
.
.
.
.
.
.....
.....
..........................
........................
V12
SC
SC
V21
V26
V24
Abbildung 14.4: Kondensierter Digraph zu Graph2
398
KAPITEL 14. WEGE UND ZUSAMMENHANG
..................
....
.......
..
....
..
...
..
...
...
...
...
..
.
.
...
.
.
.
......
.
.
......................
.. .. ...
. . . ..... .... ..... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
..
.... .... ....
... ... ...
... ... ...
... ... ...
.
..... ..... .....
.. .. ...
... ... ...
... ... ...
... ... ...
...
...
...
...
...
...
..
...
...
...
...
...
...
...
...
...
.....
...
...
..
...
...........
...
...
......
...
...
. .......
......................
... ........... ............
....
...
......
...
... ...
...
...
...
...
... ..
..
...
...
..
..
................................................
... ...
.
.
.
.
...
.
...................................................................................................................
.
.
.
.
.
.
.
........
...
.. ...
.
..
...
.....
.
.
.
.
...
.
.
.
.
.
.
......
... ...
...
...
.
...
....
.
.
.
.
.
.
.
.
.
.
.
.
... .....
....
.
.
.
.....
...
...
..
............. .........
.
.....
.
... ........................ ......... ........
.
...
.
...
..........
.... .......
..... ...
...
...
...
...
.....
...
...
.
...
.
...
...
...
...
.
....
.
...
...
.
...
.
.
.
...
...
...
..
...
.
.
...
.
...
...
.
...
.
.
...
...
...
...
.
.
..
.
.
.
...
...
.
...............
.
....
.
...
.
.
.
.
.
.
.
.
..
....
..
.
....
...
....
....
..
...
...
.
...
..
...
..
...
..
...
.
...
...
...
..
......................
...
.
.
.
.
...
.
...
...
...
.
.
.
.
.
1
...
...
...
.
..
.
.
.
....
.
.
.....
....
...
...
.
..
.
.
.
.
.
..
.
.
...
.
.
.
..................
...
...
.
.
...
.
...
.
...
...
...
.
...
.
.
.
.
....
...
...
...
.
...
.
...
.
.
.
.
...
...
...
.
.
...
...
.
.
...
...
...
.
...
.
...
...
.
... .
...
..
...
...
...
.......
.
.
.
...
...
.......
......
...
...
.
.
.
.
.
.
.
.
.
.............
. .......
...
.. ....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
....
.
.
.
.
.
.
.
.
.....
.
.
..........
.....
.....
.........
...
....
.
......
.....
...
.
.
.
..
...
.
.
.
.
.
.
...
...
.
...
.......
.
...
.
.
.
.
...
.
...
.
.
.
.
.
.
..
........
...
.
....
..
.
.
.
.
.
...........
.
....
.
.
.
....
...
.
...
...
.
.........................................
...
.
..
...
...
.
.
.
.
..
...
...
.
...
.
.
...
.
.
.
..
.
.
.
....
....
...
....
.
...
.
...
.
.
....... ..........
.
.
.
.....................
...
.
.............
...
...
.
.
.
.
...
...
...
...
...............
.
.
... ....
.
.
.
.
.
.
.
.....
...
..
.....
....
... ....
.
.
.....
.
...
...
...
..
...
.
... ..
....
..
...
.
...
.. ..
.
.
...
.
.
.
.
.
...
...
..
.
..
....
...
...
.... ...
.
.
...
.
...
.
...
...
.. ...
...
.
.
...
.
..
.
.......
.. ..
...
.
..
.
.
.
.
.
.
.
...
...
...
... ..................
...
.. ...
.
.
.
...
.
..
...
...
.
.
.. ...
.
.
.
.
.
...
...
...
...
...
.
...
.
.
.
.
.
.
...
..
...
.
....
.
....
....
.
.....
...
...
..
.
..
.
...
.
.
.
.
...
..
.
..
.
.....
....
...
.
.
...
.
.
...
...
..
.
.
...
.
...
.
.
.
.
.
...
...
....
..
....
...
...
.
.
.
.
.
...
.
.......
.
.
.
.
...
..
.
.
...
.
.
.
. . . ... . . . .. . ........... . ......... . . . . . . . . . . . . . . . . . . . . . . . ... . . . . . . . . . . . . . . . . ..... . . . . ..... . .
...
....
....
.. ........................................
....
.
...
...
...
...
.
.. ....
.
.
.
...
.
...
..
...................................
....
....
... ...
.....
...
........
......
.
.
.
.
..
.
.... ....
.
...
.
.
.
...
.....
.
...
.
.
....
.
...
....
.
.
.... ...
.
.
.
.
...
....
...
..
. ........
.
.
...
.
.
.. .....
.
.
...
.
.........
...
...
.
.
..
.
......
.
.
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.........
... .. ..........
..
...
................
.
....
.
....
.
.
.
.
.
.
..
......... .......
...
..
..
...
.
.
.
.
.
.
.
...
.
.
.. ................
.
.
..
.....
..
...
...
.......
..
...
..
...
....
...
...
...
...
...
....
...
..
.
..
.
...
.
.....
.
.
.
..
.
2
...
..
....
..
...
..
.
.
...
.
.
.
...
...
..
.
.
... ... ....
.
.
...
...
...
.......
.
.
.
.
.
.
.
.
.
.
.
.
.... .... ... ............................
.
.
.
.....
.
.
..
...
..
.
.
.
.
... ...............
.
.
.
.
.
.
.
... ... ... ...
.
.
.............
...
.
.......
..
....
... .. ... ...
....
...
..
......
... ... ... ..
...
......
.......
..
.......
............
... ... ... ...
...
.......................
.........................
...
.
... ... ..
.
.
.............
....
...
. . ..
.............
........ ...........
.. .............
.. ... .
......
........
. . . .................................. . . . . . . . . . . . . . . . ............................ . . . . . . . . . . . ............ . . . . . . . . . . . . . . . . . . . . . . . .
.............
.................
........................
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.............
....
....
...
............ .... ......
...
...
...
.................
..
..
...
...........
..
...
...
....
.
.
..
.
.
...
.
...
...
..
...
.
.
.
.
.
.....
.....
..........................
........................
V12
V09
V02
V25
SC
V17
V20
V13
V18
SC
V26
V23
V21
V24
Abbildung 14.5: Komponentengraph zu Graph2
14.8. EULERSCHE UND HAMILTONSCHE WEGE
14.8
399
Eulersche und hamiltonsche Wege
Bestimmte „maximale“ Wege sind von besonderem Interesse in der Graphentheorie. Sie
sind nach Euler 4 und Hamilton 5 benannt.
14.8.1
Eulerwege
Definition 14.6 Es sei G ein allgemeiner Graph. Ein linieneinfacher a-Weg, der jede Linie aus G enthält, heißt Eulerweg (Eulerian path). Er wird a-Eulerkreis (a-Euler circuit),
genannt, wenn er geschlossen ist, und a-Eulerzug (a-Euler trail), wenn er offen ist.
Ein linieneinfacher f-Weg, der jede Linie aus G enthält, heißt f-Eulerweg (f-Euler path,
directed Euler path). Er wird f-Euklerkreis (gerichteter Eulerkreis, f-Euler circuit, directed
Euler circuit) genannt, wenn er geschlossen ist, und f-Eulerzug (gerichteter Eulerzug, fEuler trail, directed Euler trail), wenn er offen ist.
Ein allgemeiner Graph heißt a-eulersch (a-Eulerian), wenn er einen a-Eulerkreis enthält.
Ein allgemeiner Graph heißt f-eulersch (f-Eulerian), wenn er einen f-Eulerkreis enthält.
2
Im Englischen wird für Eulerkreise auch die Bezeichnung Euler tour und für eulersche
Graphen die Bezeichnung unicursal graphs benutzt.
Man beachte, daß Eulerkreise i. a. keine Kreise sind. f-eulersche Digraphen werden auch
gerichtete Eulergraphen (directed Eulerian graph) genannt. Abgesehen von isolierten Knoten ist jeder a-eulersche Graph schwach zusammenhängend und brückenfrei. f-eulersche
Graphen sind stark zusammenhängend und natürlich auch brückenfrei. Schlingen und
Mehrfachlinien können in beiden Fällen beliebig vorhanden sein.
14.8.1.1
a-Eulerkreise
a-Eulerkreise sind leicht zu charakterisieren und algorithmisch zu finden.
Satz 14.7 Ein allgemeiner Graph G ist genau dann a-eulersch, wenn er schwach zusammenhängend ist und der Gesamtgrad eines jeden Knoten gerade ist.
Euler, Leonhard ∗15. April 1707, Basel, †18. September 1783, Sankt Petersburg. Schweizer Mathematiker. Lebte und wirkte 20 Jahre in Berlin, davor und danach bis zu seinem Lebensende in Sankt
Petersburg. War in der zweiten Lebenshälfte blind. Einer der genialsten Mathematiker aller Zeiten und
wohl der produktivste. Wesentliche Beiträge zu allen Teilen der Mathematik, insbesondere der Analysis.
Ebenso wichtige Beiträge zur Physik und Angewandten Mathematik. Euler war Zeitgenosse der Bernoullis und mit Daniel Bernoulli, siehe Seite 619, befreundet. Seine Lösung des Königsberger Brückenproblems
– es geht dabei um die Aufgabe, alle Pregelbrücken, die einige Inseln und die Ufer verbinden, auf einem
Rundweg genau einmal zu überqueren – ist eine der ersten Arbeiten der Graphentheorie.
5
Hamilton, Sir William Rowan ∗4. August 1805, Dublin, † 2. September 1865, Dunsink(Irland). Irischer Mathematiker. Professor für Astronomie am Trinity College, Dublin. Beiträge zur Theoretischen
Mechanik. Führte die Quaternionen ein. Die Bezeichnung „Hamiltonweg“ ergab sich eher nebenbei aus
einer Denksportaufgabe.
4
400
KAPITEL 14. WEGE UND ZUSAMMENHANG
Beweis: Die Bedingungen sind notwendig: Daß G schwach zusammenhängend sein muß,
wurde schon festgestellt. Außerdem muß ein Eulerkreis beim Durchgang durch einen Knoten über eine Nichtschlinge betreten und über eine andere Nichtschlinge wieder verlassen
werden. Der Anfangsknoten des Eulerkreises, wenn er überhaupt verlassen wird, wird
das erste Mal über eine Nichtschlinge verlassen und bei der letzten Rückkehr über eine
andere Nichtschlinge wieder betreten. Die Anzahl Nichtschlingen, mit denen ein Knoten
inzidiert, muß gerade sein. Da Schlingen den Gesamtgrad um 2 erhöhen (Seite 355), ist
der Gesamtgrad gerade.
Daß die Bedingungen hinreichend sind, zeigt der in Tabelle 14.4 aufgeführte Algorithmus
von Hierholzer 6. Ist der Graph schwach zusammenhängend und hat jeder Knoten geraden
Gesamtgrad, so so endet der Algorithmus stets mit einem Eulerkreis
2
14.8.1.2
f-Eulerkreise
Festzustellen, ob ein allgemeiner Graph f-eulersch ist und in diesem Fall einen f-Eulerkreis
anzugeben, ist jedoch keineswegs einfach. Wie ist vorzugehen? Die folgenden Überlegung
hilft weiter. Wenn es in einem allgemeinen Graphen G einen f-Eulerkreis gibt und man
diesen durchläuft, erhält man eine vollständige Orientierung von G, bei der in jedem
Knoten Eingangsgrad gleich Ausgangsgrad ist. Eine vollständige Orientierung mit dieser
Eigenschaft soll Eulerorientierung (Eulerian orientation) genannt werden. Damit ist die
notwendige Bedingung des folgenden Satzes gegeben.
Satz 14.8 Ein schwach zusammenhängender allgemeiner Graph besitzt genau dann einen
f-Eulerkreis, wenn er eine Eulerortierung besitzt.
Beweis: Daß die Existenz einer Eulerorientierung ausreicht, um einen f-Eulerkreis zu
finden, zeigt eine leicht zu findende Abwandlung des Algorithmus von Hierholzer.
2
Aus diesem Satz sind leicht Folgerungen für ungerichtete Graphen und für Digraphen zu
ziehen. Ein ungerichteter a-eulerscher Graph weist auch einen f-Eulerkreis auf. Also nach
Satz 14.7:
Korollar 1: Ein ungerichteteter Graph besitzt dann und nur dann eine Eulerorientierung, wenn er schwach zusammenhängend ist und alle Knoten einen geraden Grad haben.
Ein Digraph ist vollständig orientiert. Also
Hierholzer, Carl ∗2. Oktober 1840, Freiburg im Breisgau, †13. September 1871, Karlsruhe. Deutscher
Mathematiker. Sein Beweis, daß die Geradzahligkeit aller Knoten auch hinreichend für die Existenz eines
Eulerkreises ist, wurde nach seinem Tod von Christian Wiener und Jacob Lüroth aus dem Gedächtnis
aufgeschrieben und 1873 veröffentlicht [Hier1873].
Auch ein anderer, von M. Fleury stammender Beweis [Fleu1883] wird in der Literatur des öfteren zitiert.
6
14.8. EULERSCHE UND HAMILTONSCHE WEGE
'
401
$
Algorithmus von Hierholzer
Der Algorithmus bearbeitet einen allgemeinen Graphen mit nicht-leerer Linienmenge. Ist der Graph a-eulersch, so liefert der Algorithmus einen a-Eulerweg. Ist der
Graph nicht a-eulersch, so endet er mit einer entsprechenden Fehlermeldung.
Man startet in einem beliebigen Knoten einen Weg, markiert jede durchlaufene Linie,
um sie nicht noch einmal zu berücksichtigen, und stoppt, wenn es nicht mehr weitergeht, d. h wenn es in dem erreichten Knoten keine unmarkierten Linien mehr gibt.
Dabei werden beim ersten Besuch eines Knotens auch alle Schlingen durchlaufen.
Außerdem wird beim ersten Besuch der Knoten in eine Warteschlange eingehängt,
wenn es beim Verlassen nicht markierte Linien gibt.
1. Wird in einem anderen als dem Anfangspunkt gestoppt, so hat man einen
Knoten ungeraden Grades gefunden.
2. Wird im Anfangsknoten gestoppt und ist die Warteschlange leer, so prüft man,
ob im Graphen unmarkierte Linien verbleiben. Wenn ja, ist der Graph nicht
schwach zusammenhängend und kann keinen Eulerkreis aufweisen. Wenn nein,
hat man einen Eulerkreis gefunden.
3. Wird im Anfangsknoten gestoppt und ist die Warteschlange nicht leer, so entnimmt man ihr so lange Knoten, bis einer auftaucht, der mit einer noch nicht
markierten Linie inzidiert. Man startet mit diesem Knoten das Verfahren aufs
neue. Man findet so entweder einen Knoten ungeraden Grades oder einen linieneinfachen geschlossenen Teilweg, der in den schon gefundenen Weg eingefügt
wird. Danach wird wieder die Warteschlange geprüft.
2
&
%
Tabelle 14.4: Algorithmus von Hierholzer
Korollar 2: Ein Digraph ist genau dann f-eulersch, wenn er schwach zusammenhängend
und in jedem Knoten Eingangsgrad gleich Ausgangsgrad ist.
Um von einem allgemeinen Graphen zu wissen, daß er f-eulersch ist, muß man nicht immer
eine Eulerorientierung kennen.
Satz 14.9 Hat ein schwach zusammenhängender allgemeiner Graph G in jedem Knoten
einen geraden Gesamtgrad und ist in jedem Knoten Eingangsgrad gleich Ausgangsgrad,
so ist der Graph f-eulersch.
402
KAPITEL 14. WEGE UND ZUSAMMENHANG
Beweis: Wir betrachten die beiden Untergraphen, die durch die Kanten bzw. durch die
Bögen von G erzeugt werden. Es seien HE1 , HE2 , . . . , HEl und HA1 , HA2 , . . . , HAm deren
schwache Zusammenhangskomponenten. Nach Satz 14.7 und Korollar 2 von Satz 14.8
besitzt jede einen f-Eulerkreis. Wegen des schwachen Zuammenhangs von G lassen sich
diese zu einem Gesamteulerkreis zusammensetzen.
2
Wann hat denn nun ein allgemeiner Graph eine Eulerorientierung? Einfache notwendigen
Bedingungen dafür sind leicht aufzustellen. Man braucht so viele Kanten, daß man die
Anzahl Eingangsbögen bis zur Anzahl Ausgangsbögen ergänzen kann oder umgekehrt.
Die Anzahl dann noch verbleibender Kanten muß gerade sein. In Formeln
d(v) − |id(v) − od(v)| = 2p mit p ≥ 0
(14.1)
Leider es nicht richtig, daß diese notwendige Bedingung auch hinreichend ist. Der Graph
von Abbildung 14.6 ist stark zusammenhängend und genügt Gleichung 14.1. Macht man
.................
...
......................
.........................
...............
.
...............
.
..........
.
.
.
.
.
.
.
..................
.
.
.........
..
... ......
........
.......
......
.......
......
.
.
.
.
.
.
.
.
.
......
. .. .
.....
.
.
.
.....
.
.
.
.
.. ..
.....
...
.
.
.
.
.
.
.
.
....
....... ..
...
.
.
....
.
.
.
.
.
..............
..
...
.
.
.
.
.
...
...
..
..
.
.
.
.
.
.
.
.
.
.
...
. ..........
............................
.
.
.
...
.
...
.
.
.
.
....
.... ....
...
....
.
.
.
.
....
.
.......
...
...
..
...
.....
...
...... ..
...
..
.......
...
..
.......
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. ....
.. ....
.. ............
.. .... .
..
..
....
...............................................................
..
....
....
.
.
.
.
...
.
.
...
.
.
.
.
.
.
...
...
...
. ..
...... .......
.
...... .......
.
.
.
.
.
.
.
.
.............
.........
.....
....
.
.
.
.....
.........
..
...
...
.....
...
...... ........ .. ......
......
........ ....
...............................
.
...
................. ..
x
w
r
s
t
u
v
Abbildung 14.6: f-eulersche und nicht-f-eulersche Orientierungen
aus der Kante (r, s) einen Bogen von r nach s, so bleiben diese Eigenschaften erhalten.
Orientiert man danach die Kante (s, t) in irgendeiner Richtung, so ergibt sich immer noch
ein stark zusammenhängender Graph. Gleichung 14.1 ist aber auf jeden Fall verletzt. Wird
am Anfang die Kante (r.s) von s nach r orientiert, so können, wie man leicht sieht, alle
anderen Kanten so orientiert werden, daß ein f-eulerscher Digraph ensteht. D. h. orientiert
man in einem gegebenen allgemeinen Graphen nacheinander alle Kanten unter Beachtung
von Gleichung 14.1, so kann es günstige und ungünstige Reihefolgen geben. Wenn man
sich das Beispiel in Abbildung 14.6 genauer ansieht, merkt man, daß für die Kante (t, s)
die Richtung von t nach s erzwungen wird, wenn es eine Eulerorientierung geben soll. Aus
dem gleichen Grund werden danach die Richtungen von s nach r, von r nach x und x
nach u erzwungen und der Eulerkreis ist fertig.
Wir wollen nun den Algorithmus Präeuler kennenlernen, der solche erzwungenen Orientierungen solange durchführt, wie es geht. Die wesentlichen Gedanken, aber nicht die
Einzelheiten, sind in Tabelle 14.5 zu sehen. Der Algorithmus benutzt eine anfangs leere
Warteschlange. Als Ergebnis von Präeuler erhalten wir :
14.8. EULERSCHE UND HAMILTONSCHE WEGE
'
403
1. Wandle alle ungerichteten Schlingen in gerichtete um.
$
2. Durchlaufe nacheinander alle Knoten des Graphen. Prüfe bei jedem, ob Gleichung 14.1 gilt. Falls nein, Ende. Der Graph läßt keine Eulerorientierung zu.
Falls ja
(a) Ausbalancierung ist möglich:
i. Die Zahl der Kanten entspricht genau der Differenz zwischen ankommenden und abgehenden Bögen. Die Kanten werden zu Ausgangsbögen. Jeder Zielknoten wird in die Warteschlange eingefügt.
ii. Die Zahl der Kanten entspricht genau der Differenz zwischen abgehenden und ankommemdem Bögen. Die Kanten werden zu Eingangsbögen. Jeder Startknoten wird in die Warteschlange eingefügt.
(b) Von vornherein ausbalanciert. Verbleibende Anzahl Kanten gerade.
(c) Ausbalancierung nicht möglich: Es bleiben Kanten übrig. Der Graph
bleibt Kandidat für den widerspenstigem Fall.
3. Entnimm nacheinander die Knoten der Warteschlange, bis sie leer ist. Gehe
wie bei Schritt 2 vor.
4. Mit einem erneuten Durchlauf durch alle Knoten, stelle fest ob die Voraussetzungen für Satz 14.9 erfüllt sind. Wenn ja, kann eine Eulerorientierung gefunden und ein f-Eulerkreis angegeben werden. Falls nein, liegt der widerspenstige
Fall vor.
&
%
Tabelle 14.5: Präeuler: Algorithmus zum Finden ein einer maximalen Anzahl von Bögen
1. entweder einen allgemeinen Graphen, in dem Gleichung 14.1 nicht gilt
2. oder einen allgemeinen Graphen, in dem in jedem Knoten die Anzahl ankommender
Bögen gleich der Anzahl abgehender Bögen ist
3. oder einen allgemeinen Graphen, in dem Gleichung 14.1 gilt, aber Knoten existieren,
für die die Anzahl ankommender Bögen ungleich der Anzahl abgehender Bögen ist.
Fall 1. Der Graph besitzt keine Eulerorientierung, ist also nicht f-eulersch.
Fall 2. Nach Satz 14.9 kann ein f-Eulerkreis gefunden werden.
Fall 3. Das ist der widerspenstige Fall. Es kann immer noch sein, daß der Graph f-eulersch
ist oder nicht. Man siehe hierzu Abbildung 14.7 und mache sich klar, daß die beiden
Graphen wirklich die behaupteten Eigenschaften haben.
404
KAPITEL 14. WEGE UND ZUSAMMENHANG
.........................................................................
............
..........
.........
........
........
.......
.......
.......
.
.
.
.
.
......
...
.
.
.
.
......
.
....
.
.....
.
.
.
...
...
.
.
....
.
...
.
...
.
...
...
.
...
..
.
...
..
...
.
..
...
.
...
....
...
...
...
...
.
.
.
.
......
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.. .....
... .......
... .....
.
.
.
.
.....
...
............................................................................................
............................................................................................
...
...
.
.
.
...
..... ......
...... .......
. ...
...... ...
..............
.... ........ .....
.. ..
... ....
...
.
....
.
.
.
.
... .....
.
...
..
....
...
...
...
....
....
... ......
...
... .........
...
...
..
..... ......
.............
.. .... .
...................
...
....... ..... .....
..
...
...
....................................................
....................................
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
....
.
.
.
.
.
..........
.
.
.
.
...
.
.
.
.
.
.
...
...
.... ....
.
.
.
...
.... .... .
.
.
.
.
.
.
.
.
...
.
.
.
..............
........... ....
.............
.
.............
.
.
.
.
.
.....
.
...
.
.
.
....
.
. ....
.. ....
.
.
.
.
.
.
...
.
.
.
...
... ....
.
.
...
..
.
.
.
.
.
.
.
... .....
.
...
... ...
.
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. .
...
.... ........
.... .......
..
...... .......
...
...
...
...
..
...
.....
...
....
....
..
...
....
...
...
.
.... .....
.
.
.
.
.
.
.
.
................
.............
....
...........
..
.
.....
.
.
.
......
...
.
.
.
.
.
.......
.
........
.......
..........
.......
.........
...............
...............................................
y
x
w
s
p
u
t
q
nicht f-eulersch
v
r
.........................................................................
............
..........
.........
........
........
.......
.......
.......
.
.
.
.
.
......
...
.
.
.
.
......
.
....
.
.....
.
.
.
....
....
.
.
....
..
.
...
.
...
...
.
...
..
.
...
..
.
...
..
...
.
..
....
...
...
...
...
.
.
.
.
.
.
......
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.. .....
... .......
... .....
.
.
....
.
.
...........................................................................................
............................................................................................
.
...
...
.
..
.
...
.
..... ......
... .....
........ ..
......................
... .........
...
.. ...
....
... ....
....
...
.
.
.
.
... .....
...
..
....
..
...
...
....
...
...
... .........
...............
...
..
........ ...
........ ...
...
...
.... .......
..
.... .......
.... ..... ...
.. .......
....
...
...................................................
.......................................
...
.....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
..
.
.
.
.
.
...
...
...
.
. ....
.
. ....
...
...
.
.
.
.
.
.
.
.
.
.
.
...
.
.
.
.
..............
........... ...
.............
...............
.
.
.
.
.
.
.....
.
...
.
.
.
.
....
..
. ....
.. ....
.
.
.
.
.
...
.
.
.
....
... ....
.
.
.
...
.
.
.
.
.
.
... .....
.
....
... ...
...
.
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
. ....
.... ......
... ......
..... ......
...
...
...
...
..
...
....
.
...
....
...
.
....
...
..
...
..
..
...
...
.
.
.
.
.
.
.
.
.
.
.
.
................
.............
............
....
.....
....
......
.....
.
.
.
.
.
.......
.
........
.......
..........
.......
.........
...............
...............................................
w
s
p
y
x
u
t
q
v
r
f-eulersch
Abbildung 14.7: f-eulersche und nicht-f-eulersche Orientierungen (widerspenstiger Fall)
Man braucht einen Algorithmus, der in polynomieller Zeit entscheidet, ob ein f-eulerscher
Graph vorliegt, und in diesem Fall eine Eulerorientung liefert. Einen solchen Algorithmus
gibt es. Er braucht allerdings Ergebnisse aus der Flußtheorie und soll hier nur skizziert
werden. Es sei G ein widerspenstiger Graph. Wir versuchen einen Rundfluß in G zu finden.
Das ist eine Belegung aller Linien mit einer positiven Zahl und Angabe einer Richtung,
so daß in jeden Knoten genau soviel hineinfließt wie aus ihm herausfließt. Gelingt es,
eine Rundfluß zu finden, der jeder Linie eine Richtung und den Wert 1 zuweist, so hat
man eine Eulerorientierung gefunden. In der Flußtheorie kennt man Algorithmen, die in
polynomieller Zeit feststellen, ob ein solcher Rundfluß exitiert, und im positiven Fall die
zugehörigen Orientierungen liefern. Siehe die Literaturhinweise auf Seite 544.
14.8.2
Hamiltonwege
Eulerwege sind Wege, die alle Linien eines Graphen enthalten. Es liegt nahe, auch Wege
zu untersuchen, die alle Knoten eines Graphen durchlaufen. Wenn diese Wege einfach und
linieneinfach sind, spricht man von Hamiltonwegen (Hamiltonian path).
Definition 14.7 Es sei G ein allgemeiner Graph. Ein einfacher und linieneinfacher aWeg, der jeden Knoten aus G enthält, heißt a-Hamiltonkreis (a-Hamilton circuit), wenn
er geschlossen ist, und a-Hamiltonzug (a-Hamilton trail), wenn er offen ist.
Ein einfacher und linieneinfacher f-Weg, der jeden Knoten aus G enthält, heißt f-Hamiltonkreis
(gerichteter Hamiltonkreis, f-Hamilton circuit, directed Hamilton circuit), wenn er geschlossen ist, und f-Hamiltonzug (gerichteter Hamiltonzug, f-Hamilton trail, directed Hamilton trail), wenn er offen ist.
14.9. DATENSTRUKTUREN FÜR WEGE UND KREISZERLEGUNG
405
Ein allgemeiner Graph heißt a-hamiltonsch (a-Hamiltonian), wenn er einen a-Hamiltonkreis
enthält. Ein allgemeiner Graph heißt f-hamiltonsch (f-Hamiltonian), wenn er einen fHamiltonkreis enthält.
Trotz der großen Ähnlichkeit der Definitionen 14.6 und 14.7 sind die Probleme bei Hamiltonwegen wesentlich schwieriger als bei Eulerwegen. Es gibt keine einfachen charakterisierenden Eigenschaften von Hamiltonwegen. Die algorithmische Bestimmung aller vier
Typen von Hamiltonwegen ist NP-vollständig. Einiges hierzu wurde in Unterabschnitt
6.2.3, Seite 201, erläutert. Siehe auch Abschnitt E.5, Seite 667, im Anhang.
Aus diesem Grund werden Hamiltonwege in diesem Buch nicht weiter behandelt. In einigen Sonderfällen lassen sich Hamiltonwege in polynomieller Zeit bestimmen. Siehe Aufgaben 14.13, 14.12 und 14.14. Weitere Anmerkungen zu Hamiltonwegen sind bei den
Literaturhinweisen zu finden.
14.9
14.9.1
Datenstrukturen für Wege und Kreiszerlegung
Wege als verkettete Listen
Naheliegenderweise wird man zur Darstellung von Wegen im Rechner eine verkettete
Liste benutzen. Man könnte in der Kette entsprechend Definition 14.1 Knoten und Linien
abwechselnd speichern. Das ist jedoch nicht nötig, da die Folge der Linien allein den Weg
eindeutig beschreibt. Abbildung 14.8 zeigt eine geeignete Datenstruktur. Ein Satz vom
PHDR
?
PTHA
?
PTHA
?
..
.
?
PTHA
Abbildung 14.8: Darstellung von Wegen
Typ PHDR enthält allgemeine Information über den Weg: Weglänge, erste Linie, letzte
406
KAPITEL 14. WEGE UND ZUSAMMENHANG
Linie, Wegtyp (a-Weg, f-Weg, b-Weg) und eventuell weitere. Es folgt eine Kette von
Sätzen vom Typ PTHA, die den Weg darstellt. Die wesentliche Information in Sätzen vom
Typ PTHA ist ein Zeiger auf den zugehörigen Liniensatz (vom Typ EDGE) und Verweise auf
zwei Knoten (vom Typ VERTEX), und zwar den ersten Knoten in Wegrichtung und den
zweiten Knoten in Wegrichtung der Linie. Zu Knoten- und Liniensätzen siehe Abbildung
13.6, Seite 369. Für Linien, die in einem Wege mehrfach auftreten, sind entsprechend viele
PTHA-Sätze vorhanden.
14.9.2
Kreiszerlegung*
Als Anwendung dieser Datenstruktur wollen wir die Kreiszerlegung (cycle decomposition)
eines Weges bestimmen. Intuitiv ist klar, daß jeder nicht einfache, geschlossene Weg sich
in einfache, geschlossene Wege zerlegen läßt. Hilfssatz 14.2 und sein Beweis sind ein erstes
Ergebnis in dieser Richtung. Wir wollen Wegzerlegungen dieser Art näher untersuchen
und beginnen mit einem Beispiel. Wir betrachten im Digraphen der Abbildung 14.9 den
..................
...
... ....
....
..
.................... ..........................................
.
.
.
.
.
.
.
.
.
.
.
.
.....
...............
.
.........
......
.............. ...................
..... ......................................
.......
..
....
.. ...
.
...
.
...
...... ........
................
.....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
... .
...
...
... .
.
........
...
...
...
.. .......
..
..
.. .......
....
.
....
.
.
.
...........................................
...........................................
. .....
...
.
.
.
.
...
.
.
.
.
.
..
. ..
.... ...............
.... ........
...
...... ...
.......... .........
.......... ............
....... ...
....
..... ............
.........
..... .........
.........
...
...
.....
.....
......
.....
....... ..
.....
.....
......
...
.....
. .. ..........
.....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
..
.
.
......
..... ... ........
..........
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
....... ..........
...... ......
.. ......
.. .....
.
.
...
.
..
......................................................... ....
.....
.
.
...
......................................................................
.
.
.
.
.
.
...
.
...
.
.
.
.
.
.
..
.
.
... ........
... ....
...... ..........
..
...............
.............. ...............
.... ......
...........................................
.
.
.
.
.
.
.
......
.....
.... .
.... ..
.
.
.
.
.
......
.
.
.
.
.
.
.
.
..... .
....
..... ..
....
..... ..
......
.. .
.....
......
.....
.................................
..................................
......................
........................
..
..
..
...
................................................
..
..
................................................
....
.
.
.
.
.
.
.
.
.
.
...
.... ....
.... ....
.... ....
................
................
................
..........
l
m
o
n
a
k
i
c
g
j
d
b
h
e
f
Abbildung 14.9: Kreiszerlegungen eines geschlossenen Weges
geschlossenen f-Weg a, b, c, d, e, f, g, h, i, d, j, g, k, l, m, d, n, o, a. Da es in diesem Digraphen
keine Mehrfachbögen gibt, reicht es aus, die Folge der Knoten anzugeben. Der Weg ist
nicht einfach. Er enthält innere Knoten, die mehrfach auftreten: d und g. Ein Weg, in dem
innere Knoten mehrfach auftreten, enthält immer einen geschlossenen einfachen Unterweg,
der in einem Mehrfachknoten beginnt. Im betrachteten Weg ist z. B. d, e, f, g, h, i, d ein solcher Unterweg. Wir können diesen Unterweg aus dem Originalweg entfernen und erhalten
einen verkürzten geschlossenen Weg. In unserem Beispiel: a, b, c, d, j, g, k, l, m, d, n, o, a.
In diesem tritt nur der innere Knoten d mehrfach auf. Es gibt nur einen einfachen geschlossenen Unterweg, der in d beginnt, nämlich d, j, g, k, l, m, d. Wenn wir diesen Unterweg entfernen, bleibt der einfache geschlossene Weg a, b, c, d, n, o, a übrig. Wir haben
eine Kreiszerlegung7 des Ausgangsweges gefunden. Die Zerlegung ist nicht eindeutig. Wir
hätten als erstes auch den einfachen geschlossenen Weg g, h, i, d, j, g entfernen können
Die Bezeichnung ist nicht ganz korrekt, da bei allgemeinen Graphen auch einfache geschlossene Wege
der Länge 2 auftreten können, die nicht linieneinfach sind.
7
14.9. DATENSTRUKTUREN FÜR WEGE UND KREISZERLEGUNG
407
und a, b, c, d, e, f, g, k, l, m, d, n, o, a erhalten. Nach Abspaltung von d, e, f, g, k, l, m, d wäre schließlich wieder a, b, c, d, n, o, a übriggeblieben.
Im folgenden wird ein Algorithmus vorgestellt, der für jeden Weg eine Zerlegung in einfache Teilwege liefert. Alle Teilwege bis auf den letzten sind geschlossen. Der letzte ist
genau dann geschlossen, wenn der Originalweg geschlossen ist. Der Grundgedanke ist,
den Weg zu durchlaufen und jeden geschlossenen einfachen Teilweg so früh wie möglich
zu identifizieren und abzuspalten. Tabelle 14.6 zeigt den Algorithmus. Er benutzt als zusätzliche Datenstrukturen einen aktuellen Restweg R und eine Menge Q zur Identifikation
mehrfach auftretender Knoten. Der Pseudocode ist weitgehend selbsterklärend. Die Angabe “p an R anhängen” in Zeilen 5 bedeutet, daß man den entsprechenden PTHA-Satz an
die Kette von R anhängt. In Zeile 7 wird ein Teilweg aus R ausgekettet. Das soll näher
erläutert werden. Diese Zeile werden aufgerufen, wenn ein Mehrfachknoten in R erkannt
wurde. Es ergibt sich dann zum ersten Mal die in Abbildung 14.10 gezeigte Situation.
pi−1
..
.......................................
..
li−1 vi−1 u
pj−1
pi
..
.....................................
..
li
u vi
..
......................................
..
•••
..
......................................
..
lj−1 vj−1 u
pj
..
......................................
..
lj
u vj
..
......................................
..
Abbildung 14.10: Ausketten bei Kreiszerlegung
Die pv sind PTHA-Sätze, die lv geben die zugehörigen Liniensätze an. Die mit den Linien
inzidierenden Knoten sind in Wegreihenfolge angegeben. Der Knoten u tritt in den PTHASätzen pi und pj als Anfangsknoten auf. Der von diesen beiden Stellen begrenzte Teilweg
wird als geschlossener einfacher Weg der gesuchten Kreiszerlegung angelegt und einer im
Algorithmus nicht explizit aufgeführten Zerlegunsgstruktur hinzugefügt. Der geschlossene
einfache Teilweg wird aus den PTHA-Sätzen pi bis einschließlich pj−1 gebildet und ausgekettet. Alle von der Umkettung betroffenen Einträge, die von u verschieden sind, werden
in Q gelöscht. Der zu u gehörenden PTHA-Satz wird aktualisiert. pj wird an pi−1 angefügt.
pi−1 kann fehlen, dann ist pj das erste Element des neuen Restweges.
Anmerkung 14.7 Nicht jeder Mehrfachknoten eines Weges wird durch den Algorithmus
PATHDCMP als solcher erkannt. Dann treten diese Knoten in verschiedenen Zerlegungswegen auf. Im Beispiel des Weges zur Abbildung 14.9 wird der Knoten g nicht als Mehrfachknoten erkannt. Er tritt in den Kreisen d, e, f, g, h, i, d und d, j, g, k, l, m, d als innerer
Knoten auf. Wenn wir den Graphen der Abbildung durch einen Bogen von a nach g
ergänzen und den Weg um den Schritt von a nach g erweitern, tritt g drei Mal als Mehrfachknoten auf und wird immer noch nicht als solcher erkannt. Bei dritten Mal ist g
Endknoten des letzten (offenen) Zerlegungswegs a, g.
2
408
'
KAPITEL 14. WEGE UND ZUSAMMENHANG
PATHDCMP(∗P )
//
//
//
//
//
//
//
//
//
P ist der zu zerlegende Weg.
R ist der anfangs leere Restweg.
Q ist eine anfangs leere Menge von Elementen,
deren Identifikationsmerkmal eine Knotenname ist und
die außerdem einen Verweis auf einen Satz
vom Typ PTHA (in R) enthalten.
p ist eine Variable vom Typ PTHA.
x ist eine Variable vom Typ VERTEX.
y ist eine Variable vom Typ VERTEX.
1 p = erste Linie von P ;
2 while (p != NULL)
3
{ x = erster Knoten von p;
4
y = zweiter Knoten von p;
5
p an R anhängen;
6
if (Element mit Knotennamen von x in Q vorhanden)
7
{ Teilweg aus R ausketten;
8
Verweis auf PTHA-Satz aktualisieren;
9
}
10
else
11
{ Element für x in Q anlegen;
12
// Verweis zeigt auf PTHA-Satz in R
13
}
14
p = Nachfolger von p;
15
}
16 return; // Falls R leer, war der Originalweg geschlossen.
//Anderenfalls verbeibt ein offener einfacher Weg.
&
$
%
Tabelle 14.6: Algorithmus PATHDCMP zum Finden der Kreiszerlegung eines Weges
Aufgaben
Aufgabe 14.1
a. In einem allgemeinen Graphen sei Pn ein a-Kreis der Länge n ≥ 3 und Cn sein Träger.
Es sind alle auf Cn möglichen a-Kreise und f-Kreise zu bestimmen.
b. Welche Eigenschaften charakterisieren die Untergraphen Cn eines schlichten Graphen,
14.9. DATENSTRUKTUREN FÜR WEGE UND KREISZERLEGUNG
409
die Träger eines a-Kreises sein können?
Aufgabe 14.2 Man beweise Hilfssatz 14.1 (Seite 375):
1. Jeder offene Weg von u nach v einhält einen linieneinfachen Weg von u nach v.
2. Jeder linieneinfache offene Weg von u nach v einhält einen einfachen Weg von u nach
v.
3. Jeder einfache offene Weg ist linieneinfach.
Aufgabe 14.3
Beweisen Sie Proposition 14.1, Seite 380: Enthält eine starke Zusammenhangskomponente
einen Bogen, so enthält sie auch einen f-Kreis
Aufgabe 14.4
In welcher Form ist Proposition 14.9 auf den Graphen der Abbildung 14.11 anwendbar?
Was ändert sich, wenn der Bogen von u nach v durch eine Kante ersetzt wird?
....
...........
...... ........
.... .....
.....
..
....
..
..
...
..
......................
...............................
.
.
.
.....
.
..
.....
.......
....
......
.
.
.....
.
.
.
.
...
......
..... .
....
.. .....
...
........
.........
.....
.....
.....
....
.................................
................................
......................
.
.
.
..
...
.
.
.
..
..
...
.
.
..
.
.....................................................................
....
....
...
.
.
.
..
.
.
.
.
.
.
.
.
.
...
.
.
....... ...............
.....................
.
.
...................
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
.
.
.
..........
...
...
.
.
.
.
.
.
.
.
.
.
.
...
.
.
.
.
......
..
.
..
......
..
... .. ..........
.....
. ....
...... ....... ..........
.......... ..........
........... .............
..
.
.... .........
...
...
.....
.....
.... .....
.... .....
..........
..........
u
v
Abbildung 14.11: Kanten auf f-Kreisen
Aufgabe 14.5
a.Jeder freie Baum mit mindestens zwei Knoten hat mindestens zwei Knoten vom Grad 1.
b. Wie hängt die Anzahl Knoten vom Grad 1 von der Struktur des Baumes ab?
Aufgabe 14.6
Die Bedingung |E ∪A| = |V |−1 reicht nicht aus, um einen allgemeinen Graphen zu einem
a-Baum zu machen. Wieso?
Aufgabe 14.7
Zeigen Sie daß Binärbäume, wie sie in Unterabschnitt 9.1.2 eingeführt wurden, gerichtete
f-Bäume im Sinne von Unterabschnitt 14.4.3 sind.
Aufgabe 14.8
Weist der vollständige Graph K18 (siehe Seite 350) einen Eulerkreis auf? Einen Hamiltonkreis?
410
KAPITEL 14. WEGE UND ZUSAMMENHANG
Aufgabe 14.9
Geben sie einen allgemeinen Graphen an, der unter Fall 3, Seite 403, fällt und bei passenden Orientierungen von Kanten f-eulersch oder nicht f-eulersch wird.
Aufgabe 14.10
Was kann man zu a-Eulerzügen und f-Eulerzügen in allgemeinen Graphen sagen ?
Aufgabe 14.11
Man zeige: Jeder allgemeine Graph kann mit einem geschlossenen Weg so durchlaufen
werden, daß jede Linie genau zweimal vorkommt. Darüber hinaus wird jede Kante je
einnmal in jeder Richtung und jeder Bogen genau einmal in f-Richtung und einmal in
b-Richtung durchlaufen.
Diese Eigenschaft ist unter dem Begriff double tracing bekannt.
Aufgabe 14.12
Untersuchen Sie die Struktur von Digraphen, in denen kein Knoten einen Eingangsgrad
größer 1 aufweist. Geben Sie einen polynomiellen Algorithmus an, der diese Struktur aufdeckt.
Wann ist ein solcher Digraph f-hamiltonsch?
Was läßt sich über Digraphen sagen, bei denen der maximale Ausgangsgrad 1 ist?
Aufgabe 14.13
Es sei ein allgemeiner Graph gegeben, in dem alle Knoten den Gesamtgrad 2 aufweisen. Ist
der Graph stets a-hamiltonsch? Wenn nein, geben Sie einen polynomiellen Algorithmus an,
der feststellt, ob der Graph a-hamiltonsch ist, und gegebenenfalls einen a-Hamiltonkreis
findet.
Aufgabe 14.14
Untersuchen Sie die f-kreisfreien, schwach zusammenhängenden allgemeinen Graphen, in
denen es einen f-Hamiltonzug gibt. Geben Sie einen Algorithmus an der, alle f-Hamiltonwege
findet bzw. meldet, daß es keine gibt. Was kann man zur Komplexität des Algorithmus
sagen?
Hinweise: Für den anzugebenden Algorithmus kann angenommen werden, daß ein Algorithmus gegeben ist, der für einen schwach zusammenhängenden allgemeinen Graphen die
starken Zusammenhangskomponenten, den externen Dag und die Schichtennummern in
polynomieller Zeit liefert. Als Beispiel kann Algorithus STRONGCOMP aus Kapitel 15
dienen.
Aufgabe 14.15 (Bestimmung von Wegen)
Einige einfache graphentheoretische Eigenschaften sind der Adjazenzmatrix eines Digraphen (ohne Mehrfachbögen) unmittelbar anzusehen. So kann man zum Beispiel den Eingangsgrad eines Knoten als Anzahl Einsen in „seiner“ Spalte ablesen oder Schlingen als
14.9. DATENSTRUKTUREN FÜR WEGE UND KREISZERLEGUNG
411
Einsen in der Diagonale erkennen. Andere Eigenschaften sind nicht offensichtlich und erfordern Rechenschritte mit der Adjazenzmatrix. Der folgende Satz ist ein Beispiel dafür.
Satz : Es sei D = (V, A) ein Digraph und M seine Adjazenmatrix. Dann gibt das Element
(k)
mij der Matrix M k für k = 1, 2, 3, . . . die Anzahl f-Wege der Länge k an, die von Knoten
i zu Knoten j führen.
a. Beweisen Sie den Satz.
b. Wie kann man feststellen, ob es überhaupt einen f-Weg von Knoten i zu Knoten j gibt?
c. Wie kann man feststellen, ob der Digraph stark zusammenhängend ist?
d. Was läßt sich zu ungerichteten Graphen (ohne Mehrfachkanten) und ihren Adjazenzmatrizen sagen?
Literatur
Wege und Zusammenhangskomponenten gehören zu den wesentlichen Grundbegriffen der
Graphentheorie und sind in allen Büchern über Graphentheorie beschrieben. Es sei deshalb
hier noch einmal auf die zu Abschnitt 12.1 angegeben Literatur, Seite 360, hingewiesen.
Die Bezeichnungen sind uneinheitlich. Im Englischen wird path oft im Sinne von einfacher
Weg gebraucht. Manche Autoren definieren Wege als Graphen, also durch ihren Träger,
und nicht als Abbildungen, z. B. Diestel [Dies2000]. Wir benutzen die in der Informatik
üblichen Bezeichnungen, siehe Cormen/Leiserson/Rivest [CormLR1990].
Die explizite Einführung von f-Wegen, b-Wegen und a-Wegen in allgemeinen Graphen
und ihre systematische Anwendung auf die Definition von schwachen und starken Zusammenhangskomponenten ist in dieser Form neu. Für Digraphen wurde ähnliches im Bericht
[Stie2001c] beschrieben.
Gerichtete Bäume, insbesondere solche, bei denen auf der Menge der Nachfolger eines
Knotens eine Ordnung definiert ist, sind in der Informatik wichtige Datenstrukturen. Sie
werden intensiv als Suchbäume genutzt. Siehe Kapitel 9, Seite 257.
Eulerwege wurden von Leonhard Euler 1736 zur Lösung des „Königsberger-BrückenProblems“ eingeführt. Es besteht darin, alle Pregelbrücken des damaligen Königsberg
bei einem Spaziergang genau einmal zu überqueren. Eine Ansicht der Stadt Königsberg
in jener Zeit findet man bei Diestel [Dies2000]. Zur graphentheoretischen Formulierung
des Problems siehe auch
http://www.jcu.edu/math/vignettes/bridges.htm.
Eulerwege für ungerichtete Graphen und Digraphen werden in allen Büchern über Graphentheorie behandelt, meistens kurz. Eine ausführliche Darstellung einschließlich Algorithmen ist in [Volk1996] zu finden. Zu Algorithmen für Eulerwege siehe auch [Sedg2002]
und [McHu1990]. Algorithmen zur Bestimmung von f-Eulerwegen in allgemeinen Graphgen sind in den Lehrbüchern i.a. nicht zu finden. Das mag an dem allgemeinen Desinteresse
an „gemischten“ Graphen liegen. Eine polynomielle Lösung des Problems haben Ford und
412
KAPITEL 14. WEGE UND ZUSAMMENHANG
Fulkerson schon 1962 in ihrem Lehrbuch [FordF1962] angegeben.
Hamiltonwege sind ein schwieriges Kapitel der Graphentheorie. Gute und anwendbare
charakterisierende Eigenschaften sind nicht bekannt. Oftmals sind charktrisierende Eigenschafte von der Form „Genau dann, wenn dieser Graph einen Hamiltonweg besitzt,
hat auch jener einen“. In Diestel [Dies2000] und Volkmann [Volk1996] sind Hamiltonwegen eigene Kapitel gewidmet.
Kapitel 15
Tiefensuche und Breitensuche
Es ist bei Algorithmen auf Graphen immer wieder nötig, alle Knoten nacheinander zu
untersuchen und zu bearbeiten. Man spricht davon, daß die Knoten besucht (visit) werden. In den meisten Fällen werden dann alle Linien, mit denen der Knoten inzidiert, auch
besucht. Das ist möglich, indem man die rechnerinterne Darstellung benutzt, bei Inzidenzlisten z. B. die Knotenliste und für jeden Knoten die Liste seiner Inzidenzsätze durchläuft.
In vielen Fällen ist es jedoch zweckmäßiger, die Struktur des Graphen in den vollständigen
Durchlauf einzubeziehen. Die beiden wichtigsten Methoden hierfür sind Tiefensuche und
Breitensuche. Beide Methoden werden auf vielfältige Weise angewendet, zum Teil auch
ohne kompletten Durchlauf durch den Graphen.
15.1
Tiefensuche
Es sei G(V, E, A, ϕ, ψ) ein allgemeiner Graph und v einer seiner Knoten. Wir wollen in
ihm drei Formen von Tiefensuche mit Startpunkt v untersuchen.
a. Die Prozedur f-DFS in Tabelle 15.1 definiert f-Tiefensuche (Tiefensuche in Vorwärtsrichtung, f-depth-first search).
b. Ersetzt man in Zeile 4 die Ausgangsbögen durch die Eingangsbögen, so wird b-Tiefensuche
(Tiefensuche in Rückwärtsrichtung, b-depth-first search) definiert. Diese Prozedur soll bDFS heißen.
c. Erweitert man die Prozedur so, daß Ausgangs- und Eingangsbögen berücksichtigt werden, so erhält man die Prozedur a-DFS, mit der a-Tiefensuche (Tiefensuche in beliebiger
Richtung, a-depth-first search) definiert wird.
Tiefensuche ist leicht zu programmieren und für Listendarstellungen gut geignet.
Tiefensuche versucht, einen einfachen Weg so weit wie möglich fortzusetzen. Sie kehrt erst
um, wenn ein Knoten erreicht wird, von dem es nicht mehr weitergeht, weil es entweder
keine Knoten gibt, mit denen man fortsetzen könnte, oder weil all diese Knoten schon
markiert sind.
413
414
'
KAPITEL 15. TIEFENSUCHE UND BREITENSUCHE
f-DFS(v)
1
2
3
4
5
6
7
8
9
10
&
if (v markiert) return;
markiere v;
setze v aktiv;
for (alle Kanten und Ausgangsbögen l, die mit v inzidieren)
{ if (l nicht markiert)
{ markiere l;
f-DFS (otherend(l, v));
} }
setze v inaktiv;
return;
$
%
Tabelle 15.1: Prozedur f-DFS für f-Tiefensuche in einem allgemeinen Graphen
f-Tiefensuche soll jetzt genauer untersucht werden. Sie arbeitet mit Markierungen für
Knoten und Markierungen für Linien. Wir sagen, ein Knoten v werde besucht, wenn für
ihn in einer Rekursionsstufe von f-DFS Zeile 1 ausgeführt wird. Ein Knoten kann gar nicht,
genau einmal oder mehrmals besucht werden. Nur wenn er zum ersten Mal besucht wird
und zu diesem Zeitpunkt nicht markiert ist, wird er bearbeitet, d. h. werden die weiteren
Anweisungen von f-DFS ausgeführt. Nur in diesem Fall wird er aktiviert (activated).
Nach der Bearbeitung aller seiner Kanten und Ausgangsbögen wird er deaktiviert und
seine gesamte Bearbeitung beendet.
Anmerkung 15.1 In Tabelle 15.1 ist Zeile 4 so zu verstehen, daß die Kanten und Ausgangsbögen in beliebiger Reihenfolge drankommen. Welche Reihenfolge wirklich auftritt,
hängt von der internen Darstellung von Knotenliste und Inzidenzlisten ab. Z. B. können
die Kanten vor den Ausgangsbögen bearbeitet werden. Das muß aber nicht so sein, sondern auch jede beliebige Mischung ist zugelassen. Jede Festlegung der Reihenfolge von
Knoten und Inzidenzen bestimmt einen Ablauf der Tiefensuche. Für b-Tiefensuche und
a-Tiefensuche gelten die gleichen Feststellungen.
2
Für weitere Überlegungen betten wir f-DFS in den Rahmenablauf f-DFSgesamt ein. Siehe
dazu Tabelle 15.2. b-DFSgesamt und a-DFSgesamt ergeben sich durch die entsprechenden
Änderungen in Zeile 3. Zu Anfang sind alle Knoten unmarkiert und inaktiv. Auch alle
Linien sind unmarkiert. Am Ende ist jeder Knoten markiert. f-DFSgesamt und f-DFS
geben das Muster an, nach dem die meisten Anwendungen von Tiefensuche aufgebaut
sind. Allerdings gibt es Abweichungen. So wird z. B. bei der Bestimmung von Bipartitionsmengen, Seite 354, die Bearbeitung und Markierung eines neuen, d. h. nicht über
15.1. TIEFENSUCHE
'
&
415
$
f-DFSgesamt(v)
1
2
3
4
for (alle Knoten v aus V ) /* Knotenliste */
{ if (v nicht markiert)
{ f-DFS (v);
} }
%
Tabelle 15.2: Rahmenablauf einer vollständigen f-Tiefensuche in einem allgemeinen Graphen
BPT gefundenen unmarkierten Knotens im Rahmenablauf BIPART und nicht in der
rekursiven Prozedur BPT ausgeführt. Der Grund ist, daß sich die Menge, in der der
Knoten eingefügt wird, nicht durch einen schon bearbeiteten Nachbarn bestimmen läßt,
sondern frei gewählt werden kann.
Beispiel 15.1 Am Beispiel von Graph3, Abbildung 15.1, soll der Ablauf einer f-Tiefensuche
..........
..... ...... .
... .........
..................
...........
..............
.
.
.
...
... ......
.
...
..
...
.....
.....
.................
.
.
.....
.... 7.....
... 4.....
.... 1....
...........
.
.
...............
.
...
.
.
.
.
.
.
.
.
.
.
.
.
... ....
... ...
... ...
...
... ....
... ....
.
.
.... .....
.
.
....
.
.
...
...
...
.
...
...
...
...
...
...
..
...
...
...
...
...
...
..
...
...
...
...
...
...
...
...
...
...
.
.
...
.
.
.....
...
.
.
.
...
...
..
..
...
.
.
...
...
..
.
.
...
.
...
.
..
...
.
.
...
.
.
...
...
...
.
.
...
.
.
...
..
.
.
.
.
.
.
...
...
..
..
.
.
.
...
.....
...
.
.
.........
........ . ...
..
... ..
...
.
..
.
.
.
.
....... ........
.......
.............. ....
....
.
....
.
.
.
.
.
.
.
.
..................
..............
.
....................
.
.
.
....
....
.
.
.
.
.
..
.. ........
... .........
...
.....
...
.
...........................................
.. ......................................
... 8....
... 3..... ........
..
..
.... 5...... ......
...............
............
..... .....
....
....
... ... .....
..
.
...
...
.
...
..
...
...
.....
...
...
...
...
...
....
...
...
...
...
...
...
...
...
...
..
.
.
...
.
.
...
...
...
..
...
.
.
.
.
...
...
.
..
...
.
.
.
.
.
...
...
...
.
...
.
.
.
.
..
...
...
..
.
...
.
.
....
.
...
...
..
...
.
.
.
... ..........
........ . ...
...
.
.
.
.
....... ......
... .......
...
.
.
.
.
.
.
.
.
.
.
.
.
...
................
...
... .. .....................
...
........................
..
..
....
.
........ ..... 2....
.
..
..... 6
.............
...........
v
v
v
v
v
v
v
v
Abbildung 15.1: Graph3
detailliert durchgespielt werden. Die Bezeichnung der Linien folgt den Regeln, die in Abbildung 14.1, Seite 377, angegeben sind. Der Graph enthält die Kanten uv3 v4 und uv3 v6 ,
alles andere sind Bögen. Der Ablauf der f-Tiefensuche hängt von der Reihenfolge der
Knoten in der Knotenliste und der Reihenfolge der Linien eines jeden Knoten in den
entsprechenden Inzidenzlisten ab. Für Graph3 mögen die in Abbildung 15.2 dargestellte
Knotenliste und zugehörigen Inzidenzlisten vorliegen. Siehe auch die Schemadarstellung
in Abbildung 13.6, Seite 369.
416
KAPITEL 15. TIEFENSUCHE UND BREITENSUCHE
v3
...
...
...
...
..
...
...
...
...
...
...
....... ..
......
...
v2
...
...
...
...
...
..
...
...
...
...
...
....... ..
......
...
v6
...
...
...
...
...
...
...
...
...
...
..
....... ..
......
...
v5
...
...
...
...
...
...
..
...
...
...
...
....... ..
......
...
v8
...
...
...
...
...
...
...
..
...
...
...
....... ..
......
...
v4
...
...
...
...
...
...
...
...
...
...
..
....... ..
......
...
v7
.....
..............
... .
...
.
.
.
... ..
...............................
... ..
...
...
... .
..............
......
.
...
...
...
...
... .
..............
......
.
.....
...............
.. .
...
.
.
.
...
.....
...
...
...
... .
..............
......
.
..
..............................
... ..
...
...
... .
..............
......
.
..
..............................
... ..
...
...
... .
..............
......
.....
.............
.. .
...
.
.
.
... ..
................................
... ..
...
...
... .
..............
......
..
............................
..
uv3 v4
..
............................
..
uv3 v6
..
............................
..
dv1 v3
dv3 v2
dv5 v3
.......
dv1 v2 b ......................... dv3 v2
..
.............................
..
dv1 v2 a
..
.............................
..
dv8 v5
uv3 v6
dv5 v6
dv5 v6
..
............................
..
dv5 v3
dv4 v5
..
............................
..
dv7 v5
..
............................
..
dv4 v5
..
............................
..
dv7 v5
dv8 v5
dv7 v8
uv3 v4
dv4 v4
dv4 v4
dv7 v8
...
...
...
...
...
...
...
...
..
...
...
.........
......
...
v1
..
............................
..
dv1 v2 a ................................ dv1 v2 b ................................. dv1 v3
Abbildung 15.2: Knotenliste und Inzidenzlisten zu Graph3
15.1. TIEFENSUCHE
417
Die Knotenliste beginnt mit v3 und endet mit v1 . Zu den Knoten sind die Liste der Kanten,
die Liste der Ausgangsbögen und die Liste der Eingangsbögen – in dieser Reihenfolge –
angegeben. Leere Listen sind nicht eingezeichnet.
Das Ablaufprotokoll der f-Tiefensuche ist in Tabelle 15.3 zu sehen.
Die Spalten der Tabelle geben aufeinanderfolgende Rekursionsstufen von f-DFS an, die
erste Stufe wird von f-DFSgesamt aufgerufen. Übergänge zur nächsten Zeile bedeuten
Eintritt in die nächste bzw. Rückkehr zur vorangegangenen Rekursionsstufe. Knoten oder
Linien, die zum ersten Mal besucht und dann markiert und bearbeitet werden, sind in
der Tabelle in Normalschrift geschrieben. Knoten werden an dieser Stelle aktiviert, z. B.
Knoten v3 in Zeile 1, Knoten v6 in Zeile 7, Knoten v2 in Zeile 16. Aktivierung wird durch
Umrahmung hervorgehoben. Knoten oder Linien, die bei einem Besuch markiert angetroffen und nicht weiter berücksichtigt werden, stehen in Kursivschrift. Knoten werden in der
letzten Zeile ihrer Bearbeitung deaktiviert, z. B. Knoten v3 in Zeile 18. Auch Deaktivierung wird durch Umrahmung hervorgehoben. Die f-Tiefensuche kann auf einen markierten
Knoten innerhalb seines Aktivitätsintervalls (z. B. Knoten v4 in Zeile 4, Knoten v3 in Zeile
7) oder außerhalb desselben (z. B. Knoten v5 in Zeile 20, Knoten v3 in Zeile 32) treffen.
2
Der folgende Satz ist von zentraler Bedeutung für die Tiefensuche.
Satz 15.1 Es sei G(V, E, A, ϕ, ψ) ein allgemeiner Graph und v ∈ V . Sind anfangs alle
Knoten unmarkiert, so besucht f-DFS(v) den Knoten v und alle von v f-erreichbaren
Knoten und nur diese.
Beweis: v wird besucht.
Es sei u 6= v ein Knoten, der von v auf dem f-Wege
v = v0 , l1 , v1 , . . . , vk−1 , lk , vk = u
erreichbar ist. Anfangs sind die Knoten des Weges unmarkiert. Induktion: Jeder Knoten
des Weges wird besucht. Für v1 passiert das spätestens über l1 . Wird vi besucht, so wird
auch vi+1 besucht, nämlich spätestens über li+1 . u wird also besucht.
Angenommen, es werde u 6= v besucht. Wir betrachten den Erstbesuch von u. Dieser
geschieht in der zweiten oder einer späteren Aufrufstufe von f-DFS. Dann gibt es in der
Vorgängerstufe einen eindeutig bestimmten Knoten w und eine eindeutig bestimmte Linie
von w zu u. Ist w 6= v, so hat w selber einen eindeutig bestimmten Vorgänger für den
Erstbesuch und wird von diesem auf einer eindeutig bestimmten Linie erreicht. Nach
endlich vielen Schritten (es gibt nur endlich viele Rekursionsstufen!) hat man so einen
b-Weg von u zu v gefunden. D. h. u ist von v f-erreichbar.
2
Satz 15.1 und sein Beweis gelten entsprechend auch für b-Tiefensuche und a-Tiefensuche.
418
KAPITEL 15. TIEFENSUCHE UND BREITENSUCHE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
1. Stufe
v3
uv3 v4
2. Stufe
v4
3. Stufe
4. Stufe
5. Stufe
uv3 v4
dv4 v4
v4
dv4 v5
v5
dv5 v6
v6
uv3 v6
v3
v6
dv5 v3
v3
v5
v4
uv3 v6
dv3 v2
v2
v2
v3
v2
v6
v5
v8
dv8 v5
v5
v8
v7
dv7 v8
v8
dv7 v5
v5
v7
v1
dv1 v2 a
v2
dv1 v2 b
v2
dv1 v3
v3
v1
Tabelle 15.3: Ablaufprotokoll einer f-Tiefensuche
15.2. TIEFENSUCHBÄUME
419
Ein Knoten u kann durchaus mehrfach besucht werden; markiert und bearbeitet wird er
nur beim ersten Besuch. Was passiert, wenn es anfangs, also beim Aufruf von f-DFS (v),
Knoten gibt, die schon markiert sind? Ist v selbst markiert, so wird kein weiterer Knoten
besucht. Ist v unmarkiert, so gilt die folgende Proposition.
Proposition 15.1 Es sei G(V, E, A, ϕ, ψ) ein allgemeiner Graph und v ein unmarkierter
Knoten. Dann werden durch den Aufruf f-DFS(v) genau die Knoten u besucht, für die es
zum Zeitpunkt des Aufrufs einen f-Weg von v nach u gibt, bei dem alle von u verschiedenen
Knoten unmarkiert sind.
Beweis: Nach dem Muster des Beweises von Satz 15.1. Man beachte dabei, daß auf
dem b-Weg, der im zweiten Teil des Beweises gefunden wird, alle Knoten außer u anfangs
unmarkiert sein müssen.
2
In entsprechender Abwandlung gilt Proposition 15.1 auch für b-DFS und a-DFS. Als
Beispiel soll wieder auf Graph3 und den Ablauf in Tabelle 15.3 herangezogen werden.
Beim Aufruf von f-DFS (v4 ) gibt es den markierten Knoten v3 . Die Knoten v5 , v6 und v3
werden bei diesem Aufruf besucht. Die ersten beiden Knoten sind beim Aufruf unmarkiert,
der dritte ist markiert. Es ist auch möglich, daß ein f-erreichbarer und nicht markierter
Knoten nicht besucht wird, weil es zu ihm keinen Weg mit unmarkierten Knoten gibt, er
also von v durch markierte Knoten getrennt wird. Ein Beispiel dafür ist in Tabelle 15.3
Knoten v2 beim Aufruf von f-DF S(v5 ).
Tiefensuche ist ein effizienter Algorithmus, wie die folgende Proposition zeigt.
Proposition 15.2 Tiefensuche in Graphen erfordert O(|V | + |E| + |A|) Zeitschritte.
Beweis: Beim Durchlauf durch die Knotenliste wird jeder Knoten genau einmal angesprochen. Bei der Bearbeitung eines Knotens werde alle mit ihm inzidenten Linien
höchstens einmal bearbeitet, jede Linie also insgesamt höchstens zweimal. Wegen der
Markierung wird jeder Knoten von f-DFSgesamt genau einmal bearbeitet. Die Behauptung ergibt sich dann durch die Tatsache, daß die Bearbeitung eines Knotens und die
Bearbeitung einer Linie durch Konstanten beschränkt sind.
2
15.2
Tiefensuchbäume
Beim Ablauf einer f-Tiefensuche (a-Tiefensuche, b-Tiefensuche) ergeben sich charakteristische Eigenschaften von Linien und bestimmte Bäume. Die Linien, mit denen bei der
Tiefensuche von einem Knoten zu einem unmarkierten Knoten und damit zur nächsten
Rekursionsstufe übergegangen wird, heißen Baumbögen (tree arc). Diese Bezeichnung soll
unabhängig davon benutzt werden, ob es sich um eine Kante oder einen Bogen handelt
und in welcher Richtung der Bogen bei dem Übergang durchlaufen wird. Mit Bezug auf
420
KAPITEL 15. TIEFENSUCHE UND BREITENSUCHE
den Zielknoten eines Baumbogens wird dieser auch Eintrittsbogen (entry arc) genannt, da
über ihn der Knoten zum ersten Mal besucht wird und die Bearbeitung beginnt. Es gilt
der folgende Hilfssatz.
Hilfssatz 15.1 Es sei G(V, E, A, ϕ, ψ) ein allgemeiner Graph und v ∈ V . Sind anfangs
alle Knoten unmarkiert, so bilden die Baumbögen von f-DF S(v) (b-DF S(v), a-DF S(v))
einen gerichteten Baum mit Wurzel v.
Beweis: Nach den obigen Festlegungen handelt es sich um einen Digraphen. Besteht er
nur aus einem isolierten Knoten, ist die Behauptung richtig. Andernfalls ergibt sich aus
ähnlichen Überlegungen wie beim Beweis von Satz 15.1, daß er f-kreisfrei ist, jeder Knoten
höchstens einen Vorgänger hat und jeder von v verschiedene Knoten von v f-erreichbar
ist. Nach Satz 14.6, Seite 387, ist der Digraph dann ein gerichteter Baum.
2
Wir starten nun mit einem vollständig unmarkierten allgemeinen Graphen. Nach dem Aufruf von f-DFS mit dem ersten Knoten der Knotenliste ergibt sich ein gerichteter Baum.
Bleiben Knoten übrig, die nicht zum Baum gehören, so erzeugen diese einen Untergraphen,
zu dem auch der erste unmarkierte Knoten der Knotenliste gehört. Wir betrachten den
Untergraphen als selbständigen, unmarkierten Graphen und starten auf ihm im ersten
unmarkierten Knoten der Knotenliste erneut f-DFS. Wir erhalten wieder einen gerichteten Baum. Wir fahren so fort, bis kein Knoten mehr übrig bleibt, und erhalten eine
Zerlegung der ursprünglichen Knotenmenge in Knoten, die zu gerichteten Bäumen gehören. Wir erhalten auch eine entsprechende Zerlegung der Baumbögen. Die so erhaltenen
gerichteten Bäume und den so erhaltenen gerichteten Wald wollen wir die zum Ablauf
der Tiefensuche gehörenden Tiefensuchbäume (depth-first search tree) bzw. Tiefensuchwald (depth-first search forest) nennen. Es ist leicht zu sehen, daß sich alle Ergebnisse
auf b-Tiefensuche und a-Tiefensuche übertragen lassen. Ebenso sieht man leicht, daß der
entstehende Tiefensuchwald vom Ablauf der Tiefensuche abhängt. Siehe dazu Aufgabe
15.2.
Eine Linie mit der von einem Knoten zu einem markierten und aktiven Knoten übergegangen wird, heißt Rückwärtsbogen (backward arc). Ein Rückwärtsbogen verbindet stets zwei
Knoten des gleichen Tiefensuchbaums. Vom aktiven Zielknoten des Rückwärtsbogens zu
seinem Startknoten gibt es einen einfachen Weg. Dieser wird mit dem Rückwärtsbogen,
so geschlossen, daß er linieneinfach bleibt. Wir haben also das folgende Ergebnis.
Hilfssatz 15.2 Bei dem Ablauf einer f-Tiefensuche schließt jeder Rückwärtsbogen einen
f-Kreis durch Start- und Zielpunkt des Bogens.
Für b-Tiefensuche und a-Tiefensuche gilt der Hilfssatz entsprechend.
Ein Bogen, der bei dem Ablauf einer f-Tiefensuche von einem Knoten zu einem markierten
inaktiven Knoten führt, heißt Vorwärtsbogen (forward arc), wenn er zu einem Knoten
des gleichen Tiefensuchbaumes führt. Er heißt Querbogen (cross arc), wenn er zu einem
15.2. TIEFENSUCHBÄUME
421
Knoten eines anderen Tiefensuchbaumes führt.
Achtung: Die hier gegebene Definiton für Vorwärtsbogen und Querbogen weicht von der
sonst üblichen ab. Siehe z. B. Cormen/Leiserson/Rivest [CormLR1990].
Hilfssatz 15.3 Eine Kante kann niemals Vorwärtsbogen oder Querbogen sein.
Beweis: Es werde bei der Tiefensuche der Knoten v bearbeitet. e sei eine mit v inzidente
Kante. Wird bei der Tiefensuche v über e erreicht, so ist e ein Baumbogen. Das gleiche
gilt, wenn über e ein unmarkierter Nachbar von v erreicht wird. Bleibt der Fall, daß e zu
einem markierten Nachbarn u führt. Dessen Bearbeitung kann aber nicht beendet sein,
denn sonst wäre die Kante e von u nach v durchlaufen worden, also doch Eingangsbogen
für v. Also ist u aktiv und e ein Rückwärtsbogen.
2
Aus Hilfssatz 15.3 ergibt sich unmittelbar das folgende Korollar.
Korollar. Bei a-Tiefensuche gibt es keine Vorwärtsbögen und keine Querbögen.
Beispiel 15.2 Dieses Beispiel soll zeigen, daß es vom Ablauf einer f-Tiefensuche abhängt,
ob ein f-Kreis nach Hilfssatz 15.2 gefunden wird oder nicht. Wir betrachten dazu Abbildung 15.3 und starten eine f-Tiefensuche in v0 . Wird in v2 zuerst der Bogen durchlaufen,
.............................................................................................
.............
...............
...............
...............
...............
................
................
...
.. ...
.. ...
.. ...
.. ....
..
..
..
...
........................................ .....
...
.................................................
..................................................
.................................................
................................................
....
.....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.. ... 3....
.. ... 4.....
.. ... 5.....
.... 1...
.... 2...
..... 0....
............
............
............
..........
..........
........
v
v
v
v
v
v
Abbildung 15.3: f-Tiefensuche findet nicht jeden f-Kreis
so wird in v4 die Kante als Rückwärtsbogen eingeordnet und der f-Kreis erkannt. Wird
jedoch in v2 zuerst die Kante durchlaufen, so erstreckt sich die Tiefensuche bis v5 , kehrt
dann zu v2 zurück, fährt mit dem Bogen dv2 v3 fort und ordnet als letztes den Bogen dv3 v4
als Vorwärtsbogen ein.
2
Allerdings führt bei a-Tiefensuche oder bei Digraphen die Existenz eines Kreises unabhängig vom Ablauf der Tiefensuche immer zu einem Rückwärtsbogen. Es gilt nämlich der
folgende Satz.
Satz 15.2
1. Ein allgemeiner Graph enthält genau dann einen a-Kreis, wenn jeder
Ablauf einer a-Tiefensuche einen Rückwärtsbogen findet.
2. Ein Digraph enthält genau dann einen f-Kreis, wenn jeder Ablauf einer f-Tiefensuche
einen Rückwärtsbogen findet.
422
KAPITEL 15. TIEFENSUCHE UND BREITENSUCHE
Beweis: Nach Hilfssatz 15.2 schließt jeder a-Rückwärtsbogen (f-Rückwärtsbogen) einen
a-Kreis (einen f-Kreis).
Es sei v0 , l1 , v1 , l2 , . . . , lk−1, vk−1 , lk , vk = v0 ein a-Kreis (bzw. f-Kreis). Bei einem Ablauf
einer a-Tiefensuche wird einer der Knoten des Kreises als erster markiert. Ohne Beschränkung der Allgemeinheit können wir annehmen, daß v0 dieser Knoten ist. Ähnlich wie beim
Beweis von Satz 15.1 zeigt man, daß alle anderen Knoten des Kreises besucht und bearbeitet werden, bevor die Bearbeitung von v0 beendet ist. Bei einer a-Tiefensuche können alle
l in beliebiger Richtung durchlaufen werden. Sind l1 , l2 , . . . , lk−1 Baumbögen, so muß lk
ein Rückwärtsbogen sein. Andernfalls sei li mit 1 ≤ i ≤ k − 1 das kleinste i, für das li kein
Baumbogen ist. Dann muß es nach dem Korollar zu Hilfssatz 15.3 ein Rückwärtsbogen
sein.
Ist der Graph ein Digraph, auf den eine f-Tiefensuche angewandt wird, so wird die Bearbeitung von vk−1 vor der von v0 beendet und lk muß ein Rückwärtsbogen sein.
2
Der Satz gilt auch für b-Kreise in Digraphen, denn jeder f-Kreis ist in umgekehrter Richtung ein b-Kreis.
Für stark zusammenhängende allgemeine Graphen gilt der folgenden Satz.
Satz 15.3 Es sei G ein stark zusammenhängender allgemeiner Graph.
1. Kein Ablauf einer f-Tiefensuche in G liefert einen Querbogen.
2. Es gibt in G genau dann einen a-Kreis, wenn es einen f-Kreis gibt. Das ist genau
dann der Fall, wenn jeder Ablauf einer f-Tiefensuche auf einen markierten Knoten
trifft.
Beweis: 1. In G ist jeder Knoten von jedem f-erreichbar. Jede f-Tiefensuche liefert also
nur einen einzigen Tiefensuchbaum. Die Wurzel ist der Startknoten der Tiefensuche. Es
kann keine Querbögen geben.
2.a Nach Satz 14.4, Seite 380, enthält der Graph G genau dann einen a-Kreis, wenn er
einen f-Kreis enthält. Es laufe in G eine Tiefensuche ab. Trifft sie einen markierten Knoten,
so gibt es einen Rückwärtsbogen oder einen Vorwärtsbogen. In beiden Fällen gibt es einen
a-Kreis.
b. Es gebe in G einen a-Kreis. Es soll gezeigt werden, daß dann jede f-Tiefensuche einen
markierten Knoten trifft. Ist der a-Kreis eine Schlinge, so ist das offenbar richtig. Andernfalls sei l die letzte Kante / der letzte Bogen des Kreises, den die Tiefensuche bearbeitet.
Nehmen wir an, die Bearbeitung führt von Knoten u zu Knoten v. Mit v inzidiert auch
noch eine zweite Kante / ein zweiter Bogen k des Kreises. k ist von l verschieden und
wurde vor l von der Tiefensuche bearbeitet. k verbindet v mit w, wobei u = w zugelassen
ist. v wurde von der Tiefensuche erreicht, bevor l erreicht wurde. Die Bearbeitung von l
führt auf jeden Fall zu einem markierten Knoten.
2
15.3. BESTIMMUNG SCHWACHER ZUSAMMENHANGSKOMPONENTEN
15.3
423
Bestimmung schwacher Zusammenhangskomponenten
Wir haben gesehen, daß wichtige Eigenschaften der Tiefensuche ablaufabhängig sind.
Tiefensuche kann aber auch sehr gut benutzt werden, um Eigenschaften, die nur vom
Graphen abhängen, aufzudecken. Besonders wichtig ist dabei die Zerlegung von Graphen
in schwache und starke Zusammenhangskomponenten (siehe Abschnitt 14.2). Schwache
Zusammenhangskomponenten sind leicht, nämlich durch unmittelbare Anwendung von aTiefensuche zu bestimmen. Tabelle 15.4 zeigt den Algorithmus WCOMP. Er durchläuft die
Knotenliste und richtet für jeden unmarkierten Knoten v eine neue schwache Zusammenhangskomponente C ein. Wenn diese eigentlich ist, wird die rekursive Prozedur WCP(v, C)
aufgerufen. Da a-Erreichbarkeit die gleiche Relation wie gegenseitige a-Erreichbarkeit ist,
werden mit diesem Aufruf alle Knoten besucht, die zur gleichen schwachen Zusammenhangskomponente gehören wie v, und nur diese. WCP(v, C) findet so alle Knoten, Kanten
und Bögen, die zur Komponente gehören, und stellt dabei durch Überwachung der Rückwärtsbögen in Zeile 2 fest, ob a-Kreisfreiheit vorliegt oder nicht (Satz 15.2).
Was kann man folgern, wenn eine eigentliche schwache Zusammenhangskomponente akreisfrei ist? Aus Proposition 14.1, Seite 380, folgt, daß sie entweder keine starken Zusammenhangskomponenten hat oder diese nur aus Kanten bestehen. Starke Zusammenhangskomponenten sind in diesem Fall a-kreisfreie ungerichtete Zusammenhangskomponenten,
als Untergraphen also freie Bäume. Der Graph als ganzes ist dann ein a-Baum. Er ist
i. a. kein f-Baum. Mit den noch zu behandelnden Methoden aus Abschnitt 15.4 lassen
sich seine Bögen klassifizieren und es läßt sich feststellen ob, er ein f-Baum ist. Enthält
eine eigentliche, a-kreisfreie schwache Zusammenhangskomponente nur Kanten, so ist sie
stark zusammenhängend. Sie ist ein freier Baum.
15.4
Bestimmung starker Zusammenhangskomponenten
Wir wollen in diesem Abschnitt nur schwach zusammenhängende allgemeine Graphen betrachten. Gegebenenfalls wenden wir vorher den Algorithmus WCOMP an. In Abschnitt
14.5, Seite 387, haben wir für die Knoten eines allgemeinen Graphen eine strikte partielle
Ordnung ≺ eingeführt und darauf eine Schichtennumerierung aufgebaut. Eine totale Ordnung < auf der Menge der Knoten, heißt topologische Sortierung (topological sort) wenn
sie Oberrelation von ≺ ist, d. h. gibt es einen f-Weg von u nach v, aber keinen von v nach
u, so gilt u < v.
Mit dem in Tabelle 15.5 aufgeführte Algorithmus PTOPSORT ist der erste Schritt zur
Bestimmung einer topologischen Sortierung getan. Wichtiger ist jedoch, daß es auch der
erste Schritt zur Bestimmung der starken Zusammenhangskomponenten ist. Bei der Aus-
424
KAPITEL 15. TIEFENSUCHE UND BREITENSUCHE
'
WCOMP
$
/* Anfangs sind alle Knoten und */
/* alle Kanten unmarkiert */
1 for (alle Knoten v aus V ) /* Knotenliste */
2
{ if (v unmarkiert)
3
{ richte neue schwache Zusammenhangskomponente C ein;
4
if (v isolierter Knoten)
5
{ kennzeichne C als uneigentliche
6
schwache Zusammenhangskomponente;
7
}
8
else
9
{ kennzeichne C als a-kreisfrei;
10
W CP (v, C);
11
} } }
WCP(v,C)
1
2
3
4
5
6
7
8
9
10
11
12
13
if (v markiert)
{ kennzeichne C als nicht a-kreisfrei;
return;
}
markiere v;
füge v in die Zusammenhangskomponente C ein;
for (alle Kanten, Ausgangsbögen und Eingangsbögen l von v)
{ if (l unmarkiert)
{ markiere l;
füge l in die Zusammenhangskomponente C ein;
W CP (otherend(l, v), C);
} }
return;
&
%
Tabelle 15.4: Algorithmus WCOMP zur Bestimmung der schwachen Zusammenhangskomponenten
führung von PTOPSORT gibt es für jede starke Zusammenhangskomponente einen ersten
Knoten, den PTOPSORT besucht. Wir wollen ihn den Eingangsknoten der Zusammenhangskomponente (bezüglich der Ausführung von PTOPSORT) nennen. Die Bedeutung
15.4. BESTIMMUNG STARKER ZUSAMMENHANGSKOMPONENTEN
'
PTOPSORT
/* Anfangs sind alle Knoten unmarkiert. */
/* Der Keller stack ist anfangs leer. */
1
2
3
4
for (alle Knoten v aus V )
{ if (v unmarkiert)
P T P S(v);
};
425
$
/* Knotenliste */
PTPS(v)
1
2
3
4
5
6
7
8
&
if (v markiert) return;
markiere v;
for (alle Kanten und Ausgangsbögen l von v)
{ if (l unmarkiert)
{ markiere l;
P T P S(otherend(l, v);
} };
push (stack, v);
/* rekursiver Aufruf */
%
Tabelle 15.5: Algorithmus PTOPSORT zur topologischen Präsortierung eines allgemeinen
Graphen
von PTOPSORT besteht darin, daß für die entscheidenden Knoten im Keller die partielle Ordnung ≺ durch eine lineare Ordnung abgebildet wird. Das besagt die folgende
Proposition.
Proposition 15.3 Es sei u ein Knoten ohne Rückkehr oder der Eingangsknoten einer
starken Zusammenhangskomponente. Es gebe einen f-Weg von u nach v aber keinen von
v nach u. Dann liegt im Keller u vor 1 v.
Beweis: 1. PTOPSORT besucht v vor u (Erstbesuch): Da u von v nicht erreicht werden
kann, wird die Bearbeitung von v abgeschlossen und v dem Keller hinzugefügt, bevor die
Bearbeitung von u beginnt.
2. PTOPSORT besucht u vor v (Erstbesuch): Die Bearbeitung von u wird nur dann v nicht
erreichen und u vor v in den Keller eingefügt werden, wenn bei Beginn der Bearbeitung
jeder f-Weg von u nach v durch einen markierten Knoten versperrt ist (Proposition 15.1).
Es sei ein einfacher f-Weg von u nach v gegeben und darauf vm der letzte markierte
1
d. h. näher an der Kellerspitze
426
KAPITEL 15. TIEFENSUCHE UND BREITENSUCHE
Knoten. Da v noch nicht markiert ist, muß vm beim Beginn der Bearbeitung von u noch
aktiv sein, d. h es gibt einen f-Weg von vm nach u. Also gehören vm und u zur gleichen
starken Zusammenhangskomponente. Dann ist jedoch u weder ein Knoten ohne Rückkehr
noch Eingangsknoten der starken Zusammenhangskomponente.
2
Anmerkung 15.2 Wird PTOPSORT auf einen Dag angewendet, so ist man fast fertig.
Es gibt keine starken Zusammenhangskomponenten und die lineare Ordnung im Keller
ist eine topologische Sortierung. Allerdings hat man die Schichtennumerierung noch nicht
gewonnen.
2
Der zweite und endgültige Schritt zur Gewinnung einer topologischen Sortierung und zur
Bestimmung der starken Zusammenhangskomponenten ist der Algorithmus STRONGCOMP. Er ist in Tabelle 15.6, Seite 427, aufgeführt. Zusammen mit der in Tabelle 15.7
angegebenen Prozedur STRCP liefert er für einen allgemeinen Graphen, auf den vorher
Algorithmus PTOPSORT angewandt wurde, das folgenden Ergebnis:
Satz 15.4 Es sei G ein allgemeiner Graph, auf den Algorithmus PTOPSORT angewendet
wurde. Dann ergibt die Anwendung von Algorithmus STRONGCOMP auf G:
1. Es werden alle Knoten ohne Rückkehr und alle starken Zusammenhangskomponenten mit ihren Knoten, Kanten und Bögen gefunden. Zu jeder starken Zusammenhangskomponente wird festgestellt, ob sie f-kreisfrei ist oder einen f-Kreis enthält.
2. Zu jeder starken Zusammenhangskomponente werden alle schwachen Verheftungspunkte identifiziert.
3. Es werden alle Bögen des externen Dag gefunden.
4. Den Knoten ohne Rückkehr und den starken Zusammenhangskomponenten wird ihre
Schichtennummer zugeordnet.
5. In einer Warteschlange queue wird eine topologische Sortierung der Knoten von G
angelegt.
Beweis: Die Knoten im Keller sind anfangs alle unmarkiert und werden der Reihe nach
abgearbeitet. Alle Kanten sind anfangs ungefärbt. Nach Proposition 15.3 ist der erste
Knoten im Keller v ein Knoten ohne Rückkehr, der keine Vorgänger hat, oder der Eingangsknoten einer starken Zusammenhangskomponente, die keine von ihr nicht erreichbaren Vorgänger hat. Ist v ein Knoten ohne Rückkehr, so wird er in Zeile 4 als solcher
erkannt. Er wird korrekt erster Knoten in der Warteschlange für die topologische Sortierung. Er behält die voreingestellte Schichtennummer 0, was auch richtig ist. Schließlich
werden alle seine Ausgangsbögen als Bögen des externen Dag erkannt und gelb gefärbt.
15.4. BESTIMMUNG STARKER ZUSAMMENHANGSKOMPONENTEN
'
427
STRONGCOMP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* Anfangs sind alle Knoten unmarkiert und alle Kanten ungefärbt. */
/* Alle Knoten und alle starken Zusammenhangskomponenten */
/* haben 0 als Voreinstellung für die Schichtennummer */
/* Im Keller stack stehen die Knoten in PTOPSORT-Reihenfolge. /*
v = pop(stack);
while (v 6= NULL) /* Keller abarbeiten */
{ if (v unmarkiert)
{ if (v hat keine Kanten und keine ungefärbten Eingangsbögen)
{ kennzeichne v als rückkehrfrei;
füge v in Warteschlange queue ein;
for (alle Eingangsbögen a von v)
{ if (lv(otherend(a, v)) >= lv(v)) lv(v) = lv(otherend(a, v)) + 1 ;
}
for (alle Ausgangsbögen a von v)
{ a als Bogen des externen Dag kennzeichnen;
färbe a gelb;
} }
else
{ richte neue starke Zusammenhangskomponente SC ein;
ST RCP (v, SC);
for (alle Knoten v in SC)
{ if (v hat gelbe oder ungefärbte Bögen)
{ kennzeichne v als schwachen Verheftungspunkt von SC;
for (alle gelben Eingangsbögen a von v)
{ if (lv(otherend(a, v)) >= lv(v))
lv(v) = lv(otherend(a, v)) + 1 ;
}
if (lv(v) > lv(SC)) lv(SC) = lv(v);
for (alle ungefärbten Ausgangsbögen a von v)
{ a als Bogen des externen Dag kennzeichnen;
färbe a gelb;
}
} }
for (alle Knoten v in SC) lv(v) = lv(SC);
} }
v = pop(stack);
}
&
Tabelle 15.6: Algorithmus STRONGCOMP zur Bestimmung der starken Zusammenhangskomponenten eines allgemeinen Graphen
$
%
428
'
KAPITEL 15. TIEFENSUCHE UND BREITENSUCHE
STRCP(v,SC)
1
2
3
4
5
6
7
8
9
10
11
12
13
if (v markiert)
{ kennzeichne SC als f-zyklisch;
return;
}
markiere v;
füge v in SC ein;
füge v in Warteschlange queue ein;
for (alle Kanten und Eingangsbögen l von v)
{ if (l ungefärbt)
{ färbe l blau;
füge l in die Zusammenhangskomponente SC ein;
ST RCP (otherend(l, v), SC);
} }
&
$
%
Tabelle 15.7: Prozedur STRCP zur Bestimmung der starken Zusammenhangskomponenten
eines allgemeinen Graphen
Ist der erste Knoten v ein Knoten mit Rückkehr, so wird er richtig als Eingangsknoten
einer starken Zusammenhangskomponente erkannt. Es wird ein Beschreibungssatz SC
eingerichtet und mit dem Aufruf ST RCP (v, SC) in v eine b-Tiefensuche gestartet. Diese
besucht nach Proposition 15.1 alle von v b-erreichbaren Knoten. Da v der erste Knoten im
Keller ist, muß (wieder nach Proposition 15.3) v auch von jedem dieser Knoten erreichbar
sein. Es werden also genau die Knoten besucht und markiert, die zur gleichen starken
Zusammenhangskomponente gehören wie v. Genau dann, wenn bei der b-Tiefensuche ein
markierter Knoten gefunden wird, enthält SC einen f-Kreis: Ist ein f-Kreis vorhanden,
wird mit Sicherheit ein Rückwärtsbogen oder ein Vorwärtsbogen gefunden. Falls andererseits die b-Tiefensuche auf einen markierten Knoten trifft, muß es nach Satz 15.3, Seite
422, einen a-Kreis und damit nach Satz 14.4, Seite 380, auch einen f-Kreis in SC geben.
Die gefundenen Knoten werden der Komponente zugeordnet. Es werden auch alle Kanten
und Bögen, die mit der b-Tiefensuche besucht werden, blau gefärbt und der Komponente
zugeordnet. v wird als erster Knoten in die Warteschlange für topologische Sortierung
eingetragen und direkt danach geschieht das gleiche für alle anderen Knoten der starken
Zusammenhangskomponente. Die Reihenfolge ist durch den Ablauf der b-Tiefensuche bestimmt, ist aber stets in Einklang mit der partiellen Ordnung ≺. Anschließend werden
alle Knoten u der starken Zusammenhangskomponente bearbeitet. Da es keine gelben
Eingangsbögen gibt, bleibt die Schichtennumerierung der starken Zusammenhangskomponente auf dem voreingestellten Wert 0. Außerdem wird geprüft, ob die Knoten mit
15.4. BESTIMMUNG STARKER ZUSAMMENHANGSKOMPONENTEN
429
nicht-blauen Bögen inzidieren. Das können nur ungefärbte Ausgangsbögen sein. Sie werden dem externen Dag zugeordnet und gelb gefärbt. Die entsprechenden Knoten werden
als schwache Verheftungsknoten der starken Zusammenhangskomponente klassifiziert und
bekommen wie diese die Schichtennummer 0.
Wir wollen nun annehmen, daß bis zu einem Punkt die Anwendung von STRONGCOMP
korrekt gelaufen ist und v der nächste nicht markierte Knoten im Keller ist. v muß dann
ein Knoten ohne Rückkehr oder der Eingangsknoten einer neuen starken Zusammenhangskomponente sein. Ist v ein Knoten ohne Rückkehr, so inzidiert er mit keiner Kante und
hat Eingangsbögen nur von Knoten, die von ihm nicht erreichbar sind. Diese müssen dann
im Keller vor v gelegen haben und schon korrekt abgearbeitet sein. D. h sie haben die
richtige Schichtennummer, stehen an einer korrekten Stelle in der Warteschlange für topologische Sortierung und alle ihre zu v führenden Ausgangsbögen sind gelb. v hat also
keine ungefärbten Eingangsbögen und wird in Zeile 4 korrekt als Knoten ohne Rückkehr
erkannt. Auch die Einordnung in die Warteschlange für topologische Sortierung ist korrekt. Aus den Schichtennummern der unmittelbaren Vorgänger wird über die (gelben)
Eingangsbögen die Schichtennummer von v richtig ermittelt. Die Ausgangsbögen von v
werden gelb gefärbt und dem externen Dag zugeordnet.
Ist v Eingangsknoten einer neuen starken Zusammenhangskomponente, dann inzidiert v
mit einer Kante oder es gibt einen Eingangsbogen von einem Knoten der gleichen Komponente. Der Eingangsbogen muß ungefärbt sein. v wird richtig als Knoten mit Rückkehr
erkannt und es wird einen neue starke Zusammenhangskomponente SC eingerichtet. Alle
Knoten, von denen es einen Bogen in SC hinein gibt, die aber von SC nicht erreichbar
sind, liegen im Keller vor v und die entprechenden Bögen sind nach Voraussetzung gelb
gefärbt. Wird nun mit der Prozedur ST RCP in v eine b-Tiefensuche längs ungefärbter Linien gestartet, so besucht diese wieder genau die Knoten von SC und findet auch
alle Kanten und Bögen der Komponente. Sie stellt fest, ob f-Kreisfreiheit vorliegt, und
sorgt dafür, daß die Knoten an richtigen Stellen in der Warteschlange für die topologische
Sortierung stehen.
Die anschließende Durchmusterung aller Knoten der Komponente findet über die gelben
Eingangsbögen die vorläufige Schichtennummer eines jeden schwachen Verheftungspunktes mit gelben Eingangsbögen. Das Maximum dieser vorläufigen Schichtennummern wird
zur Schichtennummer der starken Zusammenhangskomponente. Die ungefärbten Ausgangsbögen werden gelb gefärbt und dem externen Dag zugeordnet. Auch sie bestimmen
schwache Verheftungspunkte. Schließlich erhalten alle Knoten einer starken Zusammenhangskomponente die gleichen Schichtennummer wie diese.
2
Anmerkung 15.3 (Aufrufschemata für rekursive Unterprogramme) Die Aufrufschemata für Unterprogramme bilden einen Digraphen. Siehe Seit 88. Genau dann, wenn
dieser f-kreisfrei ist, kommen keine rekursiven Aufrufe vor. Algorithmus STRONGCOMP
bietet eine Möglichkeit, auf Rekursionsfreiheit zu testen.
2
430
KAPITEL 15. TIEFENSUCHE UND BREITENSUCHE
Proposition 15.4 Die Anwendung von STRONGCOMP auf einen allgemeinen Graphen
verursacht den Aufwand O(m + n), wobei m die Anzahl Linien und n die Antahl Knoten
des Graphen ist.
Beweis: Wird dem Leser überlassen.
15.5
Breitensuche
Der Algorithmus für f-Breitensuche (Breitensuche in Vorwärtsrichtung, f-breadth-first
search) in einem allgemeinen Graphen ist als Prozedur f-BFS in Tabelle 15.8 angegeben.
'
&
f-BFS(v)
1 enqueue(v);
2 markiere v;
3 while (Warteschlange nicht leer)
4
{ u = dequeue;
5
for (alle Kanten und Ausgangsbögen l von u)
6
{ if (l markiert) return;
7
markiere l;
8
w = otherend(l, u);
9
if (nicht markiert w)
10
{ enqueue(w);
11
markiere w;
12
} } };
$
%
Tabelle 15.8: Algorithmus f-BFS für Breitensuche in einem allgemeinen Graphen
Werden in der Prozedur Kanten und Eingangsbögen durchlaufen, so erhalten wir b-Breitensuche (Breitensuche in Rückwärtsrichtung, b-breadth-first search). Bei a-Breitensuche
(Breitensuche in beliebiger Richtung, a-breadth-first search) werden Kanten, Eingangsbögen und Ausgangsbögen durchlaufen. Ähnlich wie Tiefensuche besteht Breitensuche aus
einem Rahmenablauf – f-BFSgesamt – und der darin aufgerufenen Prozedur f-BFS. Der
Gesamtablauf f-BFSgesamt ist in Tabelle 15.9 dargestellt.
Auch Breitensuche benutzt Markierungen und geht von anfänglich unmarkierten Knoten
und Kanten aus. Die Prozedur f-BFS ist jedoch nicht rekursiv, sondern arbeitet mit einer
Warteschlange. Sie markiert den Knoten, mit dem sie aufgerufen wird, und fügt ihn als
ersten in die Warteschlange ein. Danach werden solange Knoten der Warteschlange entnommen, wie diese nicht leer ist. Zu jedem entnommenen Knoten werden alle Kanten und
15.5. BREITENSUCHE
'
&
f-BFSgesamt
1
2
3
4
/* Anfangs sind alle Knoten */
/* und alle Kanten unmarkiert */
for (alle Knoten v aus V ) /* Knotenliste */
{ if (v unmarkiert)
{ f-BFS(v);
} };
431
$
%
Tabelle 15.9: Algorithmus f-BFS für Breitensuche in einem allgemeinen Graphen
Ausgangsbögen (Kanten und Eingangsbögen, Kanten und Bögen) getestet, ob sie markiert sind. Alle noch nicht markierten Linien werden markiert und der Knoten am anderen
Ende geprüft, ob er markiert ist. Falls nein, wird er markiert und an die Warteschlange
angehängt. Wird f-BFS mit dem Anfangsknoten v aufgerufen, so wird ein Knoten u gar
nicht, einmal oder mehrmals besucht. Nur beim ersten Besuch wird er markiert und in die
Warteschlange eingereiht. Die Bearbeitung eines Knotens findet statt, wenn er der Warteschlange entnommen wird. Breitensuche ist wie Tiefensuche leicht zu programmieren und
für Listendarstellungen gut geeignet.
Anders als Tiefensuche hat Breitensuche eine Optimalitätseigenschaft: Sie findet die erreichbaren Knoten auf kürzesten Wegen. Dazu soll der Begriff der f-Schale l zu einem
Knoten v (f-shell) eingeführt werden. Ein von v f-erreichbarer Knoten w (w 6= v) gehört
zur f-Schale l von v, wenn l die Länge eines kürzesten f-Weges von v nach w ist. {v} ist
die Schale 0. Entsprechend werden die b-Schale l (b-shell) und die a-Schale l (a-shell) zu
v definiert. Der folgende Satz besagt u. a., daß Breitensuche die Schalen in aufsteigender
Reihenfolge der l bearbeitet. Der Durchlaufmodus – f, b oder a – ist dabei beliebig, aber
fest.
Satz 15.5 1. Breitensuche in einem allgemeinen Graphen erfordert c1 · n + c2 · 2m Zeitschritte.
2. In einem anfänglich unmarkierten Graphen besucht BF S(v) jeden von v erreichbaren
Knoten w (w 6= v) und nur diese.
3. Es werden erst alle Knoten der Schale l markiert, bevor ein Knoten der Schale l + 1
markiert wird.
Beweis: 1. Wegen der Markierung wird jeder Knoten von BFSgesamt genau einmal
bearbeitet. Bei jedem Knoten wird jede Kante und jeder ausgehende Bogen (jeder ankommende Bogen, jeder Bogen) genau einmal bearbeitet. Falls sie nicht markiert ist, wird
entweder mit dem Knoten am anderen Ende des Bogens die Breitensuche fortgesetzt oder
432
KAPITEL 15. TIEFENSUCHE UND BREITENSUCHE
festgestellt, daß dieser schon markiert ist. Das sind c1 · n + c2 · 2m Zeitschritte. c1 ist die
Bearbeitungszeit (ohne Untersuchung der Linien) in einem Knoten, c2 ist die Bearbeitungszeit einer Linie.2
2. und 3. Breitensuche baut einen f-Weg (b-Weg, a-Weg) von v zu jedem besuchten Knoten
w auf, d. h. jeder besuchte Knoten ist auch von v erreichbar.
Es sei w (w 6= v) von v f-erreichbar (b-erreichbar , a-erreichbar). Dann gibt es einen
kürzesten Weg von v nach w. Seine Länge sei l. w gehört zur Schale l von v. w wird
markiert und daher (mindestens einmal) besucht. Daß w auch wirklich markiert wird,
und zwar nach allen Knoten der Schale l − 1 und vor allen Knoten der Schale l + 1 wird
durch vollständige Induktion bewiesen: Es ist richtig für l = 1, denn alle Nachbarn von v,
die infrage kommen, werden in die Warteschlange eingereiht, bevor ein Knoten, der nicht
Nachbar ist, eingefügt wird. Ist die Aussage richtig für l, so werden nach dem Einfügen
der Knoten der Schale l alle Knoten der Schale l + 1 (als unmarkierte Nachbarn von
Knoten der Schale l) in die Warteschlange eingefügt, ehe der erste von ihnen bearbeitet
wird. D. h. alle Knoten der Schale l + 1 kommen vor allen Knoten der Schale l + 2 in die
Warteschlange.
2
Beispiel 15.3 Dieses Beispiel für den Ablauf einer f-Breitensuche ist so aufgebaut wie
das Beispiel 15.1, Seite 415, für den Ablauf einer f-Tiefensuche. Es wird wieder Graph3,
Seite 415, mit den in Abbildung 15.2, Seite 416, angegebenen Inzidenzlisten zugrunde
gelegt. Tabelle 15.10 zeigt den Ablauf einer f-Breitensuche. Linien, die bei Bearbeitung
eines Knotens markiert angetroffen werden oder der anderer Endknoten schon markiert
ist, sind in der Tabelle kursiv dargestellt.
2
Anmerkung 15.4 Die in Tabelle 15.10 angegebenen Schalen beziehen sich nur auf den
ersten bearbeiteten Knoten, nämlich v3 . Zu anderen Knoten, die von v3 f-erreichbar sind,
werden die Schalen nicht angegeben, z. B. zu v4 . Sind die Knoten nicht von v3 f-erreichbar,
wie zum Beispiel v7 , so werden ihre Schalen unvollständig angegeben.
2
Bei a-Breitensuche werden vom ersten besuchten Knoten v einer schwachen Zusammenhangskomponente alle ihre Knoten erreicht und die zu v gehörende Schalenstruktur festgestellt. Zur weiteren Ermittlung von Zusammenhangseigenschaften wie starker Zusammenhang, Schichtennumerierung usw. ist Breitensuche weniger gut geeignet.3
Streng genommen müßten Schlingen gesondert betrachtet werden, da sie nur an einem Knoten bearbeitet werden.
3
Allerdings ist bei anderen Problemen der Einsatz von Breitensuche von Vorteil. Z.B. bei der Bestimmung eines maximalen Flusses (siehe Abschnitt 20.4) und beim Finden von Mengerstrukturen (siehe
Stiege [Stie2006]).
2
15.5. BREITENSUCHE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
433
Schale 0
v3
uv3 v4
uv3 v6
dv3 v2
Schale 1
v4
v6
v2
Schale 2
uv3 v4
dv4 v4
dv4 v5
uv3 v6
v5
v8
v7
v1
dv5 v6
dv5 v3
dv8 v5
dv7 v8
dv7 v5
dv1 v2 a
dv1 v2 b
dv1 v3
Tabelle 15.10: Ablaufprotokoll einer f-Breitensuche
Aufgaben
Aufgabe 15.1 Für Graph3, Seite 415, sehe die Knotenliste folgendermaßen aus: v7 , v8 ,
v1 , v6 , v2 , v3 , v5 , v4 . Die Liste ist durch Inzidenzlisten zu ergänzen und für b-Tiefensuche
ein Ablaufprotokoll nach dem Muster von Beispiel 15.1 anzugeben.
Aufgabe 15.2 Geben Sie einen Graphen und auf ihm f-Tiefensuchabläufe an, die zu
unterschiedlichen Tiefensuchwäldern mit unterschiedlicher Anzahl Baumbögen führen.
Aufgabe 15.3 Es sei G(V, E, A, ϕ, ψ) ein allgemeiner Graph. Auf V × V werde die folgende reellwertige partielle Abbildung definiert

 0, falls u = v
Länge eines kürzesten f-Weges von u nach v, falls u 6= v und v von u f-erreichbar
dgf (u, v) :=

undefiniert sonst
Entsprechend sind dgb (u, v) und dga (u, v) definiert.
a. Zeigen Sie, daß dga (u, v) eine Metrik ist, wenn G schwach zusammenhängend ist.
b. Wie kann man feststellen, ob auch dgf (u, v) eine Metrik ist?
434
KAPITEL 15. TIEFENSUCHE UND BREITENSUCHE
Aufgabe 15.4 Es sei G(V, E, A, ϕ, ψ) ein allgemeiner Graph und v ∈ V . Der f-Schalengraph
von v in G ist der folgendermaßen definierte Digraph: Seine Knoten sind die Knoten der
f-Schalen von v. Ein Bogen geht stets von einem Knoten u der Schale l zu einem Knoten w der Schale l + 1. u und w werden durch einen solchen Bogen verbunden, wenn
es einen f-Übergang von u zu w gibt. Entsprechend werden der b-Schalengraph und der
a-Schalengraph zu v definiert.
Man gebe einen Algorithmus an, der zu gegebenem G und v die Schalengraphen findet.
Aufgabe 15.5 Es sei G(V, E, A, ϕ, ψ) ein allgemeiner Graph und v ∈ V . w 6= v sei ein
Knoten der a-Schale l von v. Der Distanzgraph von v bezüglich w ist der Untergraph des
a-Schalengraphen von v, der durch die a-Wege kürzester Länge von v nach w gebildet
wird.
a. Wie sieht der Distanzgraph von w bezüglich v aus?
b. Man gebe einen Algorithmus an, der zu G, v und w den Distanzgraphen von v bezüglich
w findet.
Aufgabe 15.6 Es sei G(V, E, A, ϕ, ψ) ein allgemeiner Graph und u, v ∈ V mit u 6= v. Es
sei p ein einfacher f-Weg (b-Weg, a-Weg) von u nach v. p heißt direkt (gerade, straight),
wenn kein innerer Punkt des Weges auf einem kürzeren einfachen f-Weg (b-Weg, a-Weg)
von u nach v liegt.
Skizziereb Sie einen Algorithmus, der alle direkten f-Wege (b-Wege, a-Wege) von u nach
v bestimmt.
Literatur
Tiefensuche wurde Ende der 70er-Jahre von Tarjan und Hopcroft in einer Reihe bahnbrechender Aufsätze als wichtigstes Instrument der algorithmischen Graphentheorie eingeführt [Tarj1972], [HopcT1973], [HopcT1973], [HopcT1973a], [HopcT1974]. Siehe auch das
klassische Lehrbuch von Aho/Hopcroft/Ullman [AhoHU1974]. Moderne Bücher über Algorithmen und Datenstrukturen behandeln im allgemeinen Tiefensuche ausführlich. Als
Beispiele seien Cormen/Leiserson/Rivest [CormLR1990], Mehlhorn/Näher [MehlN1999]
und Sedgewick [Sedg2002] genannt.
Mathematisch ausgerichtete Bücher über Graphentheorie erwähnen entweder Tiefensuche überhaupt nicht oder nur ganz am Rande. Ausführlicher wird Tiefensuche in algorithmisch ausgerichtete Lehrbüchern über Graphentheorie behandelt. Siehe z. B. Chartrand/Oellermann [CharO1993], McHugh [McHu1990], Thulasiraman/Swamy [ThulS1992],
Turau [Tura1996] und Jungnickel [Jung1994].
15.5. BREITENSUCHE
435
Anders als Tiefensuche ist Breitensuche nicht durch eine Reihe bestimmter Artikel eingeführt und verbreitet worden, sondern „irgendwie“ gewachsen. Moderne Darstellungen von
Breitensuche findet man in den oben genanten Büchern.
436
KAPITEL 15. TIEFENSUCHE UND BREITENSUCHE
Kapitel 16
Die Biblockzerlegung
In diesem Kapitel werden nur a-Wege betrachtet und die Ergebnisse hängen nur von der
Inzidenzstruktur der betrachteten allgemeinen Graphen ab. Sie sind für alle Graphen einer
Orientierungsklasse gleich. Schlingen und Mehrfachlinien sind zugelassen.
16.1
Klassen geschlossener a-Wege und Kantenzerlegungen
Die Biblockzerlegung eines allgemeinen Graphen wird durch Untergraphen bestimmt, in
denen es mindestens zwei „unabhängige“ a-Wege zwischen je zwei Knoten gibt. Unabhängig heißt dabei, daß die Wege keine inneren Knoten gemeinsam haben. Es wird auch
eine schwächere Form der Unabhängigkeit betrachtet, bei der zwei Wege keine gemeinsamen Linien aufweisen. Wie man sich leicht klar macht, muß es in beiden Fällen a-Kreise
in den Untergraphen geben. Wir wollen daher bei der Biblockzerlegung mit schwachen
a-zyklischen Zusammenhangskomponenten beginnen (siehe hierzu Abschnitt 14.6, Seite
392). In diesen wird ausgehend von a-Kreisen eine Hierarchie geschlossener a-Wege betrachtet:
a-Kreise ⊆ linieneinf ache geschlossene a-W ege ⊆ stoppf reie W ege
Zu linieneinfachen Wegen und Kreisen siehe Kapitel 14, Seite 373. Ein stoppfreier Weg
(stopfree path) ist ein geschlossener a-Weg, auf dem keine Linie zweimal hintereinander
auftritt und, falls die Weglänge mindestens 2 ist, die erste von der letzten Linie verschieden
ist1 . Eine schematische Darstellung ist in Abbildung 16.1 zu sehen.
Der Name läßt sich so erklären: Man kann mit einer Lokomotive den geschlossenen Weg durchfahren,
ohne anhalten und zurückfahren zu müssen.
1
437
438
KAPITEL 16. DIE BIBLOCKZERLEGUNG
.......
.... ......
....
.
..............
...............
....
.... .....
........
...............
.
....
.... .....
........
......
.... .....
....
.
...............
........
.... .....
....
.
...............
..............
...
............
............
...... ............. ..........
.
.
.
.
....
.
...
....
...
...
.
...
..
..
.......
.
.
.
.
.
. .
............
.... ....
.... .....
...
... ..
.
.
............
............
.
...
...
...
...
...
..
....
.
.
.
.....
.
....... ............. ..........
...........
............
...
..............
...............
....
.... .....
........
.........
... .....
.
...
.......................................
.
.....
......
.
.
.
.
....
...
...
...
...
.
.
.
...
.
......
.
.
.
.
.
. .
............
.... .....
.... ....
... ..
...
.
.
............
............
.
...
...
...
...
...
..
....
.
.
.
.....
.
....... ............. ..........
...........
............
...
...............
a. Geschlossener a-Weg
b. Stoppfreier Weg
..........
..............
... ....
.
...
........
...........
........
...........
....... ............... ............
....... ............. ...........
....
......
.....
.
.
.
.
.
.
.
.
...
... ...
..
.
...
.
.
.
... ...
...
...
... ...
..
...
.
.
.
.
......
.......
..............
.
..... .....
..... .....
....
.
.
.
...
...
.
.
...............
.............
..............
...
..
....
...
..
.. .....
.
.
.
.
...
...
... ......
...
...
.....
...
.....
.....
......
...... ............. ..........
......... ................. .............
............
...........
....
.
...
.
.
.
... ..
..... ....
...........
......
..............
.
...
.........
.........
....... .............. ............
.....
....
.
.
.
...
...
...
.
..
...
..
.
.
.
......
.............
.
.
...... .....
..
....
... ...
...............
............
...
..
...
..
.
.
...
..
...
...
....
.....
......
........ ............... .............
.....
......
.
... ..
...........
c. Linieneinfacher geschlossener a-Weg
d. a-Kreis
Abbildung 16.1: Klassen geschlossener a-Wege
In Graph Ugraph1, Abbildung 16.5, Seite 443, ist d2 , d1, d0 , c0 , c3 , c2 , c1 , c0 , d0 , d2 ein stoppfreier Weg2 , der nicht linieneinfach ist. d2 , d1 , d0 , c0 , c3 , c2 , c1 , c0 , d0, d1 , d2 ist kein stoppfreier Weg, da die erste und die letzte Kante übereinstimmen.
Definition 16.1 Es werden die folgenden Relationen zwischen Linien definiert: Zwei Linien e und f sind
1. stoppfrei verbunden, wenn es einen stoppfreien Weg gibt, der e und f enthält,
2. linieneinfach verbunden, wenn es einen linieneinfachen geschlossenen a-Weg gibt,
der e und f enthält,
3. kreisverbunden, wenn es einen a-Kreis gibt, der e und f enthält.
Mit Definition 16.1 erhält man eine Hierarchie von Zerlegungen der Linienmenge eines
allgemeinen Graphen G. Dazu der folgende Satz.
Satz 16.1
1. Stoppfreie Verbundenheit ist eine Äquivalenzrelation in der Menge der
Linien, die auf einem stoppfreien Weg liegen.
2. Kanteneinfache Verbundenheit ist eine Äquivalenzrelation in der Menge der Linien,
die auf einem linieneinfachen geschlossenen a-Weg liegen.
3. Kreisverbundenheit ist eine Äquivalenzrelation in der Menge der Linien, die auf
einem a-Kreis liegen.
2
Siehe Anmerkung 14.1, Seite 374.
16.2. DIE BIBLOCKZERLEGUNG ALLGEMEINER GRAPHEN
439
Beweis: Reflexivität folgt aus der Definition der betrachteten Linienmengen. Symmetrie
ist unmittelbar klar. Transitivität läßt sich mit einem einzigen Beweis für alle drei Relationen nachweisen. Es seien die Linien e und f und die Linien f und g stoppfrei verbunden
(linieneinfach verbunden, kreisverbunden). Zu zeigen ist, daß e und g auf die gleiche Weise
verbunden sind. Das ist klar, wenn zwei der drei Linien identisch sind. Das ist auch leicht
einzusehen, wenn zwei der drei Linien Mehrfachlinien mit gleichen Inzidenzpunkten sind.
Es werde angenommen, daß die Linien e, f und g paarweise verschieden und keine Mehrfachlinien zu den gleichen Inzidenzpunkten sind. Wir identifizieren die Linien durch ihre
Inzidenzpunkte und wählen einen stoppfreien Weg (einen geschlossenen linieneinfachen
a-Weg, einen a-Kreis) durch die Linien {a1 , a2 } und {b1 , b2 } und einen geschlossenen Weg
gleichen Typs durch die Linien {b1 , b2 } und {c1 , c2 }:
v0 = a1 , v1 = a2 , . . . , vi−1 , vi = b1 , vi+1 = b2 , . . . , vm−1 , vm = a1 und
w0 = c1 , w1 = c2 , . . . , wj−1, wj = b1 , wj+1 = b2 , . . . , wn−1, wn = c1 ,
dabei kann die Durchlaufsrichtung des zweiten geschlossenen Weges so gewählt werde,
daß die mittlere Linie in Richtung (b1 , b2 ) durchlaufen wird (siehe Abbildung 16.2). Wir
durchlaufen den ersten geschlossenen Weg in positiver Richtung von a1 bis b1 und treffen in
u1 zum ersten Mal auf einen Knoten, der auch zum zweiten geschlossenen Weg gehört: v0 =
a1 , v1 = a2 , . . . , u1 , . . . , vi = b1 . Entsprechend durchlaufen wir den ersten geschlossenen
Weg in negativer Richtung von a1 bis b2 und treffen in u2 zum ersten Mal auf den zweiten
geschlossenen Weg: a1 = vm , vm−1 , . . . , u2 , . . . , vi+1 = b2 . Nun wird ein neuer geschlossener
Weg konstruiert: Wir durchlaufen von a1 den ersten Weg in positiver Richtung bis u1 , dann
den zweiten Weg in negativer Richtung bis c2 , dann die Linie bis c1 und weiter bis u2 .
Danach wird in positiver Richtung auf dem ersten Weg fortgesetzt und a1 über vm−1
erreicht. Der neue geschlossene Weg enthält die Linien {a1 , a2 } und {c1 , c2 } und ist vom
gleichen Typ wie die gegebenen beiden geschlossenen Wege.
2
Der obige Beweis ist auch dann gültig, wenn Teilwege der Konstruktion die Länge 0 haben.
Einige Beispiele sind in Abbildung 16.3 zu sehen.
16.2
Die Biblockzerlegung allgemeiner Graphen
Die in Abschnitt 16.1 gefundenen Äquivalenzklassen der Verbundenheitsrelationen erhalten eigene Namen, ebenso die von ihnen erzeugten Untergraphen.
Definition 16.2
1. Eine Äquivalenzklasse stoppfrei verbundener Linien heißt stoppfreier Kern.
2. Eine Äquivalenzklasse linieneinfach verbundener Linien heißt Subkomponente.
3. Eine Äquivalenzklasse kreisverbundener Linien heißt Biblock.
440
KAPITEL 16. DIE BIBLOCKZERLEGUNG
u1
..................
.....
...
...
..
..
.................
...................
.
....................
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
............
...
.........
.
.
.
.
.........
..
.
.
.
.
.
.....
........
...
.
.......
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.......
.
.
.
...... .
.....
.
......
.
.
.
.
.
.
......
.
....
.
.
.
.
......
.
.
...
.
.
.....
.
.
.
....
.
.
.....
.
.
.
...
.
..
.
...
.
.
...
.
...
.
.
.
...
.
..
.
.
...
.
..
.
...
.
.
...
.
....
.
.
..
...
.
...
.
.
.
.
.
.
............
.
.
.
.
.
.
.
.
....
.
....
.
.
.
.
..
.
.
....
..
.
...
.
.
.
...
.
.
.
.....
...
.
.
..................
.
.......................
.....................
...
..
...
....
.
..
..
..
..
..
.....
.....
..
.
..
...
2
..
1 .......
.
...
....
......................
......................
...........
...... ........
...
...
..
..
....
..
...
..
.
.....
.
...................
.....................
......................
....
...
....
...
...
..
...
..
....
.
.....
.
...
.
1 ....
.
2 .......
..
.
.
......
..
.......................
.................
.
.
.
.
.
.
.
.
.... .........
.
.
.....
...
.
...
..
.
.
....
.
.
...
.
.
..
.
.
.
...
.
...... .........
.
.
...........
.
..
.
...
.
...
...
.
.
...
...
.
.
..
.
...
.
.
...
..
.
.
...
...
.
.
...
...
.
...
...
.
.
....
..
.
.
.
.
.....
...
.
.....
.
.....
.
......
.....
.
.
......
.....
.
.......
.......
.
.
.
.
.
........
.
.
.
.
.
.
.
.
.
.
........ ........
....
.........
...
..
.........
...........
...
..
..........
................
..........................................
..........................
...
..
.
...
.
..
.....
...................
zweiter
Weg
a2 = v1
a1 = v0 = vm
vm−1
erster
Weg
b
c
b
c
u2
Abbildung 16.2: Zum Beweis der Transitivität
a2 = u1 = c1
a2 = b1 = c1 = u1 c2
.......................
....
...
..
..
...
.
...
..
...
...
.
.
.
.
.
.
.
.
.
.
.
.. .............. .....
.
.
...
..
...
...
...
...
...
...
...
...
.
.
...
..
.
...
.
..
...
.
.
...
...
.
...
.
.
.
... ..................
.
.
.
.
.
.
.
.
.
.
.
........
....
......
.
.
....
...
...
.....
...
..
..
...
.....................................................................................
..
...
.
.
.
...
...
.
....
.
.
.
.
.
.
.
.
.
.
.
.................
................
a1 = b2
c2 = u2 = b1 = vm−1
......................
......................
...
....
....
...
..
..
..
..
..............................................
...
.
.
..
...
...
..
...
...
....
....... ........
..........................
...........
...
...
.
....
...
...
....
....
...
...
....
...
...
...
....
...
...
...
...
...
....
...
...
...
....
.
....
.... ...........
.
.
.
.
.
.
.
.
.
.
.
......
......... .......
......
...
.
.
.
...
...
..
....
....
..
...
............................................
...
..
...
.
.
...
...
....
...... ........
......................
..........
a1
b2 = u2 = vm−1
a2
u =u =b
=v
b2 = c1
......................
......................
1
2
1
...
....
....
...
..
..
..
..
.
....
...
.
.
..
...
..
.
.
..
.
.
.
.
.
.
m−1
.
....
.
..... .......
...
.
.
.
.
...................... ..............
.
.
.
.
.
.
..............
.......
.....
.
.
.
.
.
...
.
.......
.
.......
.......
...
....
........ ........................ .............
...
...
......
........
..
..
...
...
.....
...
...
...
.
.
.
...
...
.
.
. .........
..... .......
.
.
.
.
.
.
.
.
.
.
...
...
.
.
.
........
...............
.....
.
.
.
.
.
.
.
.
....
...
.
.
.......
....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...... .......
....... ..... ............
......
.
.
.
...
.
.
.
.
.
.
.
.
.
.......
......
...
....
...
..
...
..
.
...
...
..
.
.
.
.
...
....
....
.......................
.......................
a1
c2
Abbildung 16.3: Spezialfälle beim Beweis der Transitivität
Die von den Äquivalenzklassen erzeugten Untergraphen tragen die gleichen Namen. Zusammenfassend sollen sie als Komponenten der Biblockzerlegung bezeichnet werden.
16.2. DIE BIBLOCKZERLEGUNG ALLGEMEINER GRAPHEN
441
A-azyklische schwache Zusammenhangskomponenten sind a-Bäume. In ihnen muß jeder
geschlossene a-Weg mindestens eine Linie zweimal hintereinander durchlaufen. Sie können
also keine stoppfreien Kerne, und daher auch keine Subkomponenten und keine Biblöcke
enthalten. A-zyklische schwache Zusammenhangskomponenten enthalten a-Kreise und damit mindestens einen stoppfreien Kern. Wie in Proposition 16.1 bewiesen wird, enthalten
sie genau einen stoppfreien Kern.
Die Menge der Linien einer a-zyklischen schwachen Zusammenhangskomponente, die nicht
zum stoppfreien Kern gehören, ist entweder leer oder erzeugt einen a-kreisfreien Untergraphen. Die schwachen Zusammenhangskomponenten dieses Untergraphen sind a-Bäume.
Sie werden die peripheren Bäume (peripheral tree) der schwachen Zusammenhangskomponente genannt. Einige Knoten gehören sowohl zum stoppfreien Kern als auch zu einem
peripheren Baum. Sie heißen Grenzpunkte (border point).
Die Menge der Linien eines stoppfreien Kerns, die nicht zu einer Subkomponente gehören,
ist entweder leer oder erzeugt einen a-kreisfreien Untergraphen. Die schwachen Zusammenhangskomponenten dieses Untergraphen sind a-Bäume. Sie werden die internen Bäume
(internal tree) des stoppfreien Kerns genannt. Einige Knoten gehören sowohl zu einer
Subkomponente als auch zu einem internen Baum. Sie heißen Checkpunkte (check point).
Eine Subkomponente besteht aus einem oder mehreren Biblöcken und jede ihrer Linien
gehört zu genau einem Biblock. Ein Knoten kann jedoch zu mehreren Biblöcken gehören.
Ein solcher Knoten heißt Angelpunkt (hinge point).
Grenzpunkte, Checkpunkte und Angelpunkte werden zusammenfassend auch als Verheftungspunkte (attachment points) bezeichnet. Abbildung 16.4 zeigt eine schematische Darstellung der Biblockzerlegung allgemeiner Graphen.
Beispiel 16.1 Graph Ugraph1, Abbildung 16.5, Seite 443, ist ungerichtet. Er besteht aus
drei Zusammenhangskomponenten3 :
1. {h}
Uneigentliche Zusammenhangskomponente.
2. {i0 , i1 , i2 , i3 , i4 }
Eigentliche a-kreisfreie Zusammenhangskomponente.
3. {a0 , . . . , a15 , b0 , b1 , b2 , c0 , c1 , c2 , c3 , d0 , d1 , d2 , e0 , e1 , e2 , e3 , f0 , f1 , f2 }
Eigentliche a-zyklische Zusammenhangskomponente. Der stoppfreie Kern besteht
aus einer Subkomponente mit drei Biblöcken, nämlich {a0 , . . . , a15 , b0 , c0 }, {b0 , b1 , b2 },
{c0 , c1 , c2 , c3 }, und den beiden Subkomponenten {d0 , d1 , d2 } und {f0 , f1 , f2 }, die nur
3
Im folgenden werden die Zusammenhangskomponenten nur durch ihre Knoten angegeben, sind
aber als Untergraphen zu verstehen.
442
KAPITEL 16. DIE BIBLOCKZERLEGUNG
'
Graph
&
$
%
...
..................
....... .... ...............
........
........
...
........
........
...
........
........
.
.
.
.
.
.
.
...
.
........
........
........
...
........
........
.
.
.
.
.
.
.
.
........
.
......
.
.
.
.
........
.
.
.
.
..
.....
........
.
.
.
.
.
.
.
.
........
.... ..
.....
.
.
.
.
.
........ .
.
.
.
.
.......
............
...........
.
.
.
.
.
.
.
...
................
................
'
Uneigentliche
Schwache
Zush.-Komp.
&
'
$
A-kreisfreie
Schwache
Zush.-Komp.
%
&
'
Angelpunkte
&
'
...
...
...
...
...
..
...
...
...
.
.........
........
...
Biblöcke
&
'
$
$
%
&
%
Periphere Bäume
.........
...... ...........
......
......
......
......
.
.
.
.
.
.
......
......
......
......
......
.
.
.
.
.
......
....
.
.
.
......
.
.
....
......
.
.
.
.
.
......
....
.
.
.
......
.
.
....
...... .
.
.
.
.
.
.
.
........
..................
.
.................
.....
Subkomponenten
%
..
...... ...........
......
......
......
......
......
......
......
......
.
.
.
.
......
.
....
.
......
.
.
.
.
......
....
.
.
.
.
......
.
....
......
.
.
.
.
.
......
....
.
.
.
......
.
.
....
...... .
.
.
.
.
.
.
.........
...........
.
.
.
.................
.............
&
'
$
A-zyklische
Schwache
Zush.-Komp.
%
&
..
Stoppfreier Kern
Grenzpunkte
Checkpunkte
'
$
'
$
$
%
&
%
Interne Bäume
$
%
Abbildung 16.4: Biblockzerlegung eines allgemeinen Graphen
aus einem Biblock bestehen. Verbunden sind die Subkomponenten über den internen Baum {c0 , d0 , f0 }.
Außerdem gibt es die peripheren Bäume {d2 , g} und {c0 , e0 , e1 , e2 , e3 }.
c0 ist Grenzpunkt, Checkpunkt und Angelpunkt. d2 ist Grenzpunkt. d0 und f0 sind
Checkpunkte. b0 ist Angelpunkt.
2
16.3. EIGENSCHAFTEN DER KOMPONENTEN DER BIBLOCKZERLEGUNG 443
..................
...
..
.
...
.................... .................................. 8 ................................... ......................
.
.
....
...... ......
....
...
.....
.
.
.
.
.
.
.
....
..
.
.
7
.
.
.
... 9............
.
................ ......
....... ....................
......
......
.
.
.
.
.
......
.....
..... ..........
........... .....
........ ....
..... .........
...
..
...................
...................
...................
.
.
.
..
.....
..
.
.
.
..
..
.....
..
...
..
...
... 6 ...
.
.
.
.
.
.
... 10.....
... 2 ..
... 3 ...
.
...
...... ......
.
.
.
.
.
.
.
.
.
............ ..
....
....
.
.
.....
.
.
.
.. .......
.
.
.
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
..........
.
.. .........
...
.... .........
...
..
....
...
.....
.
...
....
...
......
...
............... ..........
...................
..............
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
....
...
...
..
..
..
..
.
.
.
.
.
.
.
.
.
..
..
...
.
...
...
.................
.
..................
.
.
.
.
.
.
... 2 ...
... 1 ...
... 1 ...
...
...
...
..
.
.
.
.
.
.
.
.
...............
...
.
.
........................
.......................
.........
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
....
....
... 11.....
... 5 ...
....
....
.......
.................
....
....
....
..................
.....
....
....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
.... .....
.... .....
.
.... ......
... ........
... ........
.............
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
..
..
..
...
...
.............................................
.............................................
...
.....
.....
.....
...
...
.
.
.
...
...
...
...
.....
... 0 ....
... 0 ....
... 1 .....
...
...
.... 0.......
.... 2......
...
.................
.................
... 1 ........
..... ....
............. ......
.............
...
...
...
................. .......
...... ..........
......
.
.
..
.
.
.
...
.......
.....
.
.
.
...
.
......
.
.
...
.......
.
.
.
.
.
.
.
.
.
...
.
.
.
.
...... ................ .....
....... .................
................
.
.................
.
.
.
.
.
.
.
...
.....
...
.
.
.
.
.
.
.
.
...
..
...
..
....
...
...
.
.
.
....
.....
...
..
... 0 ...
...
.... 0 ...
... 4 ....
... 12.....
..
...
...... ......
... ..
...
..
................
.......
...
..........................
....... .................
.
.
.
.
.
.
.
.
.
.
.
.
.
....
..
.....
...
.
....
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.... ........
...
.
... ............
..
..... ...
...... ......
..
...
..
...
...
....... ............
...
..
..
..
.....
...
...
...
...
..
....
.
....
... 2 ....
...
...
...
... 3 ...
.................
... 1 .....
...
.
.
.
.
..
.....................
....
...
..
...
.....
....
.... .............
..................
......
.....................
....
....
....
........
..
...
.... ...... ......
..
.................
.
........ .........
.
.....
... 3 ....
.
.
13
.
.
...
....
..
..... ......
.................
.
.....
...........
.
.
...
... 2 .....
.
..
.................
...
.
.
...
..
...
...
...
...
... ...
...
......... ........
...................
.
.
.
.
.
...
...
.
.
..
..
.....
.....
... 14.....
... 2 .....
......................
.
.....................
......
.
.
.
.
......
.....
......
......
....... ...................
...............................
......
...
...
..
..
..
.....
....
...........
.
.
.
.
.
.
.
.
.
...
... 1 .................
...... 15.
........................
................
.............................. .....................
...
.
.... 0....
..............
a
a
a
g
e
b
f
e
d
d
b
a
e
e
d
a
a
a
f
f
c
a
b
c
c
a
a
a
c
a
a
a
...............
...
....
..
.....
..
...
...... .........
......
h
....................
...
..
....
.
... 0 ....
................
.
i
a
a
..................
...
...
..
.....
... 1 .....
................
i
....................
...
..
....
.
... 2 ....
................
.
i
...............
...
....
..
.....
... 3 ....
...... .......
......
i
....................
...
..
....
.
... 4 ....
................
.
i
Abbildung 16.5: Ugraph1
16.3
Eigenschaften der Komponenten der Biblockzerlegung
Stoppfreier Kern und periphere Bäume:
Die Bezeichnung stoppfreier Kern wird durch die folgenden Proposition begründet.
Proposition 16.1 In einer schwachen a-zyklischen Zusammenhangskomponente gibt es
genau einen stoppfreien Kern.
Beweis: Der Beweis soll nur skizziert werden. Da eine eigentliche a-zyklische schwache
Zusammenhangskomponente einen a-Kreis enthält, muß sie auch einen stoppfreien Kern
besitzen.
Um zeigen, daß es nicht mehr als einen stoppfreien Kern geben kann, werden zwei verschiedene Linien betrachtet, die jeweils auf einem stoppfreien Weg liegen. Es gibt dann
auch einen stoppfreien Weg, auf dem beide Linien liegen, und sie gehören zum gleichen
stoppfreien Kern. Das ist richtig, wenn beide Linien auf einem der beiden gegebenen
stoppfreien Wege liegen. Anderenfalls liegt einer der folgenden drei Fälle vor
444
KAPITEL 16. DIE BIBLOCKZERLEGUNG
1. Die beiden stoppfreien Wege haben einen Knoten, aber keine Linie gemeinsam.
2. Die beiden stoppfreien Wege haben mindestens eine, aber nicht alle Linien gemeinsam.
3. Die beiden stoppfreien Wege haben keinen Knoten gemeinsam.
Abbildung 16.6 zeigt, wie man aus den beiden gegebenen stoppfreien Wegen einen neuen
.......
.......... .
. ..
..
..
..
..
..
..
..
..
.
.
.
..
.
.
.
.
.
.
.
...
..
....
.
.
...
....
.
.
.
.
.
..................
.
..
....
.
.
.
..
.
.
.
.
.
.
.
.
.
..... ...
.
.
...
....
.
...
....
.
...
.
.
.
.
.
.
..
.
.
.
.
.
..
.
.
.
..
.
..
..
...
..
..
. ..
.......
.......
1. Die stoppfreien
Wege haben einen
Knoten, aber keine
Linien gemeinsam.
......... .
..
..
..
.
.
.
.
. . .........
.
.
..
..
.
.
..
..
.
....
.
.
.
....
.
.
.............. .
.
.
.
.
.
.
...
.
.
.
.
.
.
.... ...
.
.
.
.
......
.
.
..
.
.
.
.
.
.
.
.
.
.
....
.
.
.
.
.
.
..........
.
.... ....
.
.
.
.
..... ....
.
.
.
.
..
.
.
.
.
.
.
..
.
.
..
.
..
..
..
..
.... . . . . . .
..
.. .
..
. ..
.
.
......
2. Die stoppfreien
Wege haben mindestens eine, aber
nicht alle Linien
gemeinsam.
........
........ .
...
..
..
..
..
..
..
.
..
.
.
..
..
.
.
.
.
.
.
.
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
..............
.............
.
.
.
..
............ . . . . . ............
.
.
....
.
.
.
.... ...
.. ..
.
.........
.
. ..........
.
..
.
.
..
.
.
..
.
.
.
..
.
.
.
..
..
.
.
.
.
.
..
..
.
.
.
. ..
.
.
...
.
.
.
.
.
.
.
.
.....
....
3. Die stoppfreien
Wege haben keinen
Knoten gemeinsam.
Abbildung 16.6: Linien auf stoppfreien Wegen sind stoppfrei verbunden
stoppfreien Weg konstruieren kann, der beide Linien enthält. Im dritten Fall wählt man
einen kürzesten Weg zwischen den gegebenen stoppfreien Wegen.
2
Die Linien von Subkomponenten und natürlich auch von Biblöcken sind Nichtbrücken
und jede Nichtbrücke gehört zu einem eindeutig bestimmten Biblock (Proposition 14.3,
Seite 382). Alle Nichtbrücken gehören zum stoppfreien Kern und jeder stoppfreie Kern
einer zyklischen Zusammenhangskomponente enthält eine Subkomponente. Ein stoppfreier Kern kann auch Brücken enthalten: Die Linien seiner internen Bäume sind Brücken.
Kann ein stoppfreier Weg ausschließlich aus Brücken bestehen? Mit dem folgenden Satz
wird bewiesen, daß das nicht möglich ist, und präzisiert, welche a-Kreise auftreten. Dazu
wird die in Abschnitt 14.2, Seite 381, eingeführte Bezeichnung [x] benutzt. Es werde eine
Brücke mit Inzidenzpunkten a und b aus einem Graphen entfernt. [a] ist die (eigentliche
oder uneigentliche) schwache Zusammenhangskomponente des reduzierten Graphen, in
der a liegt. Die schwache Zusammenhangskomponente, in der b liegt, ist [b].
Satz 16.2 Eine Brücke e mit Inzidenzpunkten a und b liegt genau dann auf einem stoppfreien Weg, wenn sowohl [a] als auch [b] einen a-Kreis enthalten.
16.3. EIGENSCHAFTEN DER KOMPONENTEN DER BIBLOCKZERLEGUNG 445
.... . ......
... .. .. ....
...
..
..
...
..
..
..
..
..
..
..
..
.
.
.
..
..
.
.
.
..
..
.
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.................
.................
..................
..................
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
...
..
..
.
..
.
.
..
.
.
.
.
.
...............................
.
.
...
........... . . . . . . . . . .............
.
........... . . . . . . . . . .............
.
.
.
.
.
.
.
.
.
.
...
...
... 2...
... 1...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...............
...............
...............
..............
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
..
.
.
..
..
..
..
..
.
.
..
.
..
..
..
..
..
..
..
..
..
..
...
..
....
.. .. .. ....
........
u
a
b
u
Abbildung 16.7: Subkomponenten auf beiden Seiten einer Brücke
Beweis: 1. Es seien SC1 und SC2 Subkomponenten mit SC1 ⊆ [a] und SC2 ⊆ [b] (siehe
Abbildung 16.7). A Wir wählen u1 ∈ [a] auf die folgende Art: Falls a ∈ SC1 setzen wir
u1 := a. Andernfalls wählen wir einen kürzesten und daher einfachen a-Weg von a zu SC1 .
Dieser Weg hat nur einen Endpunkt mit SC1 gemeinsam. Es sei u1 dieser Endpunkt. Auf
der b-Seite wird ein entsprechender Knoten u2 gewählt. Wir bilden nun den folgenden aWeg: Ausgehend von a durchlaufen wir den einfachen Weg (eventuell der Länge 0) bis u1 .
Dort wählen wir eine Linie aus SC1 . Diese liegt auf einem a-Kreis. Wir durchlaufen von u1
aus diesen Kreis. Danach geht es auf dem einfachen Weg zurück nach a und weiter über
die Brücke zu b. Von b aus folgen wir dem zweiten einfachen a-Weg nach u2 , durchlaufen
in SC2 einen a-Kreis, gehen auf dem zweiten einfachen Weg zurück nach b, überqueren
die Brücke e in entgegengesetzter Richtung und beenden den Weg in a. Der gefundene
Weg ist stoppfrei.
2. Wenn e auf einem stoppfreien Weg liegt, können wir annehmen, daß a der Anfangspunkt
ist und e die erste Linie.
a = v0 , l1 = e, v1 = b, l2 , v2 , . . . , vn−2 , ln−1 , vn−1 , ln , vn = a .
Da es sich um einen stoppfreien Weg handelt, gilt e 6= ln und, weil e eine Brücke ist,
auch vn−1 6= b. Das bedeutet vn−1 ∈
/ [b]. Es muß demnach ein k ≥ 2 geben, so daß
vi ∈ [b] (i = 1, 2, . . . , k) und vk = b, vk+1 = a. Wir betrachten den geschlossenen Weg v1 =
b, l1 , v2 , . . . , lk , vk = b. Nach Hilfssatz 14.2 kann er als einfach angenommen werden. Hat der
Weg die Länge 1, so ist er eine Schlinge. Hat er die Länge 2 und wäre er nicht linieneinfach,
so könnte nach Hilfssatz 14.3 der ursprüngliche geschlossene Weg nicht stoppfrei gewesen
sein. Er ist also linieneinfach und somit ein a-Kreis. Hat der Weg eine Länge ≥ 3, so ist
er nach Satz 14.1 ein a-Kreis. Es gibt also in [b] einen a-Kreis. Analog zeigt man, daß es
auch in [a] einen a-Kreis gibt.
2
Eine unmittelbare Folgerung von Satz 16.2 ist, daß eine a-kreisfreie schwache Zusammenhangskomponente keinen stoppfreien Kern haben kann. In Abbildung 16.4 ist das schon
dargestellt.
Proposition 16.2 Jeder periphere Baum besitzt genau einen Grenzpunkt.
446
KAPITEL 16. DIE BIBLOCKZERLEGUNG
Beweis: Es werde ein Weg von einem Knoten im peripheren Baum zu einem Knoten
auf einem a-Kreis durchlaufen. Auf diesem Weg gibt es einen ersten Knoten, der Inzidenzpunkt einer Linie eines Kreises oder einer Linie eines internen Baumes ist. Dieser Knoten
ist entweder der Anfangspunkt des Weges oder Inzidenzpunkt einer Linie des peripheren
Baumes. In beiden Fällen ist es ein Grenzpunkt. Hätte ein peripherer Baum zwei Grenzpunkte, so könnte man sie im peripheren Baum und im stoppfreien Kern durch einfache
Wege verbinden und hätte einen a-Kreis durch eine Linie des peripheren Baumes.
2
Subkomponenten und interne Bäume
Im Gegensatz zu Biblöcken kann ein Knoten nicht zu mehr als einer Subkomponente
gehören, wie sich aus dem folgenden ergibt.
Proposition 16.3 Alle Nichtbrücken, die mit dem gleichen Knoten inzidieren, gehören
zur gleichen Subkomponente.
Beweis: Es sei v Knoten einer Subkomponente und e und f Nichtbrücken, die mit ihm
inzidieren. Wir wählen einen a-Kreis,auf dem e liegt, und einen a-Kreis, auf dem f liegt.
Es gilt einer der beiden folgenden Fälle.
i. Die beiden a-Kreise haben eine Linie gemeinsam. Dann kann man ähnlich wie beim
Beweis von Satz 16.1 einen linieneinfachen geschlossenen Weg angeben, auf dem e
und f liegen.
ii. Die beiden a-Kreise haben keinen gemeinsame Linie. Dann können sie in v zu einem
linieneinfachen geschlossenen Weg verbunden werden, auf dem e und f liegen. 2
Korollar: Jeder Knoten eines Graphen gehört zu höchstens einer Subkomponente.
Ein Weg, der keine Brücke enthält, heißt brückenfrei (bridgefree). Zwei Knoten einer Zusammenhangskomponente heißen brückenfrei zusammenhängend (bridgefree connected),
wenn es einen brückenfreien a-Weg zwischen ihnen gibt. Sie heißen linieneinfach zyklisch
zusammenhängend (edge-simply cyclic connected), wenn es einen linieneinfachen, geschlossenen a-Weg gibt, auf dem die Knoten liegen, d. h. wenn sie zur gleichen Subkomponente
gehören.
Satz 16.3
1. Zwei Knoten u und w einer schwachen Zusammenhangskomponente sind
genau dann brückenfrei zusammenhängend, wenn sie linieneinfach zyklisch zusammenhängend sind.
2. Sind die Knoten verschieden und brückenfrei zusammenhängend, dann kann man
jeden brückenfreien linieneinfachen a-Weg von u nach w durch einen linieneinfachen
Weg von w nach u zu einem geschlossenen linieneinfachen a-Weg ergänzen.
16.3. EIGENSCHAFTEN DER KOMPONENTEN DER BIBLOCKZERLEGUNG 447
Beweis: 1. Keine Kante eines linieneinfachen geschlossenen a-Weges kann eine Brücke
sein. Sind u und w linieneinfach zyklisch zusammenhängend, dann gibt es einen brückenfreien a-Weg zwischen ihnen.
Für die Umkehrung unterscheiden wir die Fälle u = w und u 6= w. Im ersten Fall gibt es
einen brückenfreien geschlossenen Weg durch u. Dann gibt es auch einen geschlossenen
linieneinfachen Unterweg durch u. Dieser ist natürlich auch brückenfrei. Für den Fall
u 6= w folgt die Behauptung aus 2.
2. Es sei u = v0 , l1 , v1 , l2 , . . . , ln−1 , vn−1 , ln , vn = w und u 6= w. Wir beweisen durch Induktion über n, daß es einen linieneinfachen geschlossenen a-Weg der geforderten Art durch
u und w gibt.
• n = 1. Das ist Proposition 14.3, Seite 382.
• Der Satz sei richtig für brückenfreie Wege der Länge höchstens n.
Sei u = v0 , l1 v1 , l2 , . . . , ln , vn , ln+1, vn+1 = w ein brückenfreier a-Weg der Länge n + 1
von u nach w. Ohne Beschränkung der Allgemeinheit kann dieser Weg linieneinfach
gewählt werden. Dann kann nach Induktionsvoraussetzung das Teilstück von v0 bis
vn zu einem linieneinfachen geschlossen a-Weg durch v0 und vn ergänzt werden.
Außerdem gibt es einen a-Kreis durch ln+1 . Dieser und der linieneinfache geschlossene Weg können ähnlich wie beim Beweis von Satz 16.1, Seite 438, zu einem linieneinfachen geschlossenen Weg zusammengesetzt werden, der den ursprünglichen
brückenfreien Weg als Teilweg enthält.
2
Die folgenden Proposition gibt einfache, unmittelbar einsichtige Eigenschaften interner
Bäume wieder.
Proposition 16.4
1. Ein interner Baum hat mit einer Subkomponente höchstens einen
Knoten gemeinsam.
2. Ein interner Baum hat mindestens zwei Checkpunkte und hat jeden Checkpunkt mit
genau einer Subkomponente gemeinsam.
Biblöcke
Einige wichtige, leicht zu verifizierende Eigenschaften von Angelpunkten sind in der folgenden Proposition zusammengefaßt.
Proposition 16.5
1. Jeder Biblock einer Subkomponente, die mehrere Biblöcke umfaßt, enthält mindestens einen Angelpunkt.
2. Zwei verschiedene Biblöcke einer Subkomponente haben höchstens einen Angelpunkt
gemeinsam.
3. Zwei Angelpunkte einer Subkomponente liegen dann und nur dann auf einem gemeinsamen a-Kreis, wenn sie zum selben Biblock gehören.
448
16.4
KAPITEL 16. DIE BIBLOCKZERLEGUNG
Klassifikation von Linien und Knoten
Eine Linie ist entweder Brücke oder Nichtbrücke. Brücken treten nur in Bäumen, also
internen Bäumen, peripheren Bäumen oder a-azyklischen schwachen Zusammenhangskomponenten, auf und Nichtbrücken treten nur in Subkomponenten auf. Die folgende
Proposition liefert eine Eigenschaft, die den Typ des Baumes, in dem eine Brücke auftritt, bestimmt.
Proposition 16.6 Eine Brücke mit Inzidenzpunkten a und b ist genau dann
1. Linie eines internen Baumes, wenn sowohl [a] als auch [b] eine Subkomponente
enthalten.
2. Linie eines peripheren Baumes, wenn entweder [a] oder [b] eine Subkomponente
enthält.
3. Linie einer a-azyklischen schwachen Zusammenhangskomponente, wenn weder [a]
noch [b] eine Subkomponente enthält.
Beweis: 1. Das ist Satz 16.2.
2. Ist e Linie eines peripheren Baumes, so liegt der Grenzpunkt v0 auf einer Seite von e.
Er inzidiert mit einer Linie des stoppfreien Kerns und dieser enthält eine Subkomponente.
Gäbe es auf der anderen Seite von e auch eine Subkomponente, so gäbe es nach Satz 16.2
einen stoppfreien Weg durch e.
3. Folgt unmittelbar aus 1. und 2.
2
Die bisher gewonnenen Ergebnisse erlauben eine vollständige und einheitliche Klassifikation der a-Eigenschaften von Linien und Knoten eines allgemeinen Graphen. Die Klassifikation ist in den folgenden Tabellen 16.1 und 16.2 angegeben.
Bezeichnung
Linie einer a-kreisfreien
schwachen Zush.-Komp.
Linie eines peripheren
Baumes
Linie eines internen Baumes
Nichtbrücke
Anmerkungen
Brücke. Auf keiner Seite ein a-Kreis.
Brücke. a-Kreis auf genau einer Seite.
Brücke. a-Kreise auf beiden Seiten.
Gehört zu genau einem Biblock, einer Subkomponenten und
einem stoppfreien Kern.
Tabelle 16.1: Zusammenhangs-Klassifikation von Linien
16.5. DER BIBLOCKGRAPH
Bezeichnung
Isolierter Kno- 0
ten
Endknoten
1
Interner Knoten eines
Baumes
Interner Knoten eines
Biblocks
Jede Kombination der folgenden
Eigenschaften:
≥2
Inzidenzen
≥2
449
Anmerkungen
Einziger Knoten, der nicht zu einer eigentlichen schwachen Zush.-Komp. gehört.
Tritt nur in a-kreisfreien schwachen
Zush.-Komp. und in peripheren Bäumen auf. Kein Schnittpunkt.
Tritt in a-kreisfreien schwachen Zush.Komp., in peripheren Bäumen und in
internen Bäumen auf. Schnittpunkt.
Kein Schnittpunkt.
Falls mehrere zutreffen, fallen die Nicht-Brücken zusammen:
Grenzpunkt
≥ 1 für den peripheren
Baum.
≥ 2 Linien des stoppfreien
Kerns.
Gehört zu genau einem stoppfreien
Kern. Schnittpunkt.
Checkpunkt
≥ 1 für den internen Baum.
≥ 2 Linien der Subkomponente.
Gehört zu genau einer Subkomponente.
Schnittpunkt.
≥ 2 für jeden Biblock.
Gehört zu mindestens zwei Biblöcken.
Schnittpunkt.
Angelpunkt
Tabelle 16.2: Zusammenhangs-Klassifikation von Knoten
16.5
Der Biblockgraph
In Beispiel 16.1, Seite 441, wird die Biblockzerlegung des Graphen Ugraph1, Seite 443,
angegeben. An diesem Beispiel soll auch eine nützliche abgeleitete Datenstruktur eines
Graphen erläutert werden, der Biblockgraph (biblock graph). Der Biblockgraph eines allgemeinen Graphen ist ein ungerichteter Graph. Er wird von dem Ausgangsgraphen abgeleitet und gibt dessen Aufbau aus Biblöcken, peripheren und internen Bäumen und deren
Zusammenhang wieder. Uneigentliche und a-kreifreisfreie schwache Zusammenhangskomponten des Graphen sind im Biblockgraph isolierte Knoten. Die a-zyklischen schwachen
Zusammenhangskomponenten des Biblockgraphen bilden einen ungerichteten bipartiten
Graphen. Dessen Knoten sind einerseits die Biblöcke, peripheren und internen Bäume
450
KAPITEL 16. DIE BIBLOCKZERLEGUNG
und andererseits die Verheftungsknoten. Jeder Verheftungsknoten ist mit den Strukturelementen, zu denen er gehört, durch genau eine Kante verbunden. Andere Kanten gibt es
nicht. In der Abbildung 16.8 ist der Biblockgraph dargestellt, der zum Graphen Ugraph1
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ..
..
.
..
.
..
..... .... .... .... .... .... .... .... .... .... .... .... ....
.
..
....
.
.
..
.... .
..
...........
...
.
..
... .
.....
. ...........
..
.
... .
...
..
.
.. ...
... .
.
..
...
.
.
... .
.
..
.
.
... .
.
..
..
.. ..
... ..
.
..
.
.. ..
.
.
..
.
.. ..
.
..
.. ..
.
.
.
..
0
15
.. ..
.
0 1 2
.
..
.. ..
.
.
.
.
.. ..
.
.
.. ... . . . .
.
.
..
. ...
.
.
..
. ...
0
0
.
3 4
..
. ...
.
.. .
..
. ...
.
.
. ...
..
..
.
.. ....
.
.
. ....
..
.
.
..
..
.
....
.. .......
.
.
..
..
.
....
.
...........
.
.
.
.
..
.
.
..................
....
.
.
.
..
.
.
....
.
.
.
.
.
.
............
.
..
....
.............
..
..
..
.
.
.
....
.............
.
..
.
.
.... .
.
.
.
.............
..
.
... .
..
..
..
.............
.
.
... .
.
.............
.
.
.
... .
.
.
.
.
.............
... . ..
.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
............
.. .
..
..
.............
.
.
. ..
.
.............
.
.
.
.
.
.
.
.
.............
.
.
.............
....
.
...
... ..
....................
............. ......... ..........
.
...
.......
...
.
...
... ..
...
...
...
.
.
.. ..
... 0 ....
. ................. 0 ...........
.
.
.
.................
................. ...........
..
.
.
.
.
.
.
.
.
.
.
... ..
.
.
..........
..
.
...
.
..........
............ ...
.
....
...
... ..
..........
............
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
..........
...
.
.
........
.
.
.
.
.
.
.
.
.
... ..
.
.
.
.
.
.
.
.
.
.
..
...
. .... .... .... .... ............ .... .... .... .... .... .... .... .. .................
.
............
....
..........
... ..
...
.
............
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
..........
...
.
. ..
..
.
.......
.
.
.
.
.
.
... ..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.....
.
.
.
...
...........
......
...........
.....
.....
.
.
.
. ...
.
.
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
....
.
....
...
.
.
... .
.
....
....
...
...
.
. ...
...
...
.
..
..
... .. ..
...
.
.
..
.
.
.
.
.
.
.
...
.
.
.
. ...
.
.. .. .
.
.
.
.
.
.
.
.
.
.
.
...
.. .
.
.
. ...
...
.
...
. .
.
. .
.
...
...
.. ..
.
.
0 1
0 0 1
.
..
... .
...
.
. ...
.
....
.
. ..
.
.
.
... ..
..
.
.
.
0
0
0
0
1
2
.
.
.
.
.
.
. .
...
.
.
.
.. ..
.
.
2
3
2
3
.
.
...
.
.
.
.
.
.
.
.
.
.
.
.
...
...
.
.
. .. .
...
.
..
..
.
. ...
...
...
...
.
..
... ... .
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. ..
....
....
...
.
. ...
.
.
.
.
.
.
.
..
.
.
.
.
.
.
.
.
.
.
.
..
.
.
.
.
.
..........
..........
.. .
.
.
.........
.........
.
. ..
.
.........
.
. .
... ..
..... .........
.
.
.
.....
.....
.
.
. .. .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... ..
.....
.....
.
.....
.....
.
.
.
.
.
.
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ..
.
......
.
.
......
.....
.
.
.....
......
.
.
.....
......
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.......... ......
.. ..........
.
.
...
..
....
.
.
.
..
.
.....
.
.....
.
.
... 0 .....
... 0 .....
.
.
....... ......
.
.
.
.
.
.
.
.
.
.
.
.
.......
.....
.
.
.
.
.
.
.
.
.
.
.
.
. .... .... .... .... .... .... .... .... .... .... .... .... .
.
.... .... .... .... .... .... .... .... .... .... .... .... ..
.
.
.
..
.
. ..
.
.
.
.
..
...........
..
...........
.
.
.
.
.
.
.
.
. .
.
.
.
.
.
.
.
.
.
.
.
.....
.....
. .....
. .. ......
.
.
.
.
.
.
.
.
.
... ..
... ..
.
.
.
.
. ... ....
.
.
.
... ..
.
... ..
. ..
. . ..
.
.
.
.
.
.
.
...
...
...
. ..
.
.
. ..
.
.
...
...
. .
.
.
.
.
..
.
. ...
..
.
.
.
. ..
.
0
1
2
0
1
2
.
.
.
.
.
.
.
.
.
. ...
.
.
.
.
.
. . .
.
.
.
.. .
. ..
..........
. .. ..
.. ..
.
.
.
.
.
.
.
..... ......
.
.
.
...
.
. ..
. ..
.
..
.
.
. ... .....
.
.....
.
.
.
.
. ....
. ..
. ...
.
. . .....
.
.
.
.
.
.
.
...
.
.
.
.
.
.
...........
...........
.
...
.
.
..
..
...........
...........
.
. ...
.
.
...
.................
.
. .. .... .... .... .... .... .... .... .... .... .... .... ....
.. .... .... .... .... .... .... .... .... .... .... .... ....
.
.
.
.
.
.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ...
i ,i ,i
i .i
h
a ,...,a
b ,c
c
d
f
d ,d ,d
f ,f ,f
d
c ,c ,
c ,c
c ,e ,e
e ,e
c ,d ,f
...........
..... ......
..
.....
... 2 ....
....... ......
.....
b
..
...
....
...
..
....
...
...
...
..
.
...
..
......
.
...
....
..
.....
Verheftungspunkt
Peripherer Baum oder isolierter
Knoten
Interner Baum
Biblock
......
Stoppfreier Kern
.... .... ....
Subkomponente
d2 , g
Abbildung 16.8: Der Biblockgraph zu Graph Ugraph1
gehört.
b ,b ,b
16.6. ALGORITHMEN ZUR BESTIMMUNG DER BIBLOCKZERLEGUNG
451
Der Biblockgraph ist kreisfrei (Begründung?). Er ist zusammenhängend, wenn der Ausgangsgraph das ist, und heißt in diesem Fall Biblockbaum (biblock tree). Ein Endknoten
eines Biblockbaumes ist stets ein peripherer Baum oder ein Biblock. Die Kanten des
Biblockgraphen sind keine Kanten des Originalgraphen und werden deshalb auch Metakanten genannt.
Anmerkung 16.1 (Anwendungen der Biblockzerlegung) Die Biblockzerlegung gestattet, es eine Reihe von graphentheoretischen Fragen in einen leicht zu behandelnden
allgemeinen Teil und einen harten Teil, der die Lösung in einem Biblock betrifft, aufzuteilen. Für den leicht zu behandelnden Teil ist der Biblockbaum von großem Nutzen.
An mehreren Stellen des Buches wird davon Gebrauch gemacht. Ein wichtiges Beispiel
sind kürzeste Wege, wie sie in Abschnitt 19.4, Seite 518 untersucht werden.
2
16.6
Algorithmen zur Bestimmung der Biblockzerlegung
In diesem Abschnitt werden zwei Algorithmen vorgestellt, mit deren Hilfe man die Biblockzerlegung eines allgemeinen Graphen bestimmen kann. Es sind die Algorithmen
PERTREES, Tabelle 16.3, und STPFKERNEL, Tabellen 16.5 und 16.6. Man geht folgendermaßen vor:
1. Zuerst bestimmt man mit dem Algorithmus WCOMP, Tabelle 15.4, Seite 424, die
uneigentlichen, die a-kreisfreien und die a-zyklischen schwachen Zusammenhangskomponenten des Graphen.
2. Dann bearbeitet man jede a-zyklische schwache Zusammenhangskomponente:
(a) Mit einem Durchlauf durch die Liste ihrer Knoten werden die peripheren Bäume gefunden. Das geschieht mit PERTREES.
(b) Danach wird mit dem rekursiven Algorithmus STPFKERNEL die Struktur des
stoppfreien Kerns der schwachen Zusammenhangskomponente bestimmt.
Es werden die Linienfarben braun, rosa, gelb, magenta, orange, und grün benutzt. STPFKERNEL benutzt außerdem eine Knotenmarkierung. Anfangs sind alle Knoten unmarkiert und alle Linien ungefärbt.
Bestimmung der peripheren Bäume
Tabelle 16.3 enthält den Algorithmus PERTREES, der die peripheren Bäume einer azyklischen schwachen Zusammenhangskomponente W C findet. Zur Bestimmung der peripheren Bäume wird die Knotenliste von W C durchlaufen und von Knoten vom Grad 1
ausgegangen. Von diesen gibt es einen eindeutig bestimmten a-Weg zum Grenzpunkt des
452
'
KAPITEL 16. DIE BIBLOCKZERLEGUNG
PERTREES(W C)
$
1
2
for (jeden Knoten v von W C)
{ if (v ist mit genau einer Linie e inzident && e ist keine Schlinge)
/* v ist ein noch nicht bearbeiteter Endknoten */
3
{ w = v;
4
while (w ist inzident mit genau einer ungefärbten Linie e
&& e ist keine Schlinge)
5
{ färbe e braun;
6
kennzeichne w als Knoten eines peripheren Baumes (kein Grenzpunkt);
7
w = otherend(e, w);
8
};
9
w als Grenzpunkt kennzeichnen;
10
} };
&
Tabelle 16.3: Algorithmus zum Finden der peripheren Bäume
Baumes. Dieser Weg wird ausgehend vom Endpunkt durchlaufen, soweit jeweils nur eine
ungefärbte Linie vorhanden ist und damit die Richtung eindeutig vorgegeben wird (Zeile
4). Die Linien dieses Wegstücks werden braun gefärbt (Zeile 5). Wird ein Knoten über
eine braune Linie erreicht und inzidiert er mit mehr als eine ungefärbten Linie, so wird
er als Kandidat für einen Grenzpunkt angesehen (Zeile 9). Das wird jedoch rückgängig
gemacht, sobald er nur noch mit braunen Linien inzidiert (Zeilen 6, 7).
Hat PERTREES eine schwache a-zyklische Zusammenhangskomponente komplett bearbeitet, so sind in ihr alle Linien, die zu peripheren Bäumen gehören, braun. Alle anderen
sind ungefärbt. Alle Knoten sind noch unmarkiert, aber von jedem Knoten ist bekannt,
ob er Grenzpunkt, Knoten eines peripheren Baumes, aber kein Grenzpunkt, oder keines
von beidem ist.
Bestimmung der Struktur des stoppfreien Kerns
Der stoppfreie Kern einer schwachen a-zyklischen Zusammenhangskomponente wird mit
dem rekursiven Algorithmus STPFKERNEL bestimmt. STPFKERNEL ist im wesentlichen eine a-Tiefensuche. Diese muß allerdings in einem Knoten begonnen werden, der
Grenzpunkt ist oder nicht zu einem peripheren Baum gehört. Außerdem muß ein globaler
Zähler counter mit 0 vorbesetzt sein. Tabelle 16.4 zeigt diese Initialisierung. Tabelle 16.5
und 16.6 enthalten den rekursiven Algorithmus STPFKERNEL Er wird mit der schwachen Zusammenhangskomponente W C, dem Knoten v und der Eingangslinie entryedge
%
16.6. ALGORITHMEN ZUR BESTIMMUNG DER BIBLOCKZERLEGUNG
'
INITIALISIERUNG (W C)
1
&
453
2
3
v = erster Knoten in der Knotenliste von W C, der Grenzpunkt
oder kein Knoten eines peripheren Baumes ist;
counter = 0;
ST P F KERNEL(W C, v, NULL);
Tabelle 16.4: Anfangsknoten für STPFKERNEL
aufgerufen. Beim ersten Aufruf ist entryedge gleich NULL.
Beginnend beim ersten Knoten, mit dem STPFKERNEL aufgerufen wird, durchläuft der
Algorithmus in einer a-Tiefensuche den gesamten stoppfreien Kern. Nach dem Korollar
zu Hilfssatz 15.3, Seite 421, gibt es dabei nur Baumbögen und Rückwärtsbögen. Außer
dem ersten Knoten wird jeder Knoten über einen eingehenden Baumbogen zum ersten
Mal besucht. Alle anderen Linien, mit denen der Knoten inzidiert, sind entweder eingehende Rückwärtsbögen oder ausgehende Baumbögen oder ausgehende Rückwärtsbögen.
Es gibt mindestens einen ausgehenden Bogen und es kann sein, das alle ausgehenden Bögen Rückwärtsbögen sind. Jeder ausgehende Baumbogen bestimmt eindeutig einen Tiefensuchunterbaum. Der aktuelle Knoten wird nicht zum Tiefensuchunterbaum gerechnet.
Rückwärtsbögen können aus diesem Unterbaum herausführen. Es gilt, wie leicht zu sehen,
die folgende Proposition.
Proposition 16.7
1. Ein ausgehender Baumbogen ist genau dann eine Brücke, wenn
alle Rückwärtsbögen aus dem zugehörigen Tiefensuchunterbaum in diesem enden.
2. Ein Knoten ist genau dann trennender Schnittpunkt für den Tiefensuchunterbaum
eines ausgehenden Baumbogens, wenn alle Rückwärtsbögen des Tiefensuchunterbaumes in diesem oder dem gegebenen Knoten enden.
STPFKERNEL bearbeitet alle unmarkierten Knoten. Bei einem Grenzpunkt werden die
Linien des peripheren Baumes (braun) aufgesammelt. Dann werden alle ungefärbten Linien des Knoten bearbeitet und dabei gefärbt (Zeilen 5 bis 28). Das entscheidende an
STPFKERNEL ist nun ein globaler Zähler counter. Dieser ist anfangs Null und wird für
jeden erkannten ausgehenden Rückwärtsbogen, der keine Schlinge ist, um 1 erhöht. Der
Bogen wird gelb gefärbt (Zeilen 10 und 11). Bei einem ausgehenden Baumbogen wird der
Bogen rosa gefärbt, der Zählerstand in der lokalen Variable outcounter gesichert und die
nächste Rekursionsstufe von STPFKERNEL aufgerufen (Zeilen 14, 15 und 16).
Nach der Rückkehr zur betrachteten Rekursionsstufe (Zeile 16) wird counter mit outcounter
verglichen (Zeile 18). Ist counter größer als outcounter, dann kann es Rückwärtsbögen des
Tiefensuchunterbaumes des ausgehenden Baumbogens geben, die im gegebenen Knoten
$
%
454
KAPITEL 16. DIE BIBLOCKZERLEGUNG
enden. Diese Bögen werden magenta umgefärbt und für sie wird counter um 1 vermindert
(Zeile 19, 20 und 21). Da counter nur für Linien vermindert wird, die vorher gelb gefärbt
wurden, kann counter keine negativen Werte annehmen. Ist nach diesem Abgleich counter
gleich outcounter, so gibt es mindestens eine Rückwärtsbogen aus dem Tiefensuchunterbaum zum gegebenen Knoten, aber keinen zu einem seiner Vorgänger. Der ausgehende
Baumbogen liegt auf einem a-Kreis durch den Knoten, also in einem Biblock. Nach Proposition 16.7 trennt der Knoten, diesen Biblock von allen Vorgängern des Knotens, falls
es welche gibt. Der Biblock ist mit dem gegebenen ausgehenden Baumbogen vollständig
erfaßt. Ausgehend vom aktuellen Knoten werden dann mit einer Tiefensuche alle rosa
und magenta Bögen eingesammelt, orange umgefärbt und dem neuen Biblock zugeordnet
(Zeilen 22, 23 und 25). Weiter unten wird erläutert, warum das korrekt ist.
Ist nach dem Abgleich counter immer noch größer als outcounter, dann gibt es gibt Rückwärtsbögen aus dem Tiefensuchunterbaum zu einem Vorgänger des Knotens. Es gibt einen
a-Kreis durch den Vorgänger, den eingehenden Baumbogen des Knotens und den aktuellen
ausgehenden Baumbogen. Der entsprechende Biblock ist noch nicht abgeschlossen.
Wird nach Rückkehr zur aktuellen Rekursionsstufe festgestellt, daß counter gleich outcounter
ist (Zeile 18), so ist counter für alle Rückwärtsbögen des Tiefensuchunterbaumes in diesem abgeglichen worden. Nach Proposition 16.7 ist der ausgehende Baumbogen dann eine
Brücke und gehört zu einem internen Baum. Er wird grün gefärbt (Zeile 27).
Sind alle ungefärbten ausgehenden Bögen des Knotens bearbeitet (Zeile 28), so wird geprüft, ob counter eine größeren Wert hat als zur Eintrittszeit (Zeile 29). Ist das der Fall,
so gehört der eingehende Baumbogen zu einem noch nicht abgeschlossenen Biblock. Eventuell existierende grüne ausgehende Baumbögen des Knotens gehören zu einem internen
Baum, der sich nicht über den eingehenden Baumbogen fortsetzt. Der interne Baum ist
vollständig erfaßt und wird eingesammelt (Zeile 30). Die aktuelle Subkomponente ist noch
nicht abgeschlossen (Zeile 31).
Hat counter den gleichen Wert wie zur Eintrittszeit, ist entweder der eingehende Baumbogen eine Brücke (Proposition 16.7) oder es handelt sich um den ersten Knoten der
Tiefensuche. Im ersten Fall ist eine Subkomponente abgeschlossen worden oder der Knoten ist innerer Knoten eines internen Baumes. Es wird keine aktuelle Subkomponente
zurückgemeldet (Zeile 37). Im zweiten Fall ist entweder auch eine Subkomponente (die
letzte) abgeschlossen oder es muß noch ein existierender interner Baum eingesammelt
werden (Zeile 35) oder beides und der Algorithmus ist beendet.
Es bleibt nachzutragen, warum das Einsammeln der rosa und magenta Linien (Zeilen 23
und 25) korrekt ist.
Proposition 16.8 Der Algorithmus STPFKERNEL findet korrekt die Biblöcke, internen
Bäume und Subkomponenten des stoppfreien Kerns einer a-zyklischen schwachen Zusammenhangskomponente.
16.6. ALGORITHMEN ZUR BESTIMMUNG DER BIBLOCKZERLEGUNG
455
Beweis: Der Algorithmus arbeitet korrekt, wenn der stoppfreie Kern nur aus einem
Biblock besteht. Vom Anfangsknoten der a-Tiefensuche gibt es nur einen ausgehenden
Baumbogen. Gäbe es noch einen weiteren, der später durchlaufen wird, so wäre das ein
Widerspruch zur Tatsache, daß je zwei Linien, die mit dem Anfangsknoten inzidieren
auf einem a-Kreis liegen. Nur nach der Rückkehr über diesen einzigen Baumbogen des
Anfangsknoten wird counter gleich outcounter. Nehmen wir an, daß nach Rückkehr zu
einem Knoten v, der vom ersten Knoten der Tiefensuche verschieden ist, counter gleich
outcounter ist oder nach Ausführung der Anweisungen in Zeilen 19, 20 und 21 gleich wird.
Es muß dann einer der folgenden Fälle vorliegen:
1. Der von v ausgehende Baumbogen ist eine Brücke.
2. Der von v ausgehende Baumbogen ist eine Schlinge.
3. Der zum ausgehenden Baumbogen gehörende Tiefensuchunterbaum wird durch v
vom Rest des stoppfreien Kerns getrennt.
Keiner der drei Fälle ist in einem Graphen, der aus nur einem Biblock besteht, möglich.
Da jede Kante entweder magenta oder rosa ist, wird korrekt ein einziger Biblock erkannt
und aufgesammelt.
Daß STPFKERNEL auch im allgemeinen Fall korrekt arbeitet, soll durch vollständige
Induktion über die Anzahl b von Biblöcken des stoppfreien Kerns bewiesen werden.
Für b = 1 ist die Aussage richtig.
Der Algorithmus arbeite korrekt für alle k mit 1 ≤ k ≤ b. Es sei ein stoppfreier Kern mit
b + 1 Biblöcken gegeben. Es soll gezeigt werden, daß beim jedem Ablauf der Tiefensuche
auch dieser stoppfreie Kern richtig zerlegt wird. Dazu unterscheiden wir die folgenden
Fälle:
1. Der Anfangsknoten ist Knoten eines internen Baumes, aber kein Verheftungspunkt.
2. Der Anfangsknoten ist ein Verheftungspunkt.
3. Der Anfangsknoten ist Knoten eines Biblocks, aber kein Verheftungspunkt.
Zum Beweis ist es zweckmäßig, von einer hybriden Darstellung des stoppfreien Kerns auszugehen. In dieser Darstellung wird wie beim Biblockbaum (siehe Abschnitt 16.5) jeder
Biblock als ein einziger Knoten aufgefaßt und über eine Metakante mit seinen Verheftungspunkten verbunden. Die Knoten und Linien der internen Bäume sollen jedoch so
wie im Originalgraphen angegeben werden. Als Beispiel soll Abbildung 16.9 dienen, die
einen Ausschnitt aus einer solchen Darstellung zeigt. Biblöcke werden als Quadrate mit
gerundeten Ecken dargestellt. Knoten interner Bäume sind Kreise; wenn sie Verheftungsknoten sind, Doppelkreise.
1. Der Anfangsknoten der Tiefensuche ist Knoten eines internen Baumes, aber kein Verheftungspunkt.
456
KAPITEL 16. DIE BIBLOCKZERLEGUNG
.
.
.
.
.
.
.
.
. .
.
........
......
...
...
...
.
.
.
.
.
.
...........
.....
...
...
..
BLB9
........
......
...
...
...
.
.
.
.
.
...
...
..
.
.
.
.
.
.........
.........
.....
...
...
..
.
..
...
...
.....
...........
.
..........
..........
.........
.
.
.
.
.
.
.
.
......
.
.
.
.
.
.
.
.
.....
..........
..............
.........
.........
.....................
........ ......................
.. ..
..... ....
.. ...
... ...
......... ..........
.......................
.........
v7
.
.
.
.
.
.
...........
....
...
...
..
...
...
...
.
.
.
.
.
.........
..
...
...
.....
...........
.
.........
.....
...
...
..
...........
.....
...
...
..
BLB8
BLB7
...................
.............................
... ..
.. ..
..... .....
. ..
......
... ..
............................
..................
.
.
.
.
...
...
...
.....
..........
BLB10
.
.
...........
....
...
...
..
.
.............
.............. .....
........ ............
... ..
.. ..
.... ....
. ..
.......
.. .
............................
................
..
..
...
...
.
.
.
.
.
........
..
...
...
.....
..........
.
..........
..........
.........
.
.
.
.
.
.
.
.
......
.
.
.
.
.
.
.
.
.
.........
.........
.....................
..........
... ........................................
.. ..
.. ..
.... .....
..
... ...
... ..
...........................
....................
.
.
.
.
.
. .
.
.
.
.
.
.
.
.........
.......
........
.
.
.
.
.
.
.
.
.
.
.
.
...
.
.
...
...
...
...
..
..
...
...
v8
..
..
..
...
.
.
.
.
.
........
v6
v5
....................
...
..
.
.....
...
...
.......................
.....
.....
.....
...
..
...
.....
...
...
..
.....
...
...
...
.....
.....
......
....
.
.
.....
.
.........
.
.
.
.
.
.
.
......
.....
.....
....
.....
..........
..........
.....
..........
.....
.
.
.
.
.
.
.
.
.....
.....
.
.
.
.
.
.
.
.....
.
.
.....
.........
..........
.....
.............
..........
.............. .....
.....
........ .........................
.....
... .
.....
.....
.
.
... ...
.....
... ..
.....
... ..
.......... ....
.....
......... ......................................
.....
.........
...
.....
.........
.
.
.
.....
.
.
.
.
.
...
.....
.........
.....
.........
.....
.........
.....
.........
.....
.........
.
.
.
.
.
.
.
.
.
..... ...........
..
...... .... .................
...................
........
...
..
...
..
.....
.....
.
..
..
...
...
.
.
.
.
.................
.................
v0
BLB5
.
...........
.....
...
...
..
BLB6
.
..
...
....
.
.
.
.
.
.
.
.....
v4
................
..............................
......
.. ...
..
..... ....
... ..
......
..............................
...................
................
..............................
... ..
......
. ..
..... ....
... ..
......
..............................
...................
v2
v1
..........
.....
...
..
...
...........
.....
...
...
..
BLB1
...
...
....
.......
......
.
. .
.
.
.
..
...
...
...
.
.
.
.
.
.
.....
...........
....
...
..
...
BLB2
.
.
.
.
..
...
...
....
.
.
.
.
.
.
.....
.
.
.
.
v3
...........
.....
...
...
..
...
...
...
......
........
................
..............................
......
.. ...
..
..... ....
... ..
......
..............................................
..........
...................
..........
.........
..........
..........
..........
.........
......
...........
.... ........
..........
.
.
.
........
.
.
.
.
...
....
...
.
.
.
.
...
.....
.....
..
BLB3
...
...
...
......
........
.
.
.
.
.
.
.
.
.
..
...
...
....
.
.
.
.
.
.
.....
BLB4
...
...
...
.....
.........
.
.
.
.
..
..
..
....
.
.
.
.
.
.
.....
.
.
.
.
...........
.....
...
...
..
.
.
.
.
.
Abbildung 16.9: Modifizierter Biblockbaum (hybride Darstellung)
Als Beispiel nehmen wir den Knoten v0 in Abbildung 16.9. All Linien, mit denen ein
solcher Knoten inzidiert, gehören zum gleichen internen Baum. Die Tiefensuche beginnt
mit einer solchen Linie (z. B. mit der Kante von v0 zu v5) und durchläuft den daran
hängenden Unterbaum des internen Baumes, bevor sie zum Anfangsknoten zurückkehrt.
In v5 wird die Tiefensuche (in nicht vorhersagbarer Reihenfolge) einer Linie des Biblocks
16.6. ALGORITHMEN ZUR BESTIMMUNG DER BIBLOCKZERLEGUNG
457
BLB7 und der Kante von v5 zu v7 folgen. BLB7 bildet mit dem Knoten v5 einen Untergraphen, an dem über weitere Verheftungsknoten noch zusätzliche Biblöcke und interne
Bäume hängen können. Das ist in der Abbildung durch zwei gestrichelte Linien angedeutet. In dem so erweiterten Untergraphen beginnt mit der Bearbeitung der ersten Linie aus
BLB7 ein Ablauf von STPFKERNEL mit Knoten v5 als Anfangspunkt. Nach Induktionsvoraussetzung ist der erweiterte Untergraph korrekt und komplett abgearbeitet, wenn
STPFKERNEL über die erste Linie zu v5 zurückkehrt. Insbesondere enthält counter den
gleichen Wert, wie beim Eintritt in BLB7, in unserem Fall also 0. Folgt die Tiefensuche
der Kante von v5 zu v7, so wird dort begonnen, den Biblock BLB9 und den dahinter
liegenden Untergraphen abzuarbeiten. Nach Induktionsvoraussetzung geschieht auch das
korrekt und nach der Rückkehr zu v7 hat counter wieder den Eintrittswert, also 0. Kehrt
STPFKERNEL über die Kante von v0 zu v5 nach v5 zurück, so sind die Biblöcke BLB7
und BLB9 samt der an ihnen hängenden Untergraphen komplett und korrekt erfaßt, die
Kanten von v5 zu v7 und von v0 zu v5 sind grün gefärbt und counter hat den Wert 0. Auf
die gleiche Weise läuft STPFKERNEL bei der Bearbeitung der beiden weiteren Kanten
ab, die mit v0 inzidieren. Es sind danach die Biblöcke BLB1, . . . , BLB6, BLB8, BLB10
samt daranhängender Untergraphen korrekt erfaßt, die Kanten des internen Baums grün
gefärbt und der Endwert von counter ist 0. Als letztes werden die grünen Kanten eingesammelt und ein interner Baum aus ihnen gebildet.
2. Der Anfangsknoten der Tiefensuche ist ein Verheftungspunkt
Als Beispielknoten soll v4 dienen. Mit diesem Knoten inzidieren zwei Kanten eines internen Baumes und er ist Angelpunkt von zwei Biblöcken. Bearbeitet STPFKERNEL eine
Kante des internen Baumes, so läuft alles so ab, wie unter 1. beschrieben. Tritt STPFKERNEL über eine Linie in einen der Biblöcke BLB5 oder BLB6 ein, so läuft auch alles
so ab, wie unter 1 beschrieben. Zum Schluß haben wir in v4 den Wert 0 für counter und
müssen noch die grünen Kanten einsammeln und einen internen Baum bilden.
3. Der Anfangsknoten der Tiefensuche ist Knoten eines Biblocks, aber kein Verheftungspunkt.
Dieser Fall ist etwas komplizierter. Zur Veranschaulichung wählen wir einen Anfangsknoten in Biblock BLB5. Dieser hat die Verheftungsknoten v04 und v06 und der Anfangsknoten soll von beiden verschieden sein. Nehmen wir zunächst an, der Graph bestünde nur aus
dem Biblock BLB5. Dann wird die Bearbeitung eines vom Anfangsknoten verschiedenen
Knotens i. a. zu mehreren Baumbögen führen, die von diesem ausgehen. Das gilt auch für
die Verheftungsknoten. Es werde nun v06 von der Tiefensuche erfaßt. Dann wird zwischen
die Baumbögen, die wieder in BLB5 hineinführen, irgendwann der Baumbogen von v6
nach v8 eingeschoben werden. Dessen Bearbeitung wird, wie unter 2. beschrieben, den mit
BLB10 beginnenden Untergraphen nach Induktionsvoraussetzung korrekt erkennen und
die Linie von v6 nach v8 grün färben. Bei der Rückkehr zu v6 hat counter den gleichen
Wert wie am Beginn der Bearbeitung des Baumbogens. Dieser Wert ist allerdings von 0
verschieden. In BLB5 wird danach die Tiefensuche so fortgesetzt, als wäre die Unterbre-
458
KAPITEL 16. DIE BIBLOCKZERLEGUNG
chung nicht erfolgt. Das gleiche passiert bei der Bearbeitung des ersten Baumbogens, der
von v6 in den Biblock BLB8 hineinführt.
2
Im obigen Beweis wird nicht auf das korrekte Auffinden von Subkomponenten eingegangen, um die Betrachtungen übersichtlich zu halten. Zu den notwendigen Ergänzungen für
Subkomponenten siehe Aufgabe 16.1.
Effizienzbetrachtungen
Algorithmus PERTREES durchläuft die Knotenliste der schwachen Zusammenhangskomponente und bearbeitet jede Linie eines periphere Baumes einmal beim Braunfärben. Der
Aufwand ist O(ns + ms ), wobei ns die Anzahl der Knoten und ms die Anzahl der Linien
der schwachen Zusammenhangskomponente sind. Bei a-zyklischen Zusammenhangskomponenten ist stets ns ≤ ms , der Aufwand also O(ms ).
Algorithmus STPFKERNEL ist eine Tiefensuche. Beim Umfärben und Aufsammeln wird
jede Linie mehrmals angesprochen. Die Anzahl ist für jede Linie aber von der Größe der
Zusammenhangskomponente unabhängig. Ohne Berücksichtigung des Aufwandes für das
Anlegen der Datenstrukturen ist der Aufwand auch O(ms ). Die Datenstrukturen können
in linearer Zeit angelegt werden. Es ist jedoch zweckmäßig, die verschiedenen Listen von
Knoten und Linien als Bäume aufzubauen. Das ergibt O(ms · ln(ms )) als Aufwand.
STPFKERNEL sammelt in einer Subkomponente alle Biblöcke, die zu dieser gehören.
Dabei werden Teilsubkomponenten zusammengefügt (Zeile 17). Der dafür erforderliche
Aufwand hängt von der Anzahl der Biblöcke und von deren Lage im Biblockbaum ab. Bei
entsprechender Realisierung besteht er aus einem Anteil pro Biblock, der nicht von der
Größe des Graphen abhängt, und dem zweimaligen Einfügen in eine Liste von Biblöcken.
Das ergibt die Abschätzung O(bn · ln(bn)), wobei bn die Anzahl Biblöcke im Graphen
ist. Im allgemeinen wird bn im Vergleich zur Anzahl Linien klein sein und der Aufwand
für Zeile 17 vernachlässigbar. In keinem Fall wird jedoch die Abschätzung O(ms · ln(ms ))
verletzt.
16.7
Digraphen und vollständige Orientierungen
Die Biblockzerlegung berücksichtigt die Orientierung von Linien eines allgemeinen Graphen nicht. Sie erlaubt aber auch orientierungsabhängige Aussagen. Weiß man z. B., daß
ein Graph stark zusammenhängend ist, so müssen alle Linien, die in peripheren oder internen Bäumen auftreten, Kanten sein, denn es sind Brücken. Alle Biblöcke sind stark
zusammenhängende Untergraphen. Jeder Bogen und jede Kante liegt auf einem f-Kreis,
je zwei Linien liegen auf einem a-Kreis. Es ist jedoch nicht richtig, daß je zwei Linien auch
auf einem f-Kreis liegen (Aufgabe 16.2). Ein stark zusammenhängender Digraph ist demnach eine einzige Subkomponente. Sie kann aus mehreren Biblöcken bestehen, die durch
Angelpunkte getrennt werden.
16.7. DIGRAPHEN UND VOLLSTÄNDIGE ORIENTIERUNGEN
459
Orientierungsklassen und vollständige Orientierung wurden in Abschnitt 12.2, Seite 351,
eingeführt. Hier soll untersucht werden, ob es in einer Orientierungsklasse vollständige
Orientierungen, also Digraphen, mit bestimmten Eigenschaften gibt, und wie diese von
den allgemeinen Graphen der Klasse abhängen. Wir untersuchen zunächst die Frage, ob
es f-kreisfreie vollständige Orientierungen gibt.
Proposition 16.9 Zu einem ungerichteten Graphen gibt es genau dann eine f-kreisfreie
vollständige Orientierung, wenn er schlingenfrei ist.
Beweis: Gibt es eine Schlinge, so kann keine vollständige Orientierung f-kreisfrei sein.
Ist der Graph schlingenfrei, so kann man eine f-kreisfreie vollständige Orientierung mit
einer a-Tiefensuche erhalten. Aus allen Kanten, die als Baumbögen auftreten, werden
Bögen in der entsprechenden Richtung und aus alle Kanten, die als Rückwärtsbögen
auftreten, werden Bögen in entgegengesetzter Richtung. Zu Baum- und Rückwärtsbögen
siehe Abschnitt 15.2. Wenn man die Knoten mit der Aufrufstufe der Tiefensuche markiert,
sieht man, daß Bögen stets von einer kleineren zu einer größeren Markierung führen. Es
kann also keinen f-Kreis geben.
2
Die Frage, ob es zu einem allgemeinen Graphen eine vollständige f-kreisfreie Orientierung
gibt, ist schwieriger zu beantworten. Sicherlich gibt es keine f-kreisfreie vollständige Orientierung, wenn der Graph eine Schlinge aufweist oder schon anfangs einen f-Kreis nur aus
Bögen besitzt. Es stellt sich heraus, daß diese Bedingungen nicht nur notwendig, sondern
auch hinreichend sind.
Proposition 16.10 Ein allgemeiner Graph besitzt genau dann eine f-kreisfreie vollständige Orientierung, wenn er schlingenfrei ist und keinen f-Kreis, der nur aus Bögen besteht,
besitzt.
Beweis: Gibt es in dem allgemeinen Graphen eine Schlinge oder einen f-Kreis aus Bögen,
so kann keine vollständige Orientierung f-kreisfrei sein.
Wir nehmen nun an, es sei ein schlingenfreier Graph gegeben, der anfangs keinen f-Kreis
nur aus Bögen aufweist und der sich dennoch nicht f-kreisfrei vollständig orientieren läßt.
D. h., in welcher Reihenfolge wir auch immer aus Kanten Bögen machen, irgendwann muß
ein f-Kreis auftreten, der nur aus Bögen besteht. Wir geben uns nun eine Reihenfolge der
Kanten vor. Aus einer Kante machen wir einen Bogen, wenn auch der neue Graph keinen fKreis aus Bögen hat. Andernfalls wählen wir die entgegengesetzte Richtung. Nach unserer
Annahme muß es eine erste Kante geben, die weder in der einen noch in der anderen
Richtung zu einem Bogen gemacht werden kann, ohne auf einen f-Kreis aus Bögen zu
führen. Die beiden Bögen müssen Teil der beiden neu entstehenden f-Kreise sein. Es gibt
also einen f-Weg aus Bögen von einem Endpunkt der Kante zum anderen und einen fWeg aus Bögen zurück, wobei die Kante in keinem der beiden Wege als Bogen vorkommt.
460
KAPITEL 16. DIE BIBLOCKZERLEGUNG
Dann muß es aber schon vor der Orientierung der Kante einen f-Kreis aus Bögen gegeben
haben. Das ist ein Widerspruch zur Annahme, daß es sich um die erste Kante handelt,
mit der ein solcher f-Kreis eingeführt wird.
2
Anmerkung 16.2 Proposition 16.10 ist die Grundlage eines einfachen Algorithmus, mit
dem eine f-kreisfreie Orientierung eines allgemeinen Graphen gefunden werden kann. Der
Algorithmus soll nur skizziert werden. Es sei G ein allgemeiner Graph.
1. Auf dem Untergraphen, der durch die Bögen von G erzeugt wird, prüfe man mit
f-Tiefensuche, ob ein f-Kreis vorliegt (Satz 15.2, Nummer 2, Seite 421). Ist das nicht
der Fall, so fahre man fort.
2. Es wird die Liste der Kanten durchgegangen. Jede wird zu einem Bogen gemacht und
dann wird geprüft, ob ein f-Kreis entstanden ist. Ist das nicht der Fall, wird mit der
nächsten Kante fortgefahren. Ist das der Fall, so wird die Kante zu einem Bogen in
entgegengesetzter Richtung und es wird auch mit der nächsten Kante fortgefahren.
Beim ersten Versuch muß geprüft werden, ob der neue Bogen zu einem f-Kreis führt,
der nur aus Bögen besteht. Ist das der Fall, so muß der Bogen selber Teil des f-Kreises
sein. Daher beginnt man zweckmäßigerweise die f-Tiefensuche mit dem Zielpunkt
des neuen Bogens und beschränkt sie auf Bögen. Die „worst case“-Komplexität des
Verfahrens bleibt aber O(m · (m + n)).
2
Wir wollen nun untersuchen, wie es mit vollständigen Orientierungen steht, die in gewisser
Weise das Gegenstück zu f-kreisfreien Orientierungen sind, nämlich mit stark zusammenhängenden vollständigen Orientierungen. Damit ein allgemeiner Graph eine stark zusammenhängende vollständige Orientierung besitzen kann, muß er selber stark zusammenhängend sein und darf keine Brücken enthalten. Es stellt sich heraus, daß diese notwendigen
Bedingungen auch hinreichend sind.
Satz 16.4
1. Ein stark zusammenhängender allgemeiner Graph besitzt genau dann
eine stark zusammenhängende vollständige Orientierung, wenn er brückenfrei ist.
2. Ein stark zusammenhängender allgemeiner Graph besitzt genau dann eine stark zusammenhängende vollständige Orientierung, wenn jede Kante auf einem a-Kreis
liegt.
Beweis: 1. Ist der Graph stark zusammenhängend, so führt jede Orientierung einer
Brücke zum Verlust des starken Zusammenhangs. Ist der Graph stark zusammenhängend
und brückenfrei, so liegt eine Kante auf einem a-Kreis und ist nach Proposition 14.9, Seite 389, so orientierbar, daß der starke Zusammenhang erhalten bleibt. Weitere Kanten,
so vorhanden, liegen weiterhin in einem brückenfreien, stark zusammenhängenden Graphen und können somit ebenfalls orientiert werden, ohne den starken Zusammenhang zu
zerstören.
16.7. DIGRAPHEN UND VOLLSTÄNDIGE ORIENTIERUNGEN
461
2. In einem stark zusammenhängenden Graphen liegt genau dann jede Kante auf einem
a-Kreis, wenn er brückenfrei ist.
2
Algorithmus Es soll ein Algorithmus skizziert werden, mit dem festgestellt wird, ob
ein stark zusammenhängender allgemeiner Graph G eine stark zusammenhängende vollständige Orientierung besitzt, und, wenn das der Fall ist, eine solche gefunden wird. Dabei
werden die Überlegungen zum Beweis von Proposition 14.9 benutzt.
1. Mit den Algorithmen aus Abschnitt 16.6 wird die Biblockzerlegung von G bestimmt.
Genau dann, wenn diese keine peripheren Bäume aufweist und der stoppfreie Kern
nur aus einer Subkomponente besteht, ist der Graph brückenfrei (siehe Seite 444).
2. Ist G brückenfrei, so wird für jede Kante l der Graph G − l gebildet und seine Zerlegung in starke Zusammenhangskomponenten und externen Dag bestimmt (Abschnitt 15.4). Ergibt sich nur eine starke Zusammenhangskomponente, so kann l
beliebig orientiert werden. Ergibt sich ein ein nichtleerer externer Dag, so wird die
Kante ein Bogen, der vom Endpunkt mit positiver Schichtennummer zum Endpunkt
mit Schichtennummer 0 führt.
2
Anmerkung 16.3 Die „worst case“-Komplexität des Verfahrens ist O(m · (m + n)), da
für jede Kante l die starke Zusammenhangsstruktur von G − l gefunden werden muß.
Für den Fall, daß G ein ungerichteter, brückenfreier, zusammenhängender Graph ist,
gibt es jedoch ein effizienteres Verfahren. Siehe hierzu Aufgabe 16.3. Eine Heuristik, die
im allgemeinen Fall zu besseren Zeiten führen kann, basiert auf der Feststellung, daß
eine Kante, die mit einem Knoten inzidiert, der sonst nur Eingangsbögen hat, zu einem
Ausgangsbogen werden muß. Entsprechendes gilt für Kanten, die Eingangsbögen werden
müssen. Es werden rekursiv alle diese Kanten mit einer Tiefensuche bestimmt und dann
erst mit Schritt 2 des Algorithmus begonnen. Zu Einzelheiten siehe Aufgabe 16.4.
2
Aufgaben
Aufgabe 16.1 Ergänzen Sie den Beweis von Proposition 16.8, Seite 454, um Überlegungen, die zeigen, daß STPFKERNEL alle Subkomponenten korrekt erkennt und erfaßt.
Benutzen Sie das Beispiel der Abbildung 16.9.
Aufgabe 16.2 Geben Sie ein Beispiel für einen stark zusammenhängenden Digraphen,
in dem je 2 Bögen auf einem a-Kreis, aber nicht je 2 Bögen auf einem f-Kreis liegen.
Aufgabe 16.3 Geben Sie einen möglichst effizienten Algorithmus an, der auf einem
brückenfreien, ungerichteten und zusammenhängenden Graphen eine vollständige und
stark zusammenhängende Orientierung findet. Welche Komplexität hat Ihr Algorithmus?
462
KAPITEL 16. DIE BIBLOCKZERLEGUNG
Aufgabe 16.4 Es ist ein rekursiver Algorithmus anzugeben, der alle Bögen, die Ausgangsbögen werden müssen, und alle Bögen, die Eingangsbögen werden müssen, findet
und entsprechend orientiert.
Literatur
In der Graphentheorie ist es üblich, in schlichten Graphen Blöcke (block) zu betrachten.
Das sind maximale Untergraphen ohne Schnittpunkte. Außer den Biblöcken gehören dazu
auch isolierte Knoten sowie alle Untergraphen, die von einer Brücke erzeugt werden. Mit
den Blöcken wird eine dem Biblockgraph verwandte abgeleitete Graphstruktur, der BlockGraph (block-cutpoint graph), eingeführt, siehe Diestel [Dies2000] oder Harary [Hara1969].
Ein linearer Algorithmus zur Bestimmung der 2-zusammenhängenden Blöcke, also der
Biblöcke, ist seit den frühen 70er-Jahren bekannt. Siehe Tarjan [Tarj1972].
Die in diesem Kapitel als Alternative zur Blockzerlegung eingeführte Biblockzerlegung
stammt von Stiege [Stie1996b], [Stie1997] und [Stie1998]. Sie ist klarer, übersichtlicher
und für Anwendungen besser geeignet. Auch die Algorithmen zu Bestimmung der Biblockzerlegung stammen von Stiege [Stie1997a]. Sie sind von von gleicher Effizienz wie die
vorher bekannten, jedoch von größerer Einfachheit.
16.7. DIGRAPHEN UND VOLLSTÄNDIGE ORIENTIERUNGEN
'
SUB
463
*STPFKERNEL(WCOMP ∗W C, VERTEX ∗v, EDGE ∗entryedge)
integer
incounter, outcounter;
SUB
∗sub, ∗suba;
1 markiere v;
2 if (v Grenzpunkt) Datenstruktur für peripheren Baum anlegen und mittels
Tiefensuche die braunen Kanten aufsammeln;
3 incounter = counter;
4 sub = NULL;
5 for (jede ungefärbte Linie e, die mit v inzidiert)
6
{ if (otherend(e, v) markiert) /* Linie ist Rückwärtsbogen */
7
{ if (e Schlinge)
8
{ neuen Biblock und, falls nötig, neue Subkomponente
anlegen und e einfügen.; }
9
else
10
{ färbe e gelb;
11
counter = counter + 1; }
12
}
13
else
14
{ färbe e rosa; /* Baumbogen */
15
outcounter = counter;
16
suba = ST P F KERNEL(W C, otherend(e, v), e)
17
suba zu sub hinzufügen;
18
if (counter > outcounter)
19
{ for (alle gelben Linien, die als Rückwärtsbögen in v enden)
20
{ Linien magenta umfärben;
21
counter = counter − 1; }
22
if (counter == outcounter) /* Biblock abgeschlossen */
23
{ neuen Biblock und, falls nötig, neue Subkomponente anlegen;
24
beginnend mit e über Tiefensuche alle rosa und magenta Linien
einsammeln, orange umfärben und in Biblock einfügen; }
25
}
26
else
27
{färbe e grün; }
28
} } /* Alle Linien des Knotens sind bearbeitet */
&
Tabelle 16.5: Rekursiver Algorithmus zur Bestimmung von Biblöcken, Subkomponenten
und internen Bäumen (Teil I)
$
%
464
KAPITEL 16. DIE BIBLOCKZERLEGUNG
'
$
&
%
29 if (counter > incounter) /* Es gibt gelbe Rückwärtsbögen, die von v oder einem
Nachfolger von v ausgehen und in einem Vorgänger von v enden. */
30
{ rekursiv alle grünen Kanten, falls vorhanden, einsammeln und
internen Baum anlegen;
31
return sub;
32
}
33 else /* Subkomponente abgeschlossen oder innerer Knoten eines internen Baumes */
34
{ if (entryedge == NULL /* 1. Knoten der Komponente */
35
rekursiv alle grünen Kanten, falls vorhanden, einsammeln und
internen Baum anlegen;
36
};
37 return NULL;
Tabelle 16.6: Rekursiver Algorithmus zur Bestimmung von Biblöcken, Subkomponenten
und internen Bäumen (Teil II)
Kapitel 17
Perioden*
17.1
a-Periode und f-Periode
Wir beginnen mit einem Beispiel. Abbildung 17.1 zeigt einen stark zusmmenhängenden
.....................
......................
...
....
....
...
..
...
..
...
....
....
..
.
..
.
...
...
...
....
...
.....
..........................
.......................
...
...
...
.
.
.
.
...
....
......
......
....
...
........
.........
...
...
....
....
....
....
...
...
...
....
...
...
.... .
.
.... .
...
.
... ...
.
.
.
.
.
........... ...........
................. ...........
............
.
.
.
.
.
.
.
.
......... ......
.
.
.
.
.
.
.
.
.
.
....
....
..
...
....
.
.
.
.
.
...
... ...
.. ...
..
.
..................................
....
..
...................................
........
...
.
. .....
.
.
.
.
.
.
...
...
.
.
..
...
.
.
.
.
.
.....
.......
.
......
.
...
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
..................
.
.
.
.
.
.
.
.
.......
............... ........
....
...
.... .
....
...
...
...
...
.
.
.
.
........
..
........
....
...
..
......................
...
.....
...
....
.
....
..
...
...
....
......................
V02
V05
V03
V06
V09
V04
Abbildung 17.1: Periode eines stark zusammenhängenden Digraphen
Digraphen. Er hat folgende Eigenschaft: Jeder geschlossene Weg, der im Knoten V06
beginnt, hat eine Länge, die ein Vielfaches von 3 ist. Diese Beobachtung soll nun verallgemeinert werden.
Zuvor einige Bemerkungen zum größten gemeinsamen Teiler einer Menge natürlicher Zahlen. Es sei A eine nicht leere Menge positiver natürlicher Zahlen. Dann ist der größte
gemeinsame Teiler (greatest common divisor) von A definiert durch
gcd(A) := max{q | q teilt alle a ∈ A}
Da 1 stets gemeinsamer Teiler aller a ist und kein gemeinsamer Teiler größer als das
kleinste Element von A sein kann, ist gcd(A) für alle A definiert. Es gilt
A ⊆ A0 ⇒ gcd(A) ≥ gcd(A0 ).
465
(17.1)
466
KAPITEL 17. PERIODEN*
.
Angeregt durch das Beispiel von Abbildung 17.1 kommt man zur folgenden Definition.
Definition 17.1
1. Es sei v ein nicht-isolierter Knoten eines allgemeinen Graphen
G. Die a-Periode von v (a-period of v) ist der größte gemeinsame Teiler der Längen
der a-Wege, die in v beginnen und enden.
2. Es sei v ein Knoten mit Rückkehr eines allgemeinen Graphen G. Die f-Periode von
v (f-period of v) ist der größte gemeinsame Teiler der Längen der f-Wege, die in v
beginnen und enden.
Ist Pa (v) die Menge der geschlossenen a-Wege und Pf (v) die Menge der geschlossenen
f-Wege, die in v beginnen, so besagt Definition 17.1 in Formeln
a-Periode von v = gcd{len(p)|p ∈ Pa (v)}
f-Periode von v = gcd{len(p)|p ∈ Pf (v)}
Man kann auch eine b-Periode definieren. Sie stimmt aber immer mit der f-Periode überein. Für isolierte Knoten soll keine a-Periode und für Knoten ohne Rückkehr keine fPeriode definiert werden. In Abbildung 17.1 fällt auf, daß nicht nur V06, sondern alle
Knoten des Graphen die f-Periode 3 haben. Der folgende Satz besagt, daß das allgemein
gilt.
Satz 17.1 Alle Knoten einer schwachen Zusammenhangskomponente haben die gleiche
a-Periode.
Alle Knoten einer starken Zusammenhangskomponente haben die gleiche f-Periode.
Beweis: Wir zeigen, daß je zwei Knoten einer schwachen (starken) Zusammenhangskomponente die gleiche a-Periode (f-Periode) haben. Es seien u und v zwei Knoten einer
schwachen (starken) Zusammenhangskomponente eines allgemeinen Graphen. s sei die
a-Periode (f-Periode) von u und t die a-Periode (f-Periode) von v. Wir wählen einen geschlossenen a-Weg (f-Weg) von v nach u und von u zurück nach v. Seine Länge sei l1 .
Außerdem sei ein beliebiger geschlossener a-Weg (f-Weg), der in u beginnt, gegeben. Dessen Länge sei l2 . Siehe Abbildung 17.2. Wir setzen die beiden Wege zusammen, indem wir
auf dem ersten von v nach u gehen, dann auf dem zweiten von u nach u und schließlich
auf dem ersten von u zurück nach v. Der zusammengesetzte Weg ist ein a-Weg (f-Weg)
von v nach v und hat die Länge l1 + l2 . D. h. es gibt ein k 0 , so daß l1 + l2 = k 0 t. Außerdem
gibt es k , so daß l1 = kt. Daraus folgt
l2 = l1 + l2 − l1 = (k 0 − k)t
Das bedeutet, daß die Länge eines jeden Weges von u nach u ein Vielfaches von t ist.
Daraus folgt t ≤ s. Analog zeigt man s ≤ t und erhält s = t.
2
17.1. A-PERIODE UND F-PERIODE
p
pp
pp
ppp
pp
pp
pp
pp
pp
p
pp
pp
pp
467
Länge l2
pppppppppppp
pp
p
pp
pp
Länge l1
ppppppppppppppp
pp
ppp
pp
p
p ......................... p p
pp................................
...p
..p..
.
...
...
....
...
.
pp
ppp
u p
p
pp
...
.
...
...
...
...
..
..
....
.
.
.
.......
.................
pp
ppp
pppppppp
p
ppp
pp
p
pp
ppp
p
ppppppppppppppp
p
pp
v
...
..
....
...
...
..
...
..
.
......
.
.
....................
Abbildung 17.2: Gegenseitig erreichbare Knoten haben die gleiche Periode
Wir können also von der a-Periode einer schwachen Zusammenhangskomponente W C und
von der f-Periode einer starken Zusammenhangskomponente SC sprechen und wollen sie
mit aper(W C) bzw. fper(SC) bezeichnen. Aus Definition 17.1 und Satz 17.1 ergibt sich
unmittelbar die folgende Proposition.
Proposition 17.1 Die Länge eines jeden geschlossenen a-Weges in einer schwachen Zusammenhangskomponente ist ein Vielfaches der a-Periode. Die Länge eines jeden geschlossenen f-Weges in einer starken Zusammenhangskomponente ist ein Vielfaches der
f-Periode.
Der a-Weg von einem nicht-isolierten Knoten zu einem Nachbarn und zurück hat die Länge
zwei. Ebenso hat der f-Weg von einem Inzidenzpunkt einer Kante zum anderen und zurück
die Länge zwei. Das ergibt deutliche Einschränkungen für die möglichen a-Perioden und
f-Perioden.
Proposition 17.2 Die a-Periode einer schwachen Zusammenhangskomponente kann höchstens den Wert 2 annehmen. Enthält eine starke Zusammenhangskomponente eine Kante,
so kann ihre f-Periode höchstens den Wert 2 annehmen.
f-Perioden größer als 2 können also nur in Digraphen auftreten. Eine schwache Zusammenhangskomponente mit a-Periode 1 heißt aperiodisch (aperiodic). Ebenso nennt man
eine starke Zusammenhangskomponente mit f-Periode 1 aperiodisch. Eine schwache Zusammenhangskomponente mit a-Periode 2 heißt bipartit (bipartite). Satz 17.2 stellt die
Verbindung zur Definition in Abschnitt 12.3, Seite 353, her und rechtfertigt die Bezeichnung. Hat eine schwache (starke) Zusammenhangskomponente eine Schlinge, so ist sie
aperiodisch. Sie ist auch aperiodisch, wenn sie geschlossene a-Wege (f-Wege) sowohl gerader als auch ungerader Länge aufweist. a-Bäume haben nur geschlossene Wege gerader
Länge (warum?) und deshalb die a-Periode 2.
468
KAPITEL 17. PERIODEN*
Von besonderer Bedeutung sind bipartite schwache Zusammenhangskomponenten, das
sind im wesentlichen die in Abschnitt 12.3 eingeführten bipartiten Graphen, und starke
Zusammenhangskomponenten mit f-Periode größer 1. In Abschnitt 17.2 wird hierauf näher
eingegangen.
Die Bestimmung der a-Periode (f-Periode) über die Längen der geschlossenen a-Wege
(f-Wege) mit festem Anfangspunkt kann mühsam sein. Die folgende Proposition enthält
ein Kriterium, mit dem die Perioden auf andere Art bestimmt werden können. Dazu die
folgenden Bezeichnungen: Einen geschlossenen a-Weg, der in v beginnt, wollen wir einen
a-Weg mit Erstrückkehr zu v (a-path of first return to v) nennen, wenn v kein innerer
Knoten des Weges ist. Ganz entsprechend werden f-Wege mit Erstrückkehr zu v (f-path
of first return to v) definiert.
Proposition 17.3 Es sei v ein Knoten der schwachen Zusammenhangskomponente W C.
Dann ist die a-Periode von W C gleich dem größten gemeinsamen Teiler der Längen der
Wege mit a-Erstrückkehr zu v.
Es sei v ein Knoten der starken Zusammenhangskomponente SC. Dann ist die f-Periode
von SC gleich dem größten gemeinsamen Teiler der Längen der Wege mit f-Erstrückkehr
zu v.
Beweis: Es sei l der größte gemeinsame Teiler der Längen der a-Wege mit Erstrückkehr
zu v. Nach Formel 17.1 gilt aper(W C) ≤ l.
Andererseits setzt sich jeder geschlossene a-Weg, der in v beginnt, aus aufeinanderfolgenden a-Wegen mit Erstrückkehr zu v zusammen. Die Länge des Weges ist gleich der Summe
der Längen der Wege mit Erstrückkehr. l teilt daher die Länge eines jeden geschlossenen
a-Weges, der in v beginnt. l teilt daher auch aper(W C) und das bedeutet aper(W C) ≥ l.
Zusammen ergibt sich aper(W C) = l.
Der Beweis für starke Zusammenhangskomponenten verläuft auf die gleiche Art und Weise.
2
17.2
Periodizitätsklassen
In bipartiten Graphen alternieren die Knoten eines a-Weges zwischen den beiden Bipartitionsklassen. Etwas entsprechendes kann man auch bei f-Wegen in Digraphen beobachten. Im Digraphen von Abbildung 17.1 durchlaufen alle von V 06 ausgehenden f-Wege die
Knotenmengen {V 06}, {V 03, V 05}, {V 02, V 04, V 09} zyklisch in dieser Reihenfolge. Der
folgende Satz besagt, daß dies allgemein gilt.
Satz 17.2 Es sei G(V, E, A, ϕ, ψ) ein schwach zusammenhängender (stark zusammenhängender) allgemeiner Graph. p ≥ 2 sei seine a-Periode (f-Periode). Dann gilt
17.2. PERIODIZITÄTSKLASSEN
469
1. Gibt es einen a-Weg (f-Weg) der Länge n0 p von u nach v, so ist die Länge eines
jeden a-Weges (f-Weges) von u nach v ein Vielfaches von p.
2. Gegenseitige a-Erreichbarkeit (f-Erreichbarkeit) in np (n ≥ 1) Schritten ist eine
Äquivalenzrelation über V .
3. V wird in p Äquivalenzklassen zerlegt. Jeder Schritt auf einem a-Weg (f-Weg) führt
von einer Äquivalenzklasse in eine andere. In p Schritten werden alle p Klassen in
einer festen Reihenfolge durchlaufen und der letzte Schritt endet in der ersten Klasse
der Reihenfolge.
Beweis: 1. Der a-Weg (f-Weg) der Länge n0 p läßt sich zu einem geschlossenen a-Weg
(f-Weg), der in u beginnt, ergänzen. Dieser hat die Länge n1 p und daher hat der Rückweg
die Länge (n1 −n0 )p. Gäbe es einen Weg von u nach v mit einer Länge, die kein Vielfaches
von p ist, so könnte man diesen mit dem gefundenen Rückweg zu einem geschlossenen Weg
ergänzen, der in u beginnt und dessen Länge kein Vielfaches von p ist.
2. Reflexivität folgt aus der Definition der a-Periode (f-Periode). Transitivität ist unmittelbar klar. Der unter 1. gefundene Rückweg zeigt die Symmetrie.
3. Wir betrachten einen a-Weg (f-Weg) v0 , v1 , · · · , vp−1 der Länge p. Die Knoten müssen
zu paarweise verschiedenen Äquivalenzklassen gehören, da es sonst einen geschlossenen
Weg einer Länge kleiner p gäbe. Es gibt also mindestens p Äquivalenzklassen. Daß es
nicht mehr gibt, sieht man folgendermaßen: Es sei v ein von v0 , . . . , vp−1 verschiedener
Knoten. Wir wählen einen a-Weg (f-Weg), der von vp−1 nach v führt, und stellen ihm als
Anfangsstück den Weg v0 , . . . , vp−1 voran. Es sei l = np + r mit 0 ≤ r < p die Länge des
Weges vp−1 , . . . , v. Ist r = 0, so liegen vp−1 und v in der gleichen Äquivalenzklasse. Ist
0 < r < p, so fehlen p − r Schritte, um auf eine Länge zu kommen, die ein Vielfaches von
p ist. Nimmt man diese Schritte hinzu und startet den Weg in vr−1 , so sieht man, daß
vr−1 und v zur gleichen Äquivalenzklasse gehören. Es gibt genau p Äquivalenzklassen.
Es sei nun v von u in a-Richtung (f-Richtung) in 1 Schritt erreichbar. Ist u aus der gleichen
Äquivalenzklasse wie vi mit 0 ≤ i < p − 1, so können wir folgendermaßen von v zu vi+1
kommen: Von v zu u, von u zu vi und von vi zu vi+1 . Für die Längen dieser Wege gilt
l1 = n1 p − 1, l2 = n2 p und l3 = 1. Also gehören v und vi+1 zur gleichen Äquivalenzklasse.
Ist u aus der gleichen Äquivalenzklasse wie vp−1 , so muß v zu gleichen Äquivalenzklasse
wie v0 gehören. Wäre das nicht der Fall, sondern v in der Äquivalenzklasse von vi mit
1 ≤ i < p − 1, so könnte man auf die folgende Art von u zu u kommen: Von u zu v, von v
zu vi von vi zu vp−1 und von vp−1 zu u. Für die Längen dieser Wege gilt l1 = 1, l2 = n1 p ,
l3 = p − 1 − i und l4 = n2 p. Wegen 1 ≤ i wäre die Länge des Gesamtweges kein Vielfaches
von p, wie es sein müßte. Damit ist gezeigt, daß in p Schritten alle p Klassen in fester
zyklischer Reihenfolge durchlaufen werden.
2
Die durch Satz 17.2 gegebenen Knotenklassen werden für f-Perioden Periodizitätsklassen
(periodicity class) genannt.
470
KAPITEL 17. PERIODEN*
Anmerkung 17.1 In Abschnitt 12.3, Seite 353, wurden bipartite Graphen über eine
Einteilung der Knoten in zwei Klassen eingeführt. Satz 17.2 besagt, daß diese Partition
vorliegt, wenn die a-Periode den Wert 2 hat. Es ist umgekehrt nicht schwer, nachzuweisen,
daß ein schwach zusammenhängender Graph, dessen Knotenmenge so in zwei Klassen
zerlegt werden kann, daß in jeder Klasse die Knoten paarweise nicht benachbart sind, die
a-Periode 2 hat
Das läßt sich nicht unverändert auf f-Perioden stark zusammenhängender Digraphen übertragen. Gibt es in einem solchen eine Zerlegung der Knotenmenge in q Klassen, die bei q
Schritten stets in fester zyklischer Reihenfolge durchlaufen werden, so ist q nicht notwendigerweise die f-Periode des Digraphen. Die Periode ist jedoch ein Vielfaches von q. Siehe
hierzu Aufgabe 17.2.
2
Für aperiodische Graphen wird man eine Einteilung der Knoten in Klassen mit Eigenschaften, wie sie in Satz 17.2 beschrieben sind, nicht erwarten. In der Tat, in gewissem
Sinne gilt das Gegenteil, wie der folgenden Satz besagt.
Satz 17.3 Ist v ein Knoten einer aperiodischen schwachen (starken) Zusammenhangskomponente, so existiert ein n0 ∈ N, so daß es für jedes n ≥ n0 einen geschlossenen Weg
der Länge n durch v gibt.
Beweis: Sei l die Länge eines kürzesten geschlossene Wegs durch v. Ist l = 1, so geht
durch v eine Schlinge und die Behauptung ist richtig für n0 = 1. Sei l > 1. Da v aperiodisch
ist und l nur endlich viele Teiler hat, muß es k ≥ 1 weitere Wege von v zu v mit den Längen
l1 , · · · , lk geben, so daß ggT (l, l1, · · · , lk ) = 1. Die Behauptung folgt dann aus Hilfssatz C.2
Seite 632.
2
17.3
Ein Algorithmus zur Bestimmung der Periode
Es sei eine schwache (starke) Zusammenhangskomponente mit a-Periode (f-Periode) p ≥ 2
gegeben. Dann lassen sich die Periodizitätsklassen auf die folgende einfache Art bestimmen: Man starte eine a-Tiefensuche (f-Tiefensuche) und sammle alle Knoten, die p Schritte
voneinander entfernt sind, in einer Klasse. Das Problem ist, p zu finden. Tabelle 17.1 zeigt
einen einfachen Algorithmus, der die f-Periode einer starken Zusammenhangskomponente
berechnet. Der Algorithmus benutzt f-Tiefensuche und folgt Ausgangsbögen des externen Dags nicht. Die Komplexität ist O(n + m) = O(m). Der Algorithmus startet mit
P ERIOD(root, 0), wobei root ein beliebiger Knoten der starken Zusammenhangskomponente ist. Anfangs sind alle Knoten unmarkiert. Knoten werden durch Sätze des Datentyps
VERTEX dargestellt. Diese Sätze weisen u. a. ein Feld v→vlevel für ganzzahlige Werte auf,
das vom Algorithmus benutzt wird. p ist eine globale Variable und hat den Anfangswert 0.
17.3. EIN ALGORITHMUS ZUR BESTIMMUNG DER PERIODE
'
471
void PERIOD(VERTEX ∗v, int level )
1 { if (p == 1) return; /* aperiodisch */
2 if (v ist nicht markiert)
3
{ markiere v;
4
v→vlevel = level;
5
for (alle Kanten und Ausgangsbögen l, die mit v inzidieren)
6
{ if (l kein Bogen des externen Dag) PERIOD(otherend(l, v), level + 1);
7
}
8
}
9 else
10
{ p = gcd(p, |level − v→vlevel|);
11
};
12 return;
13 }
&
Tabelle 17.1: Rekursive Prozedur zum Auffinden der f-Periode einer starken Zusammenhangskomponente
Der Endwert von p ist, wie weiter unten gezeigt wird, die gesuchte f-Periode. gcd berechnet den größten gemeinsamen Teiler zweier nicht-negativer ganzer Zahlen, die nicht beide
0 sind.
Es ist leicht den Algorithmus so abzuändern, daß die a-Periode einer schwachen Zusammenhangskomponente berechnet wird. Dazu läßt man in Zeile 5 alle Linien, die mit v
inzidieren, zu und schließt in Zeile 6 auch Bögen des externen Dags nicht aus. Auch in
diesem Fall wird der Algorithmus mit P ERIOD(root, 0) aufgerufen.
Der Algorithmus PERIOD ist sehr einfach. Daß er tut, was er soll, ist jedoch nicht unmittelbar zu sehen und muß bewiesen werden.
Proposition 17.4 Der in Tabelle 17.1 angegebene Algorithmus berechnet korrekt die fPeriode einer starken Zusammenhangskomponente.
Beweis: Es sei vr der Knoten mit dem der Algorithmus startet. p̃ sei die gesuchte
f-Periode. pf sei der vom Algorithmus gelieferte Endwert von p. Wir zeigen: I. Jede Ausführung von Zeile 10, auch die letzte, weist p einen Wert zu, der ein Vielfaches von p̃ ist.
II. Der Endwert pf teilt die Länge eines jeden geschlossenen Weges durch vr . Zusammen
ergibt das pf = p̃.
$
%
Herunterladen