Arbeitsplan • Hashing • Sortieren • Ergänzungen zur Graphentheorie • Kürzeste Wege in Netzwerken • Ergänzung: Flüsse in Netzwerken • Weitere Ergänzungen zu Netzwerken • Vorwort, Tabellen, Anhänge ◦ Vorwort ◦ Wahrscheinlichkeitstheorie ◦ Verzeichnis der Algorithmen und Programme ◦ Namensliste B Diese Version ist vorläufig und nicht zur Verteilung bestimmt. Günther Stiege Einführung in die Informatik Version Januar 2011 (Noch nicht abgeschlossene Kapitel sind im Inhaltsverzeichnis und in der Überschrift mit einem Stern gekennzeichnet. Alle anderen sind fertig.) 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 Sommersmester 1999 an der Universität Oldenburg gehalten habe. Diese beiden Vorlesungen bilden einen einführenden, in erster Linie für Anfänger im Dimplomstudiengang Informatik bestimmten Kurs. Die gleiche Zielsetzung hat das Buch. 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 zu folgen [Knut1997] 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 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 Kenntisse zu reduzieren, halte ich für fatal. Das ausführliche 2. Kapitel erläutert anhand der Sprache C die Grundlagen der Programmierung. 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 Informatikaus- ii bildung 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 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 Datestrukturen und Algorithmen werden vorgestellt und untersucht. Teil IV des Buches handelt von allgemeine 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ührene 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 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 iii Ü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 Skript befinden sich eine Zusammenfassung mathematischer Hilfsmittel allgemeiner Art, eine kurze Darstellung der benötigten Begriffe und Ergebnisse aus der Mengenlehre. Ein weiterer Anhang über wichtige Grundlagen der Wahrscheinlichkeitstheorie war geplant. Bei der Ausarbeitung bin ich jedoch zu der Überzeugung gekommen, daß das auf wenigen Seiten nicht zu machen ist. An einigen Stellen des Buches werden diese Grundlagen gebraucht. Leser ohne Kenntnisse der Wahrscheinlichkeitstheorie können (hoffentlich) mit diesen Stellen trotzdem etwas anfangen. 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, insbesonndere in den abschließenden Abschnitten „Literatur“ angegeben.In besonderem Maße haben die Bücher von Aho/Ullman [AhoU1995], Cormen/Leiserson/Rivest [CormLR1990], Knuth ([Knut1997], [Knut1998], [Knut1998a]) sowie Kowalk [Kowa1996] Kurs und Skript beeinflußt. Ein Buch mit gleicher Zielsetzung wie dieses ist [Erns2003]. Es weicht jedoch in der Stoffauswahl etwas ab und hat einen anderen Formalisierungsgrad. Eine Reihe von Verzeichnissen ergänzt das Skript. 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. Danksagungen iv 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 Ergänzung: 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 Ergänzung: Turing-Maschinen und formale Sprachen Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . Literatur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 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 Ergänzung: 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 Ergänzung: Näherungslösungen schwieriger Probleme . . . . Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . Literatur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . III Einfache Datenstrukturen vii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 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 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257 . 257 . 257 . 258 . 266 . 276 . 276 . 279 . 292 . 295 . 295 . 297 . 298 . 302 . 302 . 304 10 Hashing * 10.1 Suchen mit Schlüsseltransformation . . . . . . . . . . . . . . . . . . . . . 10.1.1 Hashing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Literatur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305 . 305 . 305 . 305 11 Sortieren * 11.1 Allgemeines zum Sortieren . . . . . . . . . . 11.2 Heapsort und Prioritätswartesclangen . . . . 11.2.1 Halden (als Datenstruktur) . . . . . 11.2.2 Ergänzung: Prioritätswarteschlangen 11.3 Quicksort . . . . . . . . . . . . . . . . . . . 11.3.1 Deterministisches Quicksort . . . . . . . . . . . 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 . . . . . . . . . . . . . . . . . . . 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 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235 239 239 243 245 245 247 251 252 255 255 307 307 308 308 309 309 309 INHALTSVERZEICHNIS ix 11.3.2 Randomisiertes Quicksort . . . . . . . . . . . . . . . . . . . . . . . 309 11.4 Mindestkomplexität beim Sortieren . . . . . . . . . . . . . . . . . . . . . . 309 Literatur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309 IV Allgemeine Graphen 311 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 Ergänzung: Gleichheit und Isomorphie von Graphen Aufgaben . . . . . . . . . . . . . . . . . . . . . . . Literatur . . . . . . . . . . . . . . . . . . . . . . . . 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 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313 . 313 . 317 . 319 . 320 . 322 . 324 . 325 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 327 . 327 . 328 . 331 . 335 . 337 . 337 14 Wege und Zusammenhang 14.1 Wege: Definitionen und elementare Eigenschaften 14.2 Erreichbarkeit und Zusammenhang . . . . . . . . 14.3 Brücken und Schnittpunkte . . . . . . . . . . . . 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.2 Hamiltonwege . . . . . . . . . . . . . . . . 14.9 Datenstrukturen für Wege und Kreiszerlegung . . 14.9.1 Wege als verkettete Listen . . . . . . . . . 14.9.2 Ergänzung: Kreiszerlegung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 339 339 342 346 347 347 348 350 352 357 359 360 361 363 364 364 365 x INHALTSVERZEICHNIS Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367 Literatur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 370 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 . . . . . . . . . . . . . . . . . . . . . . . . . 17 Ergänzung: Perioden 17.1 a-Periode und f-Periode . . . . . . . . 17.2 Periodizitätsklassen . . . . . . . . . . 17.3 Ein Algorithmus zur Bestimmung der Aufgaben . . . . . . . . . . . . . . . Literatur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375 375 381 385 385 392 394 396 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 397 . 397 . 399 . 403 . 408 . 409 . 411 . 418 . 421 . 422 . . . . . . . . . . Periode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 425 425 428 430 433 435 18 Ergänzungen zur Graphentheorie * 18.1 Korrespondenzen . . . . . . . . . . . . . . . . 18.1.1 Überdeckungen in Graphen . . . . . . 18.1.2 Korrespondenzen (Matchings) . . . . . Aufgaben . . . . . . . . . . . . . . . . Literatur . . . . . . . . . . . . . . . . . 18.2 Mengertheorie . . . . . . . . . . . . . . . . . . 18.2.1 Trennende Mengen und disjunkte Wege 18.2.2 Die Mengersätze . . . . . . . . . . . . 18.2.3 Erweiterungen zu den Mengersätzen . . 18.2.4 Die Struktur von Mengertrennmengen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 437 437 437 437 438 438 438 438 439 441 442 INHALTSVERZEICHNIS 18.3 18.4 18.5 18.6 18.7 18.8 18.9 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . Literatur . . . . . . . . . . . . . . . . . . . . . . . . Höhere Zerlegungen . . . . . . . . . . . . . . . . . . . . . . Ein Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . Gerichteter Kern . . . . . . . . . . . . . . . . . . . . . . . k-f-Zusammenhang . . . . . . . . . . . . . . . . . . . . . . Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . Literaturhinweise . . . . . . . . . . . . . . . . . . . . . . . Partitionen, Anstriche, Färbungen . . . . . . . . . . . . . . Planarität . . . . . . . . . . . . . . . . . . . . . . . . . . . Optimumssuche in graphentheoretischer Betrachtungsweise Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . Literatur . . . . . . . . . . . . . . . . . . . . . . . . . . . . Literatur zum Abschnitt „Korrespondenzen“ . . . . 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 zwischen allen Paaren . . . . . . . . . . . . 19.5 Kürzeste Wege unter Berücksichtigung der Graphstruktur . xi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 461 . 461 . 463 . 463 . 463 . 466 20 Ergänzung: Flüsse in Netzwerken * 21 Weitere Ergänzungen zu Netzwerken * 21.1 Minimale erzeugende Bäume . . . . . . . . . . . . . . . . . . . . . . . . . 21.2 Minimale Korrespondenzen . . . . . . . . . . . . . . . . . . . . . . . . . . 21.3 Minimale Rundwege . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21.3.1 Das Problem des chinesischen Briefträgers . . . . . . . . . . . . . 21.3.2 Das Problem des Handlungsreisenden . . . . . . . . . . . . . . . . 21.4 Endliche Markovketten . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21.4.1 Stochastische Prozesse . . . . . . . . . . . . . . . . . . . . . . . . 21.4.2 Endliche Markovketten mit stationären Übergangswahrscheinlichkeiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21.4.3 Markovgraphen (Beispiel) . . . . . . . . . . . . . . . . . . . . . . 21.4.4 Markovketten mit abzählbar unendlichem Zustandsraum . . . . . Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Literatur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 443 443 443 448 449 450 458 459 459 459 459 459 459 459 469 471 . 471 . 473 . 473 . 473 . 476 . 476 . 476 . . . . . 477 478 478 478 478 xii V INHALTSVERZEICHNIS Parallelität 481 22 Parallelität in Rechensystemen und Netzen 22.1 Parallelität auf der Hardware-Ebene . . . . . 22.2 Parallelität auf der Betriebssystem-Ebene . . 22.3 Vernetzte Rechner . . . . . . . . . . . . . . 22.4 Hochleistungsrechner . . . . . . . . . . . . . Literatur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 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 Ergänzung: Semaphore, Mutexe 23.2.2 Lösung durch Simulation . . . . . . . . . 23.2.3 Ergänzung: Bediensysteme . . . . . . . . 23.3 Beispiel: Zeigerspringen . . . . . . . . . . . . . . Aufgaben . . . . . . . . . . . . . . . . . . . . . Literatur . . . . . . . . . . . . . . . . . . . . . . VI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . und Signale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 483 . 483 . 485 . 494 . 495 . 497 . . . . . . . . . . . . . 499 . 499 . 500 . 503 . 507 . 508 . 510 . 510 . 513 . 515 . 518 . 522 . 525 . 526 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 . . . . . . . . . 527 . . . . . . . . . . . . . . . . . . . . . . . . und Produkte . . . . . . . . . . . . . . . . B Wahrscheinlichkeitstheorie * B.1 Wahrscheinlichkeitsräume . . . . . . . . . . . . . B.1.1 Allgemeines zu Wahrscheinlichkeitsräumen B.1.2 Diskrete Wahrscheinlichkeitsräume: . . . . B.1.3 Stetige Wahrscheinlichkeitsräume: . . . . . B.2 Verteilungsfunktionen und Momente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 529 . 529 . 531 . 532 . 533 . 534 . 534 . . . . . 537 . 538 . 538 . 539 . 540 . 542 INHALTSVERZEICHNIS xiii B.3 Stochastische Prozesse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 542 Literatur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 542 C Hilfsmittel aus der Analysis und Zahlentheorie 543 C.1 Exponentialfunktion und Logarithmusfunktion . . . . . . . . . . . . . . . . 543 C.2 Ungleichung von Cauchy-Schwarz-Bunjakowski . . . . . . . . . . . . . . . . 545 C.3 Zahlentheoretische Hilfssätze . . . . . . . . . . . . . . . . . . . . . . . . . . 546 D Ergänzungen zu Erzeuger/Verbraucher 549 D.1 Erzeuger/Verbraucher in Realzeit . . . . . . . . . . . . . . . . . . . . . . . 549 D.2 Erzeuger/Verbraucher in der Simulation . . . . . . . . . . . . . . . . . . . 562 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 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 573 . 573 . 573 . 574 . 581 . 581 F Verzeichnis der Algorithmen * 591 G Namensliste * 595 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 Lösungen zu Kapitel 16 . . . . . . . . . . . . . L.11 Lösungen zu Kapitel 17 . . . . . . . . . . . . . L.12 Lösungen zu Kapitel 23 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 597 597 598 602 603 603 603 606 607 611 613 615 617 Abbildungsverzeichnis 619 Tabellenverzeichnis 625 Literaturverzeichnis 631 xiv Stichwortverzeichnis INHALTSVERZEICHNIS 649 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 diesem Unterabschnitt soll ein Programm für den euklidischen Algorithmus vorgestellt und damit eine erste Einführung in die Programmiersprache C verbunden werden. Doch zunächst die folgenden Ü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 Variablen 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 Welches ist der bestmögliche Fall (best case)? 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 Welches ist der schlechtestmögliche Fall (worst case)? 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 534) 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 im Anhang Abschnitt C.1, Seite 543. 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 Es soll untersucht werden, wie das Verhalten des Euklidischen Algorithmus im Mittel (average case) 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 [Knut1981]. 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 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 auf einanderfolgen 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.4 angegeben. Zum euklidischen Algorithmus siehe Cormen/Leiserson/Rivest [CormLR1990], Abschnitt 33.2; Knuth [Knut1973], Abschnitt 1.1, und Knuth [Knut1981], 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, der konventionellen Maschine, 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 pogram.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 in erster Linie Programme in Quell-oder Objektformat und kommerziell-administrative Daten gespeichert. Programmdateien waren Bibliotheken in speziellen Formaten. Dateien für kommerziell-administrative 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 Satzen 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 Datenverabeitung 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 295). 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 unterschiedlichen Ursprung: • 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 ganze 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 nTupel eines Datenbereichs eine 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 534. 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 533, 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 534) 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 532, 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 anfallenden 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 Laufzeitssystem (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 494), 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 konventionelle 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 darstellbarer 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 ausgegefü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 (Gleitkomme, 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 515. 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 Kontruktion 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 Rechnern 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 (1999) sind folgende Hauptspeichergrößen üblich: 124 KAPITEL 4. RECHENSYSTEME PCs: Workstations: Großrechner (mainframe): Größtrechner (z. B. Vektorrechner): 8 MB 32 MB 128 MB mehrere . . . 128 MB . . . 256 MB . . . 2048 MB GB Die Entwicklung von Hauptspeichergrößen ist (immer noch) im Fluß, und es 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 ein 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 starken 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 erkaubt es, auch längefristige Speicherung mit Halbleitern vorzunehmen. Sehr stark haben sich USB-Speichersticks 4 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. • Der Inhalt eines Registers wird um 7 Bits nach links verschoben. Von rechts werden Nullen nachgezogen. 130 KAPITEL 4. RECHENSYSTEME 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 TUB 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 Befehl 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 (shifts): 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-Prozessoren. 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 verschieden 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. 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 5) genannt, obwohl eher Zuse 6 als der Erfinder des modernen Computers anzusehen ist. Ein Vorläufer ist Babbage 7. 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 John von Neumann (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. 6 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]. 5 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. 7 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) Mehrpgrogrammbetrieb, 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 8 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. Sie basieren auf dem einfachen Betriebssystem MS/DOS (Microsoft Disk Operating System) und sind eindeutig die am stärksten verbreiteten 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]. 8 4.3. GRUNDSOFTWARE 143 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. • Systemladefunktionen. Programme, die den Aufbau des Betriebssystems nach dem Einschalten des Rechners steuern. Das Programm, das den Aufbau beginnt, wird Urlader (bootstrap) genannt. 144 KAPITEL 4. RECHENSYSTEME • 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 Qellprogramme 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. ◦ Systemgenerierungsprogramme. ◦ Sicherungsroutinen. 4.4. ERGÄNZUNG: VIRTUELLE ADRESSIERUNG ◦ 145 .. . • Allgemeine Anwendungssysteme. ◦ Datenbanksysteme. ◦ Mathematische Programme und Unterprogramme. ◦ Kommerzielle und branchenspezifische Anwendungssysteme. ◦ Simulationssysteme. ◦ Entwurfsunterstützungssysteme. .. ◦ . 4.4 Ergänzung: 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) 9. Als Erfinder der virtuellen Adressierung ist F.-R. Güntsch anzusehen10 . 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 (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 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. 10 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. 9 146 KAPITEL 4. RECHENSYSTEME Reelle Adressierung: Programmadresse = Speicheradresse =⇒ Speicheradresse Virtuelle Adressierung: Programmadresse . ......... ......... ... .. Adreßumsetzung Abbildung 4.9: Schema für reelle und virtuelle Adressierung) 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. Pogrammabläufe müssen (fast) genau so schnell sein, als gäbe es keine Adreßumsetzung. Mit ausgefeilter assoziativ arbeitender Hardware, die zm Teil mehrstufig arbeitet, läßt sich das erreichen. Während eines Programmlaufs werden aber immer wieder Pogrammadressen erzeugt werden, 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 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 zu gewiesen ist oder ob für sie ein Schreibzugriff erlaubt ist. 4.4. ERGÄNZUNG: 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 ([Knut1973], [Knut1981], [Knut1973a]) 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 [Knut1973], Seminumerical Algorithms [Knut1981] und Sorting and Searching [Knut1973a]) 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 591, 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 zusammengesetz möglich ist. 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. 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. 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 . lat. divide 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 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 . ........ ... ...... .... ... .... .. . .... .. ... . ....... .. . .................. ............. . ... . f1 f2 .. ... ..... ... ...... .... ... .... ... . .... .... .. .. .... ............ ................. .. ... ... ... ..... ... ...... ... ... .... ... . .... .... .. .. .... ............. .................. ... ... f.2 ...... ... ... ... ..... .. ... . ... ... . ...... . .............. ............ ..... . . . f1 f.3 f.3 ..... ... ........... ...... .... ...... ... .... ...... ...... ... . . ...... .. . . . ...... . ......... ......... . . .. ................ ......... f.3 ... .... ........... ...... .... ...... .... ...... ... . . ...... .... ...... ... ...... . . . . . ........ .............. . ................... ..... f0 f1 f.2 . ...... ... ... ... ..... .. ... . ... .. . ....... .............. ............. .... . . . f1 f0 f1 f.2 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 man von einem Optimum ausgeht und bei jedem Schritt aus den gewonnen Zwischenergebnissen ein neues Optimum bestimmt [Bell2003]. 2 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 zusammen mit Ford einen Algorithmus zur Bestimmenung kürzester Wege in Graphen (siehe Abschnitt 19.3, Seite 463). 10 5.1. DER NAIVE ALGORITHMUS-BEGRIFF 161 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 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 472 – ist ein Beispiel, in dem lokale Suche auch ein globales Optimum liefert. 11 Besser wohl der Träger ded Rucksacks. 162 KAPITEL 5. ALGORITHMEN I: NAIV UND FORMALISIERT 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 471) oder mit dem Algorithmus von Prim (Seite 472). 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. & Diese Darstellungformen werden Programmierparadigmen genannt. % 5.1. DER NAIVE ALGORITHMUS-BEGRIFF 163 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]. 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) sgn(x) = = if x ≥ 0 then x else -x fi 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 Ergänzung: 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 [Knut1973], 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]. 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] 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. Die grundlegende Idee des Verfahrens ist einfach und in Abbildung 6.1 dargestellt. Die zu sortierenden Werte sind Unsortierte Eingabeliste Sortierte Teilliste 1 Sortierte Teilliste 2 ........... ........... ........... ........... ........... ........... ........... ........... ........... ..... ............ .......... ........... ................. . . . . . . . . . . ........ . . . . . . . . . ... ........... ........... ........... ........... ........... . . . . . . . . . . . ............. .............. ........... ... ........... ........... . . . . . . . . . . ....... ........... ........... ........... ........... ........... ........... ........... ........... ........... ........... ........... ........... ........... .... .............. ........ . Unsortierte Teilliste 1 Unsortierte Teilliste 2 Sortierte Ergebnisliste Abbildung 6.1: Grundidee des Mischsortierens in einer Eingabeliste1 gegeben. Diese Liste wird in zwei gleich große Teillisten aufgeteilt. 1 Listen werden in Kapitel 8 ausführlich behandelt. 6.1. LAUFZEITANALYSE VON ALGORITHMEN UND PROGRAMMEN 179 Jede Teilliste wird für sich sortiert und die sortierten Teillisten werden zu einer sortierten Ergebnisliste zusammengefügt, gemischt 2. 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 Misch' & 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 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 $ % sortieren 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. 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ärbaumes3 . 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 Im folgenden wird ein Programm für das Mischsortieren angegeben und diskutiert. Das englische Verb to merge heißt verschmelzen. In der EDV-Fachsprache hat sich jedoch der Ausdruck mischen eingebürgert. 3 Zu Binärbäumen siehe Unterabschnitt 9.1.2. 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 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 angehängt wird. Dies nutzt split für einen rekursiven Aufruf aus und ist so sehr einfach zu programmieren. 6.1. LAUFZEITANALYSE VON ALGORITHMEN UND PROGRAMMEN 181 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 Iverson4 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. 4 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 Opertionen / 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.5 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. 5 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 Ergänzung: 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 Obermenge6 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. 6 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 finden7 , 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ß na7 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“ Notebook8 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 8 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 Systems9 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? 9 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 10 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 11 und Edmonds 12 die moderne Theorie der N P-Vollständigkeit. 10 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. 11 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. 12 6.2. DIE KOMPLEXITÄT VON PROBLEMEN 215 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 Primzahlproblem13 [AgraKS2004]. 13 Es ist festzustellen, ob eine Zahl Primzahl ist oder nicht. 216 KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT 6.3 Ergänzung: 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 Optimierungsprobleme14 . 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. 14 Lineare Optimierung wird in diesem Buch nicht behandelt. Siehe Neumann/Morlock [NeumM1993] 6.3. ERGÄNZUNG: 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 473. 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 476) 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. ERGÄNZUNG: 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 Auftrag15 , 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 476. 15 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 Abstieg16 (steepest descent) gewählt. Man spricht auch von einer Gradientensuche. Ein Beispiel für diese Vorgehen findet man in Abschnitt 18.9, Seite 459. 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 16 Wird ein Maximum der Zielfunktion gesucht, spricht man vom steilsten Anstieg. 6.3. ERGÄNZUNG: NÄHERUNGSLÖSUNGEN SCHWIERIGER PROBLEME 221 das Ergebnis falsch ist, so spricht man von einem Monte-Carlo-Algorithmus17 (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.3.2, Seite 309, ein randomisiertes Quicksort vorstellen. 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 mit Sicherheit Gleichheit der Datenbestände nachzuweisen. Das läßt sich beweisen. Dieser 17 Die Bezeichnung wurde ursprünglich in der Numerik bennutzt. 222 KAPITEL 6. ALGORITHMEN II: EFFIZIENZ UND KOMPLEXITÄT 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. ERGÄNZUNG: 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 ([Knut1973], [Knut1981], [Knut1973a]). 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 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 Implementierun 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 537, 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 (Kellerzeier) 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 Der Begriff halde (heap) wird in der Informatik in einem doppelten Sinne gebraucht. Auf beide Bedeutungen wird im folgenden kurz eingegangen. 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 486). 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 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 im nächsten Abschnitt beschrieben. Es kommt durchaus vor, daß Prioritätswarteschlangen gebraucht werden, bei denen die Anzahl der Prioritäten nicht von vornherein festliegt. Dann bietet eine effiziente Imple- 8.4. KELLER, SCHLANGEN, HALDEN 253 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 mentierung deutlich mehr Schwierigkeiten. Eine bewährte Implementierung benutzt Halden. Das sind in diesem Fall spezielle Datenstrukturen, die im Zusammenhang mit einem Sortierverfahren entwickelt wurden. Sie werden in Unterabschnitt 11.2.1, Seite 308 vorgestellt. Dynamische Speicherverwaltung Anfangs wurde der Begriff Halde (heap) nur im Sinne der in Unterabschnitt 11.2.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 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 254 KAPITEL 8. LISTEN 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 eine 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 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ö- 8.4. KELLER, SCHLANGEN, HALDEN 255 tigte Länge, gibt es auch unbenutzten Platz innerhalb der Blöcke. Dieser wird interne Fragmentierung (internal fragmentation) genannt. 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 347, 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 339, 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 340. 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) 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 9.1. BINÄRE SUCHBÄUME 263 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 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 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. 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 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. 4 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. 3 264 KAPITEL 9. SUCHBÄUME 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 ........ .............. . ..... .... ..... ..... ..... ..... ..... ..... ..... ..... .... ..... ..... ..... ..... ..... ..... ..... .... .... ..... ..... .... . ◦ Satz1 ◦ ◦ ... .. ... . .. ... ... ... .................... ................. ... .. .... .. ... ... ..... ..... . ... . . . . ... . . ..... ... . ... . ... . . . . .... ... . ... . . . . . . . . . ..... ... .. .... . . . . . ... . .... ... .. . . . . ... . . . . . ..... ... .. ... . . . . . . . .... ... ... . . . ... . . . . ... ..... . ... . . . ... . . . . . .... ... .. ... . . . . . . . . . ..... ... ... .. . . . . . . . ... .... ... .. . . . . ... . . . . ..... .. ....... . . . . .... ..... ... ......... ..... ... . ..... .. . ....... ......... ..... .......... ............... ..... ........ .... .. ............. .... ..... ◦ ◦ Satz2 ◦ .... .... .... .... ... . . ... ... .... .. ... ............... . . . . satz3 ◦............ .... ... .... .... .... .... . .. ................. .. • ◦........... ... .... .... .... .... .... .. ................... . Satz1 ist ein rechter Nachfolger. • ist ein NIL-Verweis. Abbildung 9.5: Realisierung von Binärbäumen durch Verweise 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. 9.1. BINÄRE SUCHBÄUME 265 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 L R Besuche die Wurzel Besuche die Knoten des linken Unterbaumes. Tue nichts, falls dieser leer. 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' & 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 266 zu finden. 266 KAPITEL 9. SUCHBÄUME 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 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. 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 WLR QUARK PFAU BESUCH ANTON KNABE HUT HUT 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 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 Proposition 9.1 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. 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 9.1. BINÄRE SUCHBÄUME 269 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 ' & int SIZE (VERTEX ∗v) { 1 if (v == NULL) return 0; 2 return (SIZE(v→right) + SIZE(v→lef t) + 1) } $ % Tabelle 9.4: LWR-Durchlauf durch einen Binärbaum 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). 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 $ % 270 KAPITEL 9. SUCHBÄUME 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. 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. 9.1. BINÄRE SUCHBÄUME 271 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 ' 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 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)). 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 272 KAPITEL 9. SUCHBÄUME 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. ' & 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 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)). 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 Realisierung 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 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 9.1. BINÄRE SUCHBÄUME ' & 273 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 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 jedoch LUST keinen rechten Unterbaum und der Satz mit dem Schlüsselwert LUST wird von Zeile 11 geliefert. 2 274 KAPITEL 9. SUCHBÄUME LUST ...... .... ......... ..... ...... ...... ...... ...... ...... ..... ...... . . . . . ...... ..... . . ...... . . ... . ...... . . . . ...... ..... . . . ...... . .... ...... . . . . . ........ .......... . . ... ................. ............ LAGE NOCH ... .... ...... ..... ...... ...... . . . . . ...... ..... ...... ...... ..... . . . . .. ...... ........ ............. NEIN NEIN ...... .... ......... ..... ...... ...... ...... ...... ...... ..... ...... . . . . . ...... ..... . . ...... . . ... . ...... . . . . ...... ..... . . . ...... . .... ...... . . . . . ........ .......... . . ... ................. ............. =⇒ LUST ... NOCH .... ...... ..... ...... ...... . . . . . ...... ..... ...... ...... ..... . . . . .. ...... ........ ............. LAGE Abbildung 9.8: Ersetzung eines Unterbaumes 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. 9.1. BINÄRE SUCHBÄUME ' 275 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 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 276 KAPITEL 9. SUCHBÄUME 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 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 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 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 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 ' 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 } 277 $ 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 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-NEINrechtsNEIN 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 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 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)). 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. 9.2. ROT-SCHWARZ-BÄUME 279 LUST ...... ..... .......... ..... ...... ..... ...... ...... ...... ...... ...... . . . . ...... ... . . . . . ...... ..... . ...... . . . ...... ... . . . . . ...... ..... . ...... . . . . ......... .......... . . ... ................ ............. LAGE .. .. ... ... .. ... ... .. .. • NOCH .. ... .... ..... ..... ...... ...... . . . . . ...... ...... ..... ...... ...... . . . . .. ...... ......... ............. ... ... .. ... ... .. .. • ... ... .. ... ... .. .. • NEIN. ... ... .. ... ... .. ... • ... ... .. ... ... .. .. • Abbildung 9.9: rb-Wege 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 bh(B) ≥ b h2 c. Ist h gerade, so ist b h2 c = h2 . Ist h ungerade, so ist b h2 c = h−1 . Für gerades 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 280 KAPITEL 9. SUCHBÄUME 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 Operationen 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 .... ... .... .... .... .... ... .. ................. ... u ... ... ... ... ... ....... .. ......... .. ⇐= X .... .... .... .... .... .... . .. . ................. .. ..... ...... ...... ...... ..... . . . . . ... .. ...... ......... ............. u . .... .... .... ... . . ... . .... ......... .......... Linksrotation v v Y .... ... .... .... .... .... ... .. ................. ... 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. 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. 9.2. ROT-SCHWARZ-BÄUME ' 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; & 281 $ % Tabelle 9.12: Prozedur LEFTROTATE 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 ein 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: 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. 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 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. Abbildung 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. 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]) 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 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 289), 9.18 (Seite 290) und 9.19 (Seite 291) angegeben. Anhand dieser Prozeduren wird erläutert, wie man vorgeht. 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]) 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 9.2. ROT-SCHWARZ-BÄUME HUT .................................... ............. ................. ............. ................ ............. ................ . . . . . . . . . . . . . . ............. . ... ............. ................ ............. ................. . . . . . . . . . . . ............. . . . . .......... . . ............. ... . . . . . . . . . . . . . . .................. ....................... . . . . . . . . . ...... ............ QUARK . BESUCH ...... .............. .... .......... .......... .... .......... .... .......... . . . . . .... . . . . ....... . .... . . . . . . . . .... .. ....... . . . . . . . . . ............... ................. . . . . . . . ... ............. ... .. ....... ...... ....... .... ........ .... ....... . . . .... . . . .. .... ....... .... .. ....... . . . . . . . ......... ................. . . . . . ... .... ...... ANTON DER .. ... ... ... ... ... ... .............. ...... HAT LAGE RUHEN ..... ... ....... ... ....... .... ........ ... ....... . ... . . . . . ... ........ .. ....... . . . . . . . ................ .............. . . ..... . ................ KNABE NOCH .... .. ............ .... ....... ....... .... ....... ... . . ........ . ....... .... ....... ... . . ....... .. . . ......... ................ . ............... ... ROT ..... ... ...... ....... ...... .... ...... .... ...... . . .... . . . . .... . ...... .. . ............ . . ................. .... .. .............. NEIN .. PFAU .. ... ... ... . . .. .... .. . ......... .... LUST TANNE .... ..... ... ...... ...... ...... ... .... ...... ...... .... . . . . . .... ... ...... ....... .............. . ............... . .. .............. SEHEN ZAHN .. .... .... .... .... .... .... .... .. ................. ... ZANK Abbildung 9.13: Rot-Schwarz-Baum rbbaum 285 286 KAPITEL 9. SUCHBÄ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 } & $ % Tabelle 9.14: Prozedur RBINSERT – 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 und in Zeile 10 festgehalten, ob dieser unmittelbarer Nachfolger des zu löschenden Satzes ist oder nicht, In den 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. 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 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 289). 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 290 und 9.19, Seite 291) behandelt. Diese Prozedur ruft sich rekursiv auf (Zeilen 11 und 19). Um 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 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 9.2. ROT-SCHWARZ-BÄ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 289 $ 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 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 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). 290 ' KAPITEL 9. SUCHBÄUME 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 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 ' $ & % // 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 292 9.3 KAPITEL 9. SUCHBÄUME 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 9.3. ZUFÄLLIGE BINÄRE SUCHBÄUME 293 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) 294 KAPITEL 9. SUCHBÄUME 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. 9.4. B-BÄUME UND EXTERNE DATENSPEICHERUNG 9.4 9.4.1 295 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: All 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. 296 ◦ KNABE ◦ . . ........... .......... .......... ........... .......... . . . . . . . . . ........... .......... ........... . .... ........... .................... • ANTON • BESUCH ◦ . • ... ... ... ... ... ... ... ... ... ... ... ... .. ....... .. ....... ... DER • HUT • HAT • LAGE • ... ... ... ... ... ... ... ... ... ... ... ... .. ....... .. ......... .. LUST NOCH ◦ QUARK ◦ TANNE ◦ ... . .. ... ... ... ... ... ........ ......... ... • PFAU • • NEIN • ............. ...... ............. ....... ............ ...... ............. ....... ............. ....... ............. ...... ............. ....... ............. ....... ............. ...... .................... ....... ....... ................ ...... ....... ....... ....... ...... ....... ....... ......... ................... • • ROT • RUHE ZAHN • ZANK • • SEHEN • • KAPITEL 9. SUCHBÄUME Abbildung 9.14: Mehrweg-Suchbaum 9.4. B-BÄUME UND EXTERNE DATENSPEICHERUNG 297 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 298 KAPITEL 9. SUCHBÄUME Blocktransport vom Plattenspweicher 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. 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 ◦ ............ ............ ............ ........... ............ ............ ............ ........... ............ ................. .. . ................ RUHE SEHEN TANNE ZAHN 9.4. B-BÄUME UND EXTERNE DATENSPEICHERUNG • Teil C Abbildung 9.15: Aufbau eines B-Baumes 299 300 ◦ ◦ ... ... ... ... ... .......... ........ ... ANTON BESUCH NOCH ....... ...... ....... ....... ....... . . . . . .. ....... ...... ....... ....... ...... . . . . . . ....... .. ....... .......... .............. DER ◦ ... ... ... ... ... .......... ........ ... HAT HUT KNABE LAGE ◦ ....... ....... ....... ....... ....... ....... ....... ....... ....... ....... ....... ....... ....... ....... . .......... ................. ◦ ◦ ... ... ... ... ... .......... ......... .. ... ... ... ... ... .......... ........ ... PFAU QUARK LUST NEIN ROT ◦ TANNE ... ... ... ... ... .......... ......... .. RUHE SEHEN Abbildung 9.16: Aufbau eines B-Baumes (Fortsetzung) ... ... ... ... ... .......... ......... .. ZAHN ZANK KAPITEL 9. SUCHBÄUME Teil D ◦ 9.4. B-BÄUME UND EXTERNE DATENSPEICHERUNG 301 Die folgende Proposition ist unmittelbar als richtig zu erkennen. Proposition 9.2 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 295 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, 302 KAPITEL 9. SUCHBÄUME 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 einen digitalen 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? 5 Gesprochen wie “try“. 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 9.5. DIGITALE BÄUME H.... N R S . ...... ... .. .. ... ... .... . . . . ... .. .... .. ... ..... .......... .......... . ...... t S H H H E E E R R R R M A N N 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 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 Abbildung 9.17: Digitaler Baum 303 304 KAPITEL 9. SUCHBÄUME Literatur Suchbäume werden in allen Büchwen über Datenstrukturen behandelt z. B. [AppeL1995], [CormLR1990] [AhoU1995], [SedgF1996], [Bras2008] [KrusTL1997],[AhoU1995], [Knut1997], [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] Kapitel 10 Hashing * 10.1 10.1.1 Suchen mit Schlüsseltransformation Hashing Chapter 8 of [SedgF1996]. Abschnitte 6.3 und 6.4 in [AppeL1995]. Teil II und Kapitel 28 von [CormLR1990]. Auch nachschauen in [Mehl1988]. Suchalgorithmen ([Kowa1996], S.523-548). Literatur Siehe [Bras2008]. (noch offen) [CormLR1990], Kapitel 22. Kruse/Tondo/Leung [KrusTL1997] und Aho/Ullman [AhoU1995] 305 306 KAPITEL 10. HASHING * 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 307 308 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). In diesem Kapitel wollen wir uns mit zwei weiteren Sortierverfaheren beschäftigen und die Mindestkomplexität beim Sortieren untersuchen. 11.2 Heapsort und Prioritätswartesclangen 11.2.1 Halden (als Datenstruktur) Halden als Datenstruktur wurden schon in Unterabschnitt 8.4.4, Seite 252 erwähnt. Eine Halde (heap) von n Elementen ist ein vollständig gefüllter Binärbaum von Schlüsselwerten, die von links nach rechts und dann von oben nach unten numeriert sind. Abbildung 11.1 zeigt ein Beispiel. M angewandt auf x kreist. B(M, x) .. ..................................................................................................................... .. .. .................................................................................................................... .. 0 .. ..................................................................................................................... .. 1 M0 M angewandt auf x hält. 0 B(S, x) .. ..................................................................................................................... . .. ........................................................................................................................................................ .. M0 .. ........................................................................................................................................ ...... ... .. ..... ...... ... .... . ... ... ... ... .... .. ... .. ... .. .. ... .. . . ... . . ... . .. .... .... ..... ..... ....... ................................ 1 S 0 Abbildung 11.1: Beispiel für eine Halde 11.3. QUICKSORT 11.2.2 11.3 Ergänzung: Prioritätswarteschlangen Quicksort 11.3.1 Deterministisches Quicksort 11.3.2 Randomisiertes Quicksort Sortieralgorithmen ([Kowa1996], S.499-521). 11.4 Mindestkomplexität beim Sortieren Literatur Zu Halden siehe auch [Bras2008]. Kruse/Tondo/Leung [KrusTL1997] und Aho/Ullman [AhoU1995] behandelt. bschnitt 6.5 in [AppeL1995]. Sortieralgorithmen ([Kowa1996], S.499-521). 309 310 KAPITEL 11. SORTIEREN * Teil IV Allgemeine Graphen 311 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 529). 2 Im Englischen ist auch die Bezeichnung node üblich, im Deutschen werden Knoten oft Ecken genannt. Wir wollen diese Bezeichnungen jedoch nicht benutzen. 1 313 314 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 315 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). 316 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 . m = n·(n−1) 2 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). 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. 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 3 Zur Bedeutung von Ω, O und Θ siehe Seite Unterabschnitt 6.1.5, Seite 195. 12.2. ORIENTIERUNGSKLASSEN UND UNTERGRAPHEN 317 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. 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. 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 318 KAPITEL 12. GRUNDLAGEN ALLGEMEINER GRAPHEN (erzeugenden Untergraphen, spanning subgraph) von G. Ist G ein schlichter Graph und H ein maximaler, d. h. nicht mehr erweiterbarer, vollständiger Untergraph, so wird H als Clique (clique) in G bezeichnet. Ein Untergraph eines Untergraphen ist auch Untergraph des Ausgangsgraphen. Die Untergraphbeziehung ist eine partielle Ordnung (siehe Seite 534) 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 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]. 12.3. BIPARTITE GRAPHEN 319 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). 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. 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 Inzidenzpunkt der Linie. 2 Anmerkung 12.4 Bipartitheit ist eine Periodizitätseigenschaft in Graphen. Zu Einzelheiten siehe Kapitel 17, Seite 425. 320 ' 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 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) bezeich- 12.4. DER GRAD EINES KNOTENS 321 net. 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. w s ......... ............... .... ..... .... .. .. ..... ..... .. .... ..... . .. . . ..................... .. ................. . . . . . . .... ... ... ... . .... . . . . . . .... .. .. .... ... ... .... .... .... .... .... .... ........ ............... .............. . . .................. . . . .. . . . . . ... . ... . .... ..... . ... . . . . . . . . ... ..... ... ................... . . . .................... . . . . . ... . . . . . .... ..... . . ....... ......... ...... ........ ... . .... ..... .............. . . . . .... ... . . . . ..... ... ... .... ... .. ... .... ... ... .... ... .... . . . . . . ... . . . . . .... ... ... .. .. . ... . . . .... . . . ... . . .... . .... ........ ..... . . . . . . . . ... . . . . .... . . ... ...... ... ..... ... .. .... .... ... . .. .. . . ... . . . ... ... ... . ... .. ................. ................. ... ... .. ... .. ... . . ....... .............................................................................................................................................................................................................................................................. 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. 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 322 KAPITEL 12. GRUNDLAGEN ALLGEMEINER GRAPHEN .......... .... ...... ... .. ........ ............... . . . . . . . ........ ......... . ........ ........ ........ ........ . . . . . . . ........ . ........ ........ ........ ........ . . . . . . ........ . ...... . ........ . . . . . . ........ ..... . . . . ........ . . . ..... ........ . . . . . . . ........ ..... . . . ........ . . . . ........ ..... . . . . . . . ........ ..... . . ........ . . . . . ........ ...... . . . . . ........ . . ..... . ........ . . . . . . ........ ..... . . . . ........ . . . ..... ........ . . . . . . . ........ ........... ........... ........... . . . .............. ................ . . . ........ ................ . . . . . . . . .. .. .... ... ... .... ... ... .... ..... . . . . . .... ... 3 ................................................................................................................................. 1 ... . ... 2 ... ... 1.... . 2 .. ...... ..... ..... ..... ...... ....... .................. . ....... ..... . . . . . . . . . . . . . . . ... ... ... .. . ... ...... . . .... . . .. . . . .. . .. .... . .. . . . . . . . . ... .. .. ... .... .... .... ... .... .... ... .... .... .... ... ... ... ... .... .... .... . . . . . . . . . . 1 3 2 ... .. .. .... .... .... ... .... .... .... .... ... ... .... ... ... .... ... ... ... .... . . ... . .... . . . . . . .. .... ... .... .... .... .... ... .... .... ... ... .... ... . .... ... ... .... .... .... ...... . . . . . .................... ...... . ..... ......... . .. . .... . .. .. .......................................................................................................................................................................................................................... ... ................. ................ z x x e y x y e e e x y Abbildung 12.4: Dreiecksfreier Graph Zusammen mit Gleichung 12.2 und der Ungleichung von Cauchy-Schwarz-Bunjakowski (siehe Abschnitt C.2, Seite 545) folgt daraus X X (2m)2 = ( 1 · dg(v))2 ≤ n · (dg(v))2 ≤ n2 · m, v∈V also m ≤ n2 , 4 v∈V 2 d. h. m ≤ b n4 c. 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 Ergänzung: 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 )} 12.5. ERGÄNZUNG: GLEICHHEIT UND ISOMORPHIE VON GRAPHEN 323 (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 G ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...... .. ......... .. G h ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... .. . .................... . f g .. .................................................................................................................... .. G0 Abbildung 12.5: Zusammenhang zwischen Graphisomorphismen und Graphautomorphismen ab und es sei fl (A) = A0 . Dann gilt der folgende Satz. 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. 324 KAPITEL 12. GRUNDLAGEN ALLGEMEINER GRAPHEN 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 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? 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.5. ERGÄNZUNG: GLEICHHEIT UND ISOMORPHIE VON GRAPHEN 325 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. 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]. Für weiterführende Literatur zu Graphisomorphie siehe Leeuwen [Leeu1990], Garey/Johnson [GareJ1979] und Köbler/Schöning/Torán [KoblST1993]. 326 KAPITEL 12. GRUNDLAGEN ALLGEMEINER GRAPHEN 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 314, und 12.3, Seite 321, 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 327 328 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.5 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 329 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 314, und rechts die zum Graphen der Abbildung 12.3, Seite 321. 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 330 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 314. 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 331 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 314. 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 330. 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. 332 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 314. 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 333 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 334 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 335 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 276 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 330, dienen. Tabelle 13.3 zeigt zwei externe Darstellungen in Form sequentieller Listen, die jeweils die Adjazenz- bzw. Inzidenzstruktur widerspiegeln. Diese Darstellungen sind in gedruckter 336 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 337 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. 338 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. 339 340 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 314, 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.7, Seite 371. 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 341 Anmerkung 14.1 In einem Graphen ohne Mehrfachkanten/Mehrfachbögen 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. 342 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 Kante im Ausgangsweg muß demnach die letzte Kante 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.2 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. 14.2. ERREICHBARKEIT UND ZUSAMMENHANG 343 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 371, 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 534, 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. 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. 344 KAPITEL 14. WEGE UND ZUSAMMENHANG 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. 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 371. Satz 14.3 rechtfertigt die folgende Definition. 14.2. ERREICHBARKEIT UND ZUSAMMENHANG 345 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. 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. 346 KAPITEL 14. WEGE UND ZUSAMMENHANG 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 Kanten 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] 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. 14.4. KREISFREIHEIT UND BÄUME 347 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. • 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. 348 KAPITEL 14. WEGE UND ZUSAMMENHANG 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 Kanten. 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. 14.4.2 2 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. 14.4. KREISFREIHEIT UND BÄUME 349 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 u = v0 , l1 , v1 , . . . , li , vi , li+1 , vi+1 , . . . , vk−1 , lk0 , vk0 = vj , . . . , vn = v 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. 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 350 KAPITEL 14. WEGE UND ZUSAMMENHANG 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 14.4.3 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 14.4. KREISFREIHEIT UND BÄUME 351 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 Ein Digraph, der ein f-Baum ist, soll gerichteter Baum (Wurzelbaum2, 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 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. Nicht zu verwechseln mit B-Bäumen (siehe Unterabschnitt 9.4.3, Seite 298, oder Cormen/Leiserson/Rivest [CormLR1990], Kapitel 19). 2 Manchmal wird auch ein freier Baum, in dem ein Knoten besonders hervorgehoben wird, Wurzelbaum genannt. 1 352 KAPITEL 14. WEGE UND ZUSAMMENHANG 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 534, 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. Dazu sei C(u) = {u}, wenn u ein Knoten ohne Rückkehr ist, und die von u erzeugte starke Zusammenhangskomponente sonst. 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: 14.5. PARTIELLE ORDNUNG UND SCHICHTENNUMERIERUNG 353 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. 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. 354 KAPITEL 14. WEGE UND ZUSAMMENHANG 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 die Überlegungen von Abschnitt 14.4 fortsetzen und Knoten mit „Wurzeleigenschaft“ definieren und bestimmen. In Erweiterung der Definition von Seite 350 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: 14.5. PARTIELLE ORDNUNG UND SCHICHTENNUMERIERUNG 355 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. 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 356 KAPITEL 14. WEGE UND ZUSAMMENHANG 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.1 zeigt einen allgemeinen Graphen, der aus zwei schwachen ........... .... ... .... .. .... .. ...... ........ ...... ........ . .. .............................................. .... .. ...... .... .... ..... ............... .......... u ........... .... ..... .... .. ... ................ .................. .. ................. .. .. ....... ..... ... .............................................. .. ... ................. ................ .... .. . .... .... .... ... ... .. ......... ... ......... ... . . . . . . . . . . . . . . . . .... ....... .... ......... ..... ........ . . . . ...... ........ . . . . . .. ........................................... .............................................. ........................................ 0 .... . . . . . . .. ... ...... ..... ...... ..... . . .... ..... . . . . . ............. ............... ............ .......... U U V Abbildung 14.1: f-Wege vom Wurzelknoten 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 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 14.6 357 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. 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 $ % 358 KAPITEL 14. WEGE UND ZUSAMMENHANG 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 418). 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 371, erläutert werden. Siehe dazu Tabelle 14.2. Es gibt keine uneigentlichen Zusammenhangskom' • 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 starke Zusammenhangskomponente S12 = {K01, K02, K03, K09} 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 ponenten. Die schwachen Zusammenhangskomponenten sind W1 , W2 und W3 . W1 enthält $ % 14.7. ABGELEITETE GRAPHEN 359 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 574 im Anhang zu finden. 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 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. 360 KAPITEL 14. WEGE UND ZUSAMMENHANG 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äsentiert. Beispiel 14.3 Als Beispiele sollen der kondensierte Digraph und der Komponentengraph von Graph2, Seite 372, erläutert werden. Graph2 ist schwach zusammenhängend. Die Zerlegung in starke Zusammenhangskomponenten und externen Dag zeigt Tabelle 14.3. Der zugehörige kondensierte Graph ist in Abbildung 14.9, Seite 373, und der zugehörige ' $ & % 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 Komponentengraph ist in Abbildung 14.10, Seite 374, zu sehen. In beiden Abbildungen wird die Schichtennumerierung durch horizontale punktierte Linien dargestellt. {v12 } hat die Schichtennummer 0. 2 14.8 Eulersche und Hamiltonsche Wege Bestimmte „maximale“ Wege sind von besonderem Interesse in der Graphentheorie. Sie sind nach Euler 3 und Hamilton 4 benannt. 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 537, 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. 4 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 3 14.8. EULERSCHE UND HAMILTONSCHE WEGE 14.8.1 361 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 können in beiden Fällen beliebig vorhanden sein. a-Eulerkreise a-Eulerkreise sind leicht zu chrakterisieren 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. 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 320), ist der Gesamtgrad gerade. Die Bedingungen sind hinreichend: Siehe Aufgabe 14.9. 2 Mechanik. Führte die Quaternione ein. Die Bezeichnung „Hamiltonweg“ ergab sich eher nebenbei aus einer Denksportaufgabe. 362 KAPITEL 14. WEGE UND ZUSAMMENHANG f-Eulerkreise Festzustellen, ob ein allgemeiner Graph f-eulersch ist und in diesem Fall eine f-Eulerkreis anzugeben, ist nicht mehr ganz so einfach. Zunächst ist festzustellen,daß jeder f-eulersche Graph a-eulersch – also auch brückenfrei – und stark zusammenhängend ist. Auch hier stören Schlingen nicht. Des weiteren wollen wir vorab die Spezialfälle ungerichteter Graph und gerichteter Graph betrachten. Ein ungerichteter Graph ist genau dann a-eulersch, wenn er f-eulersch ist, und jeder a-Eulerkreis ist ein f-Eulerkreis. Die gleichen Überlegungen wie beim Beweis von Satz 14.7 zeigen, daß in einem Digraphen notwendigerweise für jeden Knoten der Eingansgrad gleich dem Ausgangsgrad sein muß. Außerdem muß er schwach zusammenhängend sein. Der in Aufgabe 14.9 zu bestimmende Algorithmus findet dann mit leichten Ergänzungen auch einen f-Eulerkreis im Digraphen. Wir wollen das als Satz formulieren: Satz 14.8 Ein Digraph G ist genau dann f-eulersch, wenn er schwach zusammenhängend ist und der Eingangsgrad eines jeden Knoten gleich seinem Ausgangsgrad ist. Wie ist nun im allgemeinen Fall vorzugehen? Einfache notwendigen Bedingungen für die Existenz eines f-Eulerkreises sind leicht aufzustellen. Es ist zunächst wieder schwacher Zusammenhang zu fordern. Außerdem muß auch wieder jeder Knoten genau so oft verlassen wie betreten werden können. Man braucht also 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.2 ist stark zusammenhängend und genügt Gleichung 14.1. Macht man ................ .. .............. .......... ................... .. ...................... ..... ........... ........... .................. . ......... . . . . . . . ........ . ...... ..... . . . . . . ....... . . . . .. ........ ..... ...... . . . . . . ..... ... ... .... . . . ..... . ......... ... ... .... . . . .... ....... .. ... . . . . ... .. ............... . . ... . . . .. ........ ... ... ... . . . . . ... . ............. ...................... .... . . . .. . . . . ... ... ............... ... ... . . .... ... ... ... ... ........ ... ... . ...... .. .. . . . ............. ............. .............. .............. . . . . . . . . . . . .. . .. ........ .. ... . ... . . ..... .. ..... . .. . ... .............................................................. . . . . ... . . . . ..... .... ...... ........ ...... ...... ............... ......... .... ..... ....... . . ......... .. ... .. ... ................. .. ...... ...... ............................. ........ ..... .. ........ ... . ................ x w r s t u v Abbildung 14.2: 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 14.8. EULERSCHE UND HAMILTONSCHE WEGE 363 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. Es gilt: Proposition 14.12 Ein schwach zusammenhängender allgemeiner Graphs besitzt genau dann einen f-Eulerkreis, wenn es für ihn eine vollständige Orientierung gibt, bei der für jeden Knoten der Eingansgrad gleich dem Ausgangsgrad ist. Beweis: Durchläuft man einen f-Eulerkreis, so hat man die gewünschte Orientierung. Gibt es andererseits eine vollständige Orientierung, die die Gradbedingungen erfüllt, so folgt die Existenz eines f-Eulerkreises aus Satz 14.8. 2 Eine naive Bestimmung aller vollständigen Orientierungen ist von exponentieller Komplexität. Gibt es einen Algorithmus, der in polynomieller Zeit feststellt, ob es eine vollständige Orientierung der gewünschten Art gibt und diese im positiven Fall liefert? Ja, den gibt es. Siehe hierzu Kapitel 20, Seite 469. Anmerkung 14.7 Ein Digraph, der einen f-Eulerkreis enthält, ist stark zusammenhängend. Man beachte, daß in Satz 14.8 nur schwacher Zusammenhang gefordert wird. Der starke Zusammenhang ergibt sich aus dem schwachen in Kombination mit den Gradbedingungen. Ein Digraph, der einen f-Eulerzug enthält, kann, aber muß nicht stark zusammenhängend sein, wofür leicht Beispiele zu finden sind. 2 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 sind, spricht man von Hamiltonwegen (Hamiltonian path). Definition 14.7 Es sei G ein allgemeiner Graph. Ein einfacher a-Weg, der jeden Knoten aus G enthält, heißt a-Hamiltonkreis (a-Hamilton circuit), wenn er ein Kreis ist, und a-Hamiltonzug (a-Hamilton trail), wenn er offen ist. Ein einfacher f-Weg, der jeden Knoten aus G enthält, heißt f-Hamiltonkreis (gerichteter Hamiltonkreis, f-Hamilton circuit, directed Hamilton circuit), wenn er ein Kreis ist, und f-Hamiltonzug (gerichteter Hamiltonzug, f-Hamilton trail, directed Hamilton trail), wenn er offen ist. 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 364 KAPITEL 14. WEGE UND ZUSAMMENHANG Typen von Hamiltonwegen ist NP-vollständig. Einiges hierzu wurde in Unterabschnitt 6.2.3, Seite 201, erläutert. Sie auch Abschnitt E.5, Seite 581, 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.3 zeigt eine geeignete Datenstruktur. Ein Satz vom PHDR ? PTHA ? PTHA ? .. . ? PTHA Abbildung 14.3: Darstellung von Wegen Typ PHDR enthält allgemeine Information über den Weg: Weglänge, offen oder geschlossen, linieneinfach, einfach, 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 335. Für Linien, die in einem Wege mehrfach auftreten, sind entsprechend viele PTHA-Sätze vorhanden. 14.9. DATENSTRUKTUREN FÜR WEGE UND KREISZERLEGUNG 14.9.2 365 Ergänzung: 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.4 den .................. . ... ............... ....... ............. .................. .................................... . . . . . . . . . . . . . . . . . ........ . . . . ............... ... ...... ......................... .................... ...... . . .... ............ . ... ... .... ..... .... ...... ........... ........... ........... ................. . ................. ................. . ... ... ..... .......... . .............................................. ... . . . .. . . .... . . . . . . . . ..................................... . ... ... .. ..... ... ... .. .. ........ .. ........ .... ................. ...... ................. ..... .. ..... ... ........... ....... . .... .... .... ........... ..... ............ ..... ........... .......... . .. ....... ..... ..... ... ...... .......... ..... ..... ..... . . . . . . . . . . .. . ...... ...... ....... .. ....... ......... . . . . . . . . . ...... .......... ..... .......... ............. .............. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ... ...... ... . . ... ... .. .. ..... ........................................................................... ................................................................. .. ..... . . ... ... .. .. ..... ...... ..... .... ... ...................... .............. ................ ............... .......................................... ...... .... . . . . . . . . . . . . . ..... ..... ..... ...... ..... . ..... ...... ..... ..... ... ..... ... ...... ..... ...... ................................ . . .... ................................... . ...................... .................... ... ... . . . . . . . . . . . . . . . . . . . . . .... .. . .......................................... ... ... ... ................................................ ... ..... .... ... ...... ........ ................ ............... ............... .... l m o n a k i c g j d b h e f Abbildung 14.4: 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 Kreiszerlegung5 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 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.4 zeigt den Algorithmus. Er benutzt als zusätzliche Datenstrukturen einen aktuellen Restweg R und eine Menge Q zur Identifikation mehrfach 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. 5 366 ' 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.4: Algorithmus PATHDCMP zum Finden der Kreiszerlegung eines Weges 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.5 gezeigte Situation. Die pv sind PTHA-Sätze, die lv geben die zugehörigen Liniensätze an. Die mit den Linien inzidieren- 14.9. DATENSTRUKTUREN FÜR WEGE UND KREISZERLEGUNG pi−1 .. ...................................... .. li−1 vi−1 u pj−1 pi .. ..................................... .. li u vi .. ...................................... .. ••• .. ...................................... .. lj−1 vj−1 u 367 pj .. ...................................... .. lj u vj .. ...................................... .. Abbildung 14.5: Ausketten bei Kreiszerlegung den Knoten sind in Wegreihenfolge angegeben. Der Knoten u tritt in den PTHA-Sä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.8 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.4 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 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, die Träger eines a-Kreises sein können? Aufgabe 14.2 Man beweise Hilfssatz 14.1 (Seite 341): 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 345: Enthält eine starke Zusammenhangskomponente einen Bogen, so enthält sie auch einen f-Kreis 368 KAPITEL 14. WEGE UND ZUSAMMENHANG Aufgabe 14.4 In welcher Form ist Proposition 14.9 auf den Graphen der Abbildung 14.6 anwendbar? Was ändert sich, wenn der Bogen von u nach v durch eine Kante ersetzt wird? ................ ................ ... ... ... .. .... .... . .... ....... .... ........ . . . . ......... ..... . ... ................ ......... . ..... . . ... . ..... .. .. ..... ..... ..... ..... . .......... .... ...... .. ..... ..... .. . . . . . .. . .. ................................... .................................. .... ... ...................... ... ... . .. ....... .... . . ... .... . .... .. ............................................................ . . . ... . ... ... . . .. ... . . ..... . . . . . . ... . . . . ...... . . . . . . ... . . . . . ........ ........... . ..... ............ .... ........ . . . . . . . . . . . . ... . . . . ...... . ... ... . . . . . . . . . . . ... . . . . ..... .. .. ..... ..... .. .. ....... ..... ........... ......... ...... ........... ....... ...... ...... ............... .. .... .... . . . . . ... . . ... . ................. ............... u v Abbildung 14.6: 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 316) einen Eulerkreis auf? Einen Hamiltonkreis? Aufgabe 14.9 a. Geben Sie einen Algorithmus an, der in einem allgemeinen Graphen überprüft, ob die Bedingungen von Satz 14.7 erfüllt sind und, falls ja, einen Eulerkreis findet. Übertragen Sie den Algorithmus auf Digraphen. 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. 14.9. DATENSTRUKTUREN FÜR WEGE UND KREISZERLEGUNG 369 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 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? 370 KAPITEL 14. WEGE UND ZUSAMMENHANG 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 325, 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 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. 14.9. DATENSTRUKTUREN FÜR WEGE UND KREISZERLEGUNG 371 ......................... ......................... ..... ..... ... ... ... ... ... ... ... ... .. ... . .... .... . ............... . ................ . . ................... . . . . . . . . . . ... . .... ... ...... .... ... ............................. . . . . .... ... . ................ . . . . . ..... ... ... ............ .............. . . ... . . . . . . . . . . . .. . . . . . . ..... ............ ..... ......... ........... ... ... . . . . . .. .. . . . ... ..... ... ... ... . .......... . . . ... . . . ..... ... ... . ... . . ..... . . . . . . . . . . . ..... ..... .. ..... .... ... . .. ............. .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ... ... .................. ... ..... .. .... .................. .................. ..... ......... . ... ... ....... ........... ..... ........... ..... ..... ......... .................... ... . ..... ..... .......................... ... .. ..... ...... .... . ... ..... ... .. ... ... ....... .... .... ..... ... ... ..... .. ... . . .... .. . . . . . . ... . . ..... ... ... ... .. ..... ... .. ...... . . . . . . . . . . . . . . ..... .. ..... ...... ... ... ... . . .. . . ... . . . . . . . . . . . . ... ..... .. ..... ... ... .. .... .. . . . .. . . . . ... . . . . . . . . . . . . . . . ..... ... ... ...... .... ... . .. ................... ...... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ... ..... ... ... ..................... ............. ..... . ............... .............. ..................... ................ ..... ... ... ....... . ... ................................. ... .. ... ..... ... ....................................... ... ... .... .... .. ... .. ..... .. ........................................ . ... ... ... ... ... ... ..... .... ... .................................................................... ... .... ... ... ... . . . . . . . . . . . . . . . . . . . . . . . . . . . . ... ....... .... ..... ... ... ............................................. .... .. . . . . . . . . . . . ....................... . . . . . . . . . . . ... ... ..... ... ........ ........................... . . . . ........ .............. .. . . . ..... . . . . . . . . . . . . . . . . . . . . . . . . . . . .... . . . ..... ... ... .... .. .......................................... ... . ........ ... . ... . . . . . . . . . . . . ..... ..... ... .... ... ... ... .. . .. .. ...... . . . . . . . . . .... . . . . . . .. . . . ... ..................................................................................................................................................................................................................................................................................................................................................................................................................................................................... . . ... ... ..... .. .. ..... ... . .. . ... .. ... . . . . . . . . . . . ..... ... .... ... ... ..... ... ... ... .. . . .. . . . . . . . . . . ..... . . . . . . . ... ..... ..... ... ... ...... ... .. . . . ....................... . . . . . .... . . . . . . . . . . . . . . . . . . ....... .. ..... ... ... ..... ... ... .. ... ... ..... ..... ... ... .... ..... ... ..... .... .... ... ... ... ... ..... ... ..... .. .. ... ... ... ... ..... ... ........ .. ... ... ... ..... ... ... . . . . . . . ... . . . . .... ... ..... ... ........... ... ... . . . . . . . . ..... ... ... ........... ... . . ... . . . . . . . . . . . .... .... ..... .... ... ... .. ....... .. . . . . . . . . . . . . . . . . . . . . . . . . . . ...... ... ..... ... ..... .... ........ ........... ..... ..... ... . . . . . . . . . . . . . . . . . . . . . ... ... ..... ..... ... . ... .... . ... .. . . . . ... . . ... . . . . . . . . . . ..... ... ... ... ..... . .. . ... . ... . .. . .... . . . . . . . . . . . ... ..... ..... ... ... .. ... ... .. .. .. ... ..... .... ... .... ... ... .... ..... .. .... ..... .. .. ... . .... . . . . . . . . . . . ..... ... ..... ... ... ... .. ... . . ... . . . . . . . . . . . . . . . ...... . . . . . . . . . ..... ....... ....... ... .... ..... ... ... . .. .................... ......... . . . . . . . . . . . . . . . . . . . . . . . ... ... ..... ..... . ... .... ..... ..... ......... ... ..... ..... ...... ... ... ... ... ..... ...... ..... ... ... ... ... ........ ... ..... ..... ... ... ... ... ......... ..... ... .. ... ..... ... ... ..... ... ......... .. .. . . . . . . . . . . . . . . . ..... ... ... ..... ..... ... .. ... ... ..... .... ..... ..... ... ... ... .. ... .. ..... ... ..... . . .... . . . . . . ... . . . . . ..... ..... ..... ... ... ..... ... .. . . . . . . . .. . . . . . . . . ... ....... ..... ..... ... ... . ..... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .................. .................. ..... ........ ... ... .. ... ... ..... ..... . . . . . ... . . . . . . . . . . . . . . . ... .. ... ..... .... . ... ... ... ... . .. . . . . . . . . . . . . . . . ... ... .... ..... .. .... .. . .. .. .. .. .... .............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................. . ... ..... . ... . . . ... ..... ... ... ... .. . .... ..... ... . . . . . . . . . . ... ..... ... ..... ... ... ... ... .... . .... . . . . . . . . . . . . . . . . . . . . . . . . . ....... ....... ... ... ..... ..... ... .. ... ....... ........................ . . . . . . ........... . . . . . . . . . . . . . . . . . ..... ... ..... ... ... ... ... ... .. .. ..... .. ... ... ..... ....................... ... .. ......... ... ..... .... .... . ... ... ...... ... ..... ...... .. .................. ... ... .. .... ... ..... .. ............ ... ..... .... ... ..... .. ..... .. ........... . . . . . . . . . . . . . . . . . . . . . . . . ... ...... ..... ..... .. .. ... ........ ..... ..... ... .. ... .. .. ... .... ... ............ ..... ..... ... .. ... ... ......... ............ ... ... ..... ..... ..... ... .. ... ............ ....... .. .... ..... .. ... ........ ... ... ... ....................... ... ..... ............. .. ... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ...... . . . . . . ............... ............... .. ...... .. ..... ... ... .... ..... ... .. ... ... ... ... .... ... ... ..... ... .... ............................. ... .. ... ... ... ..... .. ... ...... .. ... ........ ... . . . . . .. ..... . ... . . . . . . .. .. .. . . . . ... ... ... ... ... ..... . .................. ... . . ... . . . . . . . . . ... ..... ... ... ... ... .. . ... ... .............. . . . . . . . . . . . . . . ... ... ..... ... ... ... .... .... ... ...... ... . . . . . . . . . . . . . . . . . . . . . . . . ....... . . . . ..... ... ........ ........ ... ... ... ... . ....... . . . .................... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ..... ... ... ... ... ... .. ............... ... .... ... ... ... ... ... ... ... ... ............. ... ... ... ... ... ... .. ... ............ .......... ... ... .. .... .. ........... ... ..... ... ... ... ... .. ............ . . . . . . . . . . ... . ... . . . . . . . . . . . . . . ..... .... ... ... ... .. ... ..... ............ ... ... ....... ... ... ... ..... . .............. ............ ... .. ... ... .... ..... .......... ............ ..... ............... .... .. ... ... ..... ........................ ... .... ............ ......... .... ... ... ..... .... .... ....... ........... . . .... . . . . . . . . . .... . . . . . . . . . . . . . ..... . ... ... ... ... ... ... ...... . . ...... . ... . . .... . .. ........................... . ... ... ... ... .............. .... ... ... ... ......... . ... ... . . ... . .. .. ......... .. .. . . . . . . . ... ... ... ... ... ..... . .. . ... . . . . . . . . . . . . . ... ..... ... .... ... .... ... . . . .. . . . ........ ........... . . . . . . . . ..... ... ...... .... ... ... ... ........ ..... ... ... ... .... ........... ... ... ..... ... ... ... .... ... ..... ... ... ....... ... ... . ..... ... ... ..... ....... ... ... ............ ..... .... . . .. . . . ... . . . . . . .... . . . ..... ...... . ... ... .. ..... ... ... . ... ... ........................ ..... .... ...................................... .... ... ... ...... ..... ... .............. ... .... .. ..... ... ... ... ... .. ... ..... ................. ..... ... .... ... ... .... ..... ..... . .. .. ... ..... . . ... ... .. ... ..... .. .. ... ... ................... .. ... ..... ... ...................... ... .... ... ..... .... ... ........ ....... ....... ... ..... ........ ........... .. .................. ............ . . . . . . . . . . . ..... .. ... ...... ... ...... .. ....................... ................................................ .. . . .... ... . ... .............. ....................... .. ... ... ... ..... .. .. .. . .... . ... .. ... . . .. . .. . . . . . . . . ... ... .. . .... .... .......................................................................................................... . . . . . . . . . ... .... .... ....... ... ........... . . .... . . ........................ . . . . . . . . . .... . . . . . . . . . ..... ... .... ........... .................................................. ... . ......... .......... . . . . . . . . . . . . . . . . . . . . . . . . . . ......... ........... .................................................. . ........ ....... ... . . ... ... ..... ..... .. .. ........ . ... ..... ........ .. ... ................................................... ........ .... ........ .... ... . . . ....... . . . . . . . . . . . ................................................................................................. .. 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.7: Graph1 372 KAPITEL 14. WEGE UND ZUSAMMENHANG .................... ....................... ....................... ..... ....... ...... .... ...... .... .... ... .... ... .... ... .. .. ... ... ... ... .. .. . . .. . .. . . . . .... . . .. ................................................................................................................................................ ... ............................................................................. . . . . . . . .... ... .. .. ... .. . .. . . ... .. .. . ... . . . . . . . . . . . . . .............. ... .... .... . . .. . . . . . . . . . . . . . ...... . . . . . ....... ... ...... .. ...................................... ...................... ..... .................... ....... ... .............. ............. ........................ ......... ....... ... .... ............ .. ...... .. ... ......... ... .. .. ....... . . . . . . . ... ... ... . .. .. . . . . ... . . . ... ... . ... ... ... ... .... .. .. .. . . . . . . ... . . . ... ... . .. . ... . . ... . . . . ... . ..................... . . .. . . . ... . . . . . . . ... .. ...... .......... .......... . . . . . . . . . . . .... . . .......... ........... . . .... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ....... ....... ....... .... .... ................ .... .... . . . . . ...... . . . . . . . . . . . . . . . . . . . . . . . . . . . . .... .... ...... .... .... ... .. ... . ... . . . . . . . ... . . . . ... ... ... . .. ... ... ... .. .. .. ... ... .... ............................................................................. ... ... ... ..... . ... ... ... . . . . . .. . . . . . . ... ... ... ... . . .. . .. . . . ... . . . . ... ... ... . . ..... . . . . . . .... . . . . . . . . . . . . . . . . ...... ...... ... ......... .. ... .............. .. ... ....... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ................. ................ ...... ...... ........ .. ................ ............... ....... .. ...... ...... ..... ......... ................ .. .... .. .......... .... ... ... .. . .... .. ... ... ... .... .. . ... .. ... ... .. ....... . ... .... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ... . . ... ... ... . ... .. ....... . . . ... ... .. .. . .... ............... . ... .... . . . ... . . . . ............ . ... .... ..... . . ... .... . . . . . . . . ... . . . ...... . ......... ... ... .. ... ......... .... . . . . . . . . . . . . . . . . . . . . . . . . . . ... . . . . . . ......... ....... ....... ......... ... . . ........... . ... . ... ......... ............ . . . . . . . . . . . . . ....... ........................ ........................ ..................... . ..... . ..................... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ....... .... .... .... .... ........... .. ... . . . . . . ..... .... . . ... ... . . . . . . . . . . . . ......... ................ . ... . ... . ... .. . . . . . . . ... .. ... . . .. .. .. .. ....... .. ... ......................................... .... ... ........................................................................ . . . . . . . . . . ... ... ... .. ... . .. . . .. . . . . . . ... ... ... ... . ... . . . .. . . . . . . . ... . . . . . .... ..... ... . . .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ......... ......... ......... ......... ............... ......................... ..... .............. ......................... ....... ........ . .. .... . . .. .... ..... .... . ... .... . ........ ... ... ......... ... ... .... .... .... ... . . . ... . . . . . . . .... . ...... .. .. .. . . ... . . . . . . . . ....... .... . . .. .. ... . . . . . . . . . . . ...... ................. . ... ............... ..... ............... ................. ...... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ......... ..... .......... ..... ....... . .... .... . . .. . . ... ..... . . . . . ..... . . . . . . ... ... . . ... ... . . .. .. . . . ... . . . .... . . . ... ... ... ... . .. .. .. . ... . . . ... . . . ................................................................... .. .. .. ... ... ..................................................................... .... . . . . . . ... . . . . ... ... ... .. . . . . ... . . . . . . ... . ... . ... . ... . .. . . . . ... . . . . . . ... .... .... .... . . . . ... . . . ... . . . . . . . . . . . . . . . ...... . . . . . . . . . . . ....... ....... . .. ............................ .... ... ..... ............... ........................ ..... ..... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ... .. ... .... ... . .. ... . . . . . . . . ... .... .... ... .... ... . . ..... . .... .... . .... ... .. ... . .... . . . . . . . . ... .... . .... ... .. ......... . . . . . . . ... .... .... . ....... ... . .. . . . . . . . . ...... ......... ... ... .... ......... ...... . . . . . . . . . . . . . . . . . . ....... . . . . . . . . . . . . . . . .............. ... ...... ........ ......... ... .... ..... ... .. .. .. ... ... ... ... ... ............... ... ... .. . . ... ... . . . .. . . . . .. . ... ...................... . .. ....................... . . . . . . . . . . . . . . .... .... ... .. ... ... ... ................... . . . .. . . . . . . . . . . ... ...................... ... ... ... ... .. . .. . ....................... ..... .... . . ... . ... . . . . .. .. .. .... . ....................... .. .. . . .... ... . . . . . . . . . . . . . . ...... ... .. ........ .. ................................................................... ... ........................ .................... ... .. ........ ... ... ... ... .... ... ... ... ... ... . ... ... .... ... ... ... ............ ... .. . . . . . . . . . . . . ... ... . . . ... . . . . ....................... ....................... ..... . ... ... . ... . . . . . ... ....... ... .... ... . ... ... . . . . . . ... . ..... ... .... ... ... . . . . . . . ... ... . ... ... ... . . . . . . . . . ... . . ... .. ..... . . . ... . . ..... . ..... . ... . ... .... . . ... . ... ... . . . .. . ... .... ... . . ... . . ... . . . ... . . .... ... ... . . .. . . . . . . . . . . . . . ........ ..... . ........ .... ............ .......... . . . . . . . . . . . . . ...... .... ..... ... . ... ..... ... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ....... ....... ....... ....... . .............. ....... ....... ....... ..... ..... .... ............ ....... .... ..... .... .... .... ....... ... ... ... ... ... ... ... ... ... ... ... . . . . ... . ..... . ..... .. .. .. ........ .. ........ .. .. . .. . . . . . .. . ... .......................................................................... ......................................................................................... ... . . .. ... . . . ... ... ... . . . ... .. . . . . . . . ... ... ... . .. ... . . . . . . . . . . . . . . . . . . . ..... ..... ..... ...... . ........ ........... ............................ ............................ .......................... .......... V26 V02 V01 V03 V04 V09 V12 V06 V05 V07 V08 V10 V11 V13 V14 V16 V15 V17 V21 V25 V20 V18 V19 V22 V23 Abbildung 14.8: Graph2 V24 14.9. DATENSTRUKTUREN FÜR WEGE UND KREISZERLEGUNG .................. .... ....... .. .... .. ... .. ... ... ... ... .. . . ... .. . ...... . . ...................... ... .. . . . . ..... . ...... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ... .... ... ... ... ... ... ... ... ... ... ... ... ... ... ..... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ..... ... .. ... .... .. . ......................................... .. ............... ................... ........ ........... ....... ... ...... ... . . . . ...... .... . .... . ..... . . ... . .... ... . ... . ... .. . ... ... . ... ... ... . . ... ... . . ... ... ... . ... ... .. . ... . ... . .. . ... ... ... . .. .... ... ... .. .. ... . . ... ... .. .... 1 ... .. ... . . . ... . ... . . ... ... ... ... ... ... ... . ... .. .... .. ... . ... . ... . ... ... ... ... ... .... ... ... ... ... ... . . ... . ..... . .. .. ...... ... ...... ....... ...... ...... ....... ... .... ....... ......... .... ... ................ ......... .... ... .................................. .... ... ... .... .... ... ... ... .... ... ... ... .... . ... .... ... ... .... ... ..... ..... .... ... .... .. .. . ... .... .... .... ... .... .. .... .... .... ... .... ... ... ... .... ... ... ... .... ... ... .... ... ... ... .... ... ... ... .... ... ... ... .... ... .... ... ... ... .... ... .... ..... ..... ... .... ... ... ... .... ... ... ... .... ... ... ... . . . ... . . . . . . . . . . . . . . . . . . . ... . . . . . . . .. . . . . . . . . . . . . . . . . . ....... . . . . . . . . . . . . .... ... ......... ..... ........ .... ... ... .... ... . .. .... ................................... ... ........ ...... ... .... . . . . . . ..... .. .... ... ... . . . . . . . .... .... ... ... ... . . .... . . ... .. .... ... . . . ..... ... ... . .... . ... . .. ... .. .. ... ... .... ... ................ .................... .. ... ........ ..... ... .... ... ... ... .... ... ... .. . . .. ... . .... . .. 2 .. ... .. ... ... ... . ... .. ... ... .. ... . . ... ... ... . . ... . . . .... . ..... ... .. ... . .. . . . . . . . . . . . . . ... . . . . ............. . ... .. ...... ....... ... ... ...... ...... ...... ... ...... ........ ... ...... ....... .... ....... ...... ............ ... ... ..... ....................... ...... . . . . .. . . . .... .. ... . .... ...... .. . .. ... . .. ...... . . . ........... . . . . ............ . . . . . . . . . . . . . . . . . . . . . . . ........ ... ............... . . . . . . . . . . . . . . . . . . . . . . . . . ....... ................. .................. .............. . . . . . . ...... . . . . . . . . . . . . . . ......... . .... ............. ... . .... . . . . . . . ... ... . .. ... .. ... .... .... .. .. .. . . ... . ... ... .. ... . . . . . ..... ..... .......................... ........................ V12 SC SC V21 V26 V24 Abbildung 14.9: Kondensierter Graph zu Graph2 373 374 KAPITEL 14. WEGE UND ZUSAMMENHANG .................. .... ....... .. .... .. ... .. ... ... ... ... .. . . ... . . . ...... . . ...................... .. .. ... . . . ..... .... ..... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. .... .... .... ... ... ... ... ... ... ... ... ... . ..... ..... ..... .. .. ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... .. ... ... ... ... ... ... ... ... ... ..... ... ... .. ... ........... ... ... ...... ... ... . ....... ...................... ... ........... ............ .... ... ...... ... ... ... ... ... ... ... ... .. .. ... ... .. .. ................................................ ... ... . . . . ... . ................................................................................................................... . . . . . . . ........ ... .. ... . .. ... ..... . . . . ... . . . . . . ...... ... ... ... ... . ... .... . . . . . . . . . . . . ... ..... .... . . . ..... ... ... .. ............. ......... . ..... . ... ........................ ......... ........ . ... . ... .......... .... ....... ..... ... ... ... ... ... ..... ... ... . ... . ... ... ... ... . .... . ... ... . ... . . . ... ... ... .. ... . . ... . ... ... . ... . . ... ... ... ... . . .. . . . ... ... . ............... . .... . ... . . . . . . . . .. .... .. . .... ... .... .... .. ... ... . ... .. ... .. ... .. ... . ... ... ... .. ...................... ... . . . . ... . ... ... ... . . . . . 1 ... ... ... . .. . . . .... . . ..... .... ... ... . .. . . . . . .. . . ... . . . .................. ... ... . . ... . ... . ... ... ... . ... . . . . .... ... ... ... . ... . ... . . . . ... ... ... . . ... ... . . ... ... ... . ... . ... ... . ... . ... .. ... ... ... ....... . . . ... ... ....... ...... ... ... . . . . . . . . . ............. . ....... ... .. .... . . . . . . . . . . . . . . . . . . . . . . . . . . . .... . . . . . . . . ..... . . .......... ..... ..... ......... ... .... . ...... ..... ... . . . .. ... . . . . . . ... ... . ... ....... . ... . . . . ... . ... . . . . . . .. ........ ... . .... .. . . . . . ........... . .... . . . .... ... . ... ... . ......................................... ... . .. ... ... . . . . .. ... ... . ... . . ... . . . .. . . . .... .... ... .... . ... . ... . . ....... .......... . . . ..................... ... . ............. ... ... . . . . ... ... ... ... ............... . . ... .... . . . . . . . ..... ... .. ..... .... ... .... . . ..... . ... ... ... .. ... . ... .. .... .. ... . ... .. .. . . ... . . . . . ... ... .. . .. .... ... ... .... ... . . ... . ... . ... ... .. ... ... . . ... . .. . ....... .. .. ... . .. . . . . . . . ... ... ... ... .................. ... .. ... . . . ... . .. ... ... . . .. ... . . . . . ... ... ... ... ... . ... . . . . . . ... .. ... . .... . .... .... . ..... ... ... .. . .. . ... . . . . ... .. . .. . ..... .... ... . . ... . . ... ... .. . . ... . ... . . . . . ... ... .... .. .... ... ... . . . . . ... . ....... . . . . ... .. . . ... . . . . . . ... . . . .. . ........... . ......... . . . . . . . . . . . . . . . . . . . . . . . ... . . . . . . . . . . . . . . . . ..... . . . . ..... . . ... .... .... .. ........................................ .... . ... ... ... ... . .. .... . . . ... . ... .. ................................... .... .... ... ... ..... ... ........ ...... . . . . .. . .... .... . ... . . . ... ..... . ... . . .... . ... .... . . .... ... . . . . ... .... ... .. . ........ . . ... . . .. ..... . . ... . ......... ... ... . . .. . ...... . . ... . . . . . . . . . . . . . . . ......... ... .. .......... .. ... ................ . .... . .... . . . . . . .. ......... ....... ... .. .. ... . . . . . . . ... . . .. ................ . . .. ..... .. ... ... ....... .. ... .. ... .... ... ... ... ... ... .... ... .. . .. . ... . ..... . . . .. . 2 ... .. .... .. ... .. . . ... . . . ... ... .. . . ... ... .... . . ... ... ... ....... . . . . . . . . . . . . .... .... ... ............................ . . . ..... . . .. ... .. . . . . ... ............... . . . . . . . ... ... ... ... . . ............. ... . ....... .. .... ... .. ... ... .... ... .. ...... ... ... ... .. ... ...... ....... .. ....... ............ ... ... ... ... ... ....................... ......................... ... . ... ... .. . . ............. .... ... . . .. ............. ........ ........... .. ............. .. ... . ...... ........ . . . .................................. . . . . . . . . . . . . . . . ............................ . . . . . . . . . . . ............ . . . . . . . . . . . . . . . . . . . . . . . . ............. ................. ........................ . . . . . . . . . . . . . . . . . . . . . . . ............. .... .... ... ............ .... ...... ... ... ... ................. .. .. ... ........... .. ... ... .... . . .. . . ... . ... ... .. ... . . . . . ..... ..... .......................... ........................ V12 V09 V02 V25 SC V17 V20 V13 V18 SC V26 V23 V21 V24 Abbildung 14.10: Komponentengraph zu Graph2 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. 375 376 ' 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 320, die Bearbeitung und Markierung eines neuen, d. h. nicht über 15.1. TIEFENSUCHE ' & 377 $ 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.7, Seite 371, 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 335. 378 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 379 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. 380 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 381 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 ergeben sich charakteristische Eigenschaften von Linien und bestimmte Bäume. Die Linien, mit denen bei der f-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 den Zielknoten eines Baumbogens 382 KAPITEL 15. TIEFENSUCHE UND BREITENSUCHE 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 351, 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 Knoten eines anderen Tiefensuchbaumes führt. 15.2. TIEFENSUCHBÄUME 383 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...... ....... ..... 5..... .. ... 4..... ...... ..... 2.... ...... ..... 1.... ..... 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. 384 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 345, 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 385 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 345, 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 352, 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- 386 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); }; 387 $ /* 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 388 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 389, 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 ' 389 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 $ % 390 ' 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 384, einen a-Kreis und damit nach Satz 14.4, Seite 345, 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 391 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 392 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 bBreitensuche (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); } }; 393 $ % 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 394 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 377, für den Ablauf einer f-Tiefensuche. Es wird wieder Graph3, Seite 377, mit den in Abbildung 15.2, Seite 378, 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 Aufgaben Aufgabe 15.1 Für Graph3, Seite 377, 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 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 Aufdeckung von Mengerstrukturen. Siehe hierzu Stiege [Stie2006]. 2 15.5. BREITENSUCHE 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 395 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 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? 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 396 KAPITEL 15. TIEFENSUCHE UND BREITENSUCHE 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]. 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. 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 357). 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 339. 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 397 398 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 403, 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 340. 16.2. DIE BIBLOCKZERLEGUNG ALLGEMEINER GRAPHEN 399 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. 400 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 401 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 403, 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. 402 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 403 .................. ... .. . ... .................... .................................. 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 404 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 346). 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 346, 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 405 .... . ...... ... .. .. .... ... .. .. ... .. .. .. .. .. .. .. .. . . . .. .. . . . .. .. . .. . . . . . . . . . . . . . . . . . . . ................. ................. .................. .................. . . . . . . . . . . . . . . ... ... .. .. . .. . . .. . . . . . ............................... . . ... ........... . . . . . . . . . ............. . ........... . . . . . . . . . ............. . . . . . . . . . . ... ... ... 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. 406 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 407 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 346. • 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 398, 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. 408 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 409 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 401, wird die Biblockzerlegung des Graphen Ugraph1, Seite 403, 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 410 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 411 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. 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 386, 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 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). 412 ' 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 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 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 383, 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 einge- % 16.6. ALGORITHMEN ZUR BESTIMMUNG DER BIBLOCKZERLEGUNG ' INITIALISIERUNG (W C) 1 & 413 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 hende 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 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 $ % 414 KAPITEL 16. DIE BIBLOCKZERLEGUNG 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. 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 16.6. ALGORITHMEN ZUR BESTIMMUNG DER BIBLOCKZERLEGUNG 415 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. 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 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 416 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) 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 16.6. ALGORITHMEN ZUR BESTIMMUNG DER BIBLOCKZERLEGUNG 417 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 Unterbrechung 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. 418 KAPITEL 16. DIE BIBLOCKZERLEGUNG 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. Orientierungsklassen und vollständige Orientierung wurden in Abschnitt 12.2, Seite 317, 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. 16.7. DIGRAPHEN UND VOLLSTÄNDIGE ORIENTIERUNGEN 419 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. 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.1 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. 420 KAPITEL 16. DIE BIBLOCKZERLEGUNG 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 383). 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 353, 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. 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. 16.7. DIGRAPHEN UND VOLLSTÄNDIGE ORIENTIERUNGEN 421 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 404). 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.2 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 414, 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? 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. 422 KAPITEL 16. DIE BIBLOCKZERLEGUNG 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 423 *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) $ % 424 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 Ergänzung: 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 ). 425 (17.1) 426 KAPITEL 17. ERGÄNZUNG: 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 427 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 319, 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. 428 KAPITEL 17. ERGÄNZUNG: 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 429 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. 430 KAPITEL 17. ERGÄNZUNG: PERIODEN Anmerkung 17.1 In Abschnitt 12.3, Seite 319, 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 546. 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 ' 431 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̃. $ % 432 KAPITEL 17. ERGÄNZUNG: PERIODEN I. Zeile 10 ergibt ein Vielfaches der f-Periode: Die f-Tiefensuche erreicht einen Knoten v zum ersten Mal über einen Baumbogen. Weitere Besuche des gleichen Knotens geschehen, wenn überhaupt, über einen Rückwärtsbogen oder einen Vorwärtsbogen. vr wird ausschließlich über Rückwärtsbögen besucht. Rückwärtsbögen und Vorwärtsbögen sollen zusammenfassend Sekundärbögen der f-Tiefensuche genannt werden. Zeile 10 wird genau dann ausgeführt, wenn ein Knoten über einen Sekundärbogen besucht wird. In Aufgabe 17.5 ist zu zeigen, daß Zeile 10 stets effektiv ausgeführt werden kann. Wir nehmen nun an, daß die f-Tiefensuche einen Knoten v über einen Sekundärbogen erreicht. Dann gibt es im Tiefensuchbaum einen eindeutig bestimmten f-Weg aus Baumbögen von vr nach v. Seine Länge sei l1 (siehe Abbildung 17.3), wobei der Fall l1 = 0 auftreten kann. Außerdem gibt es einen eindeutig bestimmten f-Weg aus Baumbögen von pppppppp pppppp v p p p p p p p pp pp pp pp ppp pppppp ppp p p p p p p p p pp p pp p p pp p p p pp p pp p pp pp p pp p pp pp p pp p pp pp pp pp pp pp ppp pp pp p pp pp ppp pp pp l1 pp pp l2 pp pp p pp pp p pp pp pp pp p p p pp pp pp pp pp p pp pp pp pp pp pp ppp pp p ppp ppp pp ...................... pppp pppp ...... .... ppp .... ... p .. ppppp p p . . . . p p . . . . p p p p....................... ... pppppppp ppp p p p p p p p p p..................................... v ....................p p p p p . . . .......... ........ ............ ... .... ... ... ... .... ......................... .................. . .... ... r .. . ... . . ... .. . . ..... ........... ................ .. lr ... .. .... ..... ....... .................... Abbildung 17.3: Berechnung der f-Periode vr zum Startpunkt des betrachteten Sekundärbogens. Zusammen mit diesem ergibt sich ein zweiter Weg von vr nach v. Seine Länge sei l2 . Beide Wege lassen sich durch einen beliebigen, aber festen f-Weg von v nach vr zu geschlossenen Wegen durch vr ergänzen. Die Länge des Ergänzungsweges sei lr . Dann gilt l1 + lr = n1 p̃ und l2 + lr = n2 p̃. Also |l1 − l2 | = |n1 − n2 |p̃. Ist p = 0 oder p ein Vielfaches von p̃, so ist p = gcd(p, |l1 − l2 |) auch ein Vielfaches von p̃. Der Algorithmus bewirkt, daß l1 = v→vlevel und l2 = level. Somit sind die in Zeile 10 berechneten Werte von p stets Vielfache der f-Periode p̃. II. Der Endwert pf teilt die Längen aller geschlossenen f-Wege durch vr : Es sei vr = v0 , l1 , v1 , . . . , ln−1 , vn−1 , ln , vn = vr 17.3. EIN ALGORITHMUS ZUR BESTIMMUNG DER PERIODE 433 ein geschlossener f-Weg. Wir betrachten eine beliebige, aber feste Linie li (1 ≤ i ≤ n) des Weges. Wenn es ein Bogen ist, wird die Tiefensuche in Algorithmus PERIOD ihn von vi−1 nach vi durchlaufen. Da PERIOD keine Linienmarkierung benutzt, wird jede Kante zweimal bearbeitet: In der Durchlaufrichtung von vi−1 nach vi und in der Durchlaufrichtung von vi nach vi−1 . In der Durchlaufrichtung von vi−1 nach vi ergibt sich das folgende System von Gleichungen: v0→vlevel + 1 − v1→vlevel v1→vlevel + 1 − v2→vlevel ··· vn−2→vlevel + 1 − vn−1→vlevel vn−1→vlevel + 1 − v0→vlevel = k 0 · pf = k 1 · pf ··· = kn−1 · pf = k n · pf Das folgt aus Zeile 10 des Algorithmus, wenn die Linie bei der Tiefensuche als Sekundärbogen durchlaufen wird. Wird sie als Baumbogen durchlaufen, so ist vi−1 →vlevel + 1 = vi→vlevel und die entsprechende Gleichung ist mit ki = 0 auch richtig. Addition der Gleichungen ergibt n = k · pf . D. h. pf teilt die Länge des Weges. Wenn es Kanten gibt, muß pf auch ein Teiler von 2 sein. Um das zu sehen, sei li eine Kante. Die beiden Durchlaufrichtungen ergeben die Gleichungen vi−1→vlevel + 1 − vi→vlevel(vi ) = ki · pf vi→vlevel + 1 − vi−1→vlevel = ki0 · pf Addition ergibt 2 = (ki + ki0 ) · pf . 2 Der Beweis gilt auch für die Korrektheit des Algorithmus, der die a-Periode einer schwachen Zusammenhangskomponente berechnet. In diesem Fall wird jede Linie des Weges zweimal von der Tiefensuche bearbeitet. Die Linie ist entweder eine Kante oder sie ist für einen Knoten Ausgangsbogen und für den anderen Eingangsbogen. Anmerkung 17.2 Bei der Berechnung der f-Periode wird für jede Kante, die in beiden Richtungen als Sekundärbogen durchlaufen wird, zweimal der größte gemeinsame Teiler berechnet. Für a-Perioden geschieht das sogar für jede entsprechende Linie zweimal. Durch geeignete Wahl der Anfangsparameter beim Aufruf von PERIOD und Linienmarkierung in diesem Algorithmus kann man erreichen, daß der größte gemeinsame Teiler höchstens einmal pro Linie berechnet wird. Siehe hierzu Aufgabe 17.6. Aufgaben Aufgabe 17.1 a. Es sei G ein stark zusammenhängender Digraph. Es sei Cr die Menge seiner f-Kreise. Zu zeigen ist fper(G) = gcd{len(p)|p ∈ Cr} 434 KAPITEL 17. ERGÄNZUNG: PERIODEN b. Kann man die Periode auch durch die f-Kreise, die durch einen festen Knoten v gehen, bestimmen? c. Ist die Aussage auch für allgemeine stark zusammenhängende Graphen richtig? Aufgabe 17.2 Es sei ein stark zusammenhängender Digraph geben. Weiter gebe es eine Zerlegung der Knotenmenge in q (q > 1) Klassen, die bei je q aufeinanderfolgenden Schritten eines fWeges stets in fester zyklischer Reihenfolge durchlaufen werden. Man zeige: Die f-Periode p des Digraphen ist ein Vielfaches von q. Man gebe Beispiele für p > q und für p = q. Für welche Digraphen gilt stets q = p? Aufgabe 17.3 Es sei D ein stark zusammenhängender Digraph mit Periode p ≥ 2. Alle Knoten von D mögen den gleichen Ausgangsgrad haben. Alle Knoten sollen auch den gleichen Eingangsgrad haben. Dann sind Ausgangsgrad und Eingangsgrad gleich und alle Priodizitätsklassen haben die gleiche Anzahl von Knoten. Aufgabe 17.4 Es sei G ein stark zusammenhängender Digraph mit f-Periode p ≥ 2 und P C eine seiner Periodizitätsklassen. a. Definieren Sie für die Knoten von P C eine Digraphstruktur mit „p-Schritt-Nachbarschaften“. b. Zeigen Sie, daß der so definierte Digraph aperiodisch ist. Aufgabe 17.5 Im Algorithmus PERIOD, Tabelle 17.1, wird in Zeile 10 der neue Wert von p durch p = gcd(p, |level − v→vlevel|) berechnet. Zeigen Sie, daß niemals beide Operanden von gcd gleichzeitig Null sind und daher der größte gemeinsame Teiler stets berechnet werden kann. Aufgabe 17.6 Man ändere Algorithmus PERIOD so ab, daß die Berechnung des größten gemeinsamen Teiler für jede Linie höchstens einmal erfolgt. Mit welchen Anfangsparametern ist PERIOD aufzurufen? Zeigen Sie daß die geänderte Fassung korrekt ist. Aufgabe 17.7 Ein allgemeiner Graph heißt periodisierbar mit Periode p ≥ 1, wenn es zu ihm eine vollständige Orientierung gibt, die die f-Periode p hat. Schlagen Sie ein Verfahren vor, mit dem man die größtmögliche Periode finden kann. 17.3. EIN ALGORITHMUS ZUR BESTIMMUNG DER PERIODE 435 Literatur Bipartitheit ist fester Bestandteil in Lehrbüchern über Graphentheorie und wird im allgemeinen nur für ungerichtete Graphen eingeführt. f-Perioden werden in Lehrbüchern über Graphentheorie oder über Algorithmen und Datenstrukturen i. a. nicht erwähnt. Sie scheinen in diesen Bereichen unbekannt zu sein. In der Theorie der Markovketten sind periodische sowie aperiodische Ketten bekannte und wichtige Begriffe. Siehe zum Beispiel Isaacson/Madsen [IsaaM1976]. Ausgehend von Markovketten führte Stiege [Stie1995d] Periodizität für Digraphen ein. 436 KAPITEL 17. ERGÄNZUNG: PERIODEN Kapitel 18 Ergänzungen zur Graphentheorie * 18.1 18.1.1 Korrespondenzen Überdeckungen in Graphen Es sei G ein allgemeiner Graph ohne isolierte Knoten. Ein Knoten v und eine Linie l überdecken einander (cover each other), wenn sie inzidieren. Eine Menge X von Knoten ist eine Knotenüberdeckung (vertex cover), wenn jede Linie aus L überdeckt ist. Eine Menge L von Linien ist eine Linienüberdeckung (line cover), wenn jeder Knoten aus V überdeckt ist. Die Anzahl Elemente einer Knotenüberdeckung minimaler Kardinalität heißt Knotenüberdeckungszahl (vertex covering number) von G und wird mit β(G) bezeichnet. Die Anzahl Elemente einer Linienüberdeckung minimaler Kardinalität heißt Linienüberdeckungszahl (line covering number) von G und wird mit β0 (G) bezeichnet. Eine Knotenmenge I heißt unabhängig (independent), wenn keine zwei Knoten aus I benachbart sind. Eine Linienmenge M heißt unabhängig (independent), wenn keine zwei Linien aus M benachbart sind. Die Anzahl Knoten einer unabhängigen Knotenmenge maximaler Kardinalität heißt Knotenunabhängigkeitszahl (vertex independence number) von G und wird mit α(G) bezeichnet. Die Anzahl Linien einer unabhängigen Linienmenge maximaler Kardinalität heißt Linienunabhängigkeitszahl (line independence number) von G und wird mit α0 (G) bezeichnet. 18.1.2 Korrespondenzen (Matchings) Unabhängige Linienmengen sind von besonderer Bedeutung und haben deshalb eigene Namen bekommen. Eine unabhängige Linienmenge heißt Korrespondenz (Paarung, matching). Entsprechend wird α0 (G) auch als Matchingzahl (matching number) bezeichnet. Es ist sinnvoll, die Bezeichnung α0 (L0 ) auch für beliebige Linienmengen L0 einzuführen. Sie gibt die Anzahl Elemente einer unabhängigen Teilmenge von L0 mit maximaler Kardinalität an. 437 438 KAPITEL 18. ERGÄNZUNGEN ZUR GRAPHENTHEORIE * Bipartite Korrspondenzen Beispiel 18.1 (Bestimmung eines f-Eulerkreises) 2 Allgemeine Korrespondenzen Aufgaben Literatur Die Bezeichnungen für Überdeckungs- und Unabhängigkeitszahlen sind in der Literatur nicht einheitlich. Mit den Symbolen α(G), αo (G), β(G) und β0 (G) folgen wir dem Lexikon der Mathematik [LexMat1-2001] und Volkmann [Volk1996]. 18.2 Mengertheorie Der Inhalt diese Abschnitts ist eine verkürzte Zusammenfassung der Kapitels 9 des Buches Stiege [Stie2006]. Weitere Erläuterungen, insbesondere Beweise, Beispiele und Algorithmen findet man dort. Im Jahr 1927 bewies Karl Menger [Meng1927] einen Satz, der sich als einer der wichtigsten Sätze der Graphentheorie herausstellen sollte. Diesen Satz und seine Varianten wollen wir im vorliegenden Abschnitt kennenlernen. Einige der zugehörigen Algorithmen werden kurz beschrieben. Die Ergebnisse bilden die Grundlage für die höheren Zerlegungen von Graphen, auf die wir im nächsten Abschnitt eingehen. 18.2.1 Trennende Mengen und disjunkte Wege Wir gehen von einem allgemeinen Graphen G(V, E, A, ϕ, ψ) aus. Weiter seien u und v in ihm zwei verschiedene, nicht benachbarte Knoten. Wir betrachten nun einerseits Knotenmengen SV , die u und v trennen, und andererseits Mengen von Wegen W , die von u nach v führen. Bei den trennenden Knotenmengen gehen wir davon aus, daß u und v nicht zur Trennmenge gehören, und unterscheiden: • SV ist eine a-Knotentrennmenge für u und v, d. h. jeder a-Weg von u nach v führt über einen Knoten aus SV . • SV ist eine f-Knotentrennmenge für u und v, d. h. jeder f-Weg von u nach v führt über einen Knoten aus SV . Die wichtigste Eigenschaft von f-Knotentrennmengen ist, daß sie i. a. nicht symmetrisch sind. D. h. eine f-Knotentrennmenge für u und v braucht keine f-Knotentrennmenge für v 18.2. MENGERTHEORIE 439 und u zu sein. Für a-Knotentrennmengen betrachten wir Mengen von a-Wegen von u nach v und für f-Knotentrennmengen betrachten wir Mengen von f-Wegen von u nach v. Dabei sollen in den Mengen W nur Wege vorkommen, die auf sinnvolle Weise „verschieden “ sind: • Kein innerer Knoten eines Weges aus W tritt in einem anderen Weg auf, auch nicht als Endknoten. Sie sind intern disjunkt (internally disjoint). Statt durch Mengen von Knoten kann man u und v auch durch Mengen SL von Linien trennen. Auch bei Linienmengen unterscheiden wir: • SL ist eine a-Linientrennmenge für u und v, d. h. jeder a-Weg von u nach v führt über eine Linie aus SL . • SL ist eine f-Linientrennmenge für u und v, d. h. jeder f-Weg von u nach v führt über eine Linie aus SV . Auch bei Trennung durch Linienmengen sollen die Wegmengen W , die wir betrachten, nicht beliebig sein. Wir verlangen: • Die Wege aus W haben paarweise keine Linien gemeinsam. Sie sind liniendisjunkt (line-disjoint). Zusammenfassend wollen wir intern disjunkte Wege bzw. liniendisjunkte Wege als unabhängige Wege (independen paths) bezeichnen. Entsprechend sollen Trennknoten, bzw. Trennlinien als Trennelemente (separating elements) bezeichnet werden. Anmerkung 18.1 Sind die Knoten u und v eines allgemeinen Graphen verschieden, aber benachbart, so können sie nicht durch eine Menge von Knoten getrennt werden. Sie können aber sehr wohl durch eine Menge von Linien getrennt werden. Die Forderung, daß u und v nicht benachbart sind, werden wir daher für Trennmengen aus Linien aufheben. 2 Anmerkung 18.2 Die verbindenden Wege sind offen, enthalten also einfache Wege von u nach v. Diese Teilwege bleiben natürlich intern disjunkt bzw. liniendisjunkt. Wir können also stets annehmen, daß die verbindenden Wege einfach sind. 2 18.2.2 Die Mengersätze Wie hängen nun Trennmengen und Wegmengen zusammen? Sind u und v nicht benachbart, so gibt es stets a-Trennmengen von Knoten für u und v. Man braucht nur SV := V \ {u, v} zu setzen. Es gibt dann auch a-Trennmengen minimaler Kardinalität. Entsprechend gibt es auch f-Knotentrennmengen minimaler Kardinalität. Mit analogen Überlegungen sieht man, daß a-Linientrennmengen und f-Linientrennmengen mit minimaler Anzahl von Elementen existieren. 440 KAPITEL 18. ERGÄNZUNGEN ZUR GRAPHENTHEORIE * Intern disjunkte a-Wege von u nach v haben keine inneren Knoten gemeinsam. Es kann also höchstens so viele Wege dieser Art geben, wie eine a-Knotentrennmenge minimaler Kardinalität Knoten hat. Entsprechend sind die Maximalzahlen intern disjunkter f-Wege, liniendisjunkter a-Wege und liniendisjunkter f-Wege durch die minimalen Kardinalitäten der entsprechenden Knoten- bzw. Linientrennmengen begrenzt. Die entscheidende Aussage der Mengersätze ist, daß in allen vier Fällen die mögliche Maximalzahl disjunkter Wege auch erreicht wird. Satz 18.1 (Knoten und a-Wege) Es sei G(V, E, A, ϕ, ψ) ein allgemeiner Graph und u und v zwei verschiedene, nicht benachbarte Knoten. Dann ist die Maximalzahl intern disjunkter a-Wege von u nach v gleich der Anzahl Knoten einer a-Knotentrennmenge minimaler Kardinalität für u und v. Satz 18.2 (Knoten und f-Wege) Es sei G(V, E, A, ϕ, ψ) ein allgemeiner Graph und u und v zwei verschiedene, nicht benachbarte Knoten. Dann ist die Maximalzahl intern disjunkter f-Wege von u nach v gleich der Anzahl Knoten einer f-Knotentrennmenge minimaler Kardinalität für u und v. Satz 18.3 (Linien und a-Wege) Es sei G(V, E, A, ϕ, ψ) ein allgemeiner Graph und u und v zwei verschiedene Knoten. Dann ist die Maximalzahl liniendisjunkter a-Wege von u nach v gleich der Anzahl Linien einer a-Linientrennmenge minimaler Kardinaltität für u und v. Satz 18.4 (Linien und f-Wege) Es sei G(V, E, A, ϕ, ψ) ein allgemeiner Graph und u und v zwei verschiedene Knoten. Dann ist die Maximalzahl liniendisjunkter f-Wege von u nach v gleich der Anzahl Linien einer f-Linientrennmenge minimaler Kardinalität für u und v. Zu allen vier Sätzen gibt es mehrere, unterschiedliche Beweise. Ein von Gallai1 [Grun1938] stammender Beweis von Satz 18.1 kann als Grundlage eines Beweises für jeden der vier Sätze dienen. Er hat algorithmischen Charakter und kann leicht zu einem Algorithmus erweitert werden, der sowohl eine Maximalzahl unabhängiger Wege als auch einen Minimalsatz von Trennelementen liefert. Die Grundidee dabei ist folgenden: Hat man m unabhängige Wege von u nach v, findet man entweder m Trennelemente oder kann m + 1 unabhängige Wege von u nach v konstruieren. Diese Konstruktion macht einigen Aufwand, der resultierende Algorithmus ist in Stiege [Stie2006] ausführlich beschrieben. Er ist hinreichend effizient: Die Algorithmen die einen maximalen Satz unahängiger Wege sowie einen minimalen Satz von Trennelementen bestimmen, haben die Komplexität k · O(m + n). Dabei ist k die minimale Anzahl von trennenden Knoten bzw. Linien, m die Anzahl Linien des Graphen und n die Anzahl Knoten des Graphen. Gallai (eigentlich Grünwald), Tibor ∗1912 in Budapest, †1992 ebenda. Ungarischer Mathematiker. Wichtig Beiträge zur Graphentheorie. Bezeichnend für die damalige Zeit ist, daß sein Beweis des Mengerschen Satzes auf Deutsch geschrieben war, aber in einer englischen Zeitschrift erscheinen mußte. 1 18.2. MENGERTHEORIE 18.2.3 441 Erweiterungen zu den Mengersätzen Wir wollen einige Erweiterungen der Sätze 18.1, 18.2, 18.3, 18.4 besprechen. Zugehörige Algorithmen findet man in Stiege [Stie2006]. Die Knoten sind benachbart (Sätze 18.1 und 18.2). Man bildet eine gemischte Trennmenge aus Knoten und den Linien, die u direkt mit v verbinden. Es sollen zwei disjunkte, nichtleere und nicht benachbarte Knotenmengen U und V getrennt werden. U und V werden zu je einem neuen Knoten geschrumpft, wobei interne Linien verschwinden. Es werden Trennmengen für die neuen Knoten und unbhängige Wege zwischen ihnen gesucht. Es sollen zwei nichtlere, nicht notwendigerweise disjunkte Knotenmengen U und V durch Knoten getrennt werden. Dazu sind einige Änderungen der bisher eingeführten Begriffe nötig. Zunächst einmal betrachten wir UW -Wege (UW -path). Das sind a-Wege (f-Wege), die in U beginnen und in W enden. Wege der Länge 0 läßt man in diesem Fall zu. Eine Menge X von Knoten ist a-Knotentrennmenge (f-Knotentrennmenge) für U und W , wenn jeder a-UW -Weg (f-UW -Weg) einen Knoten aus X enthält. Es muß dann U ∩ V ⊆ X gelten. Darüber hinaus muß man in solchen Trennmengen auch Knoten aus U ∪ W zulassen, die nicht im Durchschnitt liegen. Sind nämlich u ∈ U und w ∈ W durch eine Linie verbunden, so muß eine a-Knotentrennmenge für U und W entweder u oder v oder beide enthalten. Auch die „Verschiedenheit“ von UW -Wegen muß jetzt etwas anders definiert werden. Wir benutzen disjunkte UW -Wege, also solche die paarweise überhaupt keine Knoten gemeinsam haben. Mit diesen Festlegungen lauten die zu Satz 18.1 und Satz 18.2 analogen Sätze: Satz 18.5 Seien U und V nicht leere Knotenmengen eines allgemeinen Graphen. Die kleinste Mächtigkeit einer a-Trennknotenmenge für U und W ist gleich der größten Mächtigkeit einer Menge disjunkter a-UW -Wege in dem Graphen. Satz 18.6 Seien U und V nicht leere Knotenmengen eines allgemeinen Graphen. Die kleinste Mächtigkeit einer f-Trennknotenmenge für U und W ist gleich der größten Mächtigkeit einer Menge disjunkter f-UW -Wege in dem Graphen. Den Beweis führt man, indem man alle Knoten aus U mit einem neuen, zusätzlichen Knoten ũ und alle Knoten aus V mit einem neuen zusätzlichen Knoten ṽ verbindet. Bei f-Wegen werden Bögen in der richtigen Richtung genommen. Es ergibt sich dabei, daß jeweils die Sätze 18.1 und 18.5 sowie 18.2 und 18.6 äquivalent sind. Trennung der Knotenmengen U und W durch Linien. Ein Knoten läßt sich nicht von sich selbst durch Linien trennen, auch wenn Wege der Länge 0 zugelassen sind. Man benutzt wieder gemischte Trennmengen. Zunächst werden 442 KAPITEL 18. ERGÄNZUNGEN ZUR GRAPHENTHEORIE * die Knoten von U ∩W zur Trennmenge gerechnet. Dann werden diese Knoten und die mit ihnen inzidierenden Linien aus dem Graphen entfernt. Im Restgraphen wendet man Satz 18.3 bzw. Satz 18.4 auf die Mengen U \ W und W \ U an. Man findet im Restgraphen k liniendisjunke a-UW -Wege (f-UW -Wege) zwischen den beiden Mengen und k Linien, die sie trennen. Ist r die Anzahl Knoten von U ∩ W und beachtet man, daß Wege der Länge 0 liniendisjunkt zu allen anderen Wegen sind, erhält man k + r liniendisjunkte a-UW -Wege (f-UW -Wege) und eine a-Trennmenge (f-Trennmenge) von k + r Elementen, von denen r Knoten sind. 18.2.4 Die Struktur von Mengertrennmengen Im folgenden wollen wir uns auf a-Wege vom Knoten u zum Knoten v beschränken. Der Graph sei schwach zusammenhängend und die Knoten u und v verschieden und nicht benachbart. Wir können minimale Trennmengen von Knoten und eine maximale Anzahl intern disjunkter a-Wege von u nach v finden. Weder die Wege noch die Trennmengen sind eindeutig bestimmt. Wir wollen von Mengerwegen (Menger path) und Mengertrennmengen (Menger separating set) sprechen. Es soll die Struktur von Mengertrennmengen untersucht werden. Dazu wollen wir einen einzelnen Weg von u nach v einen Mengerweg (Menger path) nennen, wenn er durch weitere Wege zu einem System von Mengerwegen ergänzt werden kann. Ein einzelner Knoten soll Mengerknoten (Menger vertex) heißen, wenn er durch weitere Knoten zu einer Mengertrennmenge von Knoten ergänzt werden kann. Wir gehen nun von einen System von Mengerwegen P1 , P2 , . . . , Pk und einer Mengertrennmenge v1 , v2 , . . . vk aus, die mit dem zu Satz 18.1 gehörenden Algorithmus bestimmt wurden. Dann weiß man folgendes: • vi ist der erste Mengerknoten auf Pi , wenn man von u in Richtung v läuft. Das ergibt sich unmittelbar aus dem Algorithmus. • Liegt der Mengerknoten xi auf Pi vor dem Mengerknoten yi (in Richtung von u nach v), so liegt auf jedem Mengerweg, der durch beide Knoten führt, stets xi vor yi (siehe [Stie2006]). Man erhält so eine partielle Ordnung x y auf der Menge der Mengerknoten. Mit Hilfe eines einfachen und effizienten Algorithmus lassen sich auf jedem der Wege Pi alle Mengerknoten xi finden. Zu jedem xi werden auch Knoten auf den anderen Wegen bestimmt, die zusammen mit ihm eine Mengertrennmenge bilden. Allerdings werden nicht alle Mengertrennmengen bestimmt, in denen xi vorkommt, sondern nur seine minimale Mengertrennmenge (minimal Menger separating set). Das bedeutet, daß alle anderen Mengertrennmengen, in den xi vorkommt, auf mindestens einem anderen Weg Pj einen Knoten aufweisen, der näher an v liegt, als der entsprechende Knoten der minimalen Mengertrennmenge. Mit minimalen Mengertrennmengen ist es möglich, schnell zu prüfen, ob ein gegebenes k-tupel von Mengerknoten eine Mengertrennmenge ist oder nicht. 18.3. HÖHERE ZERLEGUNGEN 443 Während Mengertrennmengen effizient betimmt werden können, scheint es nicht einfach zu sein, eine Übersicht über alle Systeme von Mengerwegen zu bekommen. Insbesondere scheint es schwierig zu sein, zu zwei Mengerknoten x und y zu entscheiden, ob sie in der Relation ≺ stehen oder nicht. Aufgaben Literatur 18.3 Höhere Zerlegungen Wir haben in Kapitel 14 schwachen und starken Zusammenhang kennengelernt. Ausgangspunkt war (gegenseitige) Erreichbarkeit auf einem a-Weg bzw. gegenseitige Erreichbarkeit auf einem f-Weg. Es erscheint plausibel, daß gegeseitige Erreichbarkeit auf mehreren Wegen einen stärkeren Zusammenhang bewirkt. Wie schon in Abschnitt 18.2 werden wir auch hier verlangen, daß die Wege disjunkt, genauer intern disjunkt oder liniendisjunkt sind. Außerdem wollen wir wieder a-Wege und f-Wege unterscheiden. Das führt bei festem k zu vier verschiedenen Zusammenhangsbegriffen: Definition 18.1 Es sei U eine Knotenmenge in einem allgemeinen Graphen. In einem allgemeinen Graphen heißt eine Menge U von Knoten • k-a-zusammenhängend (k-a-connencted), wenn je zwei verschiedene Knoten durch k intern disjunkte a-Wege verbunden sind. • k-f-zusammenhängend (k-f-connencted), wenn je zwei verschiedene Knoten durch k intern disjunkte f-Wege verbunden sind. • k-a-linienzusammenhängend (k-a-lineconnencted), wenn je zwei verschiedene Knoten durch k liniendisjunkte a-Wege verbunden sind, • k-f-linienzusammenhängend (k-f-lineconnencted), wenn je zwei verschiedene Knoten durch k liniendisjunkte f-Wege verbunden sind, Im Fall von k-Zusammenhang soll U mindestens k + 1 Knoten und im Fall von k-Linienzusammenhang mindestens zwei Knoten enthalten. Definition 18.2 Es sei H ein Untergraph eines allgemeinen Graphen. H heißt k-azusammenhängend (k-f-zusammenhängend, k-a-linienzusammenhängend, k-f-linienzusammenhängend), wenn die Knotenmenge von H diese Eigenschaft in H hat. Man beachte, daß in einem (linien)zusammenhängenden Untergraphen die disjunkten Wege in diesem verlaufen müssen. 444 KAPITEL 18. ERGÄNZUNGEN ZUR GRAPHENTHEORIE * k-a-Zusammenhang und k-f-Zusammenhang Schlichter Kern Es ist es zweckmäßig k-a-Zusammenhang zunächst nur für schlichte Graphen einzuführen und danach mit Hilfe des schlichten Kerns (simle kernel) auf allgemeine Graphen zu erweitern. Der schlichte Kern eines allgemeinen Graphen G ist ein schlichter Graph mit der gleichen Knotenmenge. In ihm sind zwei verschieden Knoten dann und nur dann durch eine Kante verbunden, wenn sie in G durch eine Linie verbunden sind. Streng genommen, ist der schlichte Kern von G nicht eindeutig definiert, da seine Kantenmenge E nicht festgelegt ist. Wir wollen sie auch nicht exakt festlegen, aber verlangen, daß die Linienmenge von G und die Linienmenge des schlichten Kerns. disjunkt sind. Der schlichte Kern von G ist weder ein Untergraph von G noch gehört er zu gleichen Orientierungsklasse. Insbesondere ist er nicht die vollständige Desorientierung von G. Sind im schlichten Kern von G k intern disjunkte a-Wege gegeben, so lassen sich in G stets k entsprechende intern disjunkte a-Wege angeben. Diese durchlaufen zwar die gleichen Knoten in gleicher Reihenfolge, aber die durchlaufenen Linien sind i. a. nicht eindeutig bestimmt. Die im nächsten Abschnitt zu bestimmenden k-a-Zusammenhangskomponenten des schlichten Kerns ergeben in G eindeutige maximale Untergraphen, indem die durch die entsprechenden Knotenmengen erzeugten Untergraphen von G genommen werden. Sie haben die gleichen Zusammenhangseigenschaften und sollen auch k-a-Zusammenhangskomponenten heißen. k-a-zusammenhängende Knotenmengen Wir wollen uns zunächst einmal klarmachen, was kleine Werte von k bedeuten. Es sei also G ein schlichter Graph mit mindestens k + 1 Knoten. a. G ist 0-a-zusammenhängend (0-f-zusammenhängend). b. G ist genau dann 1-a-zusammenhängend (1-f-zusammenhängend), wenn G schwach (stark) zusammenhängend ist. c. G ist genau dann 2-a-zusammenhängend, wenn G eine Biblock ist. Siehe Kapitel 16. Für f-Wege haben wir hier keine besondere Bezeichnung. Bei k-a-Zusammenhang ist Satz 18.1 das wichtigste Instrument der Untersuchungen. Als erstes sehen wir, daß man sich bei k-Zusammenhang auf Wege zwischen nicht benachbarten Knotnen beschränken kann: Proposition 18.1 Es sei U eine Knotenmenge von mindestens k + 1 Knoten des schlichten Graphen G. U ist genau dann k-a-zusammenhängend in G, wenn jedes Paar nicht benachbarter Knoten in U durch mindestens k intern disjunkte a-Wege verbunden ist. Beweis: Ist U k-a-zusammenhängend in G, so ist die Bedingung erfüllt. Die Bedingung sei erfüllt. Es bleibt zu zeigen, daß alle benachbarten Knoten u, v ∈ U durch k intern disjunkte a-Wege verbunden sind. Es werde angenommen, daß die maximale Zahl intern disjunkter a-Wege, die die benachbarten Knoten u und v aus U verbinden, k 0 < k ist. k 0 − 1 dieser Wege haben eine Länge größer als 1. Es sei l die Kante, die 18.3. HÖHERE ZERLEGUNGEN 445 u und v verbindet. In G − l gibt es dann nach Satz 18.1 k 0 − 1 Knoten, die u und v trennen. Da |U| ≥ k + 1, gibt es mindestens einen Knoten v 0 in U, der von u, v und den Trennknoten verschieden ist. v 0 wird in G − l von u oder von v durch die Trennknoten getrennt, denn anderenfalls würden diese in G − l nicht u und v trennen. Nehmen wir an, v 0 werde von u getrennt. Dann sind v 0 und u weder in G − l noch in G benachbart. Nach Voraussetzung müßte es in G k intern disjunkte a-Wege zwischen v 0 und u geben. In G − l gibt es jedoch höchstens k 0 − 1 solcher Wege, also in G höchstens k 0 − 1 + 1 < k und das ist ein Widerspruch. 2 Aus Satz 18.1 lassen sich weitere nützliche Folgerungen ziehen. Die wichtigsten sind die nacholgende Proposition und der sich darau ergebende Satz. Es seien U und W Knotenmengen. Wir betrachten a-UW -Wege und benutzen Satz 18.5 (siehe Mengererweiterungen 441). Zur Erinnerung: Die Knoten aus U ∩W werden als UW -Wege der Länge 0 aufgefaßt. Für k-a-Zusammenhang ergibt sich dann die folgende Proposition. Proposition 18.2 Es seien U und W k-a-zusammenhängende Knotenmengen in dem schlichten Graphen G. U ∪ W ist genau dann k-a-zusammenhängend in G, wenn es k disjunkte a-UW -Wege in G gibt. Beweis: Gibt es weniger als k disjunkte a-UW-Wege, so wird U von W durch weniger als k Knoten getrennt und U ∪ W kann nicht k-a-zusammenhängend sein. Wenn es (mindestens) k disjunkte a-UW -Wege gibt, so führt die Annahme, daß w ∈ W \U von u ∈ U \ W durch weniger als k Knoten getrennt ist, zu einem Widerspruch. Es gibt dann mindestens einen a-UW -Weg, auf dem kein Trennknoten liegt. u und w können dann nicht beide auf diesem Weg liegen. Auch wenn nur einer, sagen wir u, auf diesem Wege läge ergäbe sich ein Widerspruch, denn der Endknoten des Weges, der zu W gehört, wäre von w durch weniger als k Knoten getrennt. Lägen schließlich weder u noch v auf dem UW -Weg, so müßte u vom Endknoten in U oder w vom Endknoten in W durch weniger als k Knoten getrennt sein, und das ist auch ein Widerspruch. 2 Es sei U eine k-a-zusammenhängende Knotenmenge in G. U soll maximal heißen, wenn keine echte Obermenge von U in G k-a-zusammenhängend ist. Sind U und W maximale k-a-zusammenhängende Knotenmengen in G und ist |U ∩ W | ≥ k, so gibt es k disjunkte UW -Wege (der Länge 0) und U ∪ W ist nach Proposition 18.2 k-a-zusammenhängend. Wegen der Maximalität gilt U = W . Das kann man folgendermaßen interpretieren: Erweitert man in G eine k-a-zusammenhängende Knotenmenge schrittweise so, daß schließlich eine maximale k-a-zusammenhängende Knotenmenge entsteht, so ergibt sich unabhängig von der Reihenfolge der Erweiterungen stets die gleiche maximale Knotenmenge. Wird „eindeutig bestimmt“ in diesem Sinne aufgefaßt, so gilt der folgende Satz. Satz 18.7 In einem schlichten Graphen sind maximale k-a-zusammenhängende Knotenmengen eindeutig bestimmt. 446 KAPITEL 18. ERGÄNZUNGEN ZUR GRAPHENTHEORIE * k-a-zusammenhängende Untergraphen Es sollen jetzt k-a-zusammenhängende Untergraphen eines schlichten Graphen betrachtet werden. Wir beginnen mit Proposition 18.1. Aus ihr ergibt sich unmittelbar die entsprechende Proposition für Untergraphen. Proposition 18.3 Es sei G ein schlichter Graph und H ein Untergraph von G. Dann ist H genau dann k-a-zusammenhängend, wenn 1. alle Knoten in H mindestens den Grad k haben und 2. in H jedes Paar nicht benachbarter Knoten durch mindestens k intern disjunkte a-Wege verbunden ist. 2 Es seien H und I k-a-zusammenhängende Untergraphen eines schlichten Graphen G. Dann sind V (H) und V (I) k-a-zusammenhängende Knotenmengen in H +I. Gilt |V (H)∪ V (I)| ≥ k, so ist nach Proposition 18.2 auch V (H) ∪ V (I) = V (H + I) k-a-zusammenhängend in H + I. D. h. H + I ist ein k-a-zusammenhängender Untergraph. Sind die k-azusammenhängenden Untergraphen H und I maximal und haben sie einen gemeinsamen k-a-zusammenhängenden Untergraphen, so ist also H = I. Mit der oben festgelegten Bedeutung von „eindeutig bestimmt“ ergibt sich so der folgenden Satz. Satz 18.8 In einem schlichten Graphen sind maximale k-a-zusammenhängende Untergraphen eindeutig bestimmt. Für maximale k-a-zusammenhängende Untergraphen eines schlichten Graphen G wollen wir eine eigene Bezeichnung einführen. Definition 18.3 Ein maximaler k-a-zusammenhängender Untergraph eines schlichten Graphen wird k-a-Komponente (k-a-component) des Graphen genannt. Sind H und I k-a-zusammenhängende Untergraphen, so kann H +I auch dann k-a-zusammenhängend sein, wenn H und I weniger als k gemeinsame Knoten aufweisen. Die Anwendung von Proposition 18.2 erfordert in H + I mindestens k disjunkte a-V (H)V (I)-Wege. Um das zu untersuchen, führen wir zwei weitere Begriffe ein. Sind H und I Untergraphen eines schlichten Graphen, so wird mit ap(H, I) die Menge der gemeinsamen Knoten von H und I bezeichnet. aed(H, I) bezeichnet die Menge der Linien, die H und I verbinden, aber nicht mit einem Knoten aus ap(H, I) inzidieren. Schließlich wird β0 (aed(H, I)) als die maximale Anzahl von Linien aus aed(H, I), die paarweise keine gemeinsamen Endpunkte haben, definiert. Dann gilt die folgenden Proposition. Proposition 18.4 Es sei G ein schlichter Graph und H und I k-a-zusammenhängende Untergraphen. Dann ist H + I genau dann k-a-zusammenhängend, wenn |ap(H, I)| + β0 (aed(H, I)) ≥ k gilt. 18.3. HÖHERE ZERLEGUNGEN 447 Beweis: Ist H + I k-a-zusammenhängend, so gibt es in H + I nach Proposition 18.2 mindestens k disjunkte a-V (H)V (I)-Wege. Jeder dieser Wege, der keinen Knoten aus ap(H, I) enthält, führt über mindestens eine Kante aus aed(H, I) und Kanten verschiedener Wege sind nicht benachbart. Also gilt |ap(H, I)| + β0 (aed(H, I)) ≥ k. Gilt diese Ungleichung, so gibt es in H + I offensichtlich mindestens k disjunkte aV (H)V (I)-Wege und H + I ist nach Proposition 18.2 k-a-zusammenhängend. 2 Jeder k-a-zusammenhängende Untergraph ist auch (k −1)-a-zusammenhängend. Jede k-aKomponente ist also in einer (k −1)-a-Komponente enthalten. Eine (k −1)-a-Komponente C enthält 0 oder mehr k-a-Komponenten. Ist keine k-a-Komponente vorhanden, so endet mit C die Zerlegung in a-Komponenten. Anderenfalls gibt es in C k-a-Komponenten und eventuell auch Linien, die zu keiner k-a-Komponente gehören. Der von diesen Linien erzeugte Untergraph wird k-a-Kokomponente (k-a-cocomponent) von C genannt. Anders als bei Kokantenkomponenten kann die k-a-Kokomponente von C selbst dann leer sein, wenn es in C mehrere k-a-Komponenten gibt. k-a-Komponenten sind zwar eindeutig bestimmt, aber nicht notwendigerweise disjunkt. Sie können bis zu k − 1 Knoten gemeinsam haben und haben dann wegen der Maximalität auch alle Kanten, die gemeinsame Knoten verbinden, gemeinsam. Abbildung reff-commonvt zeigt einen 2-azusammenhängenden Graphen mit drei 3-a-Komponenten, die alle die Knoten a und b und die sie verbindende Kante gemeinsam haben. Die 3-a-Kokomponente des Graphen ist leer. Eine nichtleere k-Kokomponente kann aus einer einzigen Kante und ihren Inzidenzpunkten bestehen. Ein Beispiel ist in Abbildung reff-oneedge dargestellt. Der Graph ist 3-azusammenhängend und hat zwei 4-a-Komponenten, die die c und d verbindende Kante gemeinsam haben. Die 4-a-Kokomponente wird durch die Kante zwischen a und b zusammen mit diesen Knoten gebildet. Da die 4-a-Komponenten nicht knotendisjunkt sind, ist der ganze Graph 4-a-kantenzusmmenhängend, d.h. er ist eine 4-a-Kantenkomponente. Nach Proposition 18.4 können zwei disjunkte k-a-Komponenten nicht durch k oder mehr paarweise nicht benachbarte Kanten verbunden sein. Sie können aber sehr wohl durch k knotendisjunkte Wege verbunden sein, auch wenn alle Knoten mindestens den Grad k haben. Als Beispiel kann wieder der Graph in Abbildung reff-zweikanten herangezogen werden. Es ist auch nicht richtig, daß zwei k-a-Komponenten zusammenfallen, wenn ein Knoten aus einer Komponenten mit einem einem Knoten aus der anderen durch k oder mehr intern disjunkte Wege verbunden ist. Ein Beispiel dazu zeigt Abbildung reff-kpaths. Der Graph als ganzes ist nicht 3-a-zusammenhängend. Es gibt in ihm zwei 3-a-Komponenten. Sie werden durch die Knotenmengen {a, b, c, d} und {s, t, u, v} erzeugt. Es gibt jedoch 4 intern disjunkte a-Wege von Knoten a nach Knoten t. Im Gegensatz zu k-a-Komponenten kann eine k-a-Kokomponente mehr als k Knoten mit einer k-a-Komponente gemeinsam haben, auch wenn der Graph vom Minimalgrad 448 KAPITEL 18. ERGÄNZUNGEN ZUR GRAPHENTHEORIE * k ist. Abbildung reff-cocomp zeigt einen 2-a-zusammenhängenden Graphen mit einer 3a-Komponenten und einer 3-a-Kokomponente. Von der 3-a-Komponente sind nur die Verheftungspunkte mit der 3-a-Kokomponente eingezeichnet. Es sind die doppelten Kreise. 18.4 Ein Beispiel Abbildung reff-hiconn zeigt schematisch und ausschnittsweise die Zerlegung eines schlichten Graphen in k-a-Kantenkomponenten und k-a-Komponenten. Wir starten mit einer (k − 1)-a-Kantenkomponente EC, gezeichnet als Oval. In EC gibt es als Untergraphen die k-a-Kantenkomponenten EC1 , EC2 und EC3 . Außerdem muß es in EC eine nichtleere k-a-Kokantenkomponente geben. Sie ist als Rechteck CEC eingezeichnet. EC enthält auch vier (k − 1)-a-Komponenten als Untergraphen, nämlich C1 , C2 , C3 und C4 . Diese sind zwar eindeutig bestimmt, aber nicht notwendigerweise disjunkt. Es wird angenommen, daß C2 und C3 einen nichtleeren Durchschnitt haben. C1 enthält die ka-Komponenten C11 , C12 und C13 . C2 enthält C21 und C22 – in unterschiedlichen k-aKantenkomponenten – und C3 enthält C31 und C32 . C4 enthält keine k-a-Komponente. Die (k −1)-a-Komponente C1 wird i. a. auch noch Kanten enthalten, die weder in C11 noch in C12 noch in C13 enthalten sind, also eine nichtleere k-a-Kokomponente aufweisen. Das gleiche gilt für C2 und C3 . Diese Kokomponenten können auf sehr unübersichtliche Art und Weise über CEC, EC1 , EC2 und EC3 verstreut sein und sind deshalb in Abbildung reff-hiconn nicht eingezeichnet. Die Zerlegung in Abbildung reff-hiconn ist schematisch. Im folgenden soll an Beispielen gezeigt werden, daß es reale Graphen gibt, die diesem Schema genügen. Wir wollen dabei nur die kritischen Teile eines Gesamtgraphen konstruieren. Wir wählen k = 4. Abbildung reff-4connecta zeigt die 3-a-Komponenten C1 . Sie enthält die Untergraphen C11 , C12 , C13 , die wir als K5 und damit 4-a-zusammenhängend annehmen wollen. Aus Gründen der Übersichtlichkeit ist die interne Struktur dieser Untergraphen nicht eingezeichnet. Es sind nur die Knoten zu sehen, die mit externen Kanten inzidieren. C11 , C12 und C13 sind 4-a-Komponenten, denn sie sind jeweils durch 3 Knoten voneinander getrennt. Als ganzes ist C1 4-a-kantenzusammenhängend und deshalb ist es vollständig in einer 4-a-Kantenkomponente, z. B. EC1 , enthalten. Als nächstes untersuchen wir die 3-a-Komponente C4 . Sie darf keine 4-a-Komponenten enthalten, soll aber 4-a-kantenzusammenhängend sein. Abbildung reff-4connectc zeigt einen Untergraphen, der diese Eigenschaften hat. Er muß deshalb in einer 4-a-Kantenkomponente enthalten sein. Wir nehmen an, er hat genau zwei Knoten mit C13 gemeinsam und gehört deswegen zu EC1 . In Abbildung reff-4connectb ist die 3-a-Komponente C2 zu sehen. Sie enthält die 4-aKomponenten C21 und C22 , die K5 sind und durch gepunktete Linien angegeben werden. Sie sind über die drei explizit eingezeichneten Knoten und externe Kanten miteinander 18.5. GERICHTETER KERN 449 verbunden. Sie gehören also zur gleichen 3-a-Komponente, aber zu verschiedenen 4-aKantenkomponenten. Um zu erzwingen, daß C21 und C13 disjunkt sind, aber in die gleiche 4-a-Kantenkomponente fallen, wird auch C21 über genau zwei Knoten mit C4 verheftet. Diese werden paarweise verschieden von den Knoten gewählt, mit denen C13 und C4 verheftet sind. Dann sind C13 und C21 nicht nur disjunkt, sondern gehören auch zu verschiedenen 3-a-Komponenten. Für die 3-a-Komponente C3 aus Abbildung reff-hiconn wird keine eigene Abbildung angegeben. C3 kann auf die gleiche Weise wie C1 in die 4-a-Komponenten C31 und C32 zerlegt werden. Die Überlappung von C3 und C2 kann erreicht werden, indem beide genau einen Knoten gemeinsam haben, der mit je drei Kanten mit dem Rest von C3 bzw. dem Rest von C2 verbunden ist. 18.5 Gerichteter Kern k-f-Zusammenhang wird durch intern disjunkte f-Wege definiert. Das führt, ähnlich wie bei k-a-Zusammenhang (Abschnitt refsimplekernel.sec), zu Schwierigkeiten, wenn man Mehrfachlinien allgemein zuläßt. Um diese durch Mehrfachlinien hervorgerufenen Sonderfälle und Schwierigkeiten zu vermeiden, ist es zweckmäßig k-f-Zusammenhang zunächst nur für gerichtete Kerne einzuführen und von diesen auf allgemeine Graphen zu übertragen. Definition 18.4 Der gerichtete Kern (directed kernel) eines allgemeinen Graphen G ist ein Graph mit der gleichen Knotenmenge, ohne Schlingen, ohne Mehrfachkanten und ohne Mehrfachbögen. In ihm sind zwei verschieden Knoten dann und nur dann durch eine Kante verbunden, wenn sie in G durch eine Kante verbunden. Sind zwei Knoten in G durch Bögen, aber nicht durch eine Kante verbunden, so sind sind sie im gerichteten Kern in jeder Richtung, in der sie in G durch einen Bogen verbunden sind, durch genau einen Bogen verbunden. Wie der schlichte Kern so ist, streng genommen, auch der gerichtete Kern von G nicht eindeutig definiert, da seine Linienmenge L nicht festgelegt ist. Auch hier wollen wir nur verlangen, daß die Linienmenge von G und L disjunkt sind. Wie der schlichte Kern so ist auch der gerichtete Kern von G weder ein Untergraph von G noch gehört er zu gleichen Orientierungsklasse. Sind im gerichteten Kern von G k intern disjunkte f-Wege gegeben, so lassen sich in G stets k entsprechende intern disjunkte f-Wege angeben. Diese durchlaufen zwar die gleichen Knoten in gleicher Reihenfolge, aber die durchlaufenen Linien sind i. a. nicht eindeutig bestimmt. Die im nächsten Abschnitt zu bestimmenden k-fZusammenhangskomponenten des gerichteten Kerns ergeben in G eindeutige maximale Untergraphen, indem die durch die entsprechenden Knotenmengen erzeugten Untergraphen von G genommen werden. Sie haben die gleichen Zusammenhangseigenschaften und sollen auch k-f-Zusammenhangskomponenten heißen. 450 KAPITEL 18. ERGÄNZUNGEN ZUR GRAPHENTHEORIE * Um keine neue Bezeichnung einzuführen, wollen wir im folgenden einen allgemeinen Graphen einen gerichteten Kern nennen, wenn es einen allgemeinen Graphen gibt, zu dem er gerichteter Kern ist. 18.6 k-f-Zusammenhang Gehen in einem gerichteten Kern von einem Knoten k intern disjunkte f-Wege aus, so hat der Knoten mindestens k Nachbarn und der Graph mindestens k + 1 Knoten. Ähnliche Überlegungen wie in Abschnitt refakantenzus.sec führen zu der Forderung, daß auch die Knotenmengen, die wir k-f-zusammenhängend nennen wollen, hinreichend groß sein müssen. Definition 18.5 Es sei G ein gerichteter Kern und U in ihm eine Knotenmenge mit mindestens k + 1 Knoten. U heißt k-f-zusammenhängend (k-f-connected) in G, wenn für alle Knoten u, v ∈ U mit u 6= v sowohl Knoten v von Knoten u als auch Knoten u von Knoten v in G k-f-erreichbar sind. Ein Untergraph H von G heißt k-f-zusammenhängend, wenn seine Knotenmenge V (H) k-f-zusammenhängend in H ist. Man beachte, daß in einem k-f-zusammenhängenden Untergraphen die intern disjunkten Wege in diesem verlaufen müssen. Die folgende Anmerkung entspricht Anmerkung refkantenzus.anmerk. Anmerkung 18.3 1. Jede Knotenmenge von mindestens Knoten k + 1 läßt sich zu einem k-f-zusammenhängenden gerichteten Kern machen. 2. Es sei G ein gerichteter Kerns mit mindestens zwei Knoten. a. G ist 0-f-zusammenhängend. b. G ist genau dann 1-f-zusammenhängend, wenn G stark zusammenhängend ist. 3. Auch bei k-f-Zusammenhang ist es unzweckmäßig Knotenmengen mit nur einem Knoten zuzulassen oder auf die Bedingung u 6= v zu verzichten. 4. Ist U eine k-f-zusammenhängende Knotenmenge in dem gerichteten Kern G, so braucht U keineswegs in G[U], dem von U erzeugten Untergraphen, k-f-zusammenhängend zu sein. Sind z. B. die Knoten von U in G paarweise nicht benachbart, so besteht G[U] nur aus isolierten Knoten. Andererseits kann U k-f-zusammenhängend in einem Untergraphen H sein, der ein echter Untergraph von G[U] ist. 5. Ist I ein k-f-zusammenhängender Untergraph des gerichteten Kerns G, so ist die Knotenmenge V (I) k-a-zusammenhängend in in jedem Untergraph H mit I ⊆ H ⊆ G. 2 18.6. K-F-ZUSAMMENHANG 451 Bei k-f-Zusammenhang ist Satz 18.2 das wichtigste Instrument. Auch aus diesem Satz sollen zunächst einige nützliche Folgerungen gezogen werden. Proposition 18.5 Es sei U eine Knotenmenge von mindestens k + 1 Knoten des gerichteten Kerns G. U ist genau dann k-f-zusammenhängend in G, wenn jedes Paar nicht f-benachbarter Knoten in U in beiden Richtungen durch mindestens k intern disjunkte f-Wege verbunden ist. Beweis: Der Beweis entspricht im wesentlichen dem von Proposition 18.1. Allerdings erfordert die Berücksichtigung von zwei Richtungen einige Zusatzüberlegungen. u und v mit u 6= v sind f-benachbart bedeutet, daß es einen Bogen von u nach v oder eine Kanten zwischen u und v gibt. Gibt es nur einen Bogen von v nach u, so sind u und v nicht f-benachbart, wohl aber v und u. Ist U k-f-zusammenhängend in G, so sind beide Bedingungen erfüllt. Es seien beide Bedingungen erfüllt. Es bleibt zu zeigen, daß es k intern disjunkte f-Wege von u nach v gibt, wenn u und v f-benachbart sind. Es werde angenommen, daß die maximale Zahl intern disjunkter f-Wege von u nach v gleich k 0 < k ist. Sei G0 der Graph, der entsteht, wenn man den Bogen von u nach v bzw. die Kante zwischen diesen Knoten aus G entfernt. In G0 ist die Maximalzahl intern disjunkter f-Wege von u nach v dann k 0 − 1. Nach Satz 18.2 wird in G0 dann v von u durch k 0 − 1 Knoten f-getrennt. Da |U| ≥ k + 1, gibt es einen Knoten z ∈ U, der von u, v und den Trennknoten verschieden ist. In G0 f-trennen die Trennknoten entweder z von u oder v von z. Nehmen wir an, v werde von z f-getrennt. getrennt. Dann sind z und v weder in G−l noch in G f-benachbart. Nach Voraussetzung müßte es in G k intern disjunkte f-Wege von z nach v geben. In G0 gibt es jedoch höchstens k 0 − 1 solcher Wege, also in G höchstens k 0 − 1 + 1 < k und das ist ein Widerspruch. 2 Hilfssatz 18.1 Es sei U eine k-f-zusammenhängende Knotenmenge in einem gerichteten Kern G. Werden zu G ein neuer Knoten ṽ und neue Linien so hinzugefügt, daß ṽ mit k paarweise verschiedenen Knoten aus U f-verbunden ist, k paarweise verschiedene Knoten e einen gerichteten Kern aus U mit ṽ f-verbunden sind und auch der erweiterte Graph G bildet, so ist U ∪ {ṽ} in ihm k-f-zusammenhängend. Beweis: Analog zum Beweis von Hilfssatz refkanten.hilf. 2 Auch die beiden folgenden Korollare sind wie bei Hilfssatz reffkanten.hilf zu beweisen. Korollar 1 Es sei U eine k-f-zusammenhängende Knotenmenge im gerichteten Kern G. Weiter sei W ⊂ U eine Menge von k Knoten und v ∈ U \ W . Dann gibt es k intern disjunkte f-Wege von v zu paarweise verschiedenen Knoten aus W und k intern disjunkte f-Wege von paarweise verschiedenen Knoten aus W zu v. 452 KAPITEL 18. ERGÄNZUNGEN ZUR GRAPHENTHEORIE * Korollar 2 Es sei U eine k-f-zusammenhängende Knotenmenge im gerichteten Kern G. Weiter seien W1 und W2 disjunkte Untermengen von U mit jeweils k Knoten. Dann gibt es k knotendisjunkte f-Wege, die in Knoten von W1 beginnen und in Knoten von W2 enden und k knotendisjunkte f-Wege, die in Knoten von W2 beginnen und in Knoten von W1 enden. Achtung: Auch hier entsprechen die Anfangs- und Endpunkte der „Hinwege“ im allgemeinen nicht denen der „Rückwege“. Die beiden folgenden Hilfssätze entprechen den Hilfssätzen refweakknotenalt.hilf und refweakknoten1.hilf. Die Beweise verlaufen analog. Hilfssatz 18.2 Es U eine k-f-zusammenhängende Knotenmenge in einem allgemeinen Graphen G. Ist v ∈ / U ein Knoten aus G, so ist U ∪ {v} k-f-zusammenhängend, wenn es k paarweise verschiedene Knoten in U gibt, die mit v direkt f-verbunden sind, und wenn es k paarweise verschiedene Knoten in U gibt, mit denen v direkt f-verbunden ist. Hilfssatz 18.3 Es sei U eine k-f-zusammenhängende Knotenmenge im gerichteten Kern G. Wir erweitern U um k neue Knoten. Wir fügen neue Linien so hinzu, daß von jedem neuen Knoten zu jedem anderen neuen Knoten eine Linie führt. Außerdem sollen soll von jedem neuen Knoten eine neue Linie zu einem Knoten aus U führen und jeder neue Knoten über eine neue Linie von einem Knoten aus U erreicht werden. Die Zielknoten und die Startknoten in U sollen jeweils paarweise verschieden sein. Schließlich soll der e ein gerichteter Kern bleiben. Dann ist die erweiterte Knotenmenge U e erweiterte Graph G in ihm k-f-zusammenhängend. Wir wollen nun die Propositionen reffeconn1.prop und reffeconn2.prop auf k-f-Zusammenhang übertragen. Es seien U und W Knotenmengen. Wir betrachten f-UW -Wege und benutzen die entsprechende Erweiterung des Satzes 18.2 (siehe Seite 441). Zur Erinnerung: Die Knoten aus U ∩ W werden als UW -Wege der Länge 0 aufgefaßt. Für k-f-Zusammenhang ergibt sich dann die folgende Proposition. Proposition 18.6 Es seien U und W k-f-zusammenhängende Knotenmengen in dem gerichteten Kern G. U ∪ W ist genau dann k-f-zusammenhängend in G, wenn es in jeder Richtung k disjunkte f-UW -Wege in G gibt. Beweis: Analog zum Beweis von Proposition 18.2. Man beachte, daß der Beweis für jede Richtung geführt werden muß. 2 Es sei U eine k-f-zusammenhängende Knotenmenge in G. U soll maximal heißen, wenn keine echte Obermenge von U in G k-f-zusammenhängend ist. Sind U und W maximale k-f-zusammenhängende Knotenmengen in G und ist |U ∩ W | ≥ k, so ist U ∪ W nach Proposition 18.6 k-f-zusammenhängend und wegen der Maximalität U = W . Das kann man 18.6. K-F-ZUSAMMENHANG 453 folgendermaßen interpretieren: Erweitert man in G eine k-f-zusammenhängende Knotenmenge schrittweise so, daß schließlich eine maximale k-f-zusammenhängende Knotenmenge entsteht, so ergibt sich unabhängig von der Reihenfolge der Erweiterungen stets die gleiche maximale Knotenmenge. Wird „eindeutig bestimmt“ in diesem Sinne aufgefaßt, so gilt der folgende Satz. Satz 18.9 In einem gerichteten Kern sind maximale k-f-zusammenhängende Knotenmengen eindeutig bestimmt. Es sollen jetzt k-f-zusammenhängende Untergraphen eines gerichteten Kerns betrachtet werden. Wir beginnen mit Proposition 18.5. Aus ihr ergibt sich unmittelbar die entsprechende Proposition für Untergraphen. Es seien H und I k-f-zusammenhängende Untergraphen eines gerichteten Kerns G. Dann sind V (H) und V (I) k-f-zusammenhängende Knotenmengen in H + I. Gilt V (H) ∪ V (I) ≥ k, so ist nach Proposition 18.6 auch V (H) ∪ V (I) = V (H + I) k-f-zusammenhängend in H + I. D. h. H + I ist ein k-f-zusammenhängender Untergraph. Sind die k-a-zusammenhängenden Untergraphen H und I maximal und haben sie einen gemeinsamen k-zusammenhängenden Untergraphen, so ist also H = I. Mit der oben festgelegten Bedeutung von „eindeutig bestimmt“ ergibt sich so der folgenden Satz. Satz 18.10 In einem gerichteten Kern sind k-f-zusammenhängende Untergraphen eindeutig bestimmt. Auch für maximale k-f-zusammenhängende Untergraphen eines gerichteten Kerns G wollen wir eine eigene Bezeichnung einführen. Definition 18.6 Ein maximaler k-f-zusammenhängender Untergraph eines gerichteten Kerns wird k-f-Komponente (k-f-component) des Kerns genannt. Sind H und I k-f-zusammenhängende Untergraphen, so kann H + I auch dann k-f-zusammenhängend sein, wenn H und I weniger als k gemeinsame Knoten aufweisen. Die Anwendung von Proposition 18.6 erfordert in H + I mindestens k disjunkte f-V (H)V (I)Wege in jeder Richtung. Um das zu untersuchen, führen wir zwei weitere Begriffe ein. Sind H und I Untergraphen eines gerichteten Kerns, so wird mit ap(H, I) die Menge der gemeinsamen Knoten von H und I bezeichnet. aed(H, I) bezeichnet die Menge der Linien, die von H nach I führen, aber nicht mit einem Knoten aus ap(H, I) inzidieren. Die Menge entsprechender Linien von I nach H wird mit aed(I, H) bezeichnet. Schließlich ist β0 (K) wieder die maximale Anzahl von Linien der Menge K die paarweise keine gemeinsame Inzidenzpunkte haben. Dann gilt die folgenden Proposition. Proposition 18.7 Es sei G ein gerichteter Kern und H und I k-f-zusammenhängende Untergraphen. Dann ist H + I genau dann k-f-zusammenhängend, wenn |ap(H, I)| + β0 (aed(H, I)) ≥ k und |ap(H, I)| + β0 (aed(I, H)) ≥ k gilt. 454 KAPITEL 18. ERGÄNZUNGEN ZUR GRAPHENTHEORIE * Beweis: Analog zum Beweis von Proposition 18.4. 2 Jeder k-f-zusammenhängende Untergraph ist auch (k − 1)-f-zusammenhängend. Jede k-fKomponente ist also in einer (k − 1)-f-Komponente enthalten. Eine (k − 1)-f-Komponente C enthält 0 oder mehr k-f-Komponenten. Ist keine k-f-Komponente vorhanden, so endet mit C die Zerlegung in f-Komponenten. Anderenfall gibt es in C k-f-Komponenten und eventuell auch Linien, die zu keiner k-f-Komponente gehören. Der von diesen Linien erzeugte Untergraph wird k-f-Kokomponente (k-f-cocomponent) von C genannt. Anders als bei Kokantenkomponenten kann die k-f-Kokomponente von C selbst dann leer sein, wenn es in C mehrere k-f-Komponenten gibt. k-f-Komponenten sind zwar eindeutig bestimmt, aber nicht notwendigerweise disjunkt. Sie können bis zu k − 1 Knoten gemeinsam haben und haben dann wegen der Maximalität auch alle Linien, die gemeinsame Knoten verbinden, gemeinsam. Die auf den Seiten 447ff aufgeführten Sonderfälle und Beispiele lassen sich entsprechend auf f-Komponenten übertragen. k-a-Linienzusammenhang und k-f-Linienzusammenhang Auch hier wollen uns erst einmal klarmachen, was kleine Werte von k bedeuten. Es sei also G ein allgemeiner Graph mit mindestens zwei Knoten. a. G ist 0-a-linienzusammenhängend (0-f-linienzusammenhängend). b. G ist genau dann 1-a-linienzusammenhängend (1-f-linienzusammenhängend), wenn G schwach (stark) zusammenhängend ist. c. G ist genau dann 2-a-linienzusammenhängend, wenn G eine Subkomponente ist. Siehe Kapitel 16. Für f-Wege haben wir hier keine besondere Bezeichnung. k-a-linienzusammenhängende Knotenmengen Wir wollen zuerst k-a-linienzusammenhängender Knotenmengen untersuchen. Das wichtigste Hilfsmittel ist Satz 18.3. Aus ihm kann man einige nützliche Folgerungen ziehen. Besonders wichtig sind die nachfolgende Proposition und der sich unmittelbar daraus ergebende Satz. Proposition 18.8 Es seien U und W k-a-linienzusammenhängende (k ≥ 1) Knotenmengen im allgemeinen Graphen G. Gilt U ∩ W 6= ∅, so ist auch U ∪ W k-a-linienzusammenhängend in G. Beweis: Wir zeigen, daß in G von jedem u ∈ U \W zu jedem w ∈ W \U k liniendisjunkte a-Wege existieren. Es sei z ∈ U ∩ W . k 0 Linien mit k 0 < k können in G weder u noch v von z linientrennen. Sie können also in G auch nicht u von v trennen. Nach Satz 18.3 gibt es dann aber k liniendisjunkte a-Wege von u nach v. 2 Nennen wir eine k-a-linienzusammenhängende Knotenmenge maximal, wenn keine echte Obermenge k-a-linienzusammenhängend ist, so ergibt sich aus Proposition 18.8 unmittelbar der folgende Satz. 18.6. K-F-ZUSAMMENHANG 455 Satz 18.11 Maximale k-a-linienzusammenhängende Knotenmengen in einem allgemeinen Graphen sind eindeutig bestimmt. Die Vereinigung zweier k-a-linienzusammenhängender Knotenmengen eines allgemeinen Graphen kann in diesem auch dann k-a-linienzusammenhängend sein, wenn die Mengen disjunkt sind. Dazu die folgenden Proposition. Proposition 18.9 Es seien U und W disjunkte, k-a-linienzusammenhängende Knotenmengen in einem allgemeinen Graphen G. Dann und nur dann wenn U und W über mindestens k liniendisjunkte a-Wege verbunden sind, ist auch U ∪ W k-a-linienzusammenhängend in G. Beweis: Gibt es keine Verbindung über k liniendisjunkte a-Wege, so ist U ∪ W nicht k-a-linienzusammenhängend in G. Wenn es k liniendisjunkte a-Wege gibt, die U und W verbinden, führt die Annahme, daß u ∈ U durch k 0 < k Linien von w ∈ W getrennt wird, zu einem Widerspruch. In der Tat: Mindestens einer der k verbindenden Wege enthält keine Trennlinie. Sei u0 ∈ U sein Anfangspunkt und w 0 ∈ W sein Endpunkt. u kann von u0 nicht durch k 0 Linien a-getrennt werden. Das gleiche gilt für w und w 0 . Es gibt also stets einen a-Weg von u nach w. 2 k-a-linienzusammenhängende Untergraphen Wir betrachten nun k-a-linienzusammenhängende Untergraphen H und I eines allgemeinen Graphen G. Im Untergraphen H + I 2 ist sowohl die Knotenmenge V (H) als auch die Knotenmenge V (I) k-a-ilinienzusammenhängend. Gilt H ∩ I 6= ∅, so ist nach Proposition 18.8 auch V (H) ∪ V (I) = V (H + I) in H + I k-a-linienzusammenhängend. Es gilt also analog zu Proposition 18.8 die folgende Proposition. Proposition 18.10 Es seien H und I k-a-linienzusammenhängende (k ≥ 1) Untergraphen eines allgemeinen Graphen G. Gilt H ∩ I 6= ∅, so ist auch H + I ein k-alinienzusammenhängender Untergraph von G. Unter einem maximalen k-a-linienzusammenhängenden Untergraphen von G soll ein Untergraph verstanden werden, für den jede Erweiterung nicht mehr k-a-linnienzusammenhängend ist. Proposition 18.10 ergibt dann den folgenden Satz Satz 18.12 Maximale k-a-linienzusammenhängende Untergraphen eines allgemeinen Graphen sind eindeutig bestimmt. Für maximale k-a-linienzusammenhängende Untergraphen eines allgemeinen Graphen G wollen wir eine eigene Bezeichnung einführen. 2 H + I ist der Untergraph von G, der durch die Knotenmenge V (H) ∪ V (I) erzeugt wird. 456 KAPITEL 18. ERGÄNZUNGEN ZUR GRAPHENTHEORIE * Definition 18.7 Ein maximaler k-a-linienzusammenhängender Untergraph eines allgemeinen Graphen wird k-a-Linienkomponente (k-a-linecomponent) des Graphen genannt. Der Untergraph H + I kann auch dann k-a-linienzusammenhängend sein, wenn H und I disjunkt sind. Es gilt die folgenden Proposition. Proposition 18.11 Es seien H und I disjunkte, k-a-linienzusammenhängende Untergraphen eines allgemeinen Graphen. Sind H und I über mindestens k Linien direkt verbunden, so ist auch H + I k-a-linienzusammenhängend. Beweis: siehe Stiege [Stie2006]. 2 Beispiel 18.2 Proposition 18.11 ist weniger weitreichend als Proposition 18.9. Zwei disjunkte, k-a-linienzusammenhängende Untergraphen H und I können sehr wohl durch k oder mehr liniendisjunkte a-Wege verbunden sein, ohne einen gemeinsamen k-a-linienzusammenhängenden Obergraphen zu haben. Das gilt selbst dann, wenn alle Knoten mindestens den Grad k haben. Abbildung 18.1 zeigt ein Beispiel. Der Graph ist ungerichtet, .. . . . . . . . . . . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . .. . . . . ..... ............. .............. .............. . ............... . . . . . . . . . . . . ...... ....... .. .. ... ..................................... .. .. ............ ... ..................................... . ... . ...................... . ................. ...................... ........................ . . . . . . . . . . . . . . . . . . . . . .... ........ ..... ...... ..... .... ...... ...... ...... ..... ............... . . ..... ..... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ...... ... ... ... .... .. . . ... ... ..... ..... ... .... .... . .. .. .... .. ..... . . . . . . . . . . . . . . . . . . . . . . .... ... . .. .. ... . . ...... .. . . . . . . . . . . . ... ... . . .................... . .. . .... .... . . . . . . . . . . ... ... ... . . . ... ... ... . .... . . . . . ... ... .. .... ... . . ... ... .. ... . ... ...... ........ . .. ... . . . . . . . . .... . . . . ... ... . ... ... .. ... . . ... . . ... ... ... .... . ... . ... ..... . . ...... ...... ...... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ... . . . . . . . . . . . . ... . ... ..... ... ..... . .... ...... .... ..... .... ...... .... ...... .... ..... .... ...................... . . .... ..................................... ..................................... .. ..................... .... . . . . . . . . . . . . . . . . . . ...................... . . ... . . . . . . . . ... ... ... ... ... . . . . . . . .... .... .... ..... . . . . . . . . . . . ... . . . . . . . . . . . . . . . . . . . . . . . . .......... ... ........... ........... .......... ........... . ........... . . ... ... . . ... .... ... .... .... .. .. .. .. . . .... . ... ... .. . .. ... ... ... ... .... .... ... ... . . .. .... .... ...... ... . .... .... ... .. . . . . . . . . . . . . . . . . . . . . . . . . . ...... ..... ... . . ... . . .. . . . . . ... . . . . . . . . .. ... . . . . . . . . . ... . . . . . . . . ... ... . . . . . . . ... . . . . . . . . . . . . . . . ............ ... ... .. . . .. .... .... .... . ... .... ... .... . . . . . . . . . . . . . . . ..... . . ... . . . . . . . ...... ..... . . . . . . . . ......... . .............. .............. .............. .............. .............. ........ ... . . . . . . . . . . . . . . . . . . . .. .. .. ... .. ..................... .................. .. . . . . . . . . . . . . .......... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ... ... ... ... . . .. . . . ... ... . ..... ........ ...... ........ ...... ........ ................. .................. . . . . . . . . . . . . . . . . . . . . . . .... . . .. . . . . 1 2 . . . .... . . . ... . . . . . . . .... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .... ........... ....... ..... ...... ... ..... . ... ................. H H Abbildung 18.1: Ein Graph mit zwei 3-a-Linienkomponenten, die durch drei liniendisjunkte a-Wege verbunden sind 2-a-linienzusammenhängend und hat Minimalgrad 3. Die Untergraphen H1 und H2 sind 3-a-Linienkomponenten und über drei Liniendisjunkte a-Wege verbunden. Der Graph ist jedoch nicht 3-a-linienzusammenhängend, denn die zwischen H1 und H2 liegenden Untergraphen werden vom Rest durch 2 Linien getrennt. 2 18.6. K-F-ZUSAMMENHANG 457 Hierarchische Zerlegung nach a-linienzusammenhängenden Untergraphen Jeder k-a-linienzusammenhängende Untergraph eines allgemeine Graphen ist auch (k − 1)a-linienzusammenhängend. Jede k-a-Linienkomponente ist also in einer (k − 1)-a-Lnienkomponente enthalten. Eine (k − 1)-a-Linienkomponente EC enthält 0 oder mehr k-aLinienkomponenten. Ist keine k-a-Linienkomponente vorhanden, so endet mit EC die Zerlegung in a-Linienkomponenten. Anderenfalls gibt es in EC k-a-Linienkomponenten und eventuell auch Linien, die zu keiner k-a-Linienkomponente gehören. Der von diesen Linien erzeugte Untergraph wird die k-a-Kolinienkomponente (k-a-colinecomponent) von EC genannt. Gibt es in EC genau eine k-a-Linienkomponente, so kann die k-aKolinienkomponente von EC leer sein und EC ist auch k-a-Linienkomponente. Es kann aber auch eine nichtleere k-a-Kolinienkomponente geben, z. B wenn EC Knoten von einem kleineren Gesamtgrad als k enthält. Gibt es in EC mindestens zwei k-a-Linienkomponenten, so sind diese nach Proposition 18.10 disjunkt. In EC gibt es jedoch Wege zwischen ihnen. Das bedeutet, daß es in diesem Fall in EC Linien geben muß, die zu keiner k-aLinienkomponente gehören. Die k-a-Linienkomponente von EC ist in diesem Fall nicht leer. Wie Beispiel 18.2 zeigt, können die k-a-Linienkomponenten durch k liniendisjunkte a-Wege, die in der k-a-Kolinienkomponenten verlaufen, verbunden sein. Nach Proposition 18.11 dürfen aber nicht alle verbindenden Wege die Länge 1 haben. f-Linienzusammenhang f-Linienzusammenhang ist völlig analog zu a-Linienzusammenhang. Zu beachten ist, daß es k liniendisjunkte f-Wege in jede der beiden möglichen Richtungen geben muß. Maximale k-f-linienzusammenhängende Untergraphen heißen k-f-Linienkomponenten. In hierachischen Zerlegungen gibt es auch die k-f-Linienkokomponente (k-f-linecocomponent) einer k-f-Linienkomponente. schlichten Graphen betrachtet werden. Wir beginnen mit Proposition 18.1. Aus ihr ergibt sich unmittelbar die entsprechende Proposition für Untergraphen. Die Zerlegung in Abbildung reff-hiconn ist schematisch. Im folgenden soll an Beispielen gezeigt werden, daß es reale Graphen gibt, die diesem Schema genügen. Wir wollen dabei nur die kritischen Teile eines Gesamtgraphen konstruieren. Wir wählen k = 4. Abbildung reff-4connecta zeigt die 3-a-Komponenten C1 . Sie enthält die Untergraphen C11 , C12 , C13 , die wir als K5 und damit 4-a-zusammenhängend annehmen wollen. Aus Gründen der Übersichtlichkeit ist die interne Struktur dieser Untergraphen nicht eingezeichnet. Es sind nur die Knoten zu sehen, die mit externen Kanten inzidieren. C11 , C12 und C13 sind 4-a-Komponenten, denn sie sind jeweils durch 3 Knoten voneinander getrennt. Als ganzes ist C1 4-a-kantenzusammenhängend und deshalb ist es vollständig in einer 4-a-Kantenkomponente, z. B. EC1 , enthalten. Als nächstes untersuchen wir die 3-a-Komponente C4 . Sie darf keine 4-a-Komponenten enthalten, soll aber 4-a-kantenzusammenhängend sein. Abbildung reff-4connectc zeigt einen Untergraphen, der diese Eigenschaften hat. Er muß deshalb in einer 4-a-Kantenkomponente enthalten sein. Wir nehmen an, er hat genau zwei Knoten mit C13 gemeinsam und gehört 458 KAPITEL 18. ERGÄNZUNGEN ZUR GRAPHENTHEORIE * deswegen zu EC1 . In Abbildung reff-4connectb ist die 3-a-Komponente C2 zu sehen. Sie enthält die 4-aKomponenten C21 und C22 , die K5 sind und durch gepunktete Linien angegeben werden. Sie sind über die drei explizit eingezeichneten Knoten und externe Kanten miteinander verbunden. Sie gehören also zur gleichen 3-a-Komponente, aber zu verschiedenen 4-aKantenkomponenten. Um zu erzwingen, daß C21 und C13 disjunkt sind, aber in die gleiche 4-a-Kantenkomponente fallen, wird auch C21 über genau zwei Knoten mit C4 verheftet. Diese werden paarweise verschieden von den Knoten gewählt, mit denen C13 und C4 verheftet sind. Dann sind C13 und C21 nicht nur disjunkt, sondern gehören auch zu verschiedenen 3-a-Komponenten. Für die 3-a-Komponente C3 aus Abbildung reff-hiconn wird keine eigene Abbildung angegeben. C3 kann auf die gleiche Weise wie C1 in die 4-a-Komponenten C31 und C32 zerlegt werden. Die Überlappung von C3 und C2 kann erreicht werden, indem beide genau einen Knoten gemeinsam haben, der mit je drei Kanten mit dem Rest von C3 bzw. dem Rest von C2 verbunden ist. H + I auch dann k-f-zusammenhängend sein, wenn H und I weniger als k gemeinsame Knoten aufweisen. Die Anwendung von Proposition 18.6 erfordert in H + I mindestens k disjunkte f-V (H)V (I)-Wege in jeder Richtung. Um das zu untersuchen, führen wir zwei weitere Begriffe ein. Sind H und I Untergraphen eines gerichteten Kerns, so wird mit ap(H, I) die Menge der gemeinsamen Knoten von H und I bezeichnet. aed(H, I) bezeichnet die Menge der Linien, die von H nach I führen, aber nicht mit einem Knoten aus ap(H, I) inzidieren. Die Menge entsprechender Linien von I nach H wird mit aed(I, H) bezeichnet. Schließlich ist β0 (K) wieder die maximale Anzahl von Linien der Menge K die paarweise keine gemeinsame Inzidenzpunkte haben. Dann gilt die folgenden Proposition. Aufgaben Aufgabe 18.1 Es sei eine Knotenmenge V mit n = |V | Elementen und k ≥ 1 gegeben. Bilden Sie damit einen allgemeinen Graphen, einen ungerichteten Graphen bzw. einen Digraphen, der wahlweise k-a-kantenzusammenhängend oder k-f-kantenzusammenhängend ist. Wann müssen dazu Mehrfachverbindungen zwischen Knoten eingeführt werden? Welche Mehrfachverbindungen? Aufgabe 18.2 Es soll Definition refazus.def folgendermaßen geändert werden: Es sei G ein schlichter Graph mit mindestens k + 1 Knoten und in ihm U eine Knotenmenge von mindestens zwei Knoten. U soll k-a-zusammenhängend in G heißen, wenn je 2 verschiedene Knoten in G durch k intern disjunkte a-Wege verbunden sind. 18.7. PARTITIONEN, ANSTRICHE, FÄRBUNGEN 459 Geben Sie ein Beispiel, in dem U, W und U ∩ W 4-a-zusammenhängend sind, aber U ∪ W nicht. Aufgabe 18.3 Ändern Sie Abbildung reff-4connectb, Teil B, so ab, daß C13 und C22 – und dann natürlich auch C1 und C2 – nicht disjunkt sind. Literaturhinweise Grundlage dieses Kapitels sind die Berichte [Stie2001c] und [StieS2003]. Die Ergebnisse wurden überarbeitet und erweitert. Zu k-a-Zusammenhang und k-a-Kantenzusammenhang für Untergraphen ist einiges bei Harary [Hara1969] und insbesondere bei Jungnickel [Jung1994] zu finden. k-a-Zusammenhang und k-a-Kantenzusammenhang für Knotenmengen und allgemein f-Zusammenhang haben anscheinend kein Interesse in der Literatur gefunden. 18.7 Partitionen, Anstriche, Färbungen 18.8 Planarität 18.9 Optimumssuche in graphentheoretischer Betrachtungsweise Aufgaben Literatur Literatur zum Abschnitt „Korrespondenzen“ Wir haben Abschnitt 18.2 „Mengertheorie“ betitelt, weil der erste Beweis von Satz 18.1 von Karl Menger [Meng1927] stammt. Mengers Aufsatz ist jedoch der allgemeinen Kurventheorie zuzuordnen und topologischer Natur. Der Zusammenhang mit der graphentheoretischen, kombinatorischen Fragestellung von Satz 18.1 erschließt sich nur schwer. In der Folgezeit hat es viele, zum Teil recht unterschiedliche Beweise des Satzes gegeben. Beispiele sind bei Dirac [Dira1966], Harary [Hara1969], Sachs [Sach1970], Tutte [Tutt1984], Thulasiraman/Swamy [ThulS1992] zu finden. Allein Diestel [Dies2000] bringt drei Beweise. Ein sehr schöner und klarer Beweis stammt von Gallai [Grun1938], siehe auch Wagner 460 KAPITEL 18. ERGÄNZUNGEN ZUR GRAPHENTHEORIE * [Wagn1970]. Dieser hat einen eindeutig algorithmischen Aufbau und diente als Vorlage für den Beweis und Algorithmus in Stiege [Stie2006]. Trotz der formalen Ähnlichkeit mit Knotentrennmengen wurden Linientrennmengen erst wesentlich später betrachtet und Satz 18.3 erst in den 50-er Jahren des vorigen Jahrhunderts bewiesen: Ford/Fulkerson [FordF1956], Elias/Feinstein/Shannon [EliaFS1956]. Die Übertragung der Mengersätze von a-Wegen auf f-Wege ist sehr einfach und wird in der Literatur kaum besprochen. werden auch in Harary [Hara1969] und Diestel [Dies2000] behandelt. Zu Mengerstrukturen, wie sie auf Seite 442 kurz angesprochen wurden, siehe Stiege [Stie2006]. Kapitel 19 Kürzeste Wege in Netzwerken * Sind in einem allgemeinen Graphen die Knoten und/oder die Linien mit zusätzlichen Eigenschaften versehen, so spricht man von attributierten Graphen (attributed graph) oder Netzwerken (network). Die Attributwerte sind häufig Zahlen. Manchmal sind es auch Zeichenreihen, Farben oder andere nichtnumerische Werte. Auch zusammengesetzte Werte, z. B. Zahlenpaare, können auftreten1 . Im diesem Kapitel wird ein wichtiges Gebiet, nämlich kürzeste Wege in Netzwerken ausführlich behandelt. Flüsse, ein weiteres wichtiges Gebiet der Netzwerktheorie, werden im nächsten Kapitel besprochen, allerdings als Ergänzuung. Schließlich werden weitere Fragestellungen in Netzwerken beispielhaft im Kapitel danach untersucht. 19.1 Gewichtete Wege Wir gehen von einem allgemeinen Graphen G aus. Jeder Linie l ist eine Gewicht (weight) w(l) zugeordnet. w(l) ist eine reelle Zahl, manchmal wird der Wertebereich auf ganze Zahlen oder natürliche Zahlen eingeschränkt. Das Gewicht eines Weges (weight of a path) wird definiert als die Summe der Gewichte der Linien des Weges. Weiter seien s und t zwei verschiedene Knoten in G und es sei t von s a-erreichbar bzw. f-erreichbar. Wir suchen a-Wege bzw. f-Wege minimalen Gewichts von s nacht t. Obwohl auch negative Gewichte zugelassen sind und deswegen das Gewicht eines Weges nicht ohne weiteres als eine Entfernung angesehen werden kann, ist es allgemein üblich, Wege minimalen Gewichts als kürzeste Wege (shortest path) zu bezeichnen. Auch wir wollen diese Bezeichnung benutzen. Zunächst einmal kann man sich klarmachen, daß eine Beschränkung auf einfache Wege sinnvoll ist. Bei nicht einfachen Wegen existiert ein geschlossener Teilweg. Hat dieser ein nicht-negatives Gewicht, so kann er bei der Suche nach kürzesten Wegen unberücksichtigt bleiben. Hat er ein negatives Gewicht, so kann man mit mehrfachem Durchlaufen des Der Begriff Netzwerk wird oft in einem engeren Sinne benutzt. Als Attribute sind dann nur Zahlen zugelassen. Manchmal wird zusätzlich vorausgesetzt, daß es sich um einen Digraphen handelt. 1 461 462 KAPITEL 19. KÜRZESTE WEGE IN NETZWERKEN * Teilweges Wege mit negativem Gewicht beliebig großen Betrages finden. Es gibt dann keinen kürzesten Weg. Bei der Beschränkung auf einfache Wege gibt es natürlich immer mindestens einen kürzesten Weg von s nach t. Bei der Bestimmung eines solchen taucht aber eine weitere Schwierigkeit auf. Wenn es ein effizientes, also polynomielles Verfahren zur Bestimmung kürzester Wege in allgemeinen Graphen gäbe, könnte man es auf simple Graphen anwenden, deren Kanten sämtlich das Gewicht -1 haben. Ein kürzester einfacher Weg ist dann natürlich ein längster einfacher Weg (ohne Gewichte). Dieser ist genau dann, wenn seine Länge gleich der Anzahl Knoten des Graphen ist, ein Hamiltonweg. Indem wir das Verfahren auf alle s und alle t (s 6= t) anwenden, hätten wir einen polynomiellen Algorithmus zu Lösung eines N P-vollständigen Problems. Wir sollten also nicht nach einem Algorithmus suchen, der in allgemeinen Graphen mit beliebigen Liniengewichten kürzeste Wege findet. Für wichtige Teilklassen von Gewichtungen und Teilklassen von Graphen gibt es jedoch effiziente Algorithmen. Wir wollen nun die Aufgabe „Finde einen kürzesten Weg“ präzisieren. Es sei also G ein allgemeiner Graph mit Liniengewichten. s und t seien Knoten von G, s 6= t und t von s erreichbar. 1. SPSP-Problem: s und t sind fest gegebene Knoten. Gesucht werden kürzeste Wege von s nach t. Die Bezeichnung bedeutet “single-pair shortest-path”. 2. SSSP-Problem: s ist ein fest gegebener Knoten. Gesucht werden kürzeste Wege zu allen von s verschiedenen Knoten t. Die Bezeichnung bedeudet “single-source shortest-path”. 3. SDSP-Problem: t ist ein fest gegebener Knoten. Gesucht werden kürzeste Wege von allen von t verschiedenen Knoten s zu t. Die Bezeichnung bedeudet “singledestination shortest-path”. 4. APSP-Problem: s und t variieren. Gesucht sind kürzeste Wege von allen s zu allen t. Die Bezeichnung bedeutet “all-pairs shortest-path”. Es stellt sich heraus, daß die Lösung von Problem 1. im wesentlichen genau so aufwendig wie die Lösung von Problem 2 ist. Deswegen soll Problem 1 bis auf kurze Anmerkungen im folgenden nicht betrachtet werden. Im folgenden ist mit „kürzesten Wegen“ stets Problem 2 gemeint. Die Aufgabe, kürzeste Wege dieser Art zu finden, wird für nichtnegative Gewichte in Abschnitt 19.2 und für beliebige Gewichte in Abschnitt 19.3 untersucht. Problem 3 ist Problem 2, wenn man die Bogenrichtungen im Graphen umkehrt, und wird deswegen nicht weiter betrachtet. Problem 4 wird kurz in Abschnitt 19.4 behandelt. Die folgende wichtige Aussage ist unmittelbar einzusehen: Ist ein kürzester Wege von s nach t gegeben und t0 ein innerer Punkt dieses Weges, so ist das Anfangsstück bis t0 ein 19.2. KÜRZESTE WEGE MIT NICHTNEGATIVEN GEWICHTEN 463 kürzester Weg von s nach t0 . Außerdem läßt sich sich jeder kürzeste Weg von s nach t0 durch das Endstück des gegebenen Weges zu einem kürzesten Weg von s nach t ergänzen. Es kann viele kürzeste Wege von s nach t geben. Wir wollen es als eine Lösung ansehen, wenn wir das Gewicht dieser Wege kennen und einen davon explizit bestimmt haben, also verfolgen können. 19.2 Kürzeste Wege mit nichtnegativen Gewichten Wir wollen mit wp(t) das Gewicht eines kürzesten Weges von s nach t bezeichnen und wp(s) := 0 setzen. Die auftretenden Gewichte sollen der Größe nach geordnet werden: α0 < α1 < · · · < αk . Es kann nicht mehr minimale Gewichte als Knoten geben, also ist k ≤ n, und es ist möglich, daß der Wert α0 = 0 mehrfach auftritt. Auf einen Vorschlag von Dijkstra 2 [Dijk1959] geht der folgende Lösungsansatz zurück: Wir starten mit s und sammeln schrittweise in einer Knotenmenge U alle Knoten, deren kürzeste Wege zu s mit den zugehörige Minimalgewichte schon bestimmt sind. In einer zweiten Kontenmenge W sammeln wir alle Knoten, die von einem Knoten aus U in einem Schritt erreichbar sind, aber nicht zu U gehören. Diese Knoten erhalten damit eine vorläufige Weglänge. Danach übertragen wir im nächsten Schritt aus W die Knoten mit kürzester Weglänge nach U. In einem knotenspezifischen Feld minweg wird ein Rückwärtsverweis zum Vorgänger auf dem kürzesten Weg eingetragen. Der Algorithmus von Dijkstra läuft nun folgendermaßen: 1. Es wird U0 = {s} und s->minweg := NULL gesetzt. 2. Alle von s in einem Schritt erreichbaren Knoten werden zu W hinzugefügt. Für sie wird in minweg ein Verweis auf s eingetragen. mingew = w(l) 19.3 Kürzeste Wege mit beliebigen Gewichten 19.4 Kürzeste Wege zwischen allen Paaren Edsger Wybe Dijkstra, ∗1930 Rotterdam, †2002 Nuenen. Niederländischer Informatiker. Außer dem Algorithmus zur Bestimmung kürzester Wege stammen der Begriff structurierte Programmierung (siehe Unterabshcnitt 5.1.2, Seite 155), ein grundlegender Artikel über das Programmieren mit goto und die Einführung von Semaphoren von ihm. 2 464 KAPITEL 19. KÜRZESTE WEGE IN NETZWERKEN * ' DIJKSTRA(s) /* Es werden die kuerzesten Wege von s zu allen anderen */ /* Knoten des Graphen gefunden */ /* U und V sind Knotenmengen, die anfangs leer sind */ 1 s in U einfügen; 2 DIJKNGHB(s); // Nachbarn von s in W einfügen 3 while (W 6= ∅) 4 { d = minimaler Entfernungswert in W ; 5 for all (t ∈ W ) 6 { if (t− > distanz == d) 7 { t aus W in U übetragen; 8 DIJKNGHB(t); // Nachbarn von t in W einfügen 9 } 10 } & Tabelle 19.1: Algorithnmus DIJKSTRA zur Bestimmung kürzester Wege in allgemeinen Graphen $ % 19.4. KÜRZESTE WEGE ZWISCHEN ALLEN PAAREN 465 ' $ DIJKNGHB(v) 1 2 3 4 5 6 7 8 9 10 11 12 13 // Alle Nachbarn von v, die noch nicht in W enthalten sind, // werden in W eingfuegt. // Ihnen wird eine vorläufige kuerzeste Entfernung zu s // und ein vorläufiger Vorgaengerknoten zugeordnet. // Bei allen Nachbarn von v, die schon in W enthalten sind, // wird gegebenenfalls die kuerzeste Entfernung und // der Vorgaengerknoten aktualisiert VERTEX ∗u; for alle (Linien l inzident mit v) { if (l markiert) return; markiere l; u = otherside(l, v); if (u ∈ U) return; if (u ∈ W ) { if (v->distanz + wg(l) < u->distanz) { u->distanz = v->distanz + wg(l); { t aus W in U übetragen; NF LGR(t); } } & Tabelle 19.2: Algorithnmus DIJKSTRA zur Bestimmung kürzester Wege in allgemeinen Graphen % 466 19.5 KAPITEL 19. KÜRZESTE WEGE IN NETZWERKEN * Kürzeste Wege unter Berücksichtigung der Graphstruktur Die Struktur eines allgemeinen Graphen kann bei der Bestimmung von kürzesten Wegen von Nutzen sein. Anhand eines einfachen Beispiels soll das erläutert werden. In Abbildung 19.1 1 1 ........ ........ ........ ..... ........ ..... ........ ...... ....... .. .. ...................................................... . ... .... . ... 3 ....................................................... 3 .... ... 3..... . .... .... . .. . .................. . ............. .................. . ... .... . .. ... ... .. ... ... ... ... .... ... ... ... .... ... ... ... . ... ... ... . .. ... . ... ... . ... .. . . ... ... ... ... ... . ... ... . ... .. . ... . ... ... .. ... . ... ... . .... .. . ... ... . ... .. . ... ... ... . .. ... . ... ... . ... ..................... ... ... ... . . ... ... . . .. .... ... .. ... ... 3 ... ... ... .................... . ... ... ... .. . ... . ... ... .. . . ... ... . . ... .. . . ... ... . ... .. . . ... ... . . ... .. . . ... ... . ... ... . . ... ... ... ... ... ... . ... ... ... ... ... . ... .. ... . ... ... . ... ... ... ... . ... .. . . ... ... . . ... .. . . .. . ... ... . .... .... .. ..... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .... .... .... .... .. ........... .................... .. .. .. . . ... . . . . . . .. . . . ... . . .... . .. . . ........................................................................................................................... .. . ......................................................................................... . ... ... 2 ........................................................ 2 ... ... 4 ........................................................ 4 ........................................................ .. 3 ... 4 .... . . . . . . . . . . . ...... ..... ...... ........ ...... ..... ...... ...... ....... ....... .................. ........ ....... ........ ...... .. .......... ... . . . . . . . ... ... ... .... .... ... ... .... .... .... ... .. ... .. .. ... .. . . . . .... .... ... .. ... . .. .. ... . .. .. ... . ... ... . ... .. .. ... . . .... . .. .. ... .. . ... . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . . . . . . . . . . . . . .. .... ....... .... ......... .... ....... .... ....... . . . . . . . . . . . . ... .. .. . . ... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .... . . . ... ... ... 6 .. .. .. . . . 2 ... 4 .... . . . .... . 2 .. .... ................. .. ................ ................. ....... ..... .. ... .. ... ............ . . . . . . . . . . ... . .. .... .... .... ... ... .... ... ... ... ... .... ... .. .. .... ... ... ... ... ... ... .. . . . ... . . . .. ... ... .. .. . ... . . . . .. ... ... . .. ... .. . . . . . .. . ... ... . . . . ..... . . . ... . . .. ... . . . . ... . . . .. .. ..... ... . . . . . . . . . . . . . . . . . . . . . . ...... .......... ...... ......... ...... ........... ...... ......... . . ....... .......... ....... ......... ....... .......... . . . . . . . . . . . . . . .. .. ... ... . .. . . . . . . .. .. . . . . . . . . . . . . . . . .... .................................................... .................................................. ... ....................................................................................... ... ..................................................... .. ... ... 2 .... 4....... 6 ... ... 5 .... ... 6 .... ... 2 ..... .... 2..... ....... ......... ........ ........ ........ .......... ........ ....... ................ ........ ............. ..... ... .... ... .... ...... ............... . . . . . . . ... ... . ..... .... .... ... .... ... ...... .... ... ... .... .... ...... ... ...... ... .... ... ... ... ...... . . . . ... . . ... . . . . ... .. .. ..... . ... . . . ... . . . .... .. .. .... ... . . . . . ... . . . .... .. .. ... .... . . . . . ... . . . .... .. .. ... ... . . . . . ... . . . .... ... .. .. .... . . . . . . . . . . .................... ............... . . ................. ................. ........................... . . . . . . . . . . ... ... . .. .. ... ... ... . . . . ..... .. ..... .......................................................................................... ... 6 ... ... ... 6 .... ... 5 ... ... 5 ... 1...... . .... ...... ...... ..... ...... ...... ...... .... ................. ........ ........ ........ ..... ....................... . . . . . .... .... ..... ... ..... .... ... ..... ... ..... ... ..... . ... . .. . . ... .... .. . . . . ... . ... . . . . . ... . ... .... . ... . . . ... ... .... . . . . . ... ... ... . . . ... . . ... .................. ......................... . . . . ... .. ... ..... ..... ... .. ... ... ... 1 ... . . 1 . ... ..... .... . . . . . . . ... ............. .... ............... . . . . ... ... . . . . ... . .... ..... ... . . . . ... ... .... . . . . ... ... ... . . . . ... . ... ..... ... . . . ... . ... .... . . . . ... ... ... . . . . . ... ....... . . ............. ......... . . . . . ...... ........ . .... . ... ..... ... 1 ............................................................................................................................................................ 1 .... . . .... . . . ................... ............... . x y 1 1 v 1 1 1 1 1 x 1 y 1 1 x 1 y y 1 1 1 x 1 1 1 x w u 1 t 1 1 y 1 1 z 1 w 1 1 z 1 1 w 1 v z 1 z z 1 1 v w 4 1 w v 0 v 1 x 7 1 4 y Abbildung 19.1: Graph3 mit Liniengewichten 1 z 19.5. KÜRZESTE WEGE UNTER BERÜCKSICHTIGUNG DER GRAPHSTRUKTUR467 wird ein ungerichteter Graph mit nichtnegativ bewerteten Kanten gezeigt. Gesucht sind kürzeste Wege von einem gegebenen Knoten V zu allen anderen Knoten. Ant Colony Optimazition für einen Biblock mit Kreisen negativen Gewichts 468 KAPITEL 19. KÜRZESTE WEGE IN NETZWERKEN * Kapitel 20 Ergänzung: Flüsse in Netzwerken * Alle Graphen in diesem Kapitel sind schlingenfrei. In Kapitel 19 haben wir Netzwerktheorie angewandt, um kürzeste Entfernungen zu bestimmen. Ein weiteres sehr wichtiges Gebiet der Netzwerktheorie sind Füsse. Dabei ist es durchaus sinnnvoll, sich die Linien als Röhren vorzustellen, durch die eine Flüssigkeit fließt. Die Menge, die durch eine Röhre fließt, ist variabel, aber durch eine feste Obergrenze, die Kapazität der Röhre, beschränkt, Wenn etwas fließt, muss es auch eine Richtung des Flusses geben. Wir werden das durch die Angabe „von welchem Inzidenzknoten / zu welchem Inzidenzknoten“ ausdrücken. In der entgegengesetzten Richtung haben wir den gleichen Fluß mit negativem Vorzeichen. Die Flüssigkeit muß an einer oder mehreren Stellen (d. h. Knoten) entspringen, den Quellen, und an einer oder mehreren Stellen verschwinden, den Senken. An allen anderen Stellen wird genaus so viel in einen Knoten hineinfließen, wie aus ihm herausfließt. Wenn es keine Quellen und Senken gibt, haben wir einen Rundfluß. Diese Dinge sollen nun exakt definiert werden. ~ soll die Menge der gerichteten Linien bezeichnet werden. Für eine Linie l mit Mit L den Inzdenzpunkten u und v werden die Richtungen (l, u, v, ) und (l, v, u) unterschieden. (l, u, v) bedeutet, daß das der Fluß von u nach v führt. Das soll auch gelten, wenn l ein Bogen ist und (l, u, v) entgegen der Bogenrichtung läuft. Damit kommen wir zu der folgenden Definition: Definition 20.1 In einem allgemeinen Graphen seien nichtnegative Kapazitäten cl für die Linien gegeben. ~ eine 1. Ein a-Fluß (a-flow) ist eine Abbildung, die jeder gerichteten Linien (l, u, v) in L reelle Zahl f (l, u, v) zuordnet mit den folgenden Eigenschaften f (l, u, v) = −f (l, v, u) (20.1) f (l, u, v) ≤ cl (20.2) 469 470 KAPITEL 20. ERGÄNZUNG: FLÜSSE IN NETZWERKEN * 2. Ein f-Fluß (f-flow) ist ein a-Fluß mit der Zusatzbedinung: Ist l ein Bogen und u sein Startpunkt, so muß gelten 0 ≤ f (l, u, v) (20.3) 2 Gleichung 20.3 besagt, daß f-Flüsse nur in Bogenrichtung fließen. Wir wollen uns mit Flüssen beschäftigen, die genau eine Quelle und genau eine Senke haben. In der folgenden Definition bedeuten I(s) die Menge der mit s inzidierenden Linien und os(l, v) der Inzidenzpunkt von l, der von v verschieden ist (“otherside”). Definition 20.2 Ein a-Fluß (f-Fluß) heißt Fluß mit Quelle (source) q und Senke (sink) t, wenn die Bedingungen X l∈I(s) f (l, s, os(l, s)) = g ≥ 0 X l∈I(t) f (l, t, os(l, t)) = −g X l∈I(v) erfüllt sind. (20.4) f (l, v, os(l, v)) = 0 (20.5) für alle v 6= s, t) (20.6) 2 Gleichungen 20.4 und 20.5 besagen, daß ein nichtnegativer Fluß g im Knoten S eintritt und im Knoten t das Netz verläßt. Gleichung 20.6 sagt aus, daß an allen anderen Stellen, genau so viel Fluß ein- wie austritt („Kirchhoffsche1 Regel“). Kirchoff, Gustav Robert ∗12. März 1824 in Königsberg (Preußen), †17. Januar 1887 in Berlin. Deutscher Physiker. Ab 1875 Professor für theoretische Physik in Berlin. Die Kirchhoffschen Regeln sind wesentlich in der Elektrizitätslehre der Physik und in der Elektrotechnik. Unter dem gleichen Namen werden sie auch in der mathematischen Flußtheorie benutzt. Kirchhoff entdeckte zusammen mit Bunsen mittels Spektralanalyse das Rubidium und das Caesium. Weiter stammen von Kirchoff das Gesetz zur Wärmestrahlung sowie Beiträge zu Plattentheorie der Mechanik. 1 Kapitel 21 Weitere Ergänzungen zu Netzwerken * Alle in diesem Kapitel angesprochenen Teilgebiete gelten als Ergänzungen. Die Behandlung ist überblicksartig und exemplarisch, in der Regel ohne Beweise. In den ersten drei Abschitten werden Optimierungsfragen in Netzwerken mit gewichteten Linien angesprochen. Der fünfte Abschnitt befaßt sich mit Aufgaben der Planungstheorie. Im sechsten werden im Lösungsraum einer lokalen Suche Graphstrukturen eingeführt. Die letzten beiden Abschnitte behandeln Netzwerke, die zur Stochastik bzw. zur Theoretischen Informatik gehören. 21.1 Minimale erzeugende Bäume Wir betrachten Netzwerke mit nichtnegativen, reellwertigen Linienbewertungen. Sie sollen schwach zusammenhängend und schlingenfrei sein. Dann führt jeder Ablauf einer a-Tiefensuche mit beliebigem Startknoten zu einem erzeugenden a-Baum. Gesucht sind Bäume dieser Art mit minimalem Gewicht. Eine Anwendung hierfür wäre Planung und Bau eines neuen Kommunikationsnetzes zwischen n Orten. Die Kosten für den Bau zwischen je zwei Orten seien bekannt. Das Netz soll so gebaut werden, daß man von jedem Ort direkt oder indirekt mit jedem anderen kommunizieren kann und daß die Kosten minimal werden. Zum Finden solcher Bäume gibt es einfache und effiziente Algorithmen, von denen. im folgenden drei vorgestellt. Danach wird auf minimale f-Bäume eingegangen und schließlich wird auch eine Erweiterung minimaler Bäume, die Steiner-Bäume, erwähnt. Algorithmus von Kruskal Die Linien des Netzwerks werden nach Bewertungen sortiert. Man startet mit einem Graphen, in dem nur isolierte Knoten vorhanden sind und betrachtet diese als (uneigentliche) schwache Zusammenhangskomponenten. Dann wählt man eine Linie mit minimalen 471 472 KAPITEL 21. WEITERE ERGÄNZUNGEN ZU NETZWERKEN * Gewichts aus. Die ausgewählte Linie und ihre Endknoten bilden eine neue (eigentliche) schwache Zusammenhangskomponente, in der die beiden ursprünglichen verschmolzen sind. Dann werden der sortierten Linienliste nacheinander alle Linien entnommen. Eine Linie, deren Endpunkte in der gleichen Zusammenhangskomponenten liegen, wird nicht berücksichtig. Liegen die Endpunkte der Linie in verschiedenen Zusammenhangskomponenten, so werden diese verschmolzen. Der Algorithmus endet, wenn zum ersten mal eine einzige Zusammenhangskomponente übrig bleibt. Da stets verschiedene Zusammenhangskomponenten durch eine hinzugefügte Kante verbunden werden, treten keine a-Kreise auf und es ergibt sich ein a-Baum. Daß dieser gewichtsminimal ist, kann durch vollständige Induktion bewiesen werden. Die Komplexität des Verfahrens ist bei einer einfachen Realisierung O(m ln n + n2 ). Zu Verfeinerungen und Beweisen siehe die Literaturangaben. Algorithmus von Prim Man startet mit einem markierten Knoten u. Alle anderen Knoten sind unmarkiert. Unter den Linien, die mit u inzidieren wählt man eine geringsten Gewichts und startet mit ihr einen a-Baum. Außerdem wird ihr zweiter Endpunkt markiert. Im weiteren wird stets unter den Linien, die eine markierten Knoten mit einem nicht markierten verbinden, eine geringsten Gewichts ausgewählt und zum Baum hinzugefügt. Ihr zweiter Endpunkt wird markiert. Das Verfahren endet, wenn alle Knoten markiert sind. Es ergibt sich ein minimaler erzeugender Baum. Der Aufwand ist O(m + n ln n) Bestimmung minimaler erzeugender Bäume durch lokale Suche Es sei ein erzeugender a-Baum für den Graphen gegeben. Wir bilden eine Liste aller Linien, die nicht zu ihm gehören. Die erste Linie dieser Liste fügen wir zu dem Baum hinzu. Es ergibt sich dadurch ein eindeutig bestimmter a-Kreis im Graph. Aus diesem entfernen wir eine Liste mit höchstem Gewicht, die wir im folgenden nicht mehr berücksichtgen. Wir erhalten einen (im allgmeinen verschiedenen) neuen erzeugender Baum. Wenn wir das nacheinander für alle Linien der Liste tun, ist das Resultat ein erzeugender Baum minimalen Gewichts. Die Komplexität des Verfahrens ist O(m2 ). Das Verfahren ist eine lokale Suche im Raum der erzeugenden Bäume des Graphen. Die Nachbarschaft eines Baumes besteht aus allen Bäumen, die sich um genau eine Linie von ihm unterschieden. Das Einfügen einer neuen Linie und anschließende Entfernen einer Linie mit mindestens dem gleichen Gewicht ist der Übergang zu einem „besseren Nachbarn“. Minimale erzeugende f-Untergraphen Bildet ein allgemeiner Graph z. B. Verkehrsverbindungen ab, so können Bögen nur in fRichtung durchlaufen werden. Ist ein stark zusammenhängender Graph gegeben, so kann 21.2. MINIMALE KORRESPONDENZEN 473 es sinnvoll sein, nach einer Linienmenge zu suchen, die minimales Gewicht hat und weiterhin gegenseitige f-Erreichbarkeit aller Knoten gewährleistet. Es ist nicht schwer, einen Gieralgorithmus hierfür anzugeben. Es wird eine absteigend nach Gewichten sortierte Liste der Linien gebildet. Es wird eine Linie nach der anderen überpuft, ob ein stark zusammenhängender Graph zurückbleibt, wenn sie gestrichen wird. Wenn ja, wird sie gestrichen. Wie man leicht sieht, bleibt nach Bearbeitung aller Linien ein stark zusammenhängender Graph übrig, dessen Linien minimales Gewicht haben. Der Aufwand besteht aus dem Sortieren der Linien und aus dem Überprüfen, ob ein stark zusammenhängender Graph übrig bleibt (Algorithmus STRONGCOMP, Proposition 15.4, Seite 392). Das ergibt O(m ln m + m(m + n)). Steinerbäume Manchmal wird in einem Graphen ein gewichtsminimaler a-Baum gesucht, der eine gegebene Menge U von Knoten und eventuell auch weitere des Graphen verbindet. Ein solcher Unterbaum wird Steinerbaum (Steiner tree) zu genannt. Die Menge S der zusätzlich hinzugefügten Knoten ist die Menge der Steinerknoten (Steiner vertex). Die Bezeichnung erfolgte nach Jakob Steiner 1 . Das Finden eines Steinerbaumes ist N P-vollständig. 21.2 Minimale Korrespondenzen Ungarischer Algorithmus2 21.3 Minimale Rundwege Wir betrachten Rundwege, also geschlossene Wege, in einem allgemeinen Graphen G. Die Wege können a-Wege oder f-Wege sein. Sie sollen durch jede Linie führen oder alle Knoten besuchen. Weiter legen wir ein Netzwerk mit nichtnegativen reellen oder natürlichen Zahlen als Linienbewertung zu Grunde und suchen Rundwege minimalen Gewichts. 21.3.1 Das Problem des chinesischen Briefträgers Werden Rundwege gesucht, die (mindestens einmal) durch jede Linie führen, so spricht man vom Problem des chinesischen Briefträgers (Chinese postman problem). Man stellt Jakob Steiner ∗18. März 1796 Utzendorf, Schweiz. † 1. April 1863, Bern, Schweiz. Schweizer Mathematiker, vorwiegend Geometer. Lebte lange in Berlin. 2 Kuhn hat seinerzeit den Namen „ungarischer Algorithmus“ gewählt, weil wesentliche Ideen von den ungarischen Mathematikern König und Egerváry stammen. Siehe hierzu [Fran2004] und auch http://www.cs.elte.hu/egres/tr/egres-04-14.pdf. 1 474 KAPITEL 21. WEITERE ERGÄNZUNGEN ZU NETZWERKEN * sich einen Briefträger3 vor, der ausgehend von einem festen Startpunkt alle Straßen seines Gebietes durchlaufen muß, um die Post zu verteilen Die entsprechenden Rundwege wollen wir Briefträgerwege (postman tour) nennen. Gesucht sind gewichtsminimale, also kürzeste, Briefträgerwege. Wir wollen für das Problem die Kurzbezeichnung CPP benutzen Auch die Bezeichnungen a-CPP und f-CPP wollen wir verwenden. Wir wollen für beide Fälle zunächst den graphentheoretischen Rahmen abstecken. Danach wird einiges zur Lösung des CPP angemerkt. a-CPP: Sinnvollerweise verlangen wir, daß der Graph schwach zusammenhängend ist. Als ersten Schritt bilden wir den den Biblockbaum des Graphen (siehe Anschnitt 16.5, Seite 409). Dann überlegen wir uns, wie für die einzelnen Knoten des Biblockbaumes (Biblöcke, interne Bäume, periphere Baume) ein minimaler Rundweg gefunden werden kann. Dabei wird auch festgelegt, wie bei Verheftungsknoten zu benachbarten BiblockbaumKnoten übergegangen wird. CCP-Wege in Bäumen. Man startet im Ausgangspunkt eine Tiefensuche und durchläuft jede Linie einmal in Hin- und einmal in Rückrichtung. Trifft man dabei auf einen Verheftungspunkt, der vom Startpunkt der Tiefensuche verschieden ist, so unterbricht man die Tiefensuche und bestimmt minimale Wege in den anderen Biblockbaum-Knoten, die am Verheftungspunkt hängen. CCP-Wege in Biblöcken. Ist der Biblock als Untergraph a-eulersch, so ist es einfach, einen a-Eulerkreis zufinden (siehe Abschnitt 14.8, Seite 360). Dieser ist ein minimaler Rundweg, da jede Linie genau einmal auftritt. Die Bildung des a-Eulerkreises wird an allen Verheftungspunkten unterbrochen und es werden zunächst minimale Rundwege in den anderen Biblockbaum-Knoten bestimmt. Der schwierigere und interessantere Teil beginnt, wenn der Biblock nicht a-eulersch ist. Dann muß es Knoten ungeraden Grades geben (Satz 14.7, Seite 361) und die Anzahl dieser Knoten ist gerade (Hilfssatz 12.1, Seite 321). Ein Rundweg durch alle Linien muß zwangsläufig einige Linien mehrfach durchlaufen. Es kommt darauf an, diese möglichst günstig zu wählen. Das kann man folgendermaßen erreichen: Sei X die Menge der Knoten ungeraden Grades in G. Zwischen je zwei Knoten x, y ∈ X führen wir eine neue Kante ein, auch dann wenn die Knoten schon durch eine oder mehrere Linien verbunden sind. Diese Kante erhält als Gewicht die Länge eines kürzesten a-Weges von x nach y. Durch eine geeignete Datenstruktur merkt man sich den Weg in G, der zu dieser Gewichtung führte. In dem Untergraph aus den Knoten X und den neuen gewichteten Kanten bestimmen wir eine perfekte gewichtsminimale Korrespondenz, wie in Abschnitt 21.2, Seite Es muß keineswegs ein chinesischer Briefträger sein. Das Briefträgerproblem wurde zuerst von dem chinesischen Wissenschaftler Kwan, Mei-Ko in einer chinesischen Zeitschrift behandelt [Kwan1962]. Der Name “Chinese postman problem” wurde von Alan J. Goldman, damals ein Mitarbeiter von Jack Endmonds (siehe Fußnote Seite 214), vorgeschlagen und hat sich seiner Griffigkeit wegen durchgesetzt. Siehe auch thttp://www.itl.nist.gov/div897/sqg/dads/HTML/chinesePostman.html. 3 21.3. MINIMALE RUNDWEGE 475 473, beschrieben. Der um die Kanten dieser Korrespondenz erweiterte Graph hat jetzt nur noch Knoten geraden Gesamtgrades und ist deshalb a-eulersch. In ihm bestimmen wir einen a-Eulerkreis. Wird eine der alten Linien hinzugefügt und endet diese in einem Verknüpfungspunkt, so unterbrechen wir und bestimmen zunächst minimale Rundwege in den benachbarten Biblockknoten. Ist eine neue Kante in den a-Eulerkreis einzufügen, so wird statt ihrer der zugehörige a-Weg in G in der entsprechenden Richtung durchlaufen. Der so gefundene Rundweg in G ist gewichtsminimal, weil die Korrespondenz es ist. f-CPP: Sinnvollerweise verlangen wir, daß der Graph stark zusammenhängend ist. Es gibt dann gewichtsminimale Briefträger-Rundwege. Das Problem, einen davon zu finden, ist N P-vollständig: Siehe [GareJ1979], Seite 212 [ND24]. Man kann versuchen, es näherungsweise zu lösen (siehe Abschnitt 6.3, Seite 216). In einer Reihe von Sonderfällen gibt es polynomielle Lösungsalgorithmen: 1. Der Graph ist f-eulersch Siehe Satz 14.8, Seite 362. 2. Der Graph ist ungerichtet. Siehe oben. f-Rundwege sind a-Rundwege. 3. Der Graph ist ein Digraph. Es gibt verschiedene Lösungen. Siehe die Literaturangaben. Der Graph enthält sowohl Kanten als auch Bögen und ist nicht f-eulersch. Man bildet wieder den zugehörigen Biblockbaum. Periphere und interne Bäume bestehen aus Kanten, die Biblöcke sind stark zusammenhängende Untergraphen (siehe Abschnitt 16.7, Seite 418). Man geht wieder in einer Tiefensuche durch den Biblockbaum. Periphere Bäume, interne Bäume, ungerichtete Biblöcke, gerichtete Biblöcke und f-eulersche Biblöcke können direkt bearbeitet werden. Was macht man mit nicht f-eulerschen Biblöcken, die sowohl Kanten als auch Bögen enhtalten? Da beginnen die ernsten Schwierigkeiten. Man kann zwar jede Kante in einem stark zusammenhängenden Biblock so orientieren, daß der starke Zusammenhang nicht verletzt wird. Einige Kanten darf man in beliebiger Richtung orientieren und könnte sie sogar durch zwei Bögen gleichen Gewichts und entgegengesetzter Richtung ersetzen. Das besagt Proposition 14.9, Seite 353, und das im dortigen Beweis angegene Vorgehen. So wird aus dem Biblock ein Digraph, für den man einen minimalen f-Rundweg finden kann. Leider ist es nicht richtig (siehe Aufgabe 21.1), daß der so gefundene Rundweg auch im ursprünglichen Biblock minimal ist4 . Man bleibt auf Näherungslösungen angewiesen. Anmerkung 21.1 Üblicherweise werden Graphen in ungerichtete Graphen, Digraphen und „gesmischte“ Graphen eingeteilt. Letztere werden eher selten betrachtet. Das führt zu etwas irritierenden Aussagen. So schreibt J. van Leeuwen [Leeu1990], Seite 567: It follows (see Section 3) that the Chinese postman problem is solvable in polynomial time. A 4 Vielleicht können die so gefundenen Rundwege als Bezugspunkte für Heuristiken dienen. 476 KAPITEL 21. WEITERE ERGÄNZUNGEN ZU NETZWERKEN * similar result holds in the directed case, but quite surpisingly the problem is N P-complete for mixed graphs. Betrachtet man Graphen stets als allgemeine Graphen und legt die Unterscheidung in a-Wege und f-Wege, so ist a-CCP polynomiell lösbar und f-CCP N Pvollständig. Daran ist nichts Überraschendes, auch nicht an der Tatsache, daß es bei f-CPP für Teilbereiche polynomielle Lösungsalorithmen gibt. Das zeigt, daß man gemischte Graphen nicht als eigene Klasse, sondern als Oberbegriff, als allgemeine Graphen, verwenden sollte. 2 21.3.2 Das Problem des Handlungsreisenden Werden Rundwege gesucht, die jeden Knoten mindestens einmal enthalten, so spricht man vom Problem des Handlungsreisenden (travelling salesman problem). Wir stellen uns einen Vertreter vor, der all Orte seines Gebietes (mindestens einmal) besuchen soll. Die allgemein gebräuchliche Kurzbezeichnung ist TSP. Wir wollen auch die Bezeichnungen aTSP und f-TSP verwenden. Wir wollen für beide Fälle zunächst den graphentheoretischen Rahmen abstecken. Danach wird einiges zur Lösung des TSP angemerkt. a-TSP: Sinnvollerweise verlangen wir, daß der Graph schwach zusammehängend ist. Des weiteren können wir Schlingen fortlassen und unter den Mehrfachlinien, die zwei Knoten verbinden, eine mit geringstem Gewicht wählen. D. h. wir können uns auf schlichte Graphen beschränken. Eng verwandt mit dem Problem, einen Hamiltonkreis zu finden, ist das Problem des Handlungsreisenden (travelling salesman problem, TSP). Ein Handlungsreisender sucht eine optimale Tour durch n Städte, wobei für je 2 Städte x und y die Kosten, um von x nach y zu kommen, bekannt sind. Das Problem des Handlungsreisenden ist auch NPvollständig. Eine gute Darstellung ist bei Jungnickel [Jung1994] zu finden. 21.4 21.4.1 Endliche Markovketten Stochastische Prozesse Allgemein versteht man unter einem Prozeß (process) einen zeitlichen Ablauf, in dem sich ein Zustand verändert. Es ist