<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
		<id>https://alda.iwr.uni-heidelberg.de/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Stephan.meister</id>
		<title>Alda - User contributions [en]</title>
		<link rel="self" type="application/atom+xml" href="https://alda.iwr.uni-heidelberg.de/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Stephan.meister"/>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php/Special:Contributions/Stephan.meister"/>
		<updated>2026-05-08T03:13:53Z</updated>
		<subtitle>User contributions</subtitle>
		<generator>MediaWiki 1.30.0</generator>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Graphen_und_Graphenalgorithmen&amp;diff=5390</id>
		<title>Graphen und Graphenalgorithmen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Graphen_und_Graphenalgorithmen&amp;diff=5390"/>
				<updated>2012-07-24T15:06:29Z</updated>
		
		<summary type="html">&lt;p&gt;Stephan.meister: fixed depth first search function with parents list&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Einführung zu Graphen ==&lt;br /&gt;
&lt;br /&gt;
=== Motivation -- Königsberger Brückenproblem ===&lt;br /&gt;
Leonhard Euler [http://de.wikipedia.org/wiki/Leonhard_Euler] erfand den Graphen-Formalismus 1736, um eine scheinbar banale Frage zu beantworten: Ist es möglich, in Königsberg (siehe Stadtplan von 1809 und die schematische Darstellung) einen Spaziergang zu unternehmen, bei dem jede der 7 Brücken genau einmal überquert wird?&lt;br /&gt;
&lt;br /&gt;
[[Image:Koenigsberg1809.png]]&amp;lt;br&amp;gt;&lt;br /&gt;
[[Image:Koenigsberg.jpg]]&lt;br /&gt;
&lt;br /&gt;
Ein Graph abstrahiert von der Geometrie des Problems und repräsentiert nur die Topologie. Jeder Stadtteil von Königsberg ist ein Knoten des Graphen, jede Brücke eine Kante. Der zum Brückenproblem gehörende Graph sieht also so aus:&lt;br /&gt;
&lt;br /&gt;
     O&lt;br /&gt;
    /| \&lt;br /&gt;
    \|  \&lt;br /&gt;
     O---O&lt;br /&gt;
    /|  /&lt;br /&gt;
    \| /&lt;br /&gt;
     O&lt;br /&gt;
&lt;br /&gt;
Der gesuchte Spaziergang würde existieren, wenn es maximal 2 Knoten gäbe, an denen sich eine ungerade Zahl von Kanten trifft. Die Frage muss für Königsberg also verneint werden, denn hier gibt es vier solche Knoten. Ein leicht modifiziertes Problem ist allerdings lösbar: Im obigen Stadtplan erkennt man eine Fähre, die die Stadtteile Kneiphof und Altstadt verbindet. Bezieht man dieselbe in den Spaziergang ein, ergibt sich folgender Graph, bei dem nur noch zwei Knoten mit ungerader Kantenzahl existieren:&lt;br /&gt;
&lt;br /&gt;
   --O&lt;br /&gt;
  / /| \&lt;br /&gt;
  \ \|  \&lt;br /&gt;
   --O---O&lt;br /&gt;
    /|  /&lt;br /&gt;
    \| /&lt;br /&gt;
     O&lt;br /&gt;
&lt;br /&gt;
Inzwischen haben Graphen eine riesige Zahl weiterer Anwendungen gefunden. Einige Beispiele:&lt;br /&gt;
&lt;br /&gt;
* Landkarten:&lt;br /&gt;
** Knoten: Länder&lt;br /&gt;
** Kanten: gemeinsame Grenzen&lt;br /&gt;
&lt;br /&gt;
* Logische Schaltkreise:&lt;br /&gt;
** Knoten: Gatter&lt;br /&gt;
** Kanten: Verbindungen&lt;br /&gt;
&lt;br /&gt;
* Chemie (Summenformeln):&lt;br /&gt;
** Knoten: chemische Elemente&lt;br /&gt;
** Kanten: Bindungen &lt;br /&gt;
&lt;br /&gt;
* Soziologie (StudiVZ)&lt;br /&gt;
** Soziogramm&lt;br /&gt;
*** Knoten: Personen&lt;br /&gt;
*** Kanten: Freund von ...&lt;br /&gt;
&lt;br /&gt;
=== Definitionen ===&lt;br /&gt;
&lt;br /&gt;
;Ungerichteter Graph: Ein ungerichteter Graph G = ( V, E ) besteht aus&lt;br /&gt;
:* einer endliche Menge V von Knoten (vertices)&lt;br /&gt;
:* einer endlichen Menge &amp;lt;math&amp;gt;E \subset V \times V&amp;lt;/math&amp;gt; von Kanten (edges)&lt;br /&gt;
:Die Paare (u,v) und (v,u) gelten dabei als nur ''eine'' Kante (somit gilt die Symmetriebeziehung: (u,v) ∈ E =&amp;gt; (v,u) ∈ E ). Die Anzahl der Kanten, die sich an einem Knoten treffen, wird als ''Grad'' (engl. ''degree'') dieses Knotens bezeichnet:&lt;br /&gt;
:::degree(v) = |{v' ∈ V | (v,v') ∈ E}|&lt;br /&gt;
:(Die Syntax |{...}| bezeichnet dabei die Mächtigkeit der angegebenen Menge, also die Anzahl der Elemente in der Menge.)&lt;br /&gt;
&lt;br /&gt;
Der Graph des Königsberger Brückenproblems ist ungerichtet. Bezeichnet man die Knoten entsprechend des folgenden Bildes&lt;br /&gt;
    c&lt;br /&gt;
   /| \&lt;br /&gt;
   \|  \&lt;br /&gt;
    b---d &lt;br /&gt;
   /|  /&lt;br /&gt;
   \| /&lt;br /&gt;
    a&lt;br /&gt;
&lt;br /&gt;
gilt für die Knotengrade: &amp;lt;tt&amp;gt;degree(a) == degree(c) == degree(d) == 3&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;degree(b) == 5&amp;lt;/tt&amp;gt;. Genauer muss man bei diesem Graphen von einem ''Multigraphen'' sprechen, weil es zwischen einigen Knotenpaaren (nämlich (a, b) sowie (b, c)) mehrere Kanten (&amp;quot;Mehrfachkanten&amp;quot;) gibt. Wir werden in dieser Vorlesung nicht näher auf Multigraphen eingehen.&lt;br /&gt;
&lt;br /&gt;
;Gerichteter Graph: Ein Graph heißt ''gerichtet'', wenn die Kanten (u,v) und (v,u) unterschieden werden. Die Kante (u,v) ∈ E wird nun als Kante von u nach v (aber nicht umgekehrt) interpretiert. Entsprechend unterscheidet man jetzt den ''eingehenden'' und den ''ausgehenden Grad'' jedes Knotens:&lt;br /&gt;
:*out_degree(v) = |{v' ∈ V | (v,v') ∈ E}|&amp;lt;br/&amp;gt;&lt;br /&gt;
:*in_degree(v)  = |{v' ∈ V| (v',v) ∈ E}|&lt;br /&gt;
&lt;br /&gt;
Das folgende Bild zeigt einen gerichteten Graphen. Hier gilt &amp;lt;tt&amp;gt;out_degree(1) == out_degree(3) == in_degree(2) == in_degree(4) == 2&amp;lt;/tt&amp;gt; und &lt;br /&gt;
&amp;lt;tt&amp;gt;in_degree(1) == in_degree(3) == out_degree(2) == out_degree(4) == 0&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
[[Image:digraph.png|gerichteter Graph]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
;Vollständiger Graph: Ein vollständiger Graph ist ein ungerichteter Graph, bei dem jeder Knoten mit allen anderen Knoten verbunden ist.&lt;br /&gt;
:::&amp;lt;math&amp;gt;E = \{ (v,w) |  v \in V, w \in V, v \ne w \}&amp;lt;/math&amp;gt;&lt;br /&gt;
:Ein vollständiger Graph mit |V| Knoten hat &amp;lt;math&amp;gt;|E| = \frac{|V|(|V|-1)}{2}&amp;lt;/math&amp;gt; Kanten.&lt;br /&gt;
&lt;br /&gt;
Die folgenden Abbildungen zeigen die vollständigen Graphen mit einem bis fünf Knoten (auch als K&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; bis K&amp;lt;sub&amp;gt;5&amp;lt;/sub&amp;gt; bezeichnet).&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;0&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;0&amp;quot; style=&amp;quot;margin: 1em auto 1em auto&amp;quot;&lt;br /&gt;
|- &lt;br /&gt;
| [[Image:k1.png|frame|k1]]&lt;br /&gt;
| [[Image:k2.png|frame|k2]]&lt;br /&gt;
| [[Image:k3.png|frame|k3]]&lt;br /&gt;
|-&lt;br /&gt;
| [[Image:k4.png|frame|k4]]&lt;br /&gt;
| [[Image:k5.png|frame|k5]]&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
''Rätsel''&amp;lt;br/&amp;gt;&lt;br /&gt;
Auf einer Party sind Leute. Alle stoßen miteinander an. Es hat 78 mal &amp;quot;Pling&amp;quot; gemacht.&lt;br /&gt;
Wieviele Leute waren da? Antwort: Jede Person ist ein Knoten des Graphen, jedes Antoßen eine Kante. &lt;br /&gt;
Da alle miteinander angestoßen haben, handelt es sich um einen vollständigen Graphen. Mit&lt;br /&gt;
|V|(|V|-1)/2 = 78 folgt, dass es 13 Personen waren.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
;Gewichteter Graph: Ein Graph heißt ''gewichtet'', wenn jeder Kante eine reelle Zahl zugeordnet ist. Bei vielen Anwendungen beschränkt man sich auch auf nichtnegative reelle Gewichte. In einem gerichteten Graphen können die Gewichte der Kanten (u,v) und (v,u) unterschiedlich sein.&lt;br /&gt;
&lt;br /&gt;
Die Gewichte kodieren Eigenschaften der Kanten, die für die jeweilige Anwendung interessant sind. Bei der Berechnung des maximalen Flusses in einem Netzwerk sind die Gewichte z.B. die Durchflusskapazitäten jeder Kante, bei der Suche nach kürzesten Weges kodieren Sie den Abstand zwischen den Endknoten der Kante, bei Währungsnetzwerken (jeder Knoten ist eine Währung) geben sie die Wechselkurse an, usw..&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
;Teilgraphen: Ein Graph G' = (V',E') ist ein Teilgraph eines Graphen G, wenn gilt:&lt;br /&gt;
:* V' &amp;amp;sube; V &lt;br /&gt;
:* E' &amp;amp;sub; E &lt;br /&gt;
:Er heißt ''(auf)spannender Teilgraph'', wenn gilt:&lt;br /&gt;
:* V' = V&lt;br /&gt;
:Er heißt ''induzierter Teilgraph'', wenn gilt:&lt;br /&gt;
:* e = (u,v) ∈ E' &amp;amp;sub; E &amp;amp;hArr; u ∈ V' und v ∈ V'&lt;br /&gt;
:Den von V' induzierten Teilgraphen erhält man also, indem man aus G alle Knoten löscht, die nicht in V' sind, sowie alle Kanten (und nur diese Kanten), die einen der gelöschten Knoten als Endknoten haben.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
;Wege, Pfade, Zyklen, Kreise, Erreichbarkeit: Sei G = (V,E) ein Graph (ungerichtet oder gerichteter) Graph. Dann gilt folgende rekursive Definition:&lt;br /&gt;
:* Für v ∈ V ist (v) ein Weg der Länge 0 in G&lt;br /&gt;
:* Falls &amp;lt;math&amp;gt;(v_0, v_1, ..., v_{n-1})&amp;lt;/math&amp;gt; ein Weg ist, und eine Kante &amp;lt;math&amp;gt;(v_{n-1}, v_n)\in E&amp;lt;/math&amp;gt; existiert, dann ist auch &amp;lt;math&amp;gt;(v_0, v_1, ..., v_{n-1}, v_n)&amp;lt;/math&amp;gt; ein Weg, und er hat die Länge n. &lt;br /&gt;
: Ein Weg ist also eine nichtleere Folge von Knoten, so dass aufeinander folgende Knoten stets durch eine Kante verbunden sind. Die Länge des Weges entspricht der Anzahl der Kanten im Weg (= Anzahl der Knoten - 1).&lt;br /&gt;
:* Ein ''Pfad'' &amp;lt;math&amp;gt;(v_0, v_1, ..., v_{n-1}, v_n)&amp;lt;/math&amp;gt; ist ein Weg, bei dem alle Knoten v&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; verschieden sind.&lt;br /&gt;
:* ''Ein Zyklus'' &amp;lt;math&amp;gt;(v_0, v_1, ..., v_{n-1}, v_n)&amp;lt;/math&amp;gt; ist ein Weg, der zum Ausgangspunkt zurückkehrt, wenn also v&amp;lt;sub&amp;gt;0&amp;lt;/sub&amp;gt; = v&amp;lt;sub&amp;gt;n&amp;lt;/sub&amp;gt; gilt.&lt;br /&gt;
:* Ein ''Kreis'' ist ein Zyklus ohne Überkreuzungen. Das heisst, es gilt v&amp;lt;sub&amp;gt;0&amp;lt;/sub&amp;gt; = v&amp;lt;sub&amp;gt;n&amp;lt;/sub&amp;gt; und &amp;lt;math&amp;gt;(v_0, v_1, ..., v_{n-1})&amp;lt;/math&amp;gt; ist ein Pfad.&lt;br /&gt;
:* Ein Knoten w ∈ V ist von einem anderen Knoten v ∈ V aus ''erreichbar'' genau dann, wenn ein Weg (v, ..., w) existiert. Wir schreiben dann &amp;lt;math&amp;gt;v \rightsquigarrow w&amp;lt;/math&amp;gt;.&lt;br /&gt;
In einem ungerichteten Graph ist die Erreichbarkeits-Relation stets symmetrisch, das heisst aus &amp;lt;math&amp;gt;v \rightsquigarrow w&amp;lt;/math&amp;gt; folgt &amp;lt;math&amp;gt;w \rightsquigarrow v&amp;lt;/math&amp;gt;. In einem gerichteten Graphen ist dies im allgemeinen nicht der Fall.&lt;br /&gt;
&lt;br /&gt;
Bestimmte Wege haben spezielle Namen&lt;br /&gt;
&lt;br /&gt;
;Eulerweg: Ein Eulerweg ist ein Weg, der alle '''Kanten''' genau einmal enthält.&lt;br /&gt;
&lt;br /&gt;
Die eingangs erwähnte Frage des Königsberger Brückenproblems ist equivalent zu der Frage, ob der dazugehörige Graph einen Eulerweg besitzt (daher der Name). Ein anderes bekanntes Beispiel ist das &amp;quot;Haus vom Nikolaus&amp;quot;: Wenn man diesen Graphen in üblicher Weise in einem Zug zeichnet, erhält man gerade den Eulerweg. &lt;br /&gt;
&lt;br /&gt;
    O&lt;br /&gt;
   /  \&lt;br /&gt;
  O----O&lt;br /&gt;
  | \/ |&lt;br /&gt;
  | /\ |   &amp;quot;Das Haus vom Nikolaus&amp;quot;: Alle ''Kanten'' werden nur ''einmal'' passiert&lt;br /&gt;
  O----O&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
;Hamiltonweg: Ein Hamiltonweg ist ein Weg, der alle '''Knoten''' genau einmal enthält. Das &amp;quot;Haus vom Nikolaus&amp;quot; besitzt auch einen Hamiltonweg:&lt;br /&gt;
&lt;br /&gt;
    O&lt;br /&gt;
   /   &lt;br /&gt;
  O----O&lt;br /&gt;
     /  &lt;br /&gt;
    /      Alle ''Knoten'' werden nur ''einmal'' passiert&lt;br /&gt;
  O----O&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
;Hamiltonkreis: Ein Hamiltonkreis ist ein Kreis, der alle '''Knoten''' genau einmal enthält. Auch ein solches Gebilde ist im Haus von Nilolaus enthalten:&lt;br /&gt;
&lt;br /&gt;
    O&lt;br /&gt;
   /  \&lt;br /&gt;
  O    O&lt;br /&gt;
  |    |   v0 = vn&lt;br /&gt;
  |    |   vi != vj   Für Alle i,j   i !=j; i,j &amp;gt;0; i,j &amp;lt; n&lt;br /&gt;
  O----O     &lt;br /&gt;
&lt;br /&gt;
Die folgende Skizze zeigt hingegen einen Zyklus: Der Knoten rechts unten sowie die untere Kante sind zweimal enthalten (die Kante einmal von links nach rechts und einmal von rechts nach links):&lt;br /&gt;
&lt;br /&gt;
    O&lt;br /&gt;
   /  \&lt;br /&gt;
  O    O&lt;br /&gt;
    \  |&lt;br /&gt;
     \ |   Zyklus&lt;br /&gt;
  O====O&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
;Zusammenhang, Zusammenhangskomponenten: Ein ungerichteter Graph G heißt ''zusammenhängend'', wenn für alle v,w ∈ V gilt:&lt;br /&gt;
:::&amp;lt;math&amp;gt;v \rightsquigarrow w&amp;lt;/math&amp;gt;&lt;br /&gt;
:Ein gerichteter Graph G ist zusammenhängend, wenn für alle v,w ∈ V gilt:&lt;br /&gt;
:::&amp;lt;math&amp;gt;v \rightsquigarrow w&amp;lt;/math&amp;gt; '''oder''' &amp;lt;math&amp;gt;w \rightsquigarrow v&amp;lt;/math&amp;gt;. &lt;br /&gt;
:Er ist ''stark zusammenhängend'', wenn für alle v,w ∈ V gilt:&lt;br /&gt;
:::&amp;lt;math&amp;gt;v \rightsquigarrow w&amp;lt;/math&amp;gt; '''und''' &amp;lt;math&amp;gt;w \rightsquigarrow v&amp;lt;/math&amp;gt;. &lt;br /&gt;
:Entsprechende Definitionen gelten für Teilgraphen G'. Ein Teilgraph G' heisst ''Zusammenhangskomponente'' von G, wenn er ein ''maximaler'' zusammenhängender Teilgraph ist, d.h. wenn G' zusammenhängend ist, und man keine Knoten und Kanten aus G mehr zu G' hinzufügen kann, so dass G' immer noch zusammenhängend bleibt. Entsprechend definiert man ''starke Zusammenhangskomponenten'' in einem gerichteten Graphen.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
;Planarer Graph, ebener Graph: Ein Graph heißt ''planar'', wenn er so in einer Ebene gezeichnet werden ''kann'', dass sich die Kanten nicht schneiden (außer an den Knoten). Ein Graph heißt ''eben'', wenn er tatsächlich so gezeichnet ''ist'', dass sich die Kanten nicht schneiden. Die Einbettung in die Ebene ist im allgemeinen nicht eindeutig.&lt;br /&gt;
&lt;br /&gt;
'''Beispiele:'''&lt;br /&gt;
&lt;br /&gt;
Der folgende Graph ist planar und eben:&lt;br /&gt;
 &lt;br /&gt;
      O&lt;br /&gt;
     /|\&lt;br /&gt;
    / O \&lt;br /&gt;
   / / \ \&lt;br /&gt;
   O     O&lt;br /&gt;
&lt;br /&gt;
Das &amp;quot;Haus vom Nikolaus&amp;quot; ist ebenfalls planar, wird aber üblicherweise nicht als ebener Graph gezeichnet, weil sich die Diagonalen auf der Wand überkreuzen:&lt;br /&gt;
 &lt;br /&gt;
    O&lt;br /&gt;
   /  \&lt;br /&gt;
  O----O&lt;br /&gt;
  | \/ |&lt;br /&gt;
  | /\ |   &lt;br /&gt;
  O----O&lt;br /&gt;
&lt;br /&gt;
Eine ebene Einbettung dieses Graphen wird erreicht, wenn man eine der Diagonalen ausserhalb des Hauses zeichnet. Der Graph (also die Menge der Knoten und Kanten) ändert sich dadurch nicht.&lt;br /&gt;
 &lt;br /&gt;
      O  &lt;br /&gt;
     /  \&lt;br /&gt;
  --O----O&lt;br /&gt;
 /  |  / |&lt;br /&gt;
 |  | /  |   &lt;br /&gt;
 |  O----O      Das &amp;quot;Haus vom Nikolaus&amp;quot; als ebener Graph gezeichnet.&lt;br /&gt;
  \     /&lt;br /&gt;
   -----&lt;br /&gt;
&lt;br /&gt;
Eine alternative Einbettung erhalten wir, wenn wir die andere Diagonale außerhalb des Hauses zeichnen:&lt;br /&gt;
 &lt;br /&gt;
      O  &lt;br /&gt;
     /  \&lt;br /&gt;
    O----O--|&lt;br /&gt;
    | \  |  |&lt;br /&gt;
    |  \ |  | &lt;br /&gt;
    O----O  |     Alternative Einbettung des &amp;quot;Haus vom Nikolaus&amp;quot;.&lt;br /&gt;
    |       |&lt;br /&gt;
    |-------|&lt;br /&gt;
&lt;br /&gt;
Jede Einbettung eines planaren Graphen (also jeder ebene Graph) definiert eine eindeutige Menge von ''Regionen'':&lt;br /&gt;
&lt;br /&gt;
 |----O   @&lt;br /&gt;
 |   /@ \&lt;br /&gt;
 |  O----O&lt;br /&gt;
 |  |@ / |&lt;br /&gt;
 |  | / @|   &lt;br /&gt;
 |  O----O        @ entspricht jeweils einer ''Region''. Auch ausserhalb der Figur ist eine Region (die sogenannte ''unendliche'' Region).&lt;br /&gt;
 |@      |&lt;br /&gt;
 |-------|&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Der vollständige Graph K5 ist kein planarer Graph, da sich zwangsweise Kanten schneiden, wenn man diesen Graphen in der Ebene zeichnet.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
;Dualer Graph: Jeder ebene Graph G = (V, E) hat einen ''dualen Graphen'' D = (V&amp;lt;sub&amp;gt;D&amp;lt;/sub&amp;gt;, E&amp;lt;sub&amp;gt;D&amp;lt;/sub&amp;gt;), dessen Knoten und Kanten wie folgt definiert sind:&lt;br /&gt;
:* V&amp;lt;sub&amp;gt;D&amp;lt;/sub&amp;gt; enthält einen Knoten für jede Region des Graphen G&lt;br /&gt;
:* Für jede Kante e ∈ E gibt es eine duale Kante e&amp;lt;sub&amp;gt;D&amp;lt;/sub&amp;gt; ∈ E&amp;lt;sub&amp;gt;D&amp;lt;/sub&amp;gt;, die die an e angrenzenden Regionen (genauer: die entsprechenden Knoten in D) verbindet.&lt;br /&gt;
&lt;br /&gt;
Die folgende Abbildung zeigt einen Graphen (grau) und seinen dualen Graphen (schwarz). Die Knoten des dualen Graphen sind mit Zahlen gekennzeichnet und entsprechen den Regionen des Originalgraphen. Jeder (grauen) Kante des Originalgraphen entspricht eine (schwarze) Kante des dualen Graphen.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
[[Image:dual-graphs.png]]&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für duale Graphen gilt: Wenn der Originalgraph zusammenhängend ist, enthält jede Region des dualen Graphen genau einen Knoten des Originalgraphen. Deshalb ist der duale Graph des dualen Graphen wieder der Originalgraph. Bei nicht-zusammenhängenden Graphen gilt dies nicht (vgl. das Fenster bei obigem Bild). In diesem Fall hat der duale Graph mehrere mögliche Einbettungen in die Ebene (man kann z.B. die rechte Kante zwischen Knoten 2 und 4 auch links vom Fenster einzeichnen), und man erhält nicht notwendigerweise den Originalgraphen, wenn man den dualen Graphen des dualen berechnet.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
;Baum: Ein ''Baum'' ist ein zusammenhängender, kreisfreier Graph.&lt;br /&gt;
&lt;br /&gt;
Beispiel: Binärer Suchbaum&lt;br /&gt;
&lt;br /&gt;
;Spannbaum: Ein ''Spannbaum'' eines zusammenhängenden Graphen G ist ein zusammenhängender, kreisfreier Teilgraph von G, der alle Knoten von G enthält&lt;br /&gt;
&lt;br /&gt;
Beispiel: Spannbaum für das &amp;quot;Haus des Nikolaus&amp;quot; &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    O   &lt;br /&gt;
   /       &lt;br /&gt;
  O    O&lt;br /&gt;
  |  /  &lt;br /&gt;
  | /   &lt;br /&gt;
  O----O&lt;br /&gt;
&lt;br /&gt;
Der Spannbaum eines Graphen mit |V| Knoten hat stets |V| - 1 Kanten.&lt;br /&gt;
&lt;br /&gt;
;Wald: Ein ''Wald'' ist ein unzusammenhängender, kreisfreier Graph.&lt;br /&gt;
: Jede Zusammenhangskomponente eines Waldes ist ein Baum.&lt;br /&gt;
&lt;br /&gt;
=== Repräsentation von Graphen ===&lt;br /&gt;
&lt;br /&gt;
Sei G = ( V, E ) gegeben und liege V in einer linearen Sortierung vor.&amp;lt;br/&amp;gt; &lt;br /&gt;
:::&amp;lt;math&amp;gt;V = \{ v_1, ...., v_n \}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
;Adjazenzmatrix: Ein Graph kann durch eine Adjazenzmatrix repräsentiert werden, die soviele Zeilen und Spalten enthält, wie der Graph Knoten hat. Die Elemente der Adjazenzmatrix sind &amp;quot;1&amp;quot;, falls eine Kante zwischen den zugehörigen Knoten existiert:&lt;br /&gt;
:::&amp;lt;math&amp;gt;\mathrm{\bold A} = a_{ij} = &lt;br /&gt;
\begin{cases}&lt;br /&gt;
1 &amp;amp; \mathrm{falls}\quad (v_i, v_j) \in E \\&lt;br /&gt;
0 &amp;amp; \mathrm{sonst}&lt;br /&gt;
\end{cases} &lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
:Die Indizes der Matrix entsprechen also den Indizes der Knoten gemäß der gegebenen Sortierung. Im Falle eines ungerichteten Graphen ist die Adjazenzmatrix stets symmetrisch (d.h. es gilt &amp;lt;math&amp;gt;a_{ij}=a_{ji}&amp;lt;/math&amp;gt;), bei einem gerichteten Graphen ist sie im allgemeinen unsymmetrisch.&lt;br /&gt;
&lt;br /&gt;
Beispiel für einen ungerichteten Graphen:&lt;br /&gt;
&lt;br /&gt;
 v = { a,b,c,d }     b      d&lt;br /&gt;
                     | \  / |&lt;br /&gt;
                     |  \/  |&lt;br /&gt;
                     |  /\  |&lt;br /&gt;
                     | /  \ |&lt;br /&gt;
                     a      c&lt;br /&gt;
 &lt;br /&gt;
       a b c d&lt;br /&gt;
      -----------&lt;br /&gt;
      (0 1 0 1) |a &lt;br /&gt;
  A = (1 0 1 0) |b&lt;br /&gt;
      (0 1 0 1) |c&lt;br /&gt;
      (1 0 1 0) |d&lt;br /&gt;
&lt;br /&gt;
Die Adjazenzmatrixdarstellung eignet sich besonders für dichte Graphen (d.h. wenn die Zahl der Kanten in O(|V|&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;) ist.&lt;br /&gt;
&lt;br /&gt;
;Adjazenzlisten: In der Adjazenzlistendarstellung wird der Graph als Liste von Knoten repräsentiert, die für jeden Knoten einen Eintrag enthält. Der Eintrag für jeden Knoten ist wiederum eine Liste, die die Nachbarknoten dieses Knotens enthält:&lt;br /&gt;
:* graph = {adjazencyList(v) | v ∈ V}&lt;br /&gt;
:* adjazencyList(v) = {v' ∈ V | (v, v') ∈ E}&lt;br /&gt;
&lt;br /&gt;
In Python implementieren wir Adjazenzlisten zweckmäßig als Array von Arrays:&lt;br /&gt;
&lt;br /&gt;
                   graph = [[...],[...],...,[...]]&lt;br /&gt;
 Adjazenzliste für Knoten =&amp;gt;  0     1         n&lt;br /&gt;
&lt;br /&gt;
Wenn wir bei dem Graphen oben die Knoten wie bei der Adjazenzmatrix indizieren (also &amp;lt;tt&amp;gt;a =&amp;gt; 0&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;b =&amp;gt; 1&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;c =&amp;gt; 2&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;d =&amp;gt; 3&amp;lt;/tt&amp;gt;), erhalten wir die Adjazenzlistendarstellung:&lt;br /&gt;
&lt;br /&gt;
 graph = [[b, d], [a, c],[b, d], [a, c]]&lt;br /&gt;
&lt;br /&gt;
Auf die Nachbarknoten eines durch seinen Index &amp;lt;tt&amp;gt;node&amp;lt;/tt&amp;gt; gegebenen Knotens können wir also wie folgt zugreifen:&lt;br /&gt;
&lt;br /&gt;
      for neighbors in graph[node]:&lt;br /&gt;
          ... # do something with neighbor&lt;br /&gt;
&lt;br /&gt;
Die Adjazenzlistendarstellung ist effizienter, wenn der Graph nicht dicht ist, so dass viele Einträge der Adjazenzmatrix Null wären. In der Vorlesung werden wir nur diese Darstellung verwenden.&lt;br /&gt;
&lt;br /&gt;
;&amp;lt;div id=&amp;quot;transposed_graph&amp;quot;&amp;gt;Transponierter Graph&amp;lt;/div&amp;gt;: Den ''transponierten Graphen'' G&amp;lt;sup&amp;gt;T&amp;lt;/sup&amp;gt; eines gerichteten Graphen G erhält man, wenn man alle Kantenrichtungen umkehrt.&lt;br /&gt;
&lt;br /&gt;
Bei ungerichteten Graphen hat die Transposition offensichtlich keinen Effekt, weil alle Kanten bereits in beiden Richtungen vorhanden sind, so dass G&amp;lt;sup&amp;gt;T&amp;lt;/sup&amp;gt; = G gilt. Bei gerichteten Graphen ist die Transposition einfach, wenn der Graph als Adjazenzmatrix implementiert ist, weil man einfach die transponierte Adjazenzmatrix verwenden muss (beachte, dass sich die Reihenfolge der Indizes umkehrt):&lt;br /&gt;
:::A&amp;lt;sup&amp;gt;T&amp;lt;/sup&amp;gt; = a&amp;lt;sub&amp;gt;ji&amp;lt;/sub&amp;gt;&lt;br /&gt;
Ist der Graph hingegen durch eine Adjazenzliste repräsentiert, muss etwas mehr Aufwand getrieben werden:&lt;br /&gt;
&lt;br /&gt;
 def transposeGraph(graph):&lt;br /&gt;
      gt = [[] for k in graph]   # zunächst leere Adjazenzlisten von G&amp;lt;sup&amp;gt;T&amp;lt;/sup&amp;gt;&lt;br /&gt;
      for node in range(len(graph)):&lt;br /&gt;
           for neighbor in graph[node]:&lt;br /&gt;
               gt[neighbor].append(node)  # füge die umgekehrte Kante in G&amp;lt;sup&amp;gt;T&amp;lt;/sup&amp;gt; ein&lt;br /&gt;
      return gt&lt;br /&gt;
&lt;br /&gt;
== Durchlaufen von Graphen (Graph Traversal) ==&lt;br /&gt;
&lt;br /&gt;
Wir betrachten zunächst ungerichtete Graphen mit V Knoten und E Kanten. Eine grundlegende Aufgabe in diesen Graphen besteht darin, alle Knoten in einer bestimmten Reihenfolge genau einmal zu besuchen. Hierbei darf man sich von einem gegebenen Startknoten aus nur entlang der Kanten des Graphen bewegen. Die beim Traversieren benutzen Kanten bilden einen Baum, dessen Wurzel der Startknoten ist und der den gesamten Graphen aufspannt, falls der Graph zusammenhängend ist. (Beweis: Da jeder Knoten nur einmal besucht wird, gibt es für jeden besuchten Knoten [mit Ausnahme des Startknotens] genau eine eingehende Kante. Ist der Graph zusammenhängend, wird jeder Knoten tatsächlich erreicht und es gibt genau (V-1) Kanten, exakt soviele wie für einen Baum mit V Knoten notwendig sind.) Ist der Graph nicht zusammenhängend, wird jeder zusammenhängende Teilgraph (jede &amp;lt;i&amp;gt;Zusammenhangskomponente&amp;lt;/i&amp;gt;) getrennt traversiert, und man erhält einen sogenannten &amp;lt;i&amp;gt;Wald&amp;lt;/i&amp;gt; mit einem Baum pro Zusammenhangskomponente. Die beiden grundlegenden Traversierungsmethoden &amp;lt;i&amp;gt;Tiefensuche&amp;lt;/i&amp;gt; und &amp;lt;i&amp;gt;Breitensuche&amp;lt;/i&amp;gt; werden im folgenden vorgestellt.&lt;br /&gt;
&lt;br /&gt;
=== Tiefensuche in Graphen (Depth First Search, DFS) ===&lt;br /&gt;
&lt;br /&gt;
Die Idee der Tiefensuche besteht darin, jeden besuchten Knoten sofort über die erste Kante wieder zu verlassen, die zu einem noch nicht besuchten Knoten führt. Man findet dadurch schnell einen möglichst langen Pfad durch den Graphen, und der Traversierungs-Baum wird zunächst in die Tiefe verfolgt, daher der Name des Verfahrens. Hat ein Knoten keine unbesuchten Nachbarknoten mehr, geht man im Baum zurück (sogenanntes &amp;lt;i&amp;gt;back tracking&amp;lt;/i&amp;gt;), bis man einen Knoten findet, der noch eine unbesuchte Nachbarn besitzt, und traversiert diese nach dem gleichen Muster. Gibt es gar keine unbesuchten Knoten mehr, kehrt die Suche zum Startknoten zurück und endet dort.&lt;br /&gt;
&lt;br /&gt;
WDie folgende rekursive Implementation der Tiefensuche erwartet den Graphen in Adjazenzlistendarstellung und beginnt die Suche beim Knoten &amp;lt;tt&amp;gt;startnode&amp;lt;/tt&amp;gt;. Die Information, ob ein Knoten bereits besucht wurde, wird im Array &amp;lt;tt&amp;gt;visited&amp;lt;/tt&amp;gt; gespeichert. Ein solches Array, das zusätzliche Informationen über die Knoten des Graphen bereitstellt, wir häufig &amp;lt;i&amp;gt;property map&amp;lt;/i&amp;gt; genannt.&lt;br /&gt;
&lt;br /&gt;
 def dfs(graph, startnode):&lt;br /&gt;
     visited = [False]*len(graph)  # Flags, welche Knoten bereits besucht wurden&lt;br /&gt;
     &lt;br /&gt;
     def visit(node):              # rekursive Hilfsfunktion, die den gegebenen Knoten und dessen Nachbarn besucht&lt;br /&gt;
         if not visited[node]:     # Besuche node, wenn er noch nicht besucht wurde&lt;br /&gt;
             visited[node] = True  # Markiere node als besucht&lt;br /&gt;
             print node            # Ausgabe der Knotennummer - pre-order&lt;br /&gt;
             for neighbor in graph[node]:   # Besuche rekursiv die Nachbarn&lt;br /&gt;
                 visit(neighbor)&lt;br /&gt;
     &lt;br /&gt;
     visit(startnode)&lt;br /&gt;
&lt;br /&gt;
[[Image:Tiefens.jpg]]&lt;br /&gt;
&lt;br /&gt;
Ausgabe für den Graphen in diesem Bild (es handelt sich um einen ungerichteten Graphen, die Pfeile symbolisieren nur die Suchrichtung beim Traversal):&lt;br /&gt;
&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; dfs(graph, 1)&lt;br /&gt;
 1&lt;br /&gt;
 2&lt;br /&gt;
 4&lt;br /&gt;
 3&lt;br /&gt;
 6&lt;br /&gt;
 7&lt;br /&gt;
 5&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;pre_and_post_order&amp;quot;&amp;gt;In dieser Version des Algorithmus werden die Knotennummern ausgegeben, bevor die Nachbarknoten besucht werden. Man bezeichnet die resultierende Sortierung der Knoten als &amp;lt;b&amp;gt;pre-order&amp;lt;/b&amp;gt; oder als &amp;lt;b&amp;gt;discovery order&amp;lt;/b&amp;gt;. Alternativ kann man die Knotennummern erst ausgeben, nachdem alle Nachbarn besucht wurden, also auf dem Rückweg der Rekursion. In diesem Fall spricht man von &amp;lt;b&amp;gt;post-order&amp;lt;/b&amp;gt; oder &amp;lt;b&amp;gt;finishing order&amp;lt;/b&amp;gt;:&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 def dfs(graph, startnode):&lt;br /&gt;
     visited = [False]*len(graph)  # Flags, welche Knoten bereits besucht wurden&lt;br /&gt;
     &lt;br /&gt;
     def visit(node):              # rekursive Hilfsfunktion, die den gegebenen Knoten und dessen Nachbarn besucht&lt;br /&gt;
         if not visited[node]:     # Besuche node, wenn er noch nicht besucht wurde&lt;br /&gt;
             visited[node] = True  # Markiere node als besucht&lt;br /&gt;
             for neighbor in graph[node]:   # Besuche rekursiv die Nachbarn&lt;br /&gt;
                 visit(neighbor)&lt;br /&gt;
             &amp;lt;font color=red&amp;gt;print node            # Ausgabe der Knotennummer - post-order&amp;lt;/font&amp;gt;&lt;br /&gt;
     &lt;br /&gt;
     visit(startnode)&lt;br /&gt;
&lt;br /&gt;
Es ergibt sich jetzt die Ausgabe:&lt;br /&gt;
&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; dfs(graph, 1)&amp;lt;font color=red&amp;gt;&lt;br /&gt;
 6&lt;br /&gt;
 7&lt;br /&gt;
 3&lt;br /&gt;
 4&lt;br /&gt;
 5&lt;br /&gt;
 2&lt;br /&gt;
 1&amp;lt;/font&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In realem Code ersetzt man die print-Ausgaben natürlich durch anwendungsspezifische Aktionen und Berechnungen. Einige Anwendungen sind uns im Kapitel [[Suchen]] bereits begegnet. &lt;br /&gt;
; Anwendungen der Pre-Order Traversierung&lt;br /&gt;
* Kopieren eines Graphen: kopiere zuerst den besuchten Knoten, dann seine Nachbarn und die dazugehörigen Kanten (sowie die Kanten zu bereits besuchten Knoten, die in der Grundversion der Tiefensuche ignoriert werden).&lt;br /&gt;
* Bestimmen der Zusammenhangskomponenten eines Graphen (siehe unten)&lt;br /&gt;
* In einem Zeichenprogramm: fülle eine Region mit einer Farbe (&amp;quot;flood fill&amp;quot;). Dabei ist jedes Pixel ein Knoten des Graphen und wird mit seinen 4 Nachbarpixceln verbunden. Die Tiefensuche startet bei der Mausposition und endet am Rand des betreffendcen Gebiets.&lt;br /&gt;
* Falls der Graph ein Baum ist: bestimme den Abstand jedes Knotens von der Wurzel&lt;br /&gt;
* Falls der Graph ein Parse-Baum ist, wobei innere Knoten Funktionsaufrufe, Kindknoten Funktionsargumente, und Blattknoten Werte repräsentieren: drucke den zugehörigen Ausdruck aus (also immer zuerst den Funktionsnamen, dann die Argumente, die wiederum geschachtelte Funktionsaufrufe sein können).&lt;br /&gt;
; Anwendungen der Post-Order Traversierung&lt;br /&gt;
* Löschen eines Graphen: lösche zuerst die Nachbarn, dann den Knoten selbst&lt;br /&gt;
* Bestimmen einer topologischen Sortierung eines azyklischen gerichteten Graphens (siehe unten)&lt;br /&gt;
* Falls der Graph ein Baum ist: bestimme den Abstand jedes Knotens von den Blättern (also die Tiefe des Baumes, siehe Übung 5)&lt;br /&gt;
* Falls der Graph ein Parse-Baum ist: führe die zugehörige Berechnung aus (d.h. berechne zuerst die geschachtelten inneren Funktionen, dann mit diesen Ergebnissen die nächst äußeren usw., siehe Übung 5).&lt;br /&gt;
; Anwendungen, die Pre- und Post-Order benötigen&lt;br /&gt;
* Weg aus einem Labyrinth: die Pre-Order dokumentiert die Suche nach dem Weg, die Post-Order zeigt den Rückweg aus Sackgassen (siehe Übung 9).&lt;br /&gt;
Im Spezialfall, wenn der Graph ein Binärbaum ist, unterscheidet man noch eine dritte Variante der Traversierung, nämlich die &amp;lt;i&amp;gt;in-order&amp;lt;/i&amp;gt; Traversierung. In diesem Fall behandelt man den Vaterknoten nach den linken, aber vor den rechten Kindern. Diese Reihenfolge wird beim [[Suchen#Beziehungen zwischen dem Suchproblem und dem Sortierproblem|Tree Sort Algorithmus]] verwendet. Diese Sortierung verwendet man auch, wenn man einen Parse-Baum mit binären Operatoren (statt Funktionsaufrufen) ausgeben will, siehe Übung 5.&lt;br /&gt;
&lt;br /&gt;
Eine nützliche Erweiterung der Tiefensuche besteht darin, in der property map &amp;lt;tt&amp;gt;visited&amp;lt;/tt&amp;gt; nicht nur zu dokumentieren, dass ein Knoten bereits besucht wurde, sondern auch, von welchem Knoten aus man den jeweiligen Knoten zuerst erreicht hat. Im entstehenden Tiefensuchbaum ist dies gerade der Vaterknoten, weshalb wir die verbesserte property map zweckmäßigerweise in &amp;lt;tt&amp;gt;parents&amp;lt;/tt&amp;gt; umbenennen. Für den Startknoten, also die Wurzel des Baumes, wählen wir die Konvention, dass er sein eigener Vaterknoten ist (die Konvention, dafür den Wert &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; zu verwenden, scheidet aus, weil dies bereits die Tatsache signalisiert, dass ein Knoten noch nicht besucht wurde):&lt;br /&gt;
&lt;br /&gt;
 def dfs(graph, startnode):&lt;br /&gt;
     parents = [None]*len(graph)     # Registriere für jeden Knoten den Vaterknoten im Tiefensuchbaum&lt;br /&gt;
     &lt;br /&gt;
     def visit(node, parent):        # rekursive Hilfsfunktion&lt;br /&gt;
         if parents[node] is None:   # Besuche node, wenn er noch nicht besucht wurde&lt;br /&gt;
             parents[node] = parent  # Markiere node als besucht und speichere seinen Vaterknoten&lt;br /&gt;
             for neighbor in graph[node]:   # Besuche rekursiv die Nachbarn ...&lt;br /&gt;
                 visit(neighbor, node)      #  ... wobei node zu deren Vaterknoten wird&lt;br /&gt;
     &lt;br /&gt;
     visit(startnode, startnode)     # Konvention für Wurzel: startnode ist sein eigener Vater&lt;br /&gt;
     &lt;br /&gt;
     return parents                  # Rückgabe des berechneten Tiefensuch-Baums&lt;br /&gt;
&lt;br /&gt;
Die Ausgabe für den obigen Beispielgraphen lautet: &lt;br /&gt;
  Knotennummer  |  0  |  1  |  2  |  3  |  4  |  5  |  6  |  7&lt;br /&gt;
  --------------+-----+-----+-----+-----+-----+-----+-----+-----&lt;br /&gt;
  Vaterknoten   | None|  1  |  1  |  4  |  2  |  2  |  3  |  3&lt;br /&gt;
&lt;br /&gt;
Dabei ist die Knotennummer der Index im Array &amp;lt;tt&amp;gt;parents&amp;lt;/tt&amp;gt;, und der Vaterknoten ist der dazugehörige Arrayeintrag. Beachte, dass Knoten 0 in diesem Graphen nicht existiert, daher ist sein Eintrag &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt;. Per Konvention hat der Wurzelknoten 1 sich selbst als Vater.&lt;br /&gt;
&lt;br /&gt;
=== Breitensuche in Graphen (Breadth First Search, BFS) ===&lt;br /&gt;
&lt;br /&gt;
Im Gegensatz zur Tiefensuche werden bei der Breitensuche alle Nachbarnknoten abgearbeitet, &amp;lt;i&amp;gt;bevor&amp;lt;/i&amp;gt; man rekursiv deren Nachbarn besucht. Man betrachtet somit zuerst alle Knoten, die den Abstand 1 von Startknoten haben, dann diejenigen mit dem Abstand 2 usw. Diese Reihenfolge bezeichnet man als &amp;lt;i&amp;gt;level-order&amp;lt;/i&amp;gt;. Wir sind ihr beispielsweise in Übung 6 begegnet, als die ersten 7 Ebenen eines Treap ausgegeben werden sollten. Man implementiert Breitensuche zweckmäßig mit Hilfe einer Queue, die die Knoten in First In - First Out - Reihenfolge bearbeitet. Eine geeignete Datenstruktur hierfür ist die Klasse &amp;lt;tt&amp;gt;[http://docs.python.org/library/collections.html#collections.deque deque]&amp;lt;/tt&amp;gt; aus dem Python-Modul &amp;lt;tt&amp;gt;[http://docs.python.org/library/collections.html collections]&amp;lt;/tt&amp;gt; (eine Deque implementiert sowohl die Funktionalität einer Queue wie auch die eines Stacks, siehe Übung 3):&lt;br /&gt;
&lt;br /&gt;
 from collections import deque&lt;br /&gt;
 &lt;br /&gt;
 def bfs(graph, startnode)&lt;br /&gt;
     visited = [False]*len(graph)   # Flags, welche Knoten bereits besucht wurden&lt;br /&gt;
   &lt;br /&gt;
     q = deque()                    # Queue für die zu besuchenden Knoten&lt;br /&gt;
     q.append(startnode)            # Startknoten in die Queue einfügen&lt;br /&gt;
     &lt;br /&gt;
     while len(q) &amp;gt; 0:              # Solange es noch unbesuchte Knoten gibt&lt;br /&gt;
         node = q.popleft()         # Knoten aus der Queue nehmen (first in - first out)&lt;br /&gt;
         if not visited[node]:      # Falls node noch nicht (auf einem anderen Weg) besucht wurde&lt;br /&gt;
              visited[node] = True  # Markiere node als besucht&lt;br /&gt;
              print node            # Drucke Knotennummer&lt;br /&gt;
              for neighbor in graph[node]:    # Füge Nachbarn in die Queue ein&lt;br /&gt;
                  q.append(neighbor)&lt;br /&gt;
&lt;br /&gt;
[[Image:Breitens.jpg]]&lt;br /&gt;
&lt;br /&gt;
Der Aufruf dieser Funktion liefert die Knoten des obigen Graphens ebenenweise, also zufällig genau in der Reihenfolge der Knotennummern:&lt;br /&gt;
 &amp;gt;&amp;gt;&amp;gt; bfs(graph, 1)&lt;br /&gt;
 1&lt;br /&gt;
 2&lt;br /&gt;
 3&lt;br /&gt;
 4&lt;br /&gt;
 5&lt;br /&gt;
 6&lt;br /&gt;
 7&lt;br /&gt;
&lt;br /&gt;
Neben der ebenenweisen Ausgabe hat die Breitensuche viele weitere wichtige Anwendungen, z.B. beim Testen, ob ein gegebener Graph bi-partit ist (siehe [http://en.wikipedia.org/wiki/Breadth-first_search#Testing_bipartiteness WikiPedia]), sowie bei der Suche nach kürzesten Wegen (siehe unten) und kürzesten Zyklen.&lt;br /&gt;
&lt;br /&gt;
== Weitere Anwendungen der Tiefensuche ==&lt;br /&gt;
&lt;br /&gt;
Die Tiefensuche hat zahlreiche Anwendungen, wobei der grundlegende Algorithmus immer wieder leicht modifiziert und an die jeweilige Aufgabe angepasst wird. Wir beschreiben im folgenden einige Beispiele.&lt;br /&gt;
&lt;br /&gt;
=== Damenproblem ===&lt;br /&gt;
&lt;br /&gt;
Tiefensuche wird häufig verwendet, um systematisch nach der Lösung eines logischen Rätsels (oder allgemeiner nach der Lösung eines diskreten Optimierungsproblems) zu suchen. Besonders anschaulich hierfür ist das Damenproblem. Die Aufgabe besteht darin, &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; Damen auf einem Schachbrett der Größe &amp;lt;math&amp;gt;k \times k&amp;lt;/math&amp;gt; so zu platzieren, dass sie sich (nach den üblichen Schach-Regeln) nicht gegenseitig schlagen können. Das folgende Diagramm zeigt eine Lösung für den Fall &amp;lt;math&amp;gt;k=4&amp;lt;/math&amp;gt;. Die Positionen der Damen werden dabei wie üblich durch die Angabe der Spalte (Linie) mit Buchstaben und der Zeile (Reihe) mit Zahlen kodiert, hier also A2, B4, C1, D3:&lt;br /&gt;
&lt;br /&gt;
  ---------------&lt;br /&gt;
 |   | X |   |   | 4&lt;br /&gt;
 |---|---|---|---| &lt;br /&gt;
 |   |   |   | X | 3&lt;br /&gt;
 |---|---|---|---|&lt;br /&gt;
 | X |   |   |   | 2&lt;br /&gt;
 |---|---|---|---|&lt;br /&gt;
 |   |   | X |   | 1&lt;br /&gt;
  ---------------&lt;br /&gt;
   A   B   C   D&lt;br /&gt;
&lt;br /&gt;
Um das Problem systematisch zu lösen, konstruieren wir einen gerichteten Graphen, dessen Knoten die möglichen Positionen der Damen kodieren. Wir verbinden Knoten, die zu benachbarten Linien gehören, genau dann mit einer Kante, wenn die zugehörigen Positionen kompatibel sind, also wenn sich die dort positionierten Damen nicht schlagen können. Der resultierende Graph für &amp;lt;math&amp;gt;k=4&amp;lt;/math&amp;gt; hat folgende Gestalt:&lt;br /&gt;
&lt;br /&gt;
[[Image:damenproblem-graph.png|500px|center]]&lt;br /&gt;
&lt;br /&gt;
Knoten, die zur selben Reihe oder Linie gehören, sind beispielsweise nicht direkt verbunden, weil zwei Damen niemals in derselben Linie oder Reihe stehen dürfen. Um eine erlaubte Konfiguration zu finden, verwenden wir nun eine angepasste Version der Tiefensuche: Wir beginnen die Suche beim Knoten &amp;lt;tt&amp;gt;START&amp;lt;/tt&amp;gt;. Sobald wir den Knoten &amp;lt;tt&amp;gt;STOP&amp;lt;/tt&amp;gt; erreichen, beenden wir die Suche und lesen die Lösung am gerade gefundenen Weg von Start nach Stop ab. Zwei kleine Modifikationen des Grundalgorithmus stellen sicher, dass die Bedingungen der Aufgabe eingehalten werden: Wir dürfen bei der Tiefensuche nur dann zu einem Nachbarn weitergehen, wenn die betreffende Position mit allen im Pfad bereits gesetzten Positionen kompatibel ist, andernfalls ist diese Kante tabu. Landen wir aufgrund dieser Regel in einer Sackgasse (also in einem Knoten, wo keine der ausgehenden Kanten erlaubt ist), müssen wir zur nächsten erlaubten Abzweigung zurückgehen (Backtracking). Beim Zurückgehen müssen wir das &amp;lt;tt&amp;gt;parent&amp;lt;/tt&amp;gt;-Flag wieder auf &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; zurücksetzen, weil der betreffende Knoten ja möglicherweise auf einem anderen erlaubten Weg erreichbar ist.&lt;br /&gt;
&lt;br /&gt;
Der folgende Graph zeigt einen solchen Fall: Wir haben zwei Damen auf die Felder A1 und B3 positioniert (grüne Pfeile). Die einzig ausgehende Kante von B3 führt zum Knoten C1, welcher aber mit der Position A1 inkompatibel ist, so dass diese Kante nicht verwendet werden darf (roter Pfeil). Das Backtracking muss jetzt zu Knoten A1 zurückgehen (dabei wird das &amp;lt;tt&amp;gt;parent&amp;lt;/tt&amp;gt;-Flag von B3 wieder auf &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; gesetzt), weil A1 mit der Kante nach B4 eine weitere Option hat, die geprüft werden muss (die allerdings hier auch nicht zum Ziel führt).&lt;br /&gt;
&lt;br /&gt;
[[Image:damenproblem-graph-failure.png|500px|center]]&lt;br /&gt;
&lt;br /&gt;
Nach einigen weiteren Sackgassen findet man schließlich den Pfad A2, B4, C1, D3, der im folgenden Graphen grün markiert ist und der obigen Lösung entspricht:&lt;br /&gt;
&lt;br /&gt;
[[Image:damenproblem-graph-success.png|500px|center]]&lt;br /&gt;
&lt;br /&gt;
=== Test, ob ein ungerichteter Graph azyklisch ist ===&lt;br /&gt;
&lt;br /&gt;
Ein zusammenhängender ungerichteter Graph ist azyklisch (also ein Baum) genau dann, wenn es nur einen möglichen Weg von jedem Knoten zu jedem anderen gibt. (Bei gerichteten Graphen sind die Verhältnisse komplizierter. Wir behandeln dies weiter unten.) Das kann man mittels Tiefensuche leicht feststellen: Die Kante, über die wir einen Knoten erstmals erreichen, ist eine &amp;lt;i&amp;gt;Baumkante&amp;lt;/i&amp;gt; des Tiefensuchbaums. Erreichen wir einen bereits besuchten Knoten nochmals über eine andere Kante, haben wir einen Zyklus gefunden. Dabei müssen wir allerdings beachten, dass in einem ungerichteten Graphen jede Baumkante zweimal gefunden wird, einmal in Richtung vom Vater zum Kind und einmal in umgekehrter Richtung. Im zweiten Fall endet die Kante zwar in einem bereits besuchten Knoten (dem Vater), aber es entsteht dadurch kein Zyklus. Den Vaterknoten müssen wir deshalb überspringen, wenn wir über die Nachbarn iterieren:&lt;br /&gt;
&lt;br /&gt;
 def undirected_cycle_test(graph):         # Annahme: der Graph ist zusammenhängend&lt;br /&gt;
                                           # (andernfalls führe den Algorithmus für jede Zusammenhangskomponente aus)&lt;br /&gt;
     visited = [False]*len(graph)          # Flags für bereits besuchte Knoten&lt;br /&gt;
     &lt;br /&gt;
     def visit(node, from_node):           # rekursive Hilfsfunktion: gibt True zurück, wenn Zyklus gefunden wurde&lt;br /&gt;
         if not visited[node]:             # wenn node noch nicht besucht wurde&lt;br /&gt;
             visited[node] = True          # markiere node als besucht&lt;br /&gt;
             for neighbor in graph[node]:  # besuche die Nachbarn ...&lt;br /&gt;
                 if neighbor == from_node: # ... aber überspringe den Vaterknoten&lt;br /&gt;
                     continue&lt;br /&gt;
                 if visit(neighbor, node): # ... signalisiere, wenn rekursiv ein Zyklus gefunden wurde&lt;br /&gt;
                     return True&lt;br /&gt;
             return False                  # kein Zyklus gefunden&lt;br /&gt;
         else:&lt;br /&gt;
             return True                   # Knoten schon besucht =&amp;gt; Zyklus&lt;br /&gt;
     &lt;br /&gt;
     startnode = 0                         # starte bei beliebigem Knoten (hier: Knoten 0)&lt;br /&gt;
     return visit(startnode, startnode)    # gebe True zurück, wenn ein Zyklus gefunden wurde&lt;br /&gt;
&lt;br /&gt;
Wenn wir einen Zyklus finden, wird das weitere Traversieren das Graphen abgebrochen, denn ein Graph, der einmal zyklisch war, kann später nicht wieder azyklisch werden. Die notwendige Modifikation für unzusammenhängende Graphen erfolgt analog zum Algorithmus für die Detektion von Zusammenhangskomponenten, der im nächsten Abschnitt beschrieben wird.&lt;br /&gt;
&lt;br /&gt;
=== Finden von Zusammenhangskomponenten ===&lt;br /&gt;
&lt;br /&gt;
Das Auffinden und Markieren von Zusammenhangskomponenten (also maximalen zusammenhängenden Teilgraphen) ist eine grundlegende Aufgabe in ungerichteten, unzusammenhängenden Graphen (bei gerichteten Graphen sind die Verhältnisse wiederum komplizierter, siehe unten). Zwei Knoten u und v gehören zur selben Zusammenhangskomponente genau dann, wenn es einen Pfad von u nach v gibt (da der Graph ungerichtet ist, gibt es dann auch einen Pfad von v nach u). Man sagt auch, dass &amp;quot;v von u aus erreichbar&amp;quot; ist. Unzusammenhängende Graphen entstehen in der Praxis häufig, wenn die Kanten gewisse Relationen zwischen den Knoten kodieren: &lt;br /&gt;
* Wenn die Knoten Städte sind und die Kanten Straßen, sind diejenigen Städte in einer Zusammenhangskomponente, die per Auto von einander erreichbar sind. Unzusammenhängende Graphen entstehen hier beispielsweise, wenn eine Insel nicht durch eine Brücke erschlossen ist, wenn Grenzen gesperrt sind oder wenn ein Gebirge zu unwegsam ist, um Straßen zu bauen.&lt;br /&gt;
* Wenn Knoten Personen sind, und Kanten die Eltern-Kind-Relation beschreiben, so umfasst jede Zusammenhangskomponenten die Verwandten (auch wenn sie nur über viele &amp;quot;Ecken&amp;quot; verwandt sind).&lt;br /&gt;
* In der Bildverarbeitung entsprechen Knoten den Pixeln, und dieselben werden durch eine Kante verbunden, wenn sie zum selben Objekt gehören. Die Zusammenhangskomponenten entsprechen somit den Objekten im Bild (siehe Übungsaufgabe).&lt;br /&gt;
Die Zusammenhangskomponenten bilden eine Äquivalenzrelation. Folglich kann für jede Komponente ein Reprässentant bestimmt werden, der sogenannte &amp;quot;Anker&amp;quot;. Kennt jeder Knoten seinen Anker, ist das Problem der Zusammenhangskomponenten gelöst. &lt;br /&gt;
&lt;br /&gt;
==== Lösung mittels Tiefensuche ====&lt;br /&gt;
&lt;br /&gt;
Unser erster Ansatz ist, den Anker mit Hilfe der Tiefensuche zu finden. Anstelle der property map &amp;lt;tt&amp;gt;visited&amp;lt;/tt&amp;gt; verwenden wir diesmal eine property map &amp;lt;tt&amp;gt;anchors&amp;lt;/tt&amp;gt;, die für jeden Knoten die Knotennummer des zugehörigen Ankers angibt, oder &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt;, wenn der Knoten noch nicht besucht wurde. Dabei verwenden wir wieder die Konvention, dass Anker auf sich selbst zeigen. Für viele Anwendungen ist es außerdem (oder stattdessen) zweckmäßig, die Zusammenhangskomponenten mit einer laufenden Nummer, einem sogenannten &amp;lt;i&amp;gt;Label&amp;lt;/i&amp;gt;, durchzuzählen. Dann kann man zusätzliche Informationen zu jeder Komponente (beispielsweise deren Größe) einfach in einem Array speichern, das über die Labels indexiert wird. Die folgende Version der Tiefensuche bestimmt sowohl die Anker als auch die Labels für jeden Knoten:&lt;br /&gt;
&lt;br /&gt;
 def connectedComponents(graph):&lt;br /&gt;
        anchors = [None] * len(graph)             # property map für Anker jedes Knotens&lt;br /&gt;
        labels  = [None] * len(graph)             # property map für Label jedes Knotens&lt;br /&gt;
        &lt;br /&gt;
        def visit(node, anchor):&lt;br /&gt;
                &amp;quot;&amp;quot;&amp;quot;anchor ist der Anker der aktuellen ZK&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
                if anchors[node] is None:         # wenn node noch nicht besucht wurde:&lt;br /&gt;
                    anchors[node] = anchor        # setze seinen Anker&lt;br /&gt;
                    labels[node] = labels[anchor] # und sein Label&lt;br /&gt;
                    for neighbor in graph[node]:  # und besuche die Nachbarn&lt;br /&gt;
                        visit(neighbor, anchor)&lt;br /&gt;
        &lt;br /&gt;
        current_label = 0                         # Zählung der ZK beginnt bei 0&lt;br /&gt;
        for node in xrange(len(graph)):&lt;br /&gt;
            if anchors[node] is None:             # Anker noch nicht bekannt =&amp;gt; neue ZK gefunden&lt;br /&gt;
                labels[node] = current_label      # Label des Ankers setzen&lt;br /&gt;
                visit(node, node)                 # Knoten der neuen ZK rekursiv suchen&lt;br /&gt;
                current_label += 1                # Label für die nächste ZK hochzählen&lt;br /&gt;
        return anchors, labels&lt;br /&gt;
Interessant ist hier die Schleife über alle Knoten des Graphen am Ende des Algorithmus, die bei den bisherigen Versionen der Tiefensuche nicht vorhanden war. Um ihre Funktionsweise zu verstehen, nehmen wir für den Moment an, dass der Graph zusammenhängend ist. Dann findet diese Schleife den ersten Knoten des Graphen und führt die Tiefensuche mit diesem Knoten als Startknoten aus. Sobald die Rekursion zurückkehrt, sind alle Knoten des Graphen besucht (weil der Graph ja zusammenhängend war), so dass die Schleife alle weiteren Knoten überspringt (die if-Anweisung liefert für keinen weiteren Knoten True). Bei unzusammenhängenden Graphen dagegen erreicht die Tiefensuche nur die Knoten derselben Komponente, die im weiteren Verlauf der Schleife übersprungen werden. Findet die if-Anweisung jetzt einen noch nicht besuchten Knoten, muss dieser folglich in einer neuen Komponente liegen. Wir verwenden diesen Knoten als Anker und bestimmen die übrigen Knoten dieser Komponente wiederum mit Tiefensuche.&lt;br /&gt;
&lt;br /&gt;
* Beispiel: ... &amp;lt;b&amp;gt; under construction &amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Man erkennt, dass die Tiefensuche nach dem &amp;lt;i&amp;gt;Anlagerungsprinzip&amp;lt;/i&amp;gt; vorgeht: Beginnend vom einem Startknoten (dem Anker) werden die Knoten der aktuellen Komponente nach und nach an den Tiefensuchbaum angehangen. Erst, wenn nichts mehr angelagert werden kann, geht der Algorithmus zur nächsten Komponente über.&lt;br /&gt;
&lt;br /&gt;
==== Lösung mittels Union-Find-Algorithmus ====&lt;br /&gt;
&lt;br /&gt;
Im Gegensatz zum Anlagerungsprinzip sucht der Union-Find-Algorithmus die Zusammenhangskomponenten mit dem &amp;lt;i&amp;gt;Verschmelzungsprinzip&amp;lt;/i&amp;gt;: Eingangs wird jeder Knoten als ein Teilgraph für sich betrachtet. Dann iteriert man über alle Kanten und verbindet deren Endknoten jeweils zu einem gemeinsamen Teilgraphen (falls die beiden Enden einer Kante bereits im selben Teilgraphen liegen, wird diese Kante ignoriert). Solange noch Kanten vorhanden sind, werden dadurch immer wieder Teilgraphen in größere Teilgraphen verschmolzen. Am Ende bleiben die maximalen zusammenhängenden Teilgraphen (also gerade die Zusammenhangskomponenten) übrig. Dieser Algorithmus kommt ohne Tiefensuche aus und ist daher in der Praxis oft schneller, allerdings auch etwas komplizierter zu implementieren.&lt;br /&gt;
&lt;br /&gt;
Der Schlüssel des Algorithmus ist eine Funktion &amp;lt;tt&amp;gt;findAnchor()&amp;lt;/tt&amp;gt;, die zu jedem Knoten den aktuellen Anker sucht. Der Anker existiert immer, da jeder Knoten von Anfang an zu einem Teilgraphen gehört (anfangs ist jeder Teilgraph trivial und besteht nur aus dem Knoten selbst). Die Verschmelzung wird realisiert, indem der Anker des einen Teilgraphen seine Rolle verliert und stattdessen der Anker des anderen Teilgraphen eingesetzt wird. &lt;br /&gt;
&lt;br /&gt;
Zur Verwaltung der Anker verwenden wir wieder eine property map &amp;lt;tt&amp;gt;anchors&amp;lt;/tt&amp;gt; mit der Konvention, dass die Anker auf sich selbst verweisen. Es wäre jedoch zu teuer, wenn man bei jeder Verschmelzung alle Anker-Einträge der beteiligten Knoten aktualisieren müsste, da jeder Knoten im Laufe des Algorithmus mehrmals seinen Anker wechseln kann. Statt dessen definiert man Anker rekursiv: Verweist ein Knoten auf einen Anker, der mittlerweile diese Rolle verloren hat, folgt man dem Verweis von diesem Knoten (dem ehemaligen Anker) weiter, bis man einen tatsächlichen Anker gefunden hat - erkennbar daran, dass er auf sich selbst verweist. Diese Suchfunktion kann folgendermassen implementiert werden:&lt;br /&gt;
&lt;br /&gt;
  def findAnchor(anchors, node):&lt;br /&gt;
      while node != anchors[node]:   # wenn node kein Anker ist&lt;br /&gt;
          node = anchors[node]       # ... verfolge die Ankerkette weiter&lt;br /&gt;
      return node&lt;br /&gt;
&lt;br /&gt;
Allerdings kann diese Kette im Laufe vieler Verschmelzungen sehr lang werden, so dass das Verfolgen der Kette teuer wird. Man vermeidet dies durch die sogenannte &amp;lt;i&amp;gt;Pfadkompression&amp;lt;/i&amp;gt;: Immer, wenn man den Anker gefunden hat, aktualisiert man den Eintrag am Anfang der Kette. Die Funktion &amp;lt;tt&amp;gt;findAnchor()&amp;lt;/tt&amp;gt; wird dadurch nur wenig komplizierter:&lt;br /&gt;
&lt;br /&gt;
  def findAnchor(anchors, node):&lt;br /&gt;
      start = node                   # wir merken uns den Anfang der Kette&lt;br /&gt;
      while node != anchors[node]:   # wenn node kein Anker ist&lt;br /&gt;
          node = anchors[node]       # ... verfolge die Ankerkette weiter&lt;br /&gt;
      anchors[start] = node          # Pfadkompression: aktualisiere den Eintrag am Anfang der Kette&lt;br /&gt;
      return node&lt;br /&gt;
&lt;br /&gt;
Man kann zeigen, dass die Ankersuche mit Pfadkompression zu einer fast konstanten amortisierten Laufzeit pro Aufruf führt.&lt;br /&gt;
&lt;br /&gt;
Um mit jeder Kante des (ungerichteten) Graphen nur maximal einmal eine Verschmelzung durchzuführen, betrachten wir jede Kante nur in der Richtung von der kleineren zur größeren Knotennummer, die umgekehrte Richtung wird ignoriert. Außerdem ist es zweckmäßig, bei jeder Verschmelzung denjenigen Anker mit der kleineren Knotennummer als neuen Anker zu übernehmen. Dann gilt für jede Zusammenhangskomponente, dass gerade der Knoten mit der kleinsten Knotennummer der Anker ist (genau wie bei der Lösung mittels Tiefensuche), was die weitere Analyse vereinfacht, z.B. die Zuordnung der Labels zu den Komponenten am Ende des Algorithmus. &lt;br /&gt;
&lt;br /&gt;
 def unionFindConnectedComponents(graph):&lt;br /&gt;
     anchors = range(len(graph))        # Initialisierung der property map: jeder Knoten ist sein eigener Anker&lt;br /&gt;
     &lt;br /&gt;
     for node in xrange(len(graph)):    # iteriere über alle Knoten&lt;br /&gt;
         for neighbor in graph[node]:   # ... und über deren ausgehende Kanten&lt;br /&gt;
             if neighbor &amp;lt; node:        # ignoriere Kanten, die in falscher Richtung verlaufen&lt;br /&gt;
                 continue&lt;br /&gt;
             # hier landen wir für jede Kante des Graphen genau einmal&lt;br /&gt;
             a1 = findAnchor(anchors, node)       # finde Anker ...&lt;br /&gt;
             a2 = findAnchor(anchors, neighbor)   # ... der beiden Endknoten&lt;br /&gt;
             if a1 &amp;lt; a2:                          # Verschmelze die beiden Teilgraphen&lt;br /&gt;
                 anchors[a2] = a1                 # (verwende den kleineren der beiden Anker als Anker des&lt;br /&gt;
             elif a2 &amp;lt; a1:                        #  entstehenden Teilgraphen. Falls node und neighbor &lt;br /&gt;
                 anchors[a1] = a2                 #  den gleichen Anker haben, waren sie bereits im gleichen&lt;br /&gt;
                                                  #  Teilgraphen, und es passiert hier nichts.)&lt;br /&gt;
     # Bestimme jetzt noch die Labels der Komponenten&lt;br /&gt;
     labels = [None]*len(graph)         # Initialisierung der property map für Labels&lt;br /&gt;
     current_label = 0                  # die Zählung beginnt bei 0&lt;br /&gt;
     for node in xrange(len(graph)):&lt;br /&gt;
         a = findAnchor(anchors, node)  # wegen der Pfadkompression zeigt jeder Knoten jetzt direkt auf seinen Anker&lt;br /&gt;
         if a == node:                  # node ist ein Anker&lt;br /&gt;
             labels[a] = current_label  # =&amp;gt; beginne eine neue Komponente&lt;br /&gt;
             current_label += 1         # und zähle Label für die nächste ZK hoch&lt;br /&gt;
         else:&lt;br /&gt;
             labels[node] = labels[a]   # node ist kein Anker =&amp;gt; setzte das Label des Ankers&lt;br /&gt;
                                        # (wir wissen, dass labels[a] bereits gesetzt ist, weil &lt;br /&gt;
                                        #  der Anker immer der Knoten mit der kleinsten Nummer ist)&lt;br /&gt;
     return anchors, labels&lt;br /&gt;
 &lt;br /&gt;
* Beispiel: ... &amp;lt;b&amp;gt;under construction&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Kürzeste Wege (Pfade) ==&lt;br /&gt;
&lt;br /&gt;
Eine weitere grundlegende Aufgabe in Graphen ist die Bestimmung eines kürzesten Weges zwischen zwei gegebenen Knoten. Dies hat offensichtliche Anwendungen bei Routenplanern und Navigationssystemen und ist darüber hinaus wichtiger Bestandteil anderer Algorithmen, z.B. bei der Berechnung eines maximalen Flusses mit der [http://en.wikipedia.org/wiki/Edmonds%E2%80%93Karp_algorithm Methode von Edmonds und Karp].&lt;br /&gt;
&lt;br /&gt;
=== Kürzeste Wege in ungewichteten Graphen mittels Breitensuche ===&lt;br /&gt;
&lt;br /&gt;
Im Fall eines ungewichteten Graphen ist die Länge eines Weges einfach durch die Anzahl der durchlaufenen Kanten definiert. Daraus folgt, dass kürzeste Pfade mit einer leicht angepassten Version der Breitensuche gefunden werden können: Aufgrund des first in-first out-Verhaltens der Queue betrachtet die Breitensuche alle (erreichbaren) Knoten in der Reihenfolge ihres Abstandes vom Startknoten. Wenn wir den Zielknoten zum ersten Mal erreichen, und der gerade gefundene Weg vom Start zum Ziel hat die Länge L, muss dies der kürzeste Weg sein: Alle möglichen Wege der Länge L' &amp;amp;lt; L hat die Breitensuche ja bereits betrachtet, ohne dass dabei der Zielknoten erreicht wurde. Daraus folgt übrigens eine allgemeine Eigenschaft aller Algorithmen für kürzeste Wege: Wenn der kürzeste Weg vom Start zum Ziel die Länge L hat, finden diese Algorithmen als Nebenprodukt auch die kürzesten Wege zu allen Knoten, für die L' &amp;amp;lt; L gilt. &lt;br /&gt;
&lt;br /&gt;
Um den Algorithmus zu implementieren, passen wir die Breitensuche so an, dass anstelle der property map &amp;lt;tt&amp;gt;visited&amp;lt;/tt&amp;gt; eine property map &amp;lt;tt&amp;gt;parents&amp;lt;/tt&amp;gt; verwendet wird, die für jeden besuchten Knoten den Vaterknoten im Breitensuchbaum speichert. Durch Rückverfolgen der &amp;lt;tt&amp;gt;parent&amp;lt;/tt&amp;gt;-Kette können wir den Pfad vom Ziel zum Start rekonstruieren, und durch Umdrehen der Reihenfolge erhalten wir den gesuchten Pfad vom Start zum Ziel. Sobald der Zielknoten erreicht wurde, können wir die Breitensuche abbrechen (&amp;lt;tt&amp;gt;break&amp;lt;/tt&amp;gt;-Befehl in der ersten &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;-Schleife). Falls der gegebene Graph unzusammenhängend ist, kann es passieren, dass gar kein Weg gefunden wird, weil Start und Ziel in verschiedenen Zusammenhangskomponenten liegen. Dies erkennen wir daran, dass die Breitensuche beendet wurde, ohne den Zielknoten zu besuchen. Dann gibt die Funktion statt eines Pfades dern Wert &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt; zurück:&lt;br /&gt;
&lt;br /&gt;
  from collections import deque&lt;br /&gt;
  &lt;br /&gt;
  def shortestPath(graph, startnode, destination):&lt;br /&gt;
      parents = [None]*len(graph)      # Registriere für jeden Knoten den Vaterknoten im Breitensuchbaum&lt;br /&gt;
      parents[startnode] = startnode   # startnode ist die Wurzel des Baums =&amp;gt; verweist auf sich selbst&lt;br /&gt;
      &lt;br /&gt;
      q = deque()                      # Queue für die zu besuchenden Knoten&lt;br /&gt;
      q.append(startnode)              # Startknoten in die Queue einfügen&lt;br /&gt;
      &lt;br /&gt;
      while len(q) &amp;gt; 0:                # Solange es noch unbesuchte Knoten gibt&lt;br /&gt;
          node = q.popleft()           # Knoten aus der Queue nehmen (first in - first out)&lt;br /&gt;
          if node == destination:      # Zielknoten erreicht&lt;br /&gt;
              break                    #   =&amp;gt; Suche beenden&lt;br /&gt;
          for neighbor in graph[node]: # Besuche die Nachbarn von node&lt;br /&gt;
              if parents[neighbor] is None:  # aber nur, wenn sie noch nicht besucht wurden&lt;br /&gt;
                  parents[neighbor] = node   # setze node als Vaterknoten&lt;br /&gt;
                  q.append(neighbor)         # und füge neighbor in die Queue ein&lt;br /&gt;
      &lt;br /&gt;
      if parents[destination] is None: # Breitensuche wurde beendet ohne den Zielknoten zu besuchen&lt;br /&gt;
          return None                  # =&amp;gt; kein Pfad gefunden (unzusammenhängender Graph)&lt;br /&gt;
      &lt;br /&gt;
      # Pfad durch die parents-Kette zurückverfolgen und speichern&lt;br /&gt;
      path = [destination]&lt;br /&gt;
      while path[-1] != startnode:&lt;br /&gt;
          path.append(parents[path[-1]])&lt;br /&gt;
      path.reverse()     # Reihenfolge umdrehen (Ziel =&amp;gt; Start wird zu Start =&amp;gt; Ziel)&lt;br /&gt;
      return path        # gefundenen Pfad zurückgeben&lt;br /&gt;
&lt;br /&gt;
=== Gewichtete Graphen ===&lt;br /&gt;
&lt;br /&gt;
Das Problem der Suche nach kürzesten Wegen wird wesentlich interessanter und realistischer, wenn wir zu gewichteten Graphen übergehen:&lt;br /&gt;
&lt;br /&gt;
; Definition - kantengewichteter Graph&lt;br /&gt;
: Jeder Kante (s,t) des Graphen ist eine reelle oder natürliche Zahl w&amp;lt;sub&amp;gt;st&amp;lt;/sub&amp;gt; zugeordnet, die üblicherweise als ''Kantengewicht'' bezeichnet wird.&lt;br /&gt;
&lt;br /&gt;
; Definition - knotengewichteter Graph&lt;br /&gt;
: Jedem Knoten v des Graphen ist eine reelle oder natürliche Zahl w&amp;lt;sub&amp;gt;v&amp;lt;/sub&amp;gt; zugeordnet, die üblicherweise als ''Knotengewicht'' bezeichnet wird.&lt;br /&gt;
&lt;br /&gt;
Je nach Anwendung benötigt man Knoten- oder Kantengewichte oder auch beides zugleich. Wir beschränken uns in der Vorlesung auf kantengewichtete Graphen. Beispiele für die Informationen, die man durch Kantengewichte ausdrücken kann, sind&lt;br /&gt;
* wenn die Knoten Orte sind: Abstand von Anfangs- und Endknoten jeder Kante (z.B. Luftline oder Straßenentfernung), Fahrzeit zwischen den Orten&lt;br /&gt;
* wenn der Knoten ein Rohrnetzwerk beschreibt: Durchflusskapazität der einzelnen Rohre (für max-Flussprobleme), analog bei elektrischen Netzwerken: elektrischer Widerstand&lt;br /&gt;
* wenn die Knoten Währungen repräsentieren, können deren Wechselkurse durch Kantengewichte angegeben werden.&lt;br /&gt;
Bei einigen Beispielen ergeben sich unterschiedliche Kantengewichte, wenn eine Kante von s nach t anstatt von t nach s durchlaufen wird. Beispielsweise können sich die Fahrzeiten erheblich unterscheiden, wenn es in einer Richtung bergauf, in der anderen bergab geht, obwohl die Entfernung in beiden Fällen gleich ist. Hier ergibt sich natürlicherweise ein gerichteter Graph. In anderen Beispielen (z.B. bei Luftlinienentfernungen, in guter Näherung auch bei Straßenentfernungen) sind die Gewichte von der Richtung unabhängig, so dass wir ungerichtete Graphen verwenden können.&lt;br /&gt;
&lt;br /&gt;
Die Repräsentation der Kantengewichte im Programm richtet sich nach der Repräsentation des Graphen selbst. Am einfachsten ist wiederum die Adjazenzmatrix, die aber nur für dichte Graphen (&amp;lt;math&amp;gt;E = O(V^2)&amp;lt;/math&amp;gt;, mit E als Anzahl der Kanten und V als Anzahl der Knoten) effizient ist. Bei gewichteten Graphen gibt das Matrixelement a&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; das Gewicht der Kante i &amp;amp;rArr; j (wobei a&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = 0 gesetzt wird, wenn diese Kante nicht existiert). Wie zuvor gilt für ungerichtete Graphen a&amp;lt;sub&amp;gt;ij&amp;lt;/sub&amp;gt; = a&amp;lt;sub&amp;gt;ji&amp;lt;/sub&amp;gt; (symmetrische Matrix), während dies für gerichtete Graphen nicht gelten muss.&lt;br /&gt;
&lt;br /&gt;
Bei Graphen in Adjazenzlistendarstellung hat es sich bewährt, die Gewichte in einer &amp;lt;i&amp;gt;property map&amp;lt;/i&amp;gt; zu speichern. Weiter oben haben wir bereits property maps für Knoteneigenschaften (z.B. &amp;lt;tt&amp;gt;visited&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;anchors&amp;lt;/tt&amp;gt;) gesehen. Property maps für Kanten funktionieren ganz analog, allerdings muss man jetzt Paare von Knoten (nämlich Anfangs- und Endknoten der Kante) als Schlüssel verwenden und die Daten entsprechend in einem assoziativen Array ablegen:&lt;br /&gt;
  w = weights[(i,j)]   # Zugriff auf das Gewicht der Kante i &amp;amp;rArr; j&lt;br /&gt;
Alternativ könnte man auch die Graph-Datenstruktur selbst erweitern, aber dies ist weniger zu empfehlen, weil jeder Algorithmus andere Erwiterungen benötigt und damit die Datenstruktur sehr unübersichtlich würde.&lt;br /&gt;
&lt;br /&gt;
Der kürzeste Weg ist nun definiert als der Weg, bei dem die Summe der Kantengewichte minimal ist:&lt;br /&gt;
;Definition - Problem des kürzesten Weges&lt;br /&gt;
: Sei P die Menge aller Wege von u nach v, und &amp;lt;math&amp;gt;p \in P&amp;lt;/math&amp;gt; einer dieser Wege. Wenn der Grpah einfach ist (es also keine Mehrfachkanten zwischen denselben Knoten und keine Schleifen gibt), ist der Weg p durch die Folge der besuchten Knoten eindeutig bestimmt:&lt;br /&gt;
: &amp;lt;math&amp;gt;p : \ \ u = x_0 \rightarrow x_1 \rightarrow x_2 \rightarrow ... \rightarrow v = x_{n_p}&amp;lt;/math&amp;gt;&lt;br /&gt;
:wo &amp;lt;math&amp;gt;n_p&amp;lt;/math&amp;gt; die Anzahl der Kanten im Weg p ist. Seine Kosten W&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt; ergeben sich als Summer der Gewichte der einzelnen Kanten&lt;br /&gt;
: &amp;lt;math&amp;gt;W_p = \sum_{k=1}^{n_p} w_{x_{k-1}x_k}&amp;lt;/math&amp;gt;&lt;br /&gt;
: und ein kürzester Weg &amp;lt;math&amp;gt;p^* \in P&amp;lt;/math&amp;gt; ist ein Weg mit minimalen Kosten&lt;br /&gt;
: &amp;lt;math&amp;gt;p^* = \textrm{argmin}_{p\in P}\ \ W_p&amp;lt;/math&amp;gt;&lt;br /&gt;
: Das Problem des kürzesten Weges besteht darin, einen optimalen Weg &amp;lt;i&amp;gt;p*&amp;lt;/i&amp;gt; zwischen gegebenen Knoten u und v zu finden.&lt;br /&gt;
Die Lösung dieses Problems hängt davon ab, ob alle Kantengewichte positiv sind, oder ob es auch negative Kantengewichte gibt. In letzeren Fall ist es möglich, durch eine Verlängerung des Weges die Kosten zu redizieren, während sich im ersteren Fall die Kosten immer erhöhen, wenn man den Weg verlängert. &lt;br /&gt;
&lt;br /&gt;
Negative Gewichte treten z.B. bei den Währungsgraphen auf. Auf den ersten Blick entsprechen diese Graphen nicht den Anforderungen an das Problem des kürzesten Weges, weil Wechselkurse miteinander (und mit Geldbeträgen) multipliziert anstatt addiert werden. Man beseitigt diese Schwierigkeit aber leicht, indem man die &amp;lt;i&amp;gt;Logarithmen&amp;lt;/i&amp;gt; der Wechselkurse als Kantengewichte verwendet, wodurch sich die Multiplikation in eine Addition der Logarithmen verwandelt. Wechselkurse &amp;amp;lt; 1 führen nun zu negativen Gewichten. &lt;br /&gt;
&lt;br /&gt;
Interessant werden negative Gewichte vor allem in Graphen mit Zyklen. Dann kann es nämlich passieren, dass die Gesamtkosten eines Zyklus ebenfalls negativ sind. Jeder Weg, der den Zyklus enthält, hat dann Kosten von &amp;lt;math&amp;gt;-\infty&amp;lt;/math&amp;gt;, weil man den Zyklus beliebig oft durchlaufen und dadurch die Gesamtkosten immer weiter verkleinern kann:&lt;br /&gt;
&lt;br /&gt;
     /\		1. Durchlauf: Kosten -1&lt;br /&gt;
  1 /  \ -4	2. Durchlauf: Kosten -2&lt;br /&gt;
   /____\	etc.&lt;br /&gt;
      2&lt;br /&gt;
&lt;br /&gt;
Um hier nicht in einer Endlosschleife zu landen, benötigt man spezielle Algorithmen, die mit dieser Situation umgehen können. Der [http://de.wikipedia.org/wiki/Bellman-Ford-Algorithmus Algorithmus von Bellmann und Ford] beispielsweise bricht die Suche nach dem kürzesten Weg ab, sobald er einen negativen Zyklus entdeckt, aber andernfalls kann er negative Gewichte problemlos verarbeiten. &lt;br /&gt;
&lt;br /&gt;
Die Detektion negativer Zyklen hat wiederum eine interessante Anwendung bei Währungsgraphen: Ein Zyklus bedeutet hier, dass man Geld über mehrere Stufen von einer Währung in die nächste und am Schluß wieder in die Originalwährung umtauscht, und ein negativer Zyklus führt dazu, dass man am Ende &amp;lt;i&amp;gt;mehr&amp;lt;/i&amp;gt; Geld besitzt als am Anfang (damit negative Zyklen wirklich einen Gewinn bedeuten und keinen Verlust, müssen die Wechselkurse vor der Logarithmierung in [http://de.wikipedia.org/wiki/Wechselkurs#Nominaler_Wechselkurs Preisnotierung] angegeben sein). Bei Privatpersonen ist dies ausgeschlossen, weil die Umtauschgebühren den möglichen Gewinn mehr als aufzehren. Banken mit direktem weltweitem Börsenzugang hingegen unternehmen große Anstrengungen, um solche negativen Zyklen möglichst schnell (nämlich vor der Konkurrenz) zu entdecken und auszunutzen. Diese Geschäftsmethode bezeichnet man als [http://de.wikipedia.org/wiki/Arbitrage Arbitrage] und die Existenz eines negativen Zyklus als Arbitragegelegenheit. Durch die Kursschwankungen (und durch die ausgleichende Wirkung der Arbitragegeschäfte selbst) existieren die Arbitragegelegenheiten nur für kurze Zeit, und ihre Detektion erfordert leistungsfähige Echtzeitalgorithmen.&lt;br /&gt;
&lt;br /&gt;
In dieser Vorlesung beschränken wir uns hingegen auf Graphen mit ausschließlich positiven Gewichten. In diesem Fall ist der Algorithmus von Dijkstra die Methode der Wahl, weil er wesentlich schneller arbeitet als der Bellmann-Ford-Algorithmus.&lt;br /&gt;
&lt;br /&gt;
=== Algorithmus von Dijkstra ===&lt;br /&gt;
&lt;br /&gt;
==== Edsger Wybe Dijkstra ====&lt;br /&gt;
&lt;br /&gt;
geb. 11. Mai 1930 in Rotterdam&lt;br /&gt;
&lt;br /&gt;
ges. 06. August 2002&lt;br /&gt;
&lt;br /&gt;
Dijkstra war ein niederländischer Informatiker und Wegbereiter der strukturierten Programmierung. 1972 erhielt er für seine Leistung in der Technik und Kunst der Programmiersprachen den Turing Award, der jährlich von der Association for Computing Machinery (ACM) an Personen verliehen wird, die sich besonders um die Entwicklung der Informatik verdient gemacht haben. Zu seinen Beiträgen zur Informatik gehören unter anderem der Dijkstra-Algorithmus zur Berechnung des kürzesten Weges in einem Graphen sowie eine Abhandlung über den go-to-Befehl und warum er nicht benutzt werden sollte. Der go-to-Befehl war in den 60er und 70er Jahren weit verbreitet, führte aber zu Spaghetti-Code. In seinem berühmten Paper &amp;quot;A Case against the GO TO Statement&amp;quot;[http://www.cs.utexas.edu/users/EWD/ewd02xx/EWD215.PDF], das als Brief mit dem Titel &amp;quot;Go-to statement considered harmful&amp;quot; veröffentlicht wurde, argumentiert Dijkstra, dass es umso schwieriger ist, dem Quellcode eines Programmes zu folgen, je mehr go-to-Befehle darin enthalten sind und zeigt, dass man auch ohne diesen Befehl gute Programme schreiben kann.&lt;br /&gt;
&lt;br /&gt;
==== Algorithmus ====&lt;br /&gt;
&lt;br /&gt;
Der Dijkstra-Algorithmus für kürzeste Wege ist dem oben vorgestellten Algorithmus &amp;lt;tt&amp;gt;shortestPath()&amp;lt;/tt&amp;gt; auf der Basis von Breitensuche sehr ähnlich. Insbesondere gilt auch hier, dass neben dem kürzesten Weg vom Start zum Ziel auch alle kürzesten Wege gefunden werden, deren Endknoten dem Start näher sind als der Zielknoten. Aufgrund der Kantengewichte gibt es aber einen wichtigen Unterschied: Der erste gefundene Weg zu einem Knoten ist nicht mehr notwendigerweise der kürzeste. Wir bestimmen deshalb für jeden Knoten mehrere Kandidatenwege und verwenden eine Prioritätswarteschlange (statt einer einfachen First in - First out - Queue), um diese Wege nach ihrer Länge zu sortieren. Die Kandidatenwege für einen gegebenen Knoten werden unterschieden, indem wir auch den Vorgängerknoten im jeweiligen Weg speichern. Wenn ein Knoten &amp;lt;i&amp;gt;erstmals&amp;lt;/i&amp;gt; an die Spitze der Prioritätswarteschlange gelangt, haben wir den kürzesten Weg zu diesem Knoten gefunden (das wird weiter unten formal bewiesen), und der Vorgänger des Knotens in diesem Weg wird zu seinem Vaterknoten. Erscheint derselbe Knoten später nochmals an der Spitze der Prioritätswarteschlange, handelt es sich um einen Kandidatenweg, der sich nicht als kürzester erwiesen hat und deshalb ignoriert werden kann. Wir erkennen dies leicht daran, dass der Vaterknoten in der property map &amp;lt;tt&amp;gt;parents&amp;lt;/tt&amp;gt; bereits gesetzt ist. &lt;br /&gt;
&lt;br /&gt;
Eine geeignete Datenstruktur für die Prioritätswarteschlange wird durch das Python-Modul [http://docs.python.org/library/heapq.html heapq] realisiert. Es verwendet ein normales Pythonarray als unterliegende Repräsentation für einen Heap und stellt effiziente &amp;lt;tt&amp;gt;heappush&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;heappop&amp;lt;/tt&amp;gt;-Funktionen zur Verfügung. Dies entspricht genau unserer Vorgehensweise im Kapitel [[Prioritätswarteschlangen]]. Als Datenelement erwartet die Funktion &amp;lt;tt&amp;gt;heappush&amp;lt;/tt&amp;gt; ein Tupel, dessen erstes Element die Priorität sein muss. Die übrigen Elemente des Tupels (und damit auch deren Anzahl) können je nach Anwendung frei festgelegt werden. Wir legen fest, dass das zweite Element den Endknoten des betrachteten Weges und das dritte den Vorgängerknoten speichert. &lt;br /&gt;
&lt;br /&gt;
Die Kantengewichte werden dem Algorithmus in der property map &amp;lt;tt&amp;gt;weights&amp;lt;/tt&amp;gt; übergeben:&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;code python&amp;gt;&lt;br /&gt;
    import heapq	                  # heapq implementiert die Funktionen für Heaps&lt;br /&gt;
    &lt;br /&gt;
    def dijkstra(graph, weights, startnode, destination):&lt;br /&gt;
        parents = [None]*len(graph)       # registriere für jeden Knoten den Vaterknoten im Pfadbaum&lt;br /&gt;
      &lt;br /&gt;
        q = []                            # Array q wird als Heap verwendet&lt;br /&gt;
        &amp;lt;font color=red&amp;gt;heapq.heappush(q, (0.0, startnode, startnode))&amp;lt;/font&amp;gt;  # Startknoten in Heap einfügen&lt;br /&gt;
      &lt;br /&gt;
        while len(q) &amp;gt; 0:                 # solange es noch Knoten im Heap gibt:&lt;br /&gt;
            &amp;lt;font color=red&amp;gt;length, node, predecessor = heapq.heappop(q)&amp;lt;/font&amp;gt;   # Knoten aus dem Heap nehmen&lt;br /&gt;
            &amp;lt;font color=red&amp;gt;if parents[node] is not None:&amp;lt;/font&amp;gt; # parent ist schon gesetzt =&amp;gt; es gab einen anderen, kürzeren Weg&lt;br /&gt;
                &amp;lt;font color=red&amp;gt;continue&amp;lt;/font&amp;gt;                  #   =&amp;gt; wir können diesen Weg ignorieren&lt;br /&gt;
            &amp;lt;font color=red&amp;gt;parents[node] = predecessor&amp;lt;/font&amp;gt;   # parent setzen&lt;br /&gt;
            if node == destination:       # Zielknoten erreicht&lt;br /&gt;
                break                     #   =&amp;gt; Suche beenden&lt;br /&gt;
            for neighbor in graph[node]:  # die Nachbarn von node besuchen,&lt;br /&gt;
                if parents[neighbor] is None:   # aber nur, wenn ihr kürzester Weg noch nicht bekannt ist&lt;br /&gt;
                    &amp;lt;font color=red&amp;gt;newLength = length + weights[(node,neighbor)]&amp;lt;/font&amp;gt;   # berechne Pfadlänge zu neighbor              &lt;br /&gt;
                    &amp;lt;font color=red&amp;gt;heapq.heappush(q, (newLength, neighbor, node))&amp;lt;/font&amp;gt;  # und füge neighbor in den Heap ein&lt;br /&gt;
      &lt;br /&gt;
        if parents[destination] is None:  # Suche wurde beendet ohne den Zielknoten zu besuchen&lt;br /&gt;
            return None, None             # =&amp;gt; kein Pfad gefunden (unzusammenhängender Graph)&lt;br /&gt;
      &lt;br /&gt;
        # Pfad durch die parents-Kette zurückverfolgen und speichern&lt;br /&gt;
        path = [destination]&lt;br /&gt;
        while path[-1] != startnode:&lt;br /&gt;
            path.append(parents[path[-1]])&lt;br /&gt;
        path.reverse()                    # Reihenfolge umdrehen (Ziel =&amp;gt; Start wird zu Start =&amp;gt; Ziel)&lt;br /&gt;
        return path, length               # gefundenen Pfad und dessen Länge zurückgeben&lt;br /&gt;
  &amp;lt;/code&amp;gt;&lt;br /&gt;
Die wesentlichen Unterschiede zur Breitensuche sind im Code rot markiert: Anstelle der Queue verwenden wir jetzt einen Heap, und der Startknoten wird mit Pfadlänge 0 als erstes eingefügt. In der Schleife &amp;lt;tt&amp;gt;while len(q) &amp;gt; 0:&amp;lt;/tt&amp;gt; wird jeweils der Knoten &amp;lt;tt&amp;gt;node&amp;lt;/tt&amp;gt; mit der aktuell kürzesten Pfadlänge aus dem Heap entfernt. Die Pfadlänge vom Start zu diesem Knoten wird in der Variable &amp;lt;tt&amp;gt;length&amp;lt;/tt&amp;gt; gespeichert, sein Vorgänger in der Variable &amp;lt;tt&amp;gt;predecessor&amp;lt;/tt&amp;gt;. Wenn der aktuelle Weg nicht der kürzeste ist (&amp;lt;tt&amp;gt;parents[node]&amp;lt;/tt&amp;gt; war bereits gesetzt), wird dieser Weg ignoriert. Andernfalls werden die property map &amp;lt;tt&amp;gt;parents&amp;lt;/tt&amp;gt; aktualisiert und die Nachbarn von &amp;lt;tt&amp;gt;node&amp;lt;/tt&amp;gt; besucht. Beim Scannen der Nachbarn berechnen wir zunächst die Länge &amp;lt;tt&amp;gt;newLength&amp;lt;/tt&amp;gt; das Weges &amp;lt;tt&amp;gt;startnode =&amp;amp;gt; node =&amp;amp;gt; neighbor&amp;lt;/tt&amp;gt; als Summe von &amp;lt;tt&amp;gt;length&amp;lt;/tt&amp;gt; und dem Gewicht der Kante &amp;lt;tt&amp;gt;(node, neighbode)&amp;lt;/tt&amp;gt;. Diese Länge wird beim Einfügen des Nachbarknotens in den Heap zur Priorität des aktuellen Weges.&lt;br /&gt;
&lt;br /&gt;
Die wichtigsten Prinzipien des Dijkstra-Algorithmus noch einmal im Überblick:&lt;br /&gt;
* Der Dijkstra-Algorithmus ist Breitensuche mit Prioritätswarteschlange (Heap) statt einer einfache Warteschlange (Queue).&lt;br /&gt;
* Die Prioritätswarteschlange speichert alle Wege, die bereits gefunden worden sind und ordnet sie aufsteigend nach ihrer Länge. &lt;br /&gt;
* Das Sortieren (und damit der ganze Algorithmus) funktioniert nur mit positiven Kantengewichten korrekt.&lt;br /&gt;
* Da ein Knoten auf mehreren Wegen erreichbar sein kann, kann er auch mehrmals im Heap sein. &lt;br /&gt;
* Wenn ein Knoten &amp;lt;i&amp;gt;erstmals&amp;lt;/i&amp;gt; aus der Prioritätswarteschlange entnommen wird, ist der gefundene Weg der kürzeste zu diesem Knoten. Andernfalls wird der Weg ignoriert.&lt;br /&gt;
* Wenn der Knoten &amp;lt;tt&amp;gt;destination&amp;lt;/tt&amp;gt; aus dem Heap entnommen wird, ist der kürzeste Weg von Start nach Ziel gefunden, und die Suche kann beendet werden.&lt;br /&gt;
In unserer Implementation können, wie gesagt, mehrere Wege zum selben Knoten gleichzeitig in der Prioritätswarteschlange sein. Im Prinzip wäre es auch möglich, immer nur den besten zur Zeit bekannten Weg zu jedem Enknoten in der Prioritätswarteschlange zu halten - sobald ein besserer Kandidat gefunden wird, ersetzt er den bisherigen Kandidaten, anstatt zusätzlich eingefügt zu werden. Dies erfordert aber eine wesentlich kompliziertere Prioritätswarteschlange, die eine effiziente &amp;lt;tt&amp;gt;updatePriority&amp;lt;/tt&amp;gt;-Funktion anbietet, ohne dass dadurch eine signifikante Beschleunigung erreicht wird. Deshalb verfolgen wir diesen Ansatz nicht.&lt;br /&gt;
&lt;br /&gt;
==== Beispiel ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;under construction&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Image:Bsp.jpg]]&lt;br /&gt;
&lt;br /&gt;
==== Komplexität von Dijkstra ====&lt;br /&gt;
&lt;br /&gt;
Zur Analyse der Komplexität nehmen wir an, dass der Graph V Knoten und E Kanten hat. Die Initialisierung der property map &amp;lt;tt&amp;gt;parents&amp;lt;/tt&amp;gt; am Anfang der Funktion hat offensichtlich Komplexität O(V), weil Speicher für V Knoten allokiert wird. Der Code am Ende der Funktion, der aus der property map &amp;lt;tt&amp;gt;parents&amp;lt;/tt&amp;gt; den Pfad extrahiert, hat ebenfalls die Komplexität O(V), weil der Pfad im ungünstigen Fall sämtliche Knoten des Graphen umfasst. Beides wird durch die Komplexität der Hauptschleife dominiert, zu deren Analyse wir den folgenden Codeausschnitt genauer anschauen wollen:&lt;br /&gt;
&lt;br /&gt;
      while len(q) &amp;gt; 0:&lt;br /&gt;
           ... # 1&lt;br /&gt;
           if parents[node] is not None: &lt;br /&gt;
               continue                  &lt;br /&gt;
           parents[node] = predecessor&lt;br /&gt;
           ... # 2&lt;br /&gt;
Wir erkennen, dass der Codeabschnitt &amp;lt;tt&amp;gt;# 2&amp;lt;/tt&amp;gt; für jeden Knoten höchstens einmal erreicht werden kann: Da &amp;lt;tt&amp;gt;parents[node]&amp;lt;/tt&amp;gt; beim ersten Durchlauf gesetzt wird, kann die &amp;lt;tt&amp;gt;if&amp;lt;/tt&amp;gt;-Abfrage beim gleichen Knoten nie wieder &amp;lt;tt&amp;gt;False&amp;lt;/tt&amp;gt; liefern, und das nachfolgende &amp;lt;tt&amp;gt;continue&amp;lt;/tt&amp;gt; bewirkt, dass der Abschnitt &amp;lt;tt&amp;gt;# 2&amp;lt;/tt&amp;gt; dann übersprungen wird. Man sagt auch, dass jeder Knoten &amp;lt;i&amp;gt;höchstens einmal expandiert&amp;lt;/i&amp;gt; wird, auch wenn er mehrmals im Heap war. &lt;br /&gt;
&lt;br /&gt;
Der Codeabschnitt &amp;lt;tt&amp;gt;# 2&amp;lt;/tt&amp;gt; selbst enthält eine Schleife über alle ausgehenden Kanten des Knotens &amp;lt;tt&amp;gt;node&amp;lt;/tt&amp;gt;. Im ungünstigsten Fall iterieren wir bei &amp;lt;i&amp;gt;allen&amp;lt;/i&amp;gt; Knoten über &amp;lt;i&amp;gt;alle&amp;lt;/i&amp;gt; ausgehenden Kanten, aber das sind gerade alle Kanten des Graphen je einmal in den beiden möglichen Richtungen. Die Funktion &amp;lt;tt&amp;gt;heappush&amp;lt;/tt&amp;gt; wird sogar höchstens E Mal aufgerufen, weil eine Kante nur in den Heap eingefügt wird, wenn der kürzeste Weg der jeweiligen Endknotens noch nicht bekannt ist (siehe die &amp;lt;tt&amp;gt;if&amp;lt;/tt&amp;gt;-Abfrage in der &amp;lt;tt&amp;gt;for&amp;lt;/tt&amp;gt;-Schleife), und das ist nur ein einer Richtung möglich. Dies hat zwei Konsequenzen:&lt;br /&gt;
* Die Schleife &amp;lt;tt&amp;gt;while len(q) &amp;gt; 0:&amp;lt;/tt&amp;gt; wird nur so oft ausgeführt, wie Elemente im Heap sind, also höchstens E Mal. Das gleiche gilt für den Codeabschnitt &amp;lt;tt&amp;gt;# 1&amp;lt;/tt&amp;gt;, der das &amp;lt;tt&amp;gt;heappop&amp;lt;/tt&amp;gt; enthält.&lt;br /&gt;
* Die Operationen &amp;lt;tt&amp;gt;heappush&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;heappop&amp;lt;/tt&amp;gt; haben logarithmische Komplexität in der Größe des Heaps, sind also in &amp;lt;math&amp;gt;O(\log\,E)&amp;lt;/math&amp;gt;. In einfachen Graphen gilt aber &amp;lt;math&amp;gt;E = O(V^2)&amp;lt;/math&amp;gt;, so dass sich die Komplexität der Heapoperationen vereinfacht zu &amp;lt;math&amp;gt;O(\log\,E)=O(\log\,V^2)=O(2\log\,V)=O(\log\,V)&amp;lt;/math&amp;gt;.&lt;br /&gt;
Zusammenfassend gilt: &amp;lt;tt&amp;gt;heappush&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;heappop&amp;lt;/tt&amp;gt; werden maximal E Mal aufgerufen und haben eine Komplexität in &amp;lt;math&amp;gt;O(\log\,V)&amp;lt;/math&amp;gt;. Folglich hat der Algorithmus von Dijkstra die Komplexität:&lt;br /&gt;
:&amp;lt;math&amp;gt;O(E\,\log\,V)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Vergleich mit Breitensuche und Tiefensuche ====&lt;br /&gt;
&lt;br /&gt;
Der Dijkstra-Algorithmus ist eng mit der Breiten- und Tiefensuche verwandt - man kann diese Algorithmen aus dem Dijkstra-Algorithmus gewinnen, indem man einfach die Regel zur Festlegung der Prioritäten ändert. Anstelle der Länge des Pfades verwenden wir als Priorität den Wert eine Zählvariable &amp;lt;tt&amp;gt;count&amp;lt;/tt&amp;gt;, die nach jeder Einfügung in den Heap (also nach jedem Aufruf von &amp;lt;tt&amp;gt;heappush&amp;lt;/tt&amp;gt;) aktualisiert wird. Zählen wir die Variable hoch, haben die zuerst eingefügten Kanten die höchste Priorität, der Heap verhält sich also wie eine Queue (First in-First out), und wir erhalten eine Breitensuche. Zählen wir die Variable hingegen (von E beginnend) herunter, haben die zuletzt eingefügten Kanten höchste Priorität. Der Heap verhält sich dann wie ein Stack (Last in-First out), und wir bekommen Tiefensuche. Statt eines Heaps plus Zählvariable kann man jetzt natürlich direkt eine Queue bzw. einen Stack verwenden. Dadurch fällt der Aufwand &amp;lt;math&amp;gt;O(\log\,V)&amp;lt;/math&amp;gt; für die Heapoperationen weg und wird durch die effizienten O(1)-Operationen von Queue bzw. Stack ersetzt. Damit erhalten wir für Breiten- und Tiefensuche die schon bekannte Komplexität O(E).&lt;br /&gt;
&lt;br /&gt;
==== Korrektheit von Dijkstra ====&lt;br /&gt;
&lt;br /&gt;
Wir beweisen mittels vollständiger Induktion die Schleifen-Invariante: Falls &amp;lt;tt&amp;gt;parents[node]&amp;lt;/tt&amp;gt; gesetzt (also ungleich &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt;) ist, dann liefert das Zurückverfolgen des Weges von &amp;lt;tt&amp;gt;node&amp;lt;/tt&amp;gt; nach &amp;lt;tt&amp;gt;startnode&amp;lt;/tt&amp;gt; den kürzesten Weg. &lt;br /&gt;
;Induktionsanfang: &amp;lt;tt&amp;gt;parents[startnode]&amp;lt;/tt&amp;gt; ist als einziges gesetzt. Zurückverfolgen liefert den trivialen Weg &amp;lt;tt&amp;gt;[startnode]&amp;lt;/tt&amp;gt;, der mit Länge 0 offensichtlich der kürzeste Pfad ist &amp;amp;rarr; die Bedingung ist erfüllt.&lt;br /&gt;
;Induktionsschritt: Wir zeigen mit einem indirektem Beweis, dass wir immer einen kürzesten Weg bekommen, wenn &amp;lt;tt&amp;gt;parents[node]&amp;lt;/tt&amp;gt; gesetzt wird.&lt;br /&gt;
:Sei &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; = &amp;lt;tt&amp;gt;{v | parents[v] is not None}&amp;lt;/tt&amp;gt; die Menge aller Knoten, von denen wir den kürzesten Weg schon kennen (Induktionsvoraussetzung), und &amp;lt;tt&amp;gt;node&amp;lt;/tt&amp;gt; der Knoten, der sich gerade an der Spitze des Heaps befindet. Dann ist &amp;lt;tt&amp;gt;predecessor&amp;lt;/tt&amp;gt; der Vorgänger von &amp;lt;tt&amp;gt;node&amp;lt;/tt&amp;gt; im aktuellen Weg, und es muss &amp;lt;tt&amp;gt;predecessor&amp;lt;/tt&amp;gt;&amp;lt;math&amp;gt;\in S&amp;lt;/math&amp;gt; gelten, weil die Nachbarn von &amp;lt;tt&amp;gt;predecessor&amp;lt;/tt&amp;gt; (und damit auch der aktuelle &amp;lt;tt&amp;gt;node&amp;lt;/tt&amp;gt;) erst in den Heap eingefügt werden, wenn der kürzeste Weg für &amp;lt;tt&amp;gt;predecessor&amp;lt;/tt&amp;gt; gefunden wurde. Man beachte auch, dass alle Knoten, die noch nicht in &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; enthalten sind, weiter vom Start entfernt sind als alle Knoten in &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt;, weil alle neu in den Heap eingefügten Wege länger sind als der kürzeste Weg des jeweiligen Vorgängers. &lt;br /&gt;
:Der indirekte Beweis nimmt jetzt an, dass der Weg &amp;lt;tt&amp;gt;node&amp;lt;/tt&amp;gt; &amp;amp;rarr; &amp;lt;tt&amp;gt;predecessor&amp;lt;/tt&amp;gt; &amp;amp;rarr; &amp;lt;tt&amp;gt;startnode&amp;lt;/tt&amp;gt; nicht der kürzeste Weg ist. Dann muss es einen anderen, kürzeren Weg &amp;lt;tt&amp;gt;node&amp;lt;/tt&amp;gt; &amp;amp;rarr; &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; &amp;amp;rarr; &amp;lt;tt&amp;gt;startnode&amp;lt;/tt&amp;gt; geben. Für den Vorgänger &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; in diesem Weg unterscheiden wir zwei Fälle:&lt;br /&gt;
:* &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt;&amp;lt;math&amp;gt;\in S&amp;lt;/math&amp;gt;: In diesem Fall ist die Länge des Weges &amp;lt;tt&amp;gt;node&amp;lt;/tt&amp;gt; &amp;amp;rarr; &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; &amp;amp;rarr; &amp;lt;tt&amp;gt;startnode&amp;lt;/tt&amp;gt; bereits bekannt, und dieser Weg ist in der Prioritätswarteschlange enthalten. Dann kann er aber nicht der kürzeste sein, denn an der Spitze der Warteschlange war nach Voraussetzung der Weg &amp;lt;tt&amp;gt;node&amp;lt;/tt&amp;gt; &amp;amp;rarr; &amp;lt;tt&amp;gt;predecessor&amp;lt;/tt&amp;gt; &amp;amp;rarr; &amp;lt;tt&amp;gt;startnode&amp;lt;/tt&amp;gt;.&lt;br /&gt;
:* &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt;&amp;lt;math&amp;gt;\notin S&amp;lt;/math&amp;gt;: Die Kosten des Weges &amp;lt;tt&amp;gt;node&amp;lt;/tt&amp;gt; &amp;amp;rarr; &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; &amp;amp;rarr; &amp;lt;tt&amp;gt;startnode&amp;lt;/tt&amp;gt; berechnen sich als &amp;lt;tt&amp;gt;Kosten(x &amp;amp;rarr; startnode) + weight[(x, node)]&amp;lt;/tt&amp;gt;, und die Kosten des Weges &amp;lt;tt&amp;gt;node&amp;lt;/tt&amp;gt; &amp;amp;rarr; &amp;lt;tt&amp;gt;predecessor&amp;lt;/tt&amp;gt; &amp;amp;rarr; &amp;lt;tt&amp;gt;startnode&amp;lt;/tt&amp;gt; sind analog &amp;lt;tt&amp;gt;Kosten(predecessor &amp;amp;rarr; startnode) + weight[(predecessor, node)]&amp;lt;/tt&amp;gt;. Aufgrund der Induktionsvoraussetzung gilt aber &amp;lt;tt&amp;gt;predecessor&amp;lt;/tt&amp;gt;&amp;lt;math&amp;gt;\in S&amp;lt;/math&amp;gt;, und somit &amp;lt;tt&amp;gt;Kosten(predecessor &amp;amp;rarr; startnode) &amp;amp;lt; Kosten(x &amp;amp;rarr; startnode)&amp;lt;/tt&amp;gt;, weil &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; andernfalls vor &amp;lt;tt&amp;gt;predecessor&amp;lt;/tt&amp;gt; an der Spitze des Heaps gewesen wäre, was mit der Annahme &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt;&amp;lt;math&amp;gt;\notin S&amp;lt;/math&amp;gt; unverträglich ist. Damit der Weg &amp;lt;tt&amp;gt;node&amp;lt;/tt&amp;gt; &amp;amp;rarr; &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; &amp;amp;rarr; &amp;lt;tt&amp;gt;startnode&amp;lt;/tt&amp;gt; trotzdem der kürzeste Weg sein kann, müsste &amp;lt;tt&amp;gt;Kosten(x &amp;amp;rarr; startnode) &amp;amp;lt; Kosten(node &amp;amp;rarr; startnode)&amp;lt;/tt&amp;gt; gelten, denn durch die Kante &amp;lt;tt&amp;gt;(x, node)&amp;lt;/tt&amp;gt; kommen ja noch Kosten hinzu. Das wäre aber nur möglich, wenn der Knoten &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; vor dem Knoten &amp;lt;tt&amp;gt;node&amp;lt;/tt&amp;gt; an die Spitze des Heaps gelangt, im Widerspruch zur Annahme, dass &amp;lt;tt&amp;gt;node&amp;lt;/tt&amp;gt; sich gerade an der Spitze des Heaps befindet. Somit kann die Behauptung, dass der Weg &amp;lt;tt&amp;gt;node&amp;lt;/tt&amp;gt; &amp;amp;rarr; &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; &amp;amp;rarr; &amp;lt;tt&amp;gt;startnode&amp;lt;/tt&amp;gt; der kürzeste Weg ist, nicht stimmen.&lt;br /&gt;
In beiden Fällen erhalten wir einen Widerspruch, und die Behauptung ist somit bewiesen. Da die Invariante insbesondere für den Weg zum Zielknoten &amp;lt;tt&amp;gt;destination&amp;lt;/tt&amp;gt; erfüllt ist, folgt daraus auch die Korrektheit des Algorithmus von Dijkstra.&lt;br /&gt;
&lt;br /&gt;
===  A*-Algorithmus - Wie kann man Dijkstra noch verbessern? ===&lt;br /&gt;
&lt;br /&gt;
Eine wichtige Eigenschaft des Dijkstra-Algorithmus ist, dass neben dem kürzesten Weg vom Start zum Ziel auch die kürzesten Wege zu allen Knoten berechnet werden, die näher am Startknoten liegen als das Ziel, obwohl uns diese Wege gar nicht interessieren. Sucht man beispielsweise in einem Graphen mit den Straßenverbindungen in Deutschland den kürzesten Weg von Frankfurt (Main) nach Dresden (ca. 460 km), werden auch die kürzesten Wege von Frankfurt nach Köln (190 km), Dortmund (220 km) und Stuttgart (210 km) und vielen anderen Städten gefunden. Aufgrund der geographischen Lage dieser Städte ist eigentlich von vornherein klar, dass sie mit dem kürzesten Weg nach Dresden nicht das geringste zu tun haben. Anders sieht es mit Erfurt (260 km) oder Suhl (210 km) aus - diese Städte liegen zwischen Frankfurt und Dresden und kommen deshalb als Zwischenstationen des gesuchten Weges in Frage.&lt;br /&gt;
&lt;br /&gt;
Damit Dijkstra korrekt funktioniert, würde es im Prinzip ausreichen, wenn man die kürzesten Wege nur für diejenigen Knoten ausrechnet, die auf dem kürzesten Weg vom Start zum Ziel liegen, denn nur diese Knoten braucht man, um den gesuchten Weg über die &amp;lt;tt&amp;gt;parent&amp;lt;/tt&amp;gt;-Kette zurückzuverfolgen. Das Problem ist nur, dass man diese Knoten erst kennt, wenn der Algorithmus fertig durchgelaufen ist. Schließt man Knoten zu früh von der Betrachtung aus, kommt am Ende möglicherweise nicht der korrekte kürzeste Weg heraus. &lt;br /&gt;
&lt;br /&gt;
Der A*-Algorithmus löst dieses Dilemma mit folgender Idee: Ändere die Prioritäten für den Heap so ab, dass unwichtige Knoten nur mit geringerer Wahscheinlichkeit expandiert werden, aber stelle gleichzeitig sicher, dass alle wichtigen Knoten (also diejenigen auf dem korrekten kürzesten Weg) auf jeden Fall expandiert werden. Es zeigt sich, dass man diese Idee umsetzen kann, wenn eine &amp;lt;i&amp;gt;Schätzung für den Restweg&amp;lt;/i&amp;gt; (also für die noch verbleibende Entfernung von jedem Knoten zum Ziel) verfügbar ist:&lt;br /&gt;
 rest = guess(neighbor, destination)&lt;br /&gt;
Diese Schätzung addiert man einfach zur wahren Länge des Weges &amp;lt;tt&amp;gt;startnode &amp;amp;rarr; node&amp;lt;/tt&amp;gt; dazu, um die verbesserte Priorität zu erhalten:&lt;br /&gt;
 priority = newLength + guess(neighbor, destination)&lt;br /&gt;
(Im originalen Dijkstra-Algorithmus wird als Priorität nur &amp;lt;tt&amp;gt;newLength&amp;lt;/tt&amp;gt; allein verwendet. Man beachte, dass man &amp;lt;tt&amp;gt;newLength&amp;lt;/tt&amp;gt; jetzt zusätzlich im Heap speichern muss, weil man es für die Expansion des Knotens später noch benötigt.)&lt;br /&gt;
&lt;br /&gt;
Damit sicher gestellt ist, dass der A*-Algorithmus immer noch die korrekten kürzesten Wege findet, darf die Schätzung den wahren Restweg &amp;lt;i&amp;gt;niemals überschätzen&amp;lt;/i&amp;gt;. Es muss immer gelten:&lt;br /&gt;
 0 &amp;lt;= guess(node, destination) &amp;lt;= trueDistance(node, destination)&lt;br /&gt;
Damit gilt insbesondere &amp;lt;tt&amp;gt;guess(destination, destination) = trueDistance(destination, destination) = 0&amp;lt;/tt&amp;gt;, an der Priorität des Knotens &amp;lt;tt&amp;gt;destination&amp;lt;/tt&amp;gt; ändert sich also nichts. Die Prioritäten aller anderen Knoten veschlechtern sich hingegen, weil zur bisherigen Priorität noch atwas addiert wird. Für die wichtigen Knoten auf dem kürzesten Weg vom Start nach Ziel gilt jedoch, dass deren neue Priorität immer noch besser ist als die Priorität des Zielknotens selbst. Für diese Knoten gilt nämlich&lt;br /&gt;
 falls node auf dem kürzesten Weg von startnode nach destination liegt:&lt;br /&gt;
 trueDistance(startnode, node) + guess(node, destination) &amp;lt;= trueDistance(startnode, destination)&lt;br /&gt;
weil der Weg von Start nach &amp;lt;tt&amp;gt;node&amp;lt;/tt&amp;gt; ein Teil des kürzesten Wegs von Start nach Ziel ist und die Restschätzung die wahre Entfernung immer unterschätzt. Diese Knoten werden deshalb stets vor dem Zielknoten expandiert, so dass wir die &amp;lt;tt&amp;gt;parent&amp;lt;/tt&amp;gt;-Kette immer noch korrekt zurückverfolgen können. Für alle anderen Knoten gilt idealerweise, dass die neue Priorität schlechter ist als die Priorität von &amp;lt;tt&amp;gt;destination&amp;lt;/tt&amp;gt;, so dass man sich diese irrelevanten Knotenexpansionen sparen kann.&lt;br /&gt;
&lt;br /&gt;
Für das Beispiel eines Straßennetzwerks bietet sich als Schätzung die Luftlinienentfernung an, weil Straßen nie kürzer sein können als die Luftlinie. Damit erreicht man in der Praxis deutliche Einsparungen. Generell gilt, dass der A*-Algorithmus im typischen Fall schneller ist als der Algorithmus von Dijkstra, aber man kann immer pathologische Fälle konstruieren, wo die Änderung der Prioritäten nichts bringt. Die Komplexität des A*-Algorithmus im ungünstigen Fall ist deshalb nach wie vor &amp;lt;math&amp;gt;O(E\,\log\,V)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=='''Minimaler Spannbaum'''==&lt;br /&gt;
'''(engl.: minimum spanning tree; abgekürzt: MST)'''&lt;br /&gt;
&lt;br /&gt;
[[Image:Minimum_spanning_tree.png‎ |thumb|200px|right|Ein minimal aufspannender Baum verbindet alle Punkte eines Graphen bei minimaler Kantenlänge ([http://de.wikipedia.org/wiki/Spannbaum Quelle])]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;u&amp;gt;''gegeben''&amp;lt;/u&amp;gt;: gewichteter Graph G, zusammenhängend&amp;lt;br/&amp;gt;&lt;br /&gt;
:&amp;lt;u&amp;gt;''gesucht''&amp;lt;/u&amp;gt;: Untermenge &amp;lt;math&amp;gt;E'\subseteq E&amp;lt;/math&amp;gt; der Kanten, so dass die Summe der Kantengewichte &amp;lt;math&amp;gt;\sum_{e\in E'} w_e&amp;lt;/math&amp;gt; minimal und der entstehende Graph G' zusammenhängend ist.&amp;lt;br/&amp;gt;&lt;br /&gt;
* G' definiert immer einen Baum, denn andernfalls könnte man eine Kante weglassen und dadurch die Summe &amp;lt;math&amp;gt;\sum_{e\in E'} w_e&amp;lt;/math&amp;gt; verringern, ohne dass sich am Zusammenhang von G' etwas ändert. &amp;lt;br/&amp;gt;&lt;br /&gt;
* Wenn der Graph G nicht zusammenhängend ist, kann man den Spannbaum für jede Zusammenhangskomponente getrennt ausrechnen. Man erhält dann einen aufspannenden Wald. &lt;br /&gt;
* Der MST ist ähnlich wie der Dijkstra-Algorithmus: Dort ist ein Pfad gesucht, bei dem die Summe der Gewichte über den Pfad minimal ist. Beim MST suchen wir eine Lösung, bei der die Summe der Gewichte über den ganzen Graphen minimal ist. &lt;br /&gt;
* Das Problem des MST ist nahe verwandt mit der Bestimmung der Zusammenhangskomponente, z.B. über den Tiefensuchbaum. Für die Zusammenhangskomponenten genügt allerdings ein beliebiger Baum, während beim MST ein minimaler Baum gesucht ist.&lt;br /&gt;
&lt;br /&gt;
=== Anwendungen ===&lt;br /&gt;
==== Wie verbindet man n gegebene Punkte mit möglichst kurzen Straßen (Eisenbahnen, Drähten [bei Schaltungen] usw.)?====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;0&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
|MST minimale Verbindung (Abb.1)&lt;br /&gt;
|MST = 2 (Länge = Kantengewicht)(Abb.2)&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| [[Image:mst.png]] &lt;br /&gt;
| [[Image:Gleichseitigesdreieck.png]]&lt;br /&gt;
|}&lt;br /&gt;
*In der Praxis: Die Festlegung, dass man nur die gegebenen Punkte verwenden darf, ist eine ziemliche starke Einschränkung. &lt;br /&gt;
&lt;br /&gt;
* Wenn man sich vorstellt, es sind drei Punkte gegeben, die als gleichseitiges Dreieck angeordnet sind, dann ist der MST (siehe Abb.2, schwarz gezeichnet) und hat die Länge 2. Man kann hier die Länge als Kantengewicht verwenden. &lt;br /&gt;
&lt;br /&gt;
* Wenn es erlaubt ist zusätzliche Punkte einzufügen, dann kann man in der Mitte einen neuen Punkt setzen &amp;lt;math&amp;gt;\rightarrow&amp;lt;/math&amp;gt; neuer MST (siehe Abb.2, orange gezeichnet).&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Höhe = &amp;lt;math&amp;gt;\frac{1}{2}\sqrt{3}&amp;lt;/math&amp;gt;, Schwerpunkt: teilt die Höhe des Dreiecks im Verhältnis 2:1; der Abstand von obersten Punkt bis zum neu eingeführten Punkt: &amp;lt;math&amp;gt;\frac{2}{3}h = \frac{\sqrt{3}}{3}&amp;lt;/math&amp;gt;, davon insgesamt 3 Stück, damit (gilt für den MST in orange eingezeichnet): MST = &amp;lt;math&amp;gt;3\left(\frac{1}{3}\right) \sqrt{3} = \sqrt{3} \approx 1,7&amp;lt;/math&amp;gt;&amp;lt;br\&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Damit ist der MST in orange kürzer als der schwarz gezeichnete MST. &amp;lt;br\&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;Folgerung: MST kann kürzer werden, wenn man einen Punkt dazu nimmt. &lt;br /&gt;
* Umgekehrt kann der MST auch kürzer werden, wenn man einen Punkt aus dem Graphen entfernt, aber wie das Beipiel des gleichseitigen Dreiecks zeigt, ist dies nicht immer der Fall.&lt;br /&gt;
&lt;br /&gt;
[[Image: bahn.png|Bahnstrecke Verbindung (Abb.3)]]&lt;br /&gt;
&lt;br /&gt;
* Methode der zusätzlichen Punkteinfügung hat man früher beim Bahnstreckenbau verwendet. Durch Einführung eines Knotenpunktes kann die Streckenlänge verkürzt werden (Dreiecksungleichung).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Bestimmung von Datenclustern ====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Image:cluster.png]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Daten (in der Abb.: Punkte) bilden Gruppen. &lt;br /&gt;
&lt;br /&gt;
* In der Abbildung hat man 2 verschiedene Messungen gemacht (als x- und y-Achse aufgetragen), bspw. Größe und Gewicht von Personen. Für jede Person i wird ein Punkt an der Koordinate (Größe&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;, Gewicht&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;) gezeichnet (siehe Bild a). Dies bezeichnet man als ''Scatter Plot''. Wenn bestimmte Wertkombinationen häufiger auftreten als andere, bilden sich mitunter Gruppen aus, bspw. eine Gruppe für &amp;quot;klein und schwer&amp;quot; etc.&lt;br /&gt;
&lt;br /&gt;
* Durch Verbinden der Punkte mittels eines MST (siehe Abbildung (b)) sieht man, dass es kurze (innerhalb der Gruppen) und lange Kanten (zwischen den Gruppen) gibt. &lt;br /&gt;
&lt;br /&gt;
* Wenn man geschickt eine Schwelle einführt und alle Kanten löscht, die länger sind als die Schwelle, dann bekommt man als Zusammenhangskomponente die einzelnen Gruppen. &lt;br /&gt;
&lt;br /&gt;
=== Algorithmen ===&lt;br /&gt;
&lt;br /&gt;
Genau wie bei der Bestimmung von Zusammenhangskomponenten kann man auch das MST-Problem entweder nach dem Anlagerungsprinzip oder nach dem Verschmelzungsprinzip lösen (dazu gibt es noch weitere Möglichkeiten, z.B. den [http://de.wikipedia.org/wiki/Algorithmus_von_Bor%C5%AFvka Algorithmus von Boruvka]). Der Anlagerungsalgorithmus für MST wurde zuerst von Prim beschrieben und trägt deshalb seinen Namen, der Verschmelzungsalgorithmus stammt von Kruskal. Im Vergleich zu den Algorithmen für Zusammenhangskomponenten ändert sich im wesentlichen nur die Reihenfolge, in der die Kanten betrachtet werden: Eine Prioritätswarteschlange stellt jetzt sicher, dass am Ende wirklich der Baum mit den geringstmöglichen Kosten herauskommt.&lt;br /&gt;
&lt;br /&gt;
====Algorithmus von Prim====&lt;br /&gt;
[http://de.wikipedia.org/wiki/Algorithmus_von_Prim Wikipedia (de)]&lt;br /&gt;
[http://en.wikipedia.org/wiki/Prim%27s_algorithm (en)]&lt;br /&gt;
&lt;br /&gt;
Der Algorithmus von Prim geht nach dem Anlagerungsprinzip vor (vgl. den Abschnitt [[Graphen_und_Graphenalgorithmen#Lösung mittels Tiefensuche|Zusammenhangskomponenten mit Tiefensuche]]): Starte an der Wurzel (ein willkürlich gewählter Knoten) und füge jeweils die günstigste Kante an die aktuellen Teillösung an, die keinen Zyklus verursacht. Die Sortierung der Kanten nach Priorität erfolgt analog zum Dijsktra-Algorithmus, aber die Definitionen, welche Kante die günstigste ist, unterscheiden sich. Die Konvention für die Bedeutung der Elemente des Heaps ist ebenfalls identisch: ein Tupel mit &amp;lt;tt&amp;gt;(priority, node, predecessor)&amp;lt;/tt&amp;gt;. Die folgende Implementation verdeutlicht sehr schön die Ähnlichkeit der beiden Algorithmen. Das Ergebnis wird als property map &amp;lt;tt&amp;gt;parents&amp;lt;/tt&amp;gt; zurückgegeben, in der für jeden Knoten sein Vorgänger im MST steht, wobei die Wurzel wie üblich auf sich selbst verweist.&lt;br /&gt;
&lt;br /&gt;
 import heapq&lt;br /&gt;
 &lt;br /&gt;
 def prim(graph, weights):                # Kantengewichte wie bei Dijkstra als property map&lt;br /&gt;
 	sum = 0.0                         # wird später das Gewicht des Spannbaums sein&lt;br /&gt;
 	start = 0                         # Knoten 0 wird willkürlich als Wurzel gewählt&lt;br /&gt;
        &lt;br /&gt;
        parents = [None]*len(graph)       # property map, die den resultierenden Baum kodiert&lt;br /&gt;
        parents[start] = start            # Wurzel zeigt auf sich selbst&lt;br /&gt;
        &lt;br /&gt;
 	heap = []                         # Heap für die Kanten des Graphen&lt;br /&gt;
 	for neighbor in graph[start]:     # besuche die Nachbarn von start&lt;br /&gt;
 	    heapq.heappush(heap, (weights[(start, neighbor)], neighbor, start))  # und fülle Heap &lt;br /&gt;
 	&lt;br /&gt;
        while len(heap) &amp;gt; 0:&lt;br /&gt;
 	    w, node, predecessor = heapq.heappop(heap) # hole billigste Kante aus dem Heap&lt;br /&gt;
            if parents[node] is not None: # die Kante würde einen Zyklus verursachen&lt;br /&gt;
                continue                  #   =&amp;gt; ignoriere diese Kante&lt;br /&gt;
            parents[node] = predecessor   # füge Kante in den MST ein&lt;br /&gt;
            sum += w                      # und aktualisiere das Gesamtgewicht &lt;br /&gt;
            for neighbor in graph[node]:  # besuche die Nachbarn von node&lt;br /&gt;
                if parents[neighbor] is None:  # aber nur, wenn kein Zyklus entsteht&lt;br /&gt;
                    heapq.heappush(heap, (weights[(node,neighbor)], neighbor, node)) # füge Kandidaten in Heap ein&lt;br /&gt;
        &lt;br /&gt;
 	return parents, sum               # MST und Gesamtgewicht zurückgeben&lt;br /&gt;
&lt;br /&gt;
====Algorithmus von Kruskal====&lt;br /&gt;
[http://de.wikipedia.org/wiki/Algorithmus_von_Kruskal Wikipedia (de)]&lt;br /&gt;
[http://en.wikipedia.org/wiki/Kruskal%27s_algorithm (en)]&lt;br /&gt;
&lt;br /&gt;
Die alternative Vorgehensweise ist das Verschmelzungsprinzip (vgl. den Abschnitt [[Graphen_und_Graphenalgorithmen#Lösung mittels Union-Find-Algorithmus|Zusammenhangskomponenten mit Union-Find-Algorithmus]]), das der Algorithmus von Kruskal verwendet. Jeder Knoten wird zunächst als trivialer Baum mit nur einem Knoten betrachtet, und alle Kanten werden aufsteigend nach Gewicht sortiert. Dann wird die billigste noch nicht betrachtete Kante in den MST eingefügt, falls sich dadurch kein Zyklus bildet (erkennbar daran, dass die Endknoten in verschiedenen Zusammenhangskomponenten liegen, das heisst verschiedene Anker haben). Da der fertige Baum (V-1) Kanten haben muss, wird dies (V-1) Mal zutreffen. Andernfalls wird diese Kante ignoriert. Anders ausgedrückt: Der Algorithmus beginnt mit ''V'' Bäumen; in (''V''-1) Verschmelzungsschritten kombiniert er jeweils zwei Bäume (unter Verwendung der kürzesten möglichen Kante), bis nur noch ein Baum übrig bleibt. Der einzige Unterschied zum einfachen Union-Find besteht darin, dass die Kanten in aufsteigender Reihenfolge betrachtet werden müssen, was wir hier durch eine Prioritätswarteschlange realisieren. Der Algorithmus von J.Kruskal ist seit 1956 bekannt. &lt;br /&gt;
&lt;br /&gt;
 def kruskal(graph, weights):&lt;br /&gt;
     anchors = range(len(graph))           # Initialisierung der property map: jeder Knoten ist sein eigener Anker&lt;br /&gt;
     results = []                          # result wird später die Kanten des MST enthalten    &lt;br /&gt;
     &lt;br /&gt;
     heap = []                             # Heap zum Sortieren der Kanten nach Gewicht&lt;br /&gt;
     for edge, w in weights.iteritems():   # alle Kanten einfügen&lt;br /&gt;
         heapq.heappush(heap, (w, edge))&lt;br /&gt;
     &lt;br /&gt;
     while len(heap) &amp;gt; 0:                  # solange noch Kanten vorhanden sind&lt;br /&gt;
         w, edge = heapq.heappop(heap)     # billigste Kante aus dem Heap nehmen&lt;br /&gt;
         a1 = findAnchor(anchors, edge[0]) # Anker von Startknoten der Kante&lt;br /&gt;
         a2 = findAnchor(anchors, edge[1]) # ... und Endknoten bestimmen&lt;br /&gt;
         if a1 != a2:                      # wenn die Knoten in verschiedenen Komponenten sind&lt;br /&gt;
             anchors[a2] = a1              # Komponenten verschmelzen&lt;br /&gt;
             result.append(edge)           # ... und Kante in MST einfügen&lt;br /&gt;
     &lt;br /&gt;
     return result                         # Kanten des MST zurückgeben&lt;br /&gt;
&lt;br /&gt;
Die Funktion &amp;lt;tt&amp;gt;findAnchor()&amp;lt;/tt&amp;gt; wurde im Abschnitt [[Graphen_und_Graphenalgorithmen#Lösung mittels Union-Find-Algorithmus|Zusammenhangskomponenten mit Union-Find-Algorithmus]] implementiert. Im Unterschied zum Algorithmus von Prim geben wir hier nicht die property map &amp;lt;tt&amp;gt;parents&amp;lt;/tt&amp;gt; zurück, sondern einfach eine Liste der Kanten im MST.&lt;br /&gt;
&lt;br /&gt;
Der Algorithmus eignet sich insbesondere für das Clusteringproblem, da der Schwellwert von vornerein als maximales Kantengewicht an den Algorithmus übergeben werden kann. Man hört mit dem Vereinigen auf, wenn das Gewicht der billigste Kante im Heap den Schwellwert überschreitet. Beim Algorithmus von Kruskal kann dann keine bessere Kante als der Schwellwert mehr kommen, da die Kanten vorher sortiert worden sind. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Komplexität:&amp;lt;/b&amp;gt; wie beim Dijkstra-Algorithmus, weil jede Kante genau einmal in den Heap kommt. Der Aufwand für das Sortieren ist somit &amp;lt;math&amp;gt;O\left(E\log E\right)&amp;lt;/math&amp;gt;, was sich zu &amp;lt;math&amp;gt;O \left(E\,\log\,V\right)&amp;lt;/math&amp;gt; reduziert, falls keine Mehrfachkanten vorhanden sind.&lt;br /&gt;
&lt;br /&gt;
=&amp;gt; geeignet für Übungsaufgabe&lt;br /&gt;
&lt;br /&gt;
== Algorithmen für gerichtete Graphen ==&lt;br /&gt;
&amp;lt;b&amp;gt;under construction&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Zur Erinnerung: in einem gerichteten Graphen sind die Kanten (i &amp;amp;rarr; j) und (j &amp;amp;rarr; i) voneinander verschieden, und eventuell existiert nur eine der beiden Richtungen. Im allgemeinen unterscheidet sich der [[Graphen_und_Graphenalgorithmen#transposed_graph|transponierte Graph]] G&amp;lt;sup&amp;gt;T&amp;lt;/sup&amp;gt; also vom Originalgraphen G. Beim Traversieren des Graphen und bei der Pfadsuche dürfen Kanten nur in passender Richtung verwendet werden. Bei gewichteten Graphen tritt häufig der Fall auf, dass zwar Kanten in beiden Richtungen existieren, diese aber unterschiedliche Gewichte haben.&lt;br /&gt;
&lt;br /&gt;
Gerichtete Graphen ergeben sich in natürlicher Weise aus vielen Anwendungsproblemen:&lt;br /&gt;
* Routenplanung&lt;br /&gt;
** Bei Straßennetzwerken enstehen gerichtete Graphen, sobald es Einbahnstraßen gibt.&lt;br /&gt;
** Verwendet man Gewichte, um die erwarteten Fahrzeiten entlang einer Straße zu kodieren, gibt es Asymmetrien z.B. dann, wenn Straßen in einer Richtung bergab, in der anderen bergauf befahren werden. Hier existieren zwar Kanten in beiden Richtungen, sie haben aber unterschiedliche Gewichte. Ähnliches gilt für Flüge: Durch den Gegenwind des Jetstreams braucht man von Frankfurt nach New York länger als umgekehrt von New York nach Frankfurt.&lt;br /&gt;
* zeitliche oder kausale Abhängigkeiten&lt;br /&gt;
** Wenn die Knoten Ereignissen repräsentieren, von denen einige die Ursache von anderen sind, diese wiederum die Ursache der nächsten usw., verbindet man die Knoten zweckmäßig durch gerichtete Kanten, die die Kausalitätsbeziehungen kodieren. Handelt es sich um logische &amp;quot;wenn-dann&amp;quot;-Regeln, erhält man einen Implikationengraph (siehe unten). Handelt es sich hingegen um Wahrscheinlichkeitsaussagen (&amp;quot;Wenn das Wetter schön ist, haben Studenten tendenziell gute Laune, wenn eine Prüfung bevorsteht eher schlechte usw.&amp;quot;), erhält man ein [http://de.wikipedia.org/wiki/Bayessches_Netz Bayessches Netz].&lt;br /&gt;
** Wenn bestimmte Aufgaben erst begonnen werden können, nachdem andere Aufgaben erledigt sind, erhält man einen Abhängigkeitsgraphen. Beispielsweise dürfen Sie erst an der Klausur teilnehmen, nachdem Sie die Übungsaufgaben gelöst haben, und Sie dürfen erst die Abschlussarbeit beginnen, nachdem Sie bestimmte Prüfungen bestanden haben. Ein anderes schönes Beispiel liefern die Regeln für das Anziehen weiter unten.&lt;br /&gt;
** Gerichtete Graphen kodieren die Abhängigkeiten zwischen Programmbibliotheken. Beispielsweise benötigt das Pythonmodul &amp;lt;tt&amp;gt;json&amp;lt;/tt&amp;gt; die internen Submodule &amp;lt;tt&amp;gt;json.encoder&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;json.decode&amp;lt;/tt&amp;gt; sowie das externe Modul &amp;lt;tt&amp;gt;decimal&amp;lt;/tt&amp;gt;. Die Submodule benötigen wiederum die externen Module &amp;lt;tt&amp;gt;re&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;sys&amp;lt;/tt&amp;gt;, das Modul &amp;lt;tt&amp;gt;decimal&amp;lt;/tt&amp;gt; braucht &amp;lt;tt&amp;gt;copy&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;collections&amp;lt;/tt&amp;gt; usw.&lt;br /&gt;
** Das Internet kann als gerichteter Graph dargestellt werden, wobei die Webseiten die Knoent, und die Hyperlinks die Kanten sind.&lt;br /&gt;
* Squence Alignment&lt;br /&gt;
** Eine gute Rechtschreibprüfung markiert nicht nur fehlerhafte Wörter, sondern macht auch plausible Vorschläge, was eigentlich gemeint gewesen sein könnte. Dazu muss sie das gegebene Wort mit den Wörtern eines Wörterbuchs vergleichen und die Ähnlichkeit bewerten. Ein analoges Problem ergibt sich, wenn man DNA Fragmente mit der Information in einer Genomdatenbank abgleichen will. &lt;br /&gt;
&lt;br /&gt;
=== Anwendung: Sequence Alignment / Edit Distance ===&lt;br /&gt;
&lt;br /&gt;
:gegeben: zwei Wörter (allgemein: beliebige Zeichenfolgen)&lt;br /&gt;
:gesucht: Wie kann man die Buchstaben am besten in Übereinstimmung bringen?&lt;br /&gt;
&lt;br /&gt;
:Beispiel: WORTE – NORDEN&lt;br /&gt;
&lt;br /&gt;
Zwei mögliche Alignments sind&lt;br /&gt;
&lt;br /&gt;
  W&amp;lt;font color=red&amp;gt;&amp;lt;b&amp;gt;OR&amp;lt;/b&amp;gt;&amp;lt;/font&amp;gt;T&amp;lt;font color=red&amp;gt;&amp;lt;b&amp;gt;E&amp;lt;/b&amp;gt;&amp;lt;/font&amp;gt;.          W.ORTE&lt;br /&gt;
  N&amp;lt;font color=red&amp;gt;&amp;lt;b&amp;gt;OR&amp;lt;/b&amp;gt;&amp;lt;/font&amp;gt;D&amp;lt;font color=red&amp;gt;&amp;lt;b&amp;gt;E&amp;lt;/b&amp;gt;&amp;lt;/font&amp;gt;N          NORDEN&lt;br /&gt;
&lt;br /&gt;
wobei der Punkt anzeigt, dass der untere Buchstabe keinen Partner hat, und rote Buchstaben oben und unten übereinstimmen. Jede Nicht-Übereinstimmung verursacht nun gewisse Kosten. Dabei unterscheiden wir zwei Fälle:&lt;br /&gt;
# Matche a[i] mit b[j]. Falls a[i] == b[j], ist das gut (rote Buchstaben), und es entstehen keine Kosten. Andernfalls entstehen Kosten U (schwarze Buchstaben).&lt;br /&gt;
# Wir überspringen a[i] oder b[j] (Buchstabe vs. Punkt). Dann entstehen Kosten V. (Manchmal unterscheidet man auch noch Kosten Va und Vb, wenn das Überspringen bei a und b unterschieldiche Signifikanz hat.)&lt;br /&gt;
&lt;br /&gt;
Gesucht ist nun das &amp;lt;b&amp;gt;Alignment mit minimalen Kosten&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Diese Aufgabe kann man sehr schön als gerichteten Graphen darstellen: Wir definieren ein rechteckiges Gitter und schreiben das erste Wort über das Gitter und das andere links davon. Die Gitterpunkte verbinden wir mit Pfeilen (gerichteten Kanten), wobei ein Pfeil nach rechts bedeutet, dass wir beim oberen Wort einen Buchstaben überspringen, ein Pfeil nach unten, dass wir beim linken Wort einen Buchstaben überspringen, und ein diagonaler Pfeil, dass wir zwei Buchstaben matchen (und zwar die am Pfeilende). Die Farben der Pfeile symbolisieren die Kosten: rot für das Überspringen eines Buchstabens (Kosten V), blau für das Matchen, wenn die Buchstaben nicht übereinstimmen (Kosten U), und grün, wenn die Buchstaben übereinstimmen (keine Kosten). &lt;br /&gt;
&lt;br /&gt;
[[Image:sequence-alignment.png|300px]]&lt;br /&gt;
&lt;br /&gt;
Lösung:&lt;br /&gt;
:Suche den kürzesten Pfad vom Knoten &amp;quot;START&amp;quot; (oben links) nach unten rechts. Dazu kann der [[Graphen und Graphenalgorithmen#Algorithmus von Dijkstra|Algorithmus von Dijkstra]] verwendet werden, der auf gerichteten Graphen genauso funktioniert wie auf ungerichteten.&lt;br /&gt;
&lt;br /&gt;
Für unser Beispiel von oben erhalten wir die folgenden Pfade:&lt;br /&gt;
&lt;br /&gt;
[[Image:sequence-alignment-weg1.png|400px]]&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;[[Image:sequence-alignment-weg2.png|400px]]&lt;br /&gt;
&lt;br /&gt;
Durch Addieren der Kosten entsprechend der Farben sieht man, dass der erste Weg die Kosten 2U+V und der zweite die Kosten 5U+V hat. Der erste Weg ist offensichtlich günstiger und entspricht dem besten Alignment.&lt;br /&gt;
&lt;br /&gt;
=== Anwendung: Abhängigkeitsgraph ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Beispiel: &amp;lt;/b&amp;gt; Wie erklärt man einem zerstreuten Professor, wie er sich morgens anziehen soll? Der folgende Graph enthält einen Knoten für jede Aktion, und eine Kante (i &amp;amp;rarr; j) bedeutet, dass die Aktion i vor der Aktion j abgeschlossen werden muss.&lt;br /&gt;
&lt;br /&gt;
[[Image:anziehen-graph.png|600px]]&lt;br /&gt;
&lt;br /&gt;
In derartigen Abhängigkeitsgraphen ist die wichtigste Frage immer, ob der Graph azyklisch ist. Wäre dies nämlich nicht der Fall, kann es keine Reihenfolge der Aktionen geben, die alle Abhängigkeiten erfüllt. Dies sieht man leicht, wenn man den einfachsten möglichen Zyklus betrachtet: es gibt sowohl eine Kante (i &amp;amp;rarr; j) als auch eine (j &amp;amp;rarr; i). Dann müsste man i vor j erledigen, aber ebenso j vor i, was offensichtlich unmöglich ist - das im Graph kodierte Problem ist dann unlösbar. Wegen ihrer Wichtigkeit wird für gerichtete azyklische Graphen oft die Abkürzung &amp;lt;b&amp;gt;DAG&amp;lt;/b&amp;gt; (von &amp;lt;i&amp;gt;directed acyclic graph&amp;lt;/i&amp;gt;) verwendet. Ein Graph ist genau dann ein DAG, wenn es eine topologische Sortierung gibt:&lt;br /&gt;
;topologische Sortierung: Zeichne die Knoten so auf eine Gerade, dass alle Kanten (Pfeile) nach rechts zeigen. &lt;br /&gt;
Arbeitet man die Aktionen nach einer (beliebigen) topologischen Sortierung ab, werden automatisch alle Abhängigkeiten eingehalten: Da alle Pfeile nach rechts zeigen, werden abhängige Aktionen immer später ausgeführt. Die topologische Sortierung ist im allgemeinen nicht eindeutig. Die folgende Skizze zeigt eine mögliche topologische Sortierung für das Anziehen:&lt;br /&gt;
&lt;br /&gt;
[[Image:anziehen-topologische-sortierung.png|600px]]&lt;br /&gt;
&lt;br /&gt;
Eine solche fest vorgegebene Reihenfolge ist für den zerstreuten Professor sicherlich eine größere Hilfe als der ursprüngliche Graph. Man erkennt, dass die Sortierung nicht eindeutig ist, beispielsweise bei der Uhr: Da für die Uhr keine Abhängigkeiten definiert sind, kann man diese Aktion an beliebiger Stelle einsortieren. Hier wurde willkürlich die letzte Stelle gewählt.&lt;br /&gt;
&lt;br /&gt;
==== Zwei Algorithmen zum Finden der topologischen Sortierung ====&lt;br /&gt;
&lt;br /&gt;
Die folgenden Algorithmen finden entweder eine topologische Sortierung, oder signalisieren, dass der Graph zyklisch ist.&lt;br /&gt;
&lt;br /&gt;
===== Algorithmus 1 =====&lt;br /&gt;
# Suche einen Knoten mit Eingangsgrad 0 (ohne eingehende Pfeile) =&amp;gt; in einem gerichteten azyklischen Graphen gibt es immer einen solchen Knoten&lt;br /&gt;
# Platziere diesen Knoten auf der Geraden (beliebig)&lt;br /&gt;
# Entferne den Knoten aus dem Graphen zusammen mit den ausgehenden Kanten&lt;br /&gt;
# Gehe zu 1., aber platziere in 2. immer rechts der Knoten, die schon auf der Geraden vorhanden sind.&lt;br /&gt;
: =&amp;gt; Wenn noch Knoten übrig sind, aber keiner Eingangsgrad 0 hat, muss der Graph zyklisch sein.&lt;br /&gt;
&lt;br /&gt;
[[Image:bild6.JPG]]&lt;br /&gt;
&lt;br /&gt;
Beispiel für einen zyklischen Graphen: kein Knoten hat Eingangsgrad 0.&lt;br /&gt;
&lt;br /&gt;
Um den Algorithmus zu implementieren, verwenden wir eine property map &amp;lt;tt&amp;gt;in_degree&amp;lt;/tt&amp;gt;, die wir in einem ersten Durchlauf durch den Graphen füllen und die dann für jeden Knoten die Anzahl der eingehenden Kanten speichert. Dann gehen wir sukzessive zu allen Knoten mit &amp;lt;tt&amp;gt;in_degree == 0&amp;lt;/tt&amp;gt;. Anstatt sie aber tatsächlich aus dem Graphen zu entfernen wie im obigen Pseudocode, dekrementieren wir nur den &amp;lt;tt&amp;gt;in_degree&amp;lt;/tt&amp;gt; ihrer Nachbarn. Wird der &amp;lt;tt&amp;gt;in_degree&amp;lt;/tt&amp;gt; eines Nachbarn dadurch 0, wird er ebenfalls in das Array der zu scannenden Knoten aufgenommen. Wenn der Graph azyklisch ist, enthält das Array am Ende alle Knoten des Graphen, und die Reihenfolge der Einfügungen definiert eine topologische Sortierung. Andernfalls ist das Array zu kurz, und wir signalisieren durch Zurückgeben von &amp;lt;tt&amp;gt;None&amp;lt;/tt&amp;gt;, dass der Graph zyklisch ist:&lt;br /&gt;
&lt;br /&gt;
 def topological_sort(graph):              # ein gerichteter Graph&lt;br /&gt;
     in_degree = [0]*len(graph)            # property map für den Eingangsgrad jeden Knotens&lt;br /&gt;
     for node in xrange(len(graph)):       # besuche alle Knoten&lt;br /&gt;
         for neighbor in graph[node]:      #  ... und deren Nachbarn&lt;br /&gt;
             in_degree[neighbor] += 1      #  ... und inkrementiere den Eingangsgrad&lt;br /&gt;
     &lt;br /&gt;
     result = []                           # wird später die topologische Sortierung enthalten&lt;br /&gt;
     for node in xrange(len(graph)):&lt;br /&gt;
         if in_degree[node] == 0:&lt;br /&gt;
             result.append(node)           # füge alle Knoten mit Eingangsgrad 0 in result ein&lt;br /&gt;
     &lt;br /&gt;
     k = 0&lt;br /&gt;
     while k &amp;lt; len(result):                # besuche alle Knoten mit Eingangsgrad 0&lt;br /&gt;
         node = result[k]&lt;br /&gt;
         k += 1&lt;br /&gt;
         for neighbor in graph[node]:      # besuche alle Nachbarn&lt;br /&gt;
             in_degree[neighbor] -= 1      # entferne 'virtuell' die eingehende Kante&lt;br /&gt;
             if in_degree[neighbor] == 0:  # wenn neighbor jetzt Eingangsgrad 0 hat&lt;br /&gt;
                 result.append(neighbor)   #  ... füge ihn in result ein&lt;br /&gt;
     &lt;br /&gt;
     if len(result) == len(graph):         # wenn alle Knoten jetzt Eingangsgrad 0 haben&lt;br /&gt;
         return result                     # ... ist result eine topologische Sortierung&lt;br /&gt;
     else:&lt;br /&gt;
         return None                       # andernfalls ist der Graph zyklisch&lt;br /&gt;
&lt;br /&gt;
===== Algorithmus 2 =====&lt;br /&gt;
Der obige Algorithmus hat den Nachteil, dass er jeden Knoten zweimal expandiert. Man kann eine topologische Sortierung stattdessen auch mit Tiefensuche bestimmen. Es gilt nämlich der folgende&lt;br /&gt;
;Satz: Wird ein DAG mittels Tiefensuche traversiert, definiert die &amp;lt;i&amp;gt;reverse post-order&amp;lt;/i&amp;gt; eine topologische Sortierung.&lt;br /&gt;
Zur Erinnerung: die post-order erhält man, indem man jeden Knoten ausgibt, &amp;lt;i&amp;gt;nachdem&amp;lt;/i&amp;gt; die Rekursion zu allen seinen Nachbarn beendet ist, siehe unsere [[Graphen_und_Graphenalgorithmen#pre_and_post_order|Diskussion weiter oben]]. Die reverse post-order ist gerade die Umkehrung dieser Reihenfolge. Die folgende Implementation verwendet die rekursive Version der Tiefensuche, in der Praxis wird man meist die iterative Version mit Stack bevorzugen, weil bei großen Graphen die Aufruftiefe sehr große werden kann:&lt;br /&gt;
&lt;br /&gt;
 def reverse_post_order(graph):               # gerichteter Graph&lt;br /&gt;
     result = []                              # enthält später die reverse post-order&lt;br /&gt;
     visited = [False]*len(graph)             # Flags für bereits besuchte Knoten&lt;br /&gt;
     &lt;br /&gt;
     def visit(node):                         # besuche node&lt;br /&gt;
         if not visited[node]:                # aber nur, wenn er noch nicht besucht wurde&lt;br /&gt;
             visited[node] = True             # markiere ihn als besucht&lt;br /&gt;
             for neighbor in graph[node]:     # und besuche die Nachbarn&lt;br /&gt;
                 visit(neighbor)&lt;br /&gt;
             result.append(node)              # alle Nachbarn besucht =&amp;gt; Anhängen an result liefert post-order&lt;br /&gt;
     &lt;br /&gt;
     for node in xrange(len(graph)):          # besuche alle Knoten&lt;br /&gt;
         visit(node)&lt;br /&gt;
     &lt;br /&gt;
     result.reverse()                         # post-order =&amp;gt; reverse post-order&lt;br /&gt;
     return result&lt;br /&gt;
&lt;br /&gt;
Die Tatsache, dass die reverse post-order tatsächlich eine topologische Sortierung liefert, leuchtet wahrscheinlich nicht unmittelbar ein. Bevor wir diese Tatsache beweisen. wollen wir uns anhand des Ankleidegraphen klar machen, dass die pre-order (die man intuitiv vielleicht eher wählen würde) keine topologische Sortierung ist. Startet man die Tiefensuche beim Knoten &amp;quot;Unterhemd&amp;quot;, werden die Knoten in der Reihenfolge &amp;quot;Unterhemd&amp;quot;, &amp;quot;Oberhemd&amp;quot;, &amp;quot;Schlips&amp;quot;, &amp;quot;Jackett&amp;quot;, &amp;quot;Gürtel&amp;quot; gefunden. Da dann alle von &amp;quot;Unterhemd&amp;quot; erreichbaren Knoten erschöpft sind, startet man die Tiefensuche als nächstes bei &amp;quot;Unterhose&amp;quot; und erreicht von dort aus &amp;quot;Hose&amp;quot; und &amp;quot;Schuhe&amp;quot;. Man erkennt sofort, dass diese Reihenfolge nicht funktioniert: &amp;quot;Hose&amp;quot; kommt nach &amp;quot;Gürtel&amp;quot;, und &amp;quot;Jackett&amp;quot; kommt vor &amp;quot;Gürtel&amp;quot;. Bei dieser Anordnung gibt es Pfeile nach links, die Abhängigkeitsbedingungen sind somit verletzt.&lt;br /&gt;
&lt;br /&gt;
Damit die reverse post-order eine zulässige Sortierung sein kann, muss stets gelten, dass Knoten u vor Knoten v einsortiert wurde, wenn die Kante (u &amp;amp;rarr; v) existiert. Das ist aber äquivalent zur Forderung, dass in der ursprünglichen post-order (vor dem &amp;lt;tt&amp;gt;reverse&amp;lt;/tt&amp;gt;) u hinter v stehen muss. Wir betrachten den &amp;lt;tt&amp;gt;visit&amp;lt;/tt&amp;gt;-Aufruf, bei dem u expandiert wird. Gelangt man jetzt zu u's Nachbarn v, gibt es zwei Möglichkeiten: Wenn v bereits expandiert wurde, befindet es sich bereits im Array &amp;lt;tt&amp;gt;result&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;visit&amp;lt;/tt&amp;gt; kehrt sofort zurück. Andernfalls wird v ebenfalls expandiert und damit in &amp;lt;tt&amp;gt;result&amp;lt;/tt&amp;gt; eingetragen, &amp;lt;i&amp;gt;bevor&amp;lt;/i&amp;gt; der rekursive Aufruf &amp;lt;tt&amp;gt;visit(v)&amp;lt;/tt&amp;gt; zurückkehrt. Knoten u wird aber erst in &amp;lt;tt&amp;gt;result&amp;lt;/tt&amp;gt; eingefügt, &amp;lt;i&amp;gt;nachdem&amp;lt;/i&amp;gt; alle rekursiven &amp;lt;tt&amp;gt;visit&amp;lt;/tt&amp;gt;-Aufrufe seiner Nachbarn zurückgekehrt sind. In beiden Fällen steht u in der post-order wie gefordert hinter v, und daraus folgt die Behauptung.&lt;br /&gt;
&lt;br /&gt;
Der obige Algorithmus liefert natürlich nur dann eine topologische Sortierung, wenn der Graph wirklich azyklisch ist (man kann ihn aber auch anwenden, um die reverse post-order für einen zyklischen Graphen zu bestimmen, siehe Abschnitt &amp;quot;[[Graphen_und_Graphenalgorithmen#Transitive Hülle und stark zusammenhängende Komponenten|Stark zusammenhängende Komponenten]]&amp;quot;). Dieser Fall tritt in der Praxis häufig auf, weil zyklische Graphen bei vielen Anwendungen gar nicht erst entstehen können. Weiß man allerdings nicht, ob der Graph azyklisch ist oder nicht, muss man einen zusätzlichen Test auf Zyklen in den Algorithmus einbauen. &lt;br /&gt;
&lt;br /&gt;
Zyklische Graphen sind dadurch gekennzeichnet, dass es im obigen Beweis eine dritte Möglichkeit gibt: Während der Expansion von u wird rekursiv v expandiert, und es gibt eine Rückwärtskante (v &amp;amp;rarr; u). (Es spielt dabei keine Rolle, ob v von u aus direkt oder indirekt erreicht wurde.) Ein Zyklus wird also entdeckt, wenn die Tiefensuche zu u zurückkehrt, solange u noch &amp;lt;i&amp;gt;aktiv&amp;lt;/i&amp;gt; ist, d.h. wenn die Rekursion von u aus gestartet und noch nicht beendet wurde. Dies kann man leicht feststellen, wenn man in der property map &amp;lt;tt&amp;gt;visited&amp;lt;/tt&amp;gt; drei Werte zulässt: 0 für &amp;quot;noch nicht besucht&amp;quot;, 1 für &amp;quot;aktiv&amp;quot; und 2 für &amp;quot;beendet&amp;quot;. Wir signalisieren einen Zyklus, sobald &amp;lt;tt&amp;gt;visit&amp;lt;/tt&amp;gt; für einen Knoten aufgerufen wird, der gerade aktiv ist:&lt;br /&gt;
&lt;br /&gt;
 def topological_sort_DFS(graph):             # gerichteter Graph&lt;br /&gt;
     result = []                              # enthält später die topologische Sortierung&lt;br /&gt;
     &lt;br /&gt;
     not_visited, active, finished = 0, 1, 2  # drei Zustände für visited&lt;br /&gt;
     visited = [not_visited]*len(graph)       # Flags für aktive und bereits besuchte Knoten&lt;br /&gt;
     &lt;br /&gt;
     def visit(node):                         # besuche node (gibt &amp;quot;True&amp;quot; zurück, wenn Zyklus gefunden wurde)&lt;br /&gt;
         if not visited[node]:                # ... aber nur, wenn er noch nicht besucht wurde&lt;br /&gt;
             visited[node] = active           # markiere ihn als aktiv&lt;br /&gt;
             for neighbor in graph[node]:     # und besuche die Nachbarn&lt;br /&gt;
                 if visit(neighbor):          # wenn rekursiv ein Zyklus gefunden wurde&lt;br /&gt;
                     return True              # ... brechen wir ab und signalisieren den Zyklus&lt;br /&gt;
             visited[node] = finished         # Rekursion beendet, node ist nicht mehr aktiv&lt;br /&gt;
             result.append(node)              # alle Nachbarn besucht =&amp;gt; Anhängen an result liefert post-order&lt;br /&gt;
             return False                     # kein Zyklus gefunden&lt;br /&gt;
         elif visited[node] == active:        # Rekursion erreicht einen noch aktiven Knoten&lt;br /&gt;
             return True                      #  =&amp;gt; Zyklus gefunden&lt;br /&gt;
     &lt;br /&gt;
     for node in xrange(len(graph)):          # besuche alle Knoten&lt;br /&gt;
         if visit(node):                      # wenn Zyklus gefunden wurde&lt;br /&gt;
             return None                      # ... gibt es keine topologische Sortierung&lt;br /&gt;
     &lt;br /&gt;
     result.reverse()                         # post-order =&amp;gt; reverse post-order (=topologische Sortierung)&lt;br /&gt;
     return result&lt;br /&gt;
&lt;br /&gt;
Man macht sich leicht klar, dass kein Zyklus vorliegt, wenn die Rekursion einen Knoten erreicht, der bereits auf &amp;lt;tt&amp;gt;finished&amp;lt;/tt&amp;gt; gesetzt ist. Nehmen wir an, dass u gerade expandiert wird, und sein Nachbar v ist bereits &amp;lt;tt&amp;gt;finished&amp;lt;/tt&amp;gt;. Wenn es einen Zyklus gäbe, müsste es einen Weg von v nach u geben. Dann wäre u aber bereits während der Expansion von v gefunden worden. Da v nicht mehr im Zustand &amp;lt;tt&amp;gt;active&amp;lt;/tt&amp;gt; ist, muss die Expansion von v schon abgeschlossen gewesen sein, ohne dass u gefunden wurde. Folglich kann es keinen solchen Zyklus geben.&lt;br /&gt;
&lt;br /&gt;
=== Transitive Hülle und stark zusammenhängende Komponenten ===&lt;br /&gt;
&lt;br /&gt;
Auch bei gerichteten Graphen ist die Frage, welche Knoten miteinander zusammenhängen, von großem Interesse. Wir betrachten dazu wieder die Relation &amp;quot;Knoten v ist von Knoten u aus erreichbar&amp;quot;, die anzeigt, ob es einen Weg von u nach v gibt oder nicht. In ungerichteten Graphen ist diese Relation immer symmetrisch, weil jeder Weg in beiden Richtungen benutzt werden kann. In gerichteten Graphen gilt dies nicht. Man muss hier zwei Arten von Zusammenhangskomponenten unterscheiden:&lt;br /&gt;
;Transitive Hülle: Die transitive Hülle eines Knotens u ist die Menge aller Knoten, die von u aus erreichbar sind:&lt;br /&gt;
:&amp;lt;math&amp;gt;T(u) = \{v\ |\ u \rightsquigarrow v\}&amp;lt;/math&amp;gt;&lt;br /&gt;
;Stark zusammenhängende Komponenten: Die stark zusammenhängende Komponenten &amp;lt;math&amp;gt;C_i&amp;lt;/math&amp;gt; eines gerichteten Graphen sind maximale Teilgraphen, so dass alle Knoten innerhalb einer Komponente von jedem anderen Knoten der selben Komponente aus erreichbar sind&lt;br /&gt;
:&amp;lt;math&amp;gt;u,v \in C_i\ \ \Leftrightarrow\ \ u \rightsquigarrow v \wedge v \rightsquigarrow u&amp;lt;/math&amp;gt;&lt;br /&gt;
Die erste Definition betrachtet den Zusammenhang asymmetrisch, ohne Beachtung der Frage, ob es auch einen Rückweg von Knoten v nach u gibt, die zweite hingegen symmetrisch.&lt;br /&gt;
&lt;br /&gt;
Die &amp;lt;b&amp;gt;transitive Hülle&amp;lt;/b&amp;gt; benötigt man, wenn man Fragen der Erreichbarkeit besonders effizient beantworten will. Wir hatten bespielsweise oben erwähnt, dass das Python-Modul &amp;lt;tt&amp;gt;json&amp;lt;/tt&amp;gt; direkt und indirekt von mehreren anderen Module abhängt, die vorher installiert werden müssen, damit &amp;lt;tt&amp;gt;json&amp;lt;/tt&amp;gt; funktioniert. Bittet man den Systemadministrator, das &amp;lt;tt&amp;gt;json&amp;lt;/tt&amp;gt;-Paket zu installieren, will er diese Abhängigkeiten wahrscheinlich nicht erst mühsam rekursiv heraussuchen, sonder er verlangt eine Liste aller Pakete, die installiert werden müssen. Dies ist gerade die transitive Hülle von &amp;lt;tt&amp;gt;json&amp;lt;/tt&amp;gt; im Abhängigkeitsgraphen. Damit man diese nicht manuell bestimmen muss, verwendet man Installationsprogrammen wie z.B. [http://pypi.python.org/pypi/pip/ pip], die die Abhängigkeiten automatisch herausfinden und installieren. &lt;br /&gt;
&lt;br /&gt;
Bei der Bestimmung der transitiven Hülle modifiziert man den gegebenen Graphen, indem man jedesmal eine neue Kante (u &amp;amp;rarr; v) einfügt, wenn diese Kante noch nicht existiert, aber v von u aus erreichbar ist. Dies gelingt mit einer sehr einfachen Variation der Tiefensuche: Wir rufen &amp;lt;tt&amp;gt;visit(k)&amp;lt;/tt&amp;gt; für jeden Knoten k auf, aber setzen die property map &amp;lt;tt&amp;gt;visited&amp;lt;/tt&amp;gt; zuvor auf &amp;lt;tt&amp;gt;False&amp;lt;/tt&amp;gt; zurück. Alle Knoten, die während der Rekursion erreicht werden, sind im modifizierten Graphen Nachbarn von k. Ein etwas effizienterer Ansatz ist der [http://de.wikipedia.org/wiki/Algorithmus_von_Floyd_und_Warshall Algorithmus von Floyd und Warshall].&lt;br /&gt;
&lt;br /&gt;
Die Bestimmung der &amp;lt;b&amp;gt;stark zusammenhängenden Komponenten&amp;lt;/b&amp;gt; ist etwas schwieriger. Es existieren eine ganze Reihe von effizienten Algorithmen (siehe [http://en.wikipedia.org/wiki/Strongly_connected_component WikiPedia]), deren einfachster der Algorithmus von Kosaraju ist:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;gegeben:&amp;lt;/b&amp;gt; gerichteter Graph&lt;br /&gt;
&lt;br /&gt;
# Bestimme die reverse post-order (mit der Funktion &amp;lt;tt&amp;gt;reverse_post_order&amp;lt;/tt&amp;gt;)&lt;br /&gt;
# Bilde den transponierten Graphen &amp;lt;math&amp;gt;G^T&amp;lt;/math&amp;gt; (mit der Funktion &amp;lt;tt&amp;gt;transposeGraph&amp;lt;/tt&amp;gt;)&lt;br /&gt;
# Bestimme die Zusammenhangskomponenten von &amp;lt;math&amp;gt;G^T&amp;lt;/math&amp;gt; mittels Tiefensuche, aber betrachte die Knoten dabei in der reverse post-order aus Schritt 1 (dies kann mit einer minimalen Modifikation der Funktion &amp;lt;tt&amp;gt;connectedComponents&amp;lt;/tt&amp;gt; geschehen, indem man die Zeile &amp;lt;tt&amp;gt;for node in xrange(len(graph)):&amp;lt;/tt&amp;gt; einfach nach &amp;lt;tt&amp;gt;for node in ordered:&amp;lt;/tt&amp;gt; abändert, wobei &amp;lt;tt&amp;gt;ordered&amp;lt;/tt&amp;gt; das Ergebnis der Funktion &amp;lt;tt&amp;gt;reverse_post_order&amp;lt;/tt&amp;gt; ist, also ein Array, das die Knoten in der gewünschten Reihenfolge enthält).&lt;br /&gt;
Die Zusammenhangskomponenten, die man in Schritt 3 findet, sind gerade die stark zusammenhängenden Komponenten des Originalgraphen G. Die folgende Skizze zeigt diese in grün für den schwarz gezeichneten gerichteten Graphen. &lt;br /&gt;
&lt;br /&gt;
[[Image:strongly-connected-components.png|400px]]    &lt;br /&gt;
&lt;br /&gt;
Zum Beweis der Korrektheit des Algorithmus von Kosaraju zeigen wir zwei Implikationen: 1. Wenn die Knoten u und v in der selben stark zusammenhängenden Komponente liegen, werden sie in Schritt 3 des Algorithmus auch der selben Komponente zugewiesen. 2. Wenn die Knoten u und v in Schritt 3 der selben Komponente zugewiesen wurden, müssen sie auch in der selben stark zusammenhängenden Komponente liegen. &lt;br /&gt;
# Knoten u und v gehören zur selben stark zusammenhängenden Komponente von G. Per Definition gilt, dass u von v aus erreichbar ist und umgekehrt. Dies muss auch im transponierten Graphen G&amp;lt;sup&amp;gt;T&amp;lt;/sup&amp;gt; gelten (der Weg &amp;lt;math&amp;gt;u \rightsquigarrow v&amp;lt;/math&amp;gt; wird jetzt zum Weg &amp;lt;math&amp;gt;v \rightsquigarrow u&amp;lt;/math&amp;gt; und umgekehrt). Wird u bei der Tiefensuche in Schritt 3 vor v expandiert, ist v von u aus erreichbar und gehört somit zur selben Komponente. Das umgekehrte gilt, wenn v vor u expandiert wird. Daraus folgt die Behauptung 1.&lt;br /&gt;
# Knoten u und v werden in Schritt 3 der selben Komponente zugewiesen: Sei x der Anker dieser Komponente. Da u in der gleichen Komponente wie x liegt, muss es in G&amp;lt;sup&amp;gt;T&amp;lt;/sup&amp;gt; einen Weg &amp;lt;math&amp;gt;x \rightsquigarrow u&amp;lt;/math&amp;gt;, und demnach in G einen Weg &amp;lt;math&amp;gt;u \rightsquigarrow x&amp;lt;/math&amp;gt; geben. Da x der Anker seiner Komponente ist, wissen wir aber auch, dass x in der reverse post-order &amp;lt;i&amp;gt;vor&amp;lt;/i&amp;gt; u liegt (denn der Anker ist der Knoten, mit dem eine neue Komponente gestartet wird; er muss deshalb im Array &amp;lt;tt&amp;gt;ordered&amp;lt;/tt&amp;gt; als erster Konten seiner Komponente gefunden worden sein). Wir unterscheiden jetzt im Schritt 1 des Algorithmus zwei Fälle:&lt;br /&gt;
## u wurde bei der Bestimmung der post-order vor x expandiert. Dann kann x nur dann in der reverse post-order &amp;lt;i&amp;gt;vor&amp;lt;/i&amp;gt; u liegen (oder, einfacher ausgedrückt, x kann nur dann in der post-order &amp;lt;i&amp;gt;hinter&amp;lt;/i&amp;gt; u liegen), wenn x im Graphen G nicht von u aus erreichbar war. Das ist aber unmöglich, weil wir ja schon wissen, dass es in G einen Weg &amp;lt;math&amp;gt;u \rightsquigarrow x&amp;lt;/math&amp;gt; gibt.&lt;br /&gt;
## Folglich wurde u bei der Bestimmung der post-order nach x expandiert. Da x in der post-order hinter u liegt, muss u während der Expansion von x erreicht worden sein. Deshalb muss es in G auch einen Weg &amp;lt;math&amp;gt;x \rightsquigarrow u&amp;lt;/math&amp;gt; geben.&lt;br /&gt;
#:Somit sind x und u in der selben stark zusammenhängenden Komponente. Die gleiche Überlegung gilt für x und v. Wegen der Transitivität der relation &amp;quot;ist erreichbar&amp;quot; folgt daraus, dass auch u und v in der selben Komponente liegen, also die Behauptung 2.&lt;br /&gt;
&lt;br /&gt;
Die folgende Skizze illustriert, dass der Komponentengraph stets azyklisch ist. Den Komponentengraph erhält man, indem man für jede Komponente &amp;lt;math&amp;gt;C_i&amp;lt;/math&amp;gt; einen Knoten erzeugt (grün), und die Knoten i und j durch eine gerichtete Kante verbindet (rot), wenn es im Originalgraphen eine Kante (u &amp;amp;rarr; v) mit &amp;lt;math&amp;gt;u \in C_i&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;v \in C_j&amp;lt;/math&amp;gt; gibt. Es ist dann garantiert, dass es keine Kante in umgekehrter Richtung geben kann. Daraus folgt insbesondere, dass ein DAG nur triviale stark verbundene Komponenten haben kann, die aus einzelnen Knoten bestehen.&lt;br /&gt;
&lt;br /&gt;
[[Image:strongly-connected-components-graph.png|400px]]&lt;br /&gt;
&lt;br /&gt;
=== Anwendung: Das Erfüllbarkeitsproblem in Implikationengraphen ===&lt;br /&gt;
&lt;br /&gt;
Das Erfüllbarkeitsproblem hat auf den ersten Blick nichts mit Graphen zu tun, denn es geht um Wahrheitswerte logischer Ausdrücke. Man kann logische Ausdrücke jedoch unter bestimmten Bedingungen in eine Graphendarstellung überführen und somit das ursprüngliche Problem auf ein Problem der Graphentheorie reduzieren, für das bereits ein Lösungsverfahren bekannt ist. In diesem Abschnitt wollen wir dies für die sogenannten Implikationengraphen zeigen, ein weiteres Beispiel findet sich im Kapitel [[NP-Vollständigkeit]].&lt;br /&gt;
&lt;br /&gt;
==== Das Erfüllbarkeitsproblem ====&lt;br /&gt;
&lt;br /&gt;
(vgl. [http://de.wikipedia.org/wiki/Erfüllbarkeitsproblem_der_Aussagenlogik WikiPedia (de)])&lt;br /&gt;
&lt;br /&gt;
Das Erfüllbarkeitsproblem (SAT-Problem, von &amp;lt;i&amp;gt;satisfiability&amp;lt;/i&amp;gt;) befasst sich mit logischen (oder Booleschen) Funktionen: Gegeben sei eine Menge &amp;lt;math&amp;gt;\{x_1, ... ,x_n\}&amp;lt;/math&amp;gt; Boolscher Variablen (d.h., die &amp;lt;math&amp;gt;x_i&amp;lt;/math&amp;gt; können nur die Werte True oder False annehmen), sowie eine logische Formel, in der die Variablen mit den üblichen logischen Operatoren &lt;br /&gt;
:&amp;lt;math&amp;gt;\neg\quad&amp;lt;/math&amp;gt;: Negation (&amp;quot;nicht&amp;quot;, in Python: &amp;lt;tt&amp;gt;not&amp;lt;/tt&amp;gt;)&lt;br /&gt;
:&amp;lt;math&amp;gt;\vee\quad&amp;lt;/math&amp;gt;: Disjunktion (&amp;quot;oder&amp;quot;, in Python: &amp;lt;tt&amp;gt;or&amp;lt;/tt&amp;gt;)&lt;br /&gt;
:&amp;lt;math&amp;gt;\wedge\quad&amp;lt;/math&amp;gt;: Konjuktion (&amp;quot;und&amp;quot;, in Python: &amp;lt;tt&amp;gt;and&amp;lt;/tt&amp;gt;)&lt;br /&gt;
:&amp;lt;math&amp;gt;\rightarrow\quad&amp;lt;/math&amp;gt;: Implikation (&amp;quot;wenn, dann&amp;quot;, in Python nicht als Operator definiert)&lt;br /&gt;
:&amp;lt;math&amp;gt;\leftrightarrow\quad&amp;lt;/math&amp;gt;: Äquivalenz (&amp;quot;genau, dann wenn&amp;quot;, in Python: &amp;lt;tt&amp;gt;==&amp;lt;/tt&amp;gt;)&lt;br /&gt;
:&amp;lt;math&amp;gt;\neq\quad&amp;lt;/math&amp;gt;: exklusive Disjunktion (&amp;quot;entweder oder&amp;quot;, in Python: &amp;lt;tt&amp;gt;!=&amp;lt;/tt&amp;gt;)&lt;br /&gt;
verknüpft sind. Klammern definieren die Reihenfolge der Auswertung der Operationen. Für jede Belegung der Variablen &amp;lt;math&amp;gt;x_i&amp;lt;/math&amp;gt; mit True oder False liefert die Formel den Wert der Funktion, der natürlich auch nur True oder False sein kann. Wenn Formel und Belegung gegeben sind, ist die Auswertung der Funktion ein sehr einfaches Problem: Man transformiert die Formel in einen Parse-Baum (siehe Übungsaufgabe &amp;quot;Taschenrechner) und wertet jeden Knoten mit Hilfe der üblichen Wertetabellen für logische Operatoren aus, die wir hier zur Erinnerung noch einmal angeben:&lt;br /&gt;
{| cellspacing=&amp;quot;0&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|- style=&amp;quot;text-align:center;background-color:#ffffcc;width:50px&amp;quot;&lt;br /&gt;
|width=&amp;quot;70px&amp;quot;| &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; &lt;br /&gt;
|width=&amp;quot;70px&amp;quot;| &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; &lt;br /&gt;
|width=&amp;quot;70px&amp;quot;| &amp;lt;math&amp;gt;a \vee b &amp;lt;/math&amp;gt; &lt;br /&gt;
|width=&amp;quot;70px&amp;quot;| &amp;lt;math&amp;gt;a \wedge b&amp;lt;/math&amp;gt; &lt;br /&gt;
|width=&amp;quot;70px&amp;quot;| &amp;lt;math&amp;gt;a \rightarrow b&amp;lt;/math&amp;gt; &lt;br /&gt;
|width=&amp;quot;70px&amp;quot;| &amp;lt;math&amp;gt;b \rightarrow a&amp;lt;/math&amp;gt; &lt;br /&gt;
|width=&amp;quot;70px&amp;quot;| &amp;lt;math&amp;gt;a \leftrightarrow b&amp;lt;/math&amp;gt; &lt;br /&gt;
|width=&amp;quot;70px&amp;quot;| &amp;lt;math&amp;gt;a \neq b&amp;lt;/math&amp;gt; &lt;br /&gt;
|- align=&amp;quot;center&amp;quot;&lt;br /&gt;
| 0 || 0 || 0 || 0 || 1 || 1 || 1 || 0&lt;br /&gt;
|- align=&amp;quot;center&amp;quot;&lt;br /&gt;
| 0 || 1 || 1 || 0 || 1 || 0 || 0 || 1&lt;br /&gt;
|- align=&amp;quot;center&amp;quot;&lt;br /&gt;
| 1 || 0 || 1 || 0 || 0 || 1 || 0 || 1&lt;br /&gt;
|- align=&amp;quot;center&amp;quot;&lt;br /&gt;
| 1 || 1 || 1 || 1 || 1 || 1 || 1 || 0&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Beim Erfüllbarkeitsproblem wird die Frage umgekehrt gestellt: &lt;br /&gt;
:Gegeben sei eine logische Funktion. Ist es möglich, dass die Funktion jemals den Wert True annimmt? &lt;br /&gt;
Das heisst, kann man die Variablen &amp;lt;math&amp;gt;x_i&amp;lt;/math&amp;gt; so mit True oder False belegen, dass die Formel am Ende wahr ist? Im Prinzip kann man diese Frage durch erschöpfende Suche leicht beantworten, indem man die Funktion für alle &amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt; möglichen Belegungen einfach ausrechnet, aber das dauert für große n (ab ca. &amp;lt;math&amp;gt;n\ge 40&amp;lt;/math&amp;gt;) viel zu lange. Erstaunlicherweise ist es aber noch niemanden gelungen, einen Algorithmus zu finden, der für beliebige logische Funktionen schneller funktioniert. Im Gegenteil wurde gezeigt, dass das Erfüllbarkeitsproblem [[NP-Vollständigkeit|NP-vollständig]] ist, so dass wahrscheinlich kein solcher Algorithmus existiert. Trotz (oder gerade wegen) seiner Schwierigkeit hat das Erfüllbarkeitsproblem viele Anwendungen gefunden, vor allem beim Testen logischer Schaltkreise (&amp;quot;Gibt es eine Belegung der Eingänge, so dass am Ausgang der verbotene Wert X entsteht?&amp;quot;) und bei der Planerstellung in der künstlichen Intelligenz (&amp;quot;Kann man ausschließen, dass der generierte Plan Konflikte enthält?&amp;quot;). Es ist außerdem ein beliebtes Modellproblem für die Erforschung neuer Ideen und Algorithmen für schwierige Probleme.&lt;br /&gt;
&lt;br /&gt;
==== Normalformen für logische Ausdrücke ====&lt;br /&gt;
&lt;br /&gt;
Um die Beschreibung von Erfüllbarkeitsproblemen zu vereinfachen und zu vereinheitlichen, hat man verschiedene &amp;lt;i&amp;gt;Normalformen&amp;lt;/i&amp;gt; für logische Ausdrücke eingeführt. Die wichtigste ist die &amp;lt;i&amp;gt;Konjuktionen-Normalform&amp;lt;/i&amp;gt; (CNF - conjunctive normal form). Ein Ausdruck in &amp;lt;i&amp;gt;Konjuktionen-Normalform&amp;lt;/i&amp;gt; ist eine UND-Verknüpfung von M &amp;lt;i&amp;gt;Klauseln&amp;lt;/i&amp;gt;:&lt;br /&gt;
 (CLAUSE&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;) &amp;lt;math&amp;gt;\wedge&amp;lt;/math&amp;gt; (CLAUSE&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;) &amp;lt;math&amp;gt;\wedge&amp;lt;/math&amp;gt; ...  &amp;lt;math&amp;gt;\wedge&amp;lt;/math&amp;gt; (CLAUSE&amp;lt;sub&amp;gt;M&amp;lt;/sub&amp;gt;)&lt;br /&gt;
Jede Klausel ist wiederum ein logischer Ausdruck, der aber sehr einfach sein muss: Er darf nur noch k Variablen enthalten, die nur mit den Operatoren NICHT und ODER verknüpft werden dürfen, z.B.&lt;br /&gt;
  CLAUSE&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; := &amp;lt;math&amp;gt;x_1 \vee \neg x_3 \vee x_8&amp;lt;/math&amp;gt;&lt;br /&gt;
Je nachdem, wie viele Variablen pro Klausel erlaubt sind, spricht man von &amp;lt;b&amp;gt;k-CNF&amp;lt;/b&amp;gt; und entsprechend von einem &amp;lt;b&amp;gt;k-SAT&amp;lt;/b&amp;gt; Problem. Es ist außerdem üblich, die Menge der Variablen und die Menge der negierten Variablen zusammen als Menge der &amp;lt;i&amp;gt;Literale&amp;lt;/i&amp;gt; zu bezeichnen:&lt;br /&gt;
  LITERALS := &amp;lt;math&amp;gt;\{x_1,...,x_n\} \cup \{\neg x_1,...,\neg x_n\}&amp;lt;/math&amp;gt;&lt;br /&gt;
Formal definiert man die &amp;lt;b&amp;gt;k-Konjunktionen-Normalform (k-CNF)&amp;lt;/b&amp;gt; am besten durch eine Grammatik in [http://de.wikipedia.org/wiki/Backus-Naur-Form Backus-Naur-Form]:&lt;br /&gt;
    k_CNF    ::=  CLAUSE | k_CNF &amp;lt;math&amp;gt;\wedge&amp;lt;/math&amp;gt; CLAUSE&lt;br /&gt;
    CLAUSE   ::= (LITERAL &amp;lt;math&amp;gt;\vee&amp;lt;/math&amp;gt; ... &amp;lt;math&amp;gt;\vee&amp;lt;/math&amp;gt; LITERAL)  # genau k Literale pro Klausel&lt;br /&gt;
    LITERAL  ::=  VARIABLE | &amp;lt;math&amp;gt;\neg&amp;lt;/math&amp;gt;VARIABLE&lt;br /&gt;
    VARIABLE ::=  &amp;lt;math&amp;gt;x_1&amp;lt;/math&amp;gt; | ... | &amp;lt;math&amp;gt;x_n&amp;lt;/math&amp;gt;&lt;br /&gt;
Beispiele:&lt;br /&gt;
* 3-CNF: &amp;lt;math&amp;gt;(x_1 \vee \neg x_2 \vee x_4) \wedge (x_2 \vee x_3 \vee \neg x_4) \wedge (\neg x_1 \vee x_4 \vee \neg x_5)&amp;lt;/math&amp;gt;&lt;br /&gt;
* 2-CNF: &amp;lt;math&amp;gt;(x_1 \vee \neg x_2) \wedge (x_3 \vee x_4)&amp;lt;/math&amp;gt; ...&lt;br /&gt;
&amp;lt;b&amp;gt;Gesucht&amp;lt;/b&amp;gt; ist eine Belegung der Variablen mit True und False, so dass der Ausdruck den Wert True hat. Aus den Eigenschaften der UND- und ODER-Verknüpfungen folgt, dass ein Ausdruck in k-CNF genau dann True ist, wenn jede einzelne Klausel True ist. In jeder Klausel wiederum hat man k Chancen, die Klausel True zu machen, indem man eins der Literale zu True macht. Eventuell werden dadurch aber andere Klauseln wieder zu False, was die Aufgabe so schwierig macht. Die Bedeutung der k-CNF ergibt sich aus folgendem&lt;br /&gt;
;Satz: Jeder logische Ausdruck kann effizient nach 3-CNF transformiert werden, jedoch im allgemeinen nicht nach 2-CNF.&lt;br /&gt;
Man kann sich also auf Algorithmen für 3-SAT-Probleme konzentrieren, ohne dabei an Ausdrucksmächtigkeit zu verlieren. &lt;br /&gt;
&lt;br /&gt;
Leider gilt der entsprechende Satz nicht für k=2: Ausdrücke in 2-CNF sind weit weniger mächtig, weil man in jeder Klausel nur noch zwei Wahlmöglichkeiten hat. Dafür haben sie den Vorteil, dass es effiziente Algorithmen für das 2-SAT-Problem gibt, die wir jetzt kennenlernen wollen. Es zeigt sich, dass man Ausdrücke in 2-CNF als Graphen repräsentieren kann, indem man sie zunächst in die &amp;lt;i&amp;gt;Implikationen-Normalform&amp;lt;/i&amp;gt; (INF für &amp;lt;i&amp;gt;implicative normal form&amp;lt;/i&amp;gt;) überführt. Die Implikationen-Normalform besteht ebenfalls aus einer Menge von Klauseln, die durch UND-Operationen verknüpft sind, aber jede Klausel ist jetzt eine Implikation. &lt;br /&gt;
Die Grammatik der &amp;lt;b&amp;gt;Implikationen-Normalform (INF)&amp;lt;/b&amp;gt; lautet:&lt;br /&gt;
    INF      ::=  CLAUSE | INF &amp;lt;math&amp;gt;\wedge&amp;lt;/math&amp;gt; CLAUSE&lt;br /&gt;
    CLAUSE   ::= (LITERAL &amp;lt;math&amp;gt;\rightarrow&amp;lt;/math&amp;gt; LITERAL)  # genau 2 Literale pro Implikation&lt;br /&gt;
    LITERAL  ::=  VARIABLE | &amp;lt;math&amp;gt;\neg&amp;lt;/math&amp;gt;VARIABLE&lt;br /&gt;
    VARIABLE ::=  &amp;lt;math&amp;gt;x_1&amp;lt;/math&amp;gt; | ... | &amp;lt;math&amp;gt;x_n&amp;lt;/math&amp;gt;&lt;br /&gt;
und ein gültiger Ausdruck wäre z.B.&lt;br /&gt;
&amp;lt;math&amp;gt;(x_1 \to x_2) \wedge (x_2 \to \neg x_3) \wedge (x_4 \to x_3)&amp;lt;/math&amp;gt; &lt;br /&gt;
Die Umwandlung von 2-CNF nach INF beruht auf folgender Äquivalenz, die man sich aus der obigen Wahrheitstabelle leicht herleitet:&lt;br /&gt;
:&amp;lt;math&amp;gt;(x \vee y) \equiv (\neg x \rightarrow y) \equiv (\neg y \rightarrow x)&amp;lt;/math&amp;gt;&lt;br /&gt;
Aus dieser Äquivalenz folgt der &lt;br /&gt;
;Satz: Ein Ausdruck in 2-CNF kann nach INF transformiert werden, indem man jede Klausel &amp;lt;math&amp;gt;(x \vee y)&amp;lt;/math&amp;gt; durch das Klauselpaar &amp;lt;math&amp;gt;(\neg x \rightarrow y) \wedge (\neg y \rightarrow x)&amp;lt;/math&amp;gt; ersetzt.&lt;br /&gt;
Man beachte, dass man für jede ODER-Klausel des ursprünglichen Ausdrucks &amp;lt;i&amp;gt;zwei&amp;lt;/i&amp;gt; Implikationen (eine für jede Richtung des &amp;quot;wenn, dann&amp;quot;) einfügen muss, um die Symmetrie des Problems zu erhalten.&lt;br /&gt;
&lt;br /&gt;
==== Lösung des 2-SAT-Problems mit Implikationgraphen ====&lt;br /&gt;
&lt;br /&gt;
Jeder Ausdruck in INF kann als gerichteter Graph dargestellt werden:&lt;br /&gt;
# Für jedes Literal wird ein Knoten in den Graphen eingefügt. Es gibt also für jede Variable und für ihre Negation jeweils einen Knoten, d.h. 2n Knoten insgesamt.&lt;br /&gt;
# Jede Implikation ist eine gerichtete Kante.&lt;br /&gt;
Implikationengraphen eignen sich, um Ursache-Folge-Beziehungen oder Konflikte zwischen Aktionen auszudrücken. Beispielsweise kann man die Klausel &amp;lt;math&amp;gt;(x \rightarrow \neg y)&amp;lt;/math&amp;gt; als &amp;quot;wenn man x tut, darf man y nicht tun&amp;quot; interpretieren. Ein anderes schönes Beispiel findet sich in Übung 12.&lt;br /&gt;
&lt;br /&gt;
Für die Implementation eines Implikationengraphen in Python empfiehlt es sich, die Knoten geschickt zu numerieren: Ist die Variable &amp;lt;math&amp;gt;x_i&amp;lt;/math&amp;gt; dem Knoten i zugeordnet, so sollte die negierte Variable &amp;lt;math&amp;gt;\neg x_i&amp;lt;/math&amp;gt; dem Knoten (i+n) zugeordnet werden. Zu jedem gegebenen Knoten i findet man dann den Partnerknoten j leicht durch die Formel &amp;lt;tt&amp;gt;j = (i + n ) % (2*n)&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Die Aufgabe besteht jetzt darin, folgende Fragen zu beantworten:&lt;br /&gt;
# Ist der durch den Implikationengraphen gegebene Ausdruck erfüllbar?&lt;br /&gt;
# Finde eine geeignete Belegung der Variablen, wenn der Ausduck erfüllbar ist.&lt;br /&gt;
Die erste Frage beantwortet man leicht, indem man die die stark zusammenhängenden Komponenten des Implikationengraphen bildet. Dann gilt folgender&lt;br /&gt;
;Satz: Seien u und v zwei Literale, die sich in der selben stark zusammenhängenden Komponente befinden. Dann müssen u und v stets den selben Wert haben, damit der Ausdruck erfüllt sein kann.&lt;br /&gt;
Die Korrektheit des Satzes folgt aus der Definition der stark zusammenhängenden Komponenten: Da u und v in der selben Komponente liegen, gibt es im Implikationengraphen einen Weg &amp;lt;math&amp;gt;u \rightsquigarrow v&amp;lt;/math&amp;gt; sowie einen Weg &amp;lt;math&amp;gt;v \rightsquigarrow u&amp;lt;/math&amp;gt;. Wegen der Transitivität der &amp;quot;wenn, dann&amp;quot; Relation kann man die Wege zu zwei Implikationen verkürzen, die gleichzeitig gelten müssen: &amp;lt;math&amp;gt;(u \rightarrow v) \wedge (v \rightarrow u)&amp;lt;/math&amp;gt; (die Verkürzung von Wegen zu direkten Kanten entspricht gerade der Bildung der transitiven Hülle für die Knoten u und v). In der obigen Wertetabelle für logische Operatoren erkennt mann, dass dies äquivalent zur Bedingung &amp;lt;math&amp;gt;(u \leftrightarrow v)&amp;lt;/math&amp;gt; ist. Dies ist aber gerade die Behauptung des Satzes.&lt;br /&gt;
&lt;br /&gt;
Die Erfüllbarkeit des Ausdrucks ist nun ein einfacher Spezialfall dieses Satzes. &lt;br /&gt;
;Korrolar: Der gegebene Ausdruck ist genau dann erfüllbar, wenn die Literale &amp;lt;math&amp;gt;x_i&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;\neg x_i&amp;lt;/math&amp;gt; sich für kein i in derselben stark zusammenhängenden Komponente befinden.&lt;br /&gt;
Setzt man nämlich im Satz &amp;lt;math&amp;gt;u = x_i&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;v = \neg x_i&amp;lt;/math&amp;gt;, und beide Knoten befinden sich in der selben Komponente, dann müsste gelten &amp;lt;math&amp;gt;x_i \leftrightarrow\neg x_i&amp;lt;/math&amp;gt;, was offensichtlich ein Widerspruch ist. Damit kann der Ausdruck nicht erfüllbar sein. Umgekehrt gilt, dass der Ausdruck immer erfüllbar ist, wenn &amp;lt;math&amp;gt;x_i&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;\neg x_i&amp;lt;/math&amp;gt; stets in verschiedenen Komponenten liegen, weil der folgende Algorithmus von Aspvall, Plass und Tarjan in diesem Fall stets eine gültige Belegung aller Variablen liefert:&lt;br /&gt;
# Bestimme die stark zusammenhängenden Komponenten und bilde den Komponentengraphen. Ordne die Knoten des Komponentengraphen (also die stark zusammenhängenden Komponenten des Originalgraphen) in topologische Sortierung an.&lt;br /&gt;
# Betrachte die Komponenten in der topologischen Sortierung von hinten nach vorn und weise ihnen einen Wert nach folgenden Regeln zu (zur Erinnerung: alle Literale in der selben Komponente haben den selben Wert):&lt;br /&gt;
#* Wenn die Komponente noch nicht betrachtet wurde, setze ihren Wert auf True, und den Wert der komplementären Komponente (derjenigen, die die negierten Literale enthält) auf False.&lt;br /&gt;
#* Andernfalls, gehe zur nächsten Komponente weiter.&lt;br /&gt;
Der Algorithmus beruht auf der Symmetrie des Implikationengraphen: Weil Kanten immer paarweise &amp;lt;math&amp;gt;(\neg u \rightarrow v) \wedge (\neg v \rightarrow u)&amp;lt;/math&amp;gt; eingefügt werden, ist der Graph &amp;lt;i&amp;gt;schiefsymmetrisch&amp;lt;/i&amp;gt; (skew symmetric): die eine Hälfte das Graphen ist die transponierte Spiegelung der anderen Hälfte. Enthält eine stark zusammenhängende Komponente &amp;lt;math&amp;gt;C_i&amp;lt;/math&amp;gt; die Knoten &amp;lt;tt&amp;gt;i1, i2, ...&amp;lt;/tt&amp;gt;, so gibt es stets eine komplementäre Komponente &amp;lt;math&amp;gt;C_j = \neg C_i&amp;lt;/math&amp;gt;, die die komplementären Knoten &amp;lt;tt&amp;gt;j1 = (i1 + n) % (2*n), j2 = (i2 + n) % (2*n), ...&amp;lt;/tt&amp;gt; enthält. Gilt &amp;lt;math&amp;gt;C_i = \neg C_i&amp;lt;/math&amp;gt; für irgendein i, so ist der Ausdruck nicht erfüllbar. Den Beweis für die Korrektheit des Algorithmus findet man im [http://www.math.ucsd.edu/~sbuss/CourseWeb/Math268_2007WS/2SAT.pdf Originalartikel]. Leider funktioniert dies nicht für k-SAT-Probleme mit &amp;lt;math&amp;gt;k &amp;gt; 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Will man nur die Erfüllbarkeit prüfen, vereinfacht sich der Algorithm zu:&lt;br /&gt;
# Bestimme die stark zusammenhängenden Komponenten.&lt;br /&gt;
# Teste für alle &amp;lt;tt&amp;gt;i = 0,...,n-1&amp;lt;/tt&amp;gt;, dass Knoten &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; und Knoten &amp;lt;tt&amp;gt;(i+n)&amp;lt;/tt&amp;gt; in unterschiedlichen Komponenten liegen.&lt;br /&gt;
Ist der Ausdruck erfüllbar, kann man eine gültige Belegung der Variablen jetzt mit dem randomisierten Algorithmus bestimmen, den wir im Kapitel [[Randomisierte Algorithmen]] behandeln.&lt;br /&gt;
&lt;br /&gt;
== Weitere wichtige Graphenalgorithmen ==&lt;br /&gt;
&lt;br /&gt;
Eins der wichtigsten Einsatzgebiete für Graphen ist die Optimierung, also die Suche nach der &amp;lt;i&amp;gt;besten&amp;lt;/i&amp;gt; Lösung für ein gegebenes Problem:&lt;br /&gt;
* Das &amp;lt;i&amp;gt;interval scheduling&amp;lt;/i&amp;gt; befasst sich damit, aus einer gegebenen Menge von Aufträgen die richtigen auszuwählen und sie geschickt auf die zur Verfügung stehenden Ressourcen aufzuteilen. Damit beschäftigen wir uns im Kapitel [[Greedy-Algorithmen und Dynamische Programmierung]].&lt;br /&gt;
* Beim Problem des Handlungsreisenden sucht man nach der kürzesten Rundreise, die alle gegebenen Städte genau einmal besucht. Dieses Problem behandeln wir im Kapitel [[NP-Vollständigkeit]].&lt;br /&gt;
* Viele weitere Anwendungen können wir leider in der Vorlesung nicht mehr behandeln, z.B.&lt;br /&gt;
** Algorithmen für den [http://en.wikipedia.org/wiki/Maximum_flow_problem maximalen Fluss] beantworten die Frage, wie man die Durchflussmenge durch ein Netzwerk (z.B. von Ölpipelines) maximiert.&lt;br /&gt;
** Beim [http://en.wikipedia.org/wiki/Assignment_problem Problem der optimalen Paarung] (&amp;quot;matching problem&amp;quot; oder &amp;quot;assignment problem&amp;quot;) sucht man nach einer Teilmenge der Kanten (also nach einem Teilgraphen), so dass jeder Knoten in diesem Teilgraphen höchstens den Grad 1 hat. Im neuen Graphen gruppieren die Kanten also je zwei Knoten zu einem Paar, und die Paarung soll nach jeweils anwendungsspezifischen Kriterien optimal sein. Dies benötigt man z.B. bei der optimalen Zuordnung von Gruppen, etwas beim Arbeitsamt (Zuordnung Arbeitssuchender - Stellenangebot) und in der Universität (Zuordnung Studenten - Übungsgruppen).&lt;br /&gt;
** In Statistik und maschinellem Lernen haben in den letzten Jahren die [http://en.wikipedia.org/wiki/Graphical_model graphischen Modelle] große Bedeutung erlangt.&lt;br /&gt;
* usw. usf.&lt;br /&gt;
&lt;br /&gt;
[[Randomisierte Algorithmen|Nächstes Thema]]&lt;/div&gt;</summary>
		<author><name>Stephan.meister</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Graphen_und_Graphenalgorithmen&amp;diff=5180</id>
		<title>Graphen und Graphenalgorithmen</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Graphen_und_Graphenalgorithmen&amp;diff=5180"/>
				<updated>2012-07-10T20:34:01Z</updated>
		
		<summary type="html">&lt;p&gt;Stephan.meister: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Einführung zu Graphen ==&lt;br /&gt;
&lt;br /&gt;
=== Motivation -- Königsberger Brückenproblem ===&lt;br /&gt;
Leonhard Euler [http://de.wikipedia.org/wiki/Leonhard_Euler] erfand den Graphen-Formalismus 1736, um eine scheinbar banale Frage zu beantworten: Ist es möglich, in Königsberg (siehe Stadtplan von 1809 und die schematische Darstellung) einen Spaziergang zu unternehmen, bei dem jede der 7 Brücken genau einmal überquert wird?&lt;br /&gt;
&lt;br /&gt;
[[Image:Koenigsberg1809.png]]&amp;lt;br&amp;gt;&lt;br /&gt;
[[Image:Koenigsberg.jpg]]&lt;br /&gt;
&lt;br /&gt;
Ein Graph abstrahiert von der Geometrie des Problems und repräsentiert nur die Topologie. Jeder Stadtteil von Königsberg ist ein Knoten des Graphen, jede Brücke eine Kante. Der zum Brückenproblem gehörende Graph sieht also so aus:&lt;br /&gt;
&lt;br /&gt;
     O&lt;br /&gt;
    /| \&lt;br /&gt;
    \|  \&lt;br /&gt;
     O---O&lt;br /&gt;
    /|  /&lt;br /&gt;
    \| /&lt;br /&gt;
     O&lt;br /&gt;
&lt;br /&gt;
Der gesuchte Spaziergang würde existieren, wenn es maximal 2 Knoten gäbe, an denen sich eine ungerade Zahl von Kanten trifft. Die Frage muss für Königsberg also verneint werden, denn hier gibt es vier solche Knoten. &lt;br /&gt;
&lt;br /&gt;
Inzwischen haben Graphen ein riesige Zahl weiterer Anwendungen gefunden. Einige Beispiele:&lt;br /&gt;
&lt;br /&gt;
* Landkarten:&lt;br /&gt;
** Knoten: Länder&lt;br /&gt;
** Kanten: gemeinsame Grenzen&lt;br /&gt;
&lt;br /&gt;
* Logische Schaltkreise:&lt;br /&gt;
** Knoten: Gatter&lt;br /&gt;
** Kanten: Verbindungen&lt;br /&gt;
&lt;br /&gt;
* Chemie (Summenformeln):&lt;br /&gt;
** Knoten: chemische Elemente&lt;br /&gt;
** Kanten: Bindungen &lt;br /&gt;
&lt;br /&gt;
* Soziologie (StudiVZ)&lt;br /&gt;
** Soziogramm&lt;br /&gt;
*** Knoten: Personen&lt;br /&gt;
*** Kanten: Freund von ...&lt;br /&gt;
&lt;br /&gt;
=== Definitionen ===&lt;br /&gt;
&lt;br /&gt;
;Ungerichteter Graph: Ein ungerichteter Graph G = ( V, E ) besteht aus&lt;br /&gt;
:* einer endliche Menge V von Knoten (vertices)&lt;br /&gt;
:* einer endlichen Menge &amp;lt;math&amp;gt;E \subset V \times V&amp;lt;/math&amp;gt; von Kanten (edges)&lt;br /&gt;
:Die Paare (u,v) und (v,u) gelten dabei als nur ''eine'' Kante (somit gilt die Symmetriebeziehung: (u,v) ∈ E =&amp;gt; (v,u) ∈ E ). Die Anzahl der Kanten, die sich an einem Knoten treffen, wird als ''Grad'' (engl. ''degree'') dieses Knotens bezeichnet:&lt;br /&gt;
:::degree(v) = |{v' ∈ V | (v,v') ∈ E}|&lt;br /&gt;
:(Die Syntax |{...}| bezeichnet dabei die Mächtigkeit der angegebenen Menge, also die Anzahl der Elemente in der Menge.)&lt;br /&gt;
&lt;br /&gt;
Der Graph des Königsberger Brückenproblems ist ungerichtet. Bezeichnet man die Knoten entsprechend des folgenden Bildes&lt;br /&gt;
    c&lt;br /&gt;
   /| \&lt;br /&gt;
   \|  \&lt;br /&gt;
    b---d &lt;br /&gt;
   /|  /&lt;br /&gt;
   \| /&lt;br /&gt;
    a&lt;br /&gt;
&lt;br /&gt;
gilt für die Knotengrade: &amp;lt;tt&amp;gt;degree(a) == degree(c) == degree(d) == 3&amp;lt;/tt&amp;gt; und &amp;lt;tt&amp;gt;degree(b) == 5&amp;lt;/tt&amp;gt;. Genauer muss man bei diesem Graphen von einem ''Multigraphen'' sprechen, weil es zwischen einigen Knotenpaaren (nämlich (a, b) sowie (b, c)) mehrere Kanten (&amp;quot;Mehrfachkanten&amp;quot;) gibt. Wir werden in dieser Vorlesung nicht näher auf Multigraphen eingehen.&lt;br /&gt;
&lt;br /&gt;
;Gerichteter Graph: Ein Graph heißt ''gerichtet'', wenn die Kanten (u,v) und (v,u) unterschieden werden. Die Kante (u,v) ∈ E wird nun als Kante von u nach v (aber nicht umgekehrt) interpretiert. Entsprechend unterscheidet man jetzt den ''eingehenden'' und den ''ausgehenden Grad'' jedes Knotens:&lt;br /&gt;
:*out_degree(v) = |{v' ∈ V | (v,v') ∈ E}|&amp;lt;br/&amp;gt;&lt;br /&gt;
:*in_degree(v)  = |{v' ∈ V| (v',v) ∈ E}|&lt;br /&gt;
&lt;br /&gt;
Das folgende Bild zeigt einen gerichteten Graphen. Hier gilt &amp;lt;tt&amp;gt;out_degree(1) == out_degree(3) == in_degree(2) == in_degree(4) == 2&amp;lt;/tt&amp;gt; und &lt;br /&gt;
&amp;lt;tt&amp;gt;in_degree(1) == in_degree(3) == out_degree(2) == out_degree(4) == 0&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
[[Image:digraph.png|gerichteter Graph]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
;Vollständiger Graph: Ein vollständiger Graph ist ein ungerichteter Graph, bei dem jeder Knoten mit allen anderen Knoten verbunden ist.&lt;br /&gt;
:::&amp;lt;math&amp;gt;E = \{ (v,w) |  v \in V, w \in V, v \ne w \}&amp;lt;/math&amp;gt;&lt;br /&gt;
:Ein vollständiger Graph mit |V| Knoten hat &amp;lt;math&amp;gt;|E| = \frac{|V|(|V|-1)}{2}&amp;lt;/math&amp;gt; Kanten.&lt;br /&gt;
&lt;br /&gt;
Die folgenden Abbildungen zeigen die vollständigen Graphen mit einem bis fünf Knoten (auch als K&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; bis K&amp;lt;sub&amp;gt;5&amp;lt;/sub&amp;gt; bezeichnet).&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;0&amp;quot; cellspacing=&amp;quot;0&amp;quot; cellpadding=&amp;quot;0&amp;quot; style=&amp;quot;margin: 1em auto 1em auto&amp;quot;&lt;br /&gt;
|- &lt;br /&gt;
| [[Image:k1.png|frame|k1]]&lt;br /&gt;
| [[Image:k2.png|frame|k2]]&lt;br /&gt;
| [[Image:k3.png|frame|k3]]&lt;br /&gt;
|-&lt;br /&gt;
| [[Image:k4.png|frame|k4]]&lt;br /&gt;
| [[Image:k5.png|frame|k5]]&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
''Rätsel''&amp;lt;br/&amp;gt;&lt;br /&gt;
Auf einer Party sind Leute. Alle stoßen miteinander an. Es hat 78 mal &amp;quot;Pling&amp;quot; gemacht.&lt;br /&gt;
Wieviele Leute waren da? Antwort: Jede Person ist ein Knoten des Graphen, jedes Antoßen eine Kante. &lt;br /&gt;
Da alle miteinander angestoßen haben, handelt es sich um einen vollständigen Graphen. Mit&lt;br /&gt;
|V|(|V|-1)/2 = 78 folgt, dass es 13 Personen waren.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
;Gewichteter Graph: Ein Graph heißt ''gewichtet'', wenn jeder Kante eine reelle Zahl zugeordnet ist. Bei vielen Anwendungen beschränkt man sich auch auf nichtnegative reelle Gewichte. In einem gerichteten Graphen können die Gewichte der Kanten (u,v) und (v,u) unterschiedlich sein.&lt;br /&gt;
&lt;br /&gt;
Die Gewichte kodieren Eigenschaften der Kanten, die für die jeweilige Anwendung interessant sind. Bei der Berechnung des maximalen Flusses in einem Netzwerk sind die Gewichte z.B. die Durchflusskapazitäten jeder Kante, bei der Suche nach kürzesten Weges kodieren Sie den Abstand zwischen den Endknoten der Kante, bei Währungsnetzwerken (jeder Knoten ist eine Währung) geben sie die Wechselkurse an, usw..&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
;Teilgraphen: Ein Graph G' = (V',E') ist ein Teilgraph eines Graphen G, wenn gilt:&lt;br /&gt;
:* V' &amp;amp;sube; V &lt;br /&gt;
:* E' &amp;amp;sub; E &lt;br /&gt;
:Er heißt ''(auf)spannender Teilgraph'', wenn gilt:&lt;br /&gt;
:* V' = V&lt;br /&gt;
:Er heißt ''induzierter Teilgraph'', wenn gilt:&lt;br /&gt;
:* e = (u,v) ∈ E' &amp;amp;sub; E &amp;amp;hArr; u ∈ V' und v ∈ V'&lt;br /&gt;
:Den von V' induzierten Teilgraphen erhält man also, indem man aus G alle Knoten löscht, die nicht in V' sind, sowie alle Kanten (und nur diese Kanten), die einen der gelöschten Knoten als Endknoten haben.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
;Wege, Pfade, Zyklen, Kreise, Erreichbarkeit: Sei G = (V,E) ein Graph (ungerichtet oder gerichteter) Graph. Dann gilt folgende rekursive Definition:&lt;br /&gt;
:* Für v ∈ V ist (v) ein Weg der Länge 0 in G&lt;br /&gt;
:* Falls &amp;lt;math&amp;gt;(v_0, v_1, ..., v_{n-1})&amp;lt;/math&amp;gt; ein Weg ist, und eine Kante &amp;lt;math&amp;gt;(v_{n-1}, v_n)\in E&amp;lt;/math&amp;gt; existiert, dann ist auch &amp;lt;math&amp;gt;(v_0, v_1, ..., v_{n-1}, v_n)&amp;lt;/math&amp;gt; ein Weg, und er hat die Länge n. &lt;br /&gt;
: Ein Weg ist also eine nichtleere Folge von Knoten, so dass aufeinander folgende Knoten stets durch eine Kante verbunden sind. Die Länge des Weges entspricht der Anzahl der Kanten im Weg (= Anzahl der Knoten - 1).&lt;br /&gt;
:* Ein ''Pfad'' &amp;lt;math&amp;gt;(v_0, v_1, ..., v_{n-1}, v_n)&amp;lt;/math&amp;gt; ist ein Weg, bei dem alle Knoten v&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; verschieden sind.&lt;br /&gt;
:* ''Ein Zyklus'' &amp;lt;math&amp;gt;(v_0, v_1, ..., v_{n-1}, v_n)&amp;lt;/math&amp;gt; ist ein Weg, der zum Ausgangspunkt zurückkehrt, wenn also v&amp;lt;sub&amp;gt;0&amp;lt;/sub&amp;gt; = v&amp;lt;sub&amp;gt;n&amp;lt;/sub&amp;gt; gilt.&lt;br /&gt;
:* Ein ''Kreis'' ist ein Zyklus ohne Überkreuzungen. Das heisst, es gilt v&amp;lt;sub&amp;gt;0&amp;lt;/sub&amp;gt; = v&amp;lt;sub&amp;gt;n&amp;lt;/sub&amp;gt; und &amp;lt;math&amp;gt;(v_0, v_1, ..., v_{n-1})&amp;lt;/math&amp;gt; ist ein Pfad.&lt;br /&gt;
:* Ein Knoten w ∈ V ist von einem anderen Knoten v ∈ V aus ''erreichbar'' genau dann, wenn ein Weg (v, ..., w) existiert. Wir schreiben dann &amp;lt;math&amp;gt;v \rightsquigarrow w&amp;lt;/math&amp;gt;.&lt;br /&gt;
In einem ungerichteten Graph ist die Erreichbarkeits-Relation stets symmetrisch, das heisst aus &amp;lt;math&amp;gt;v \rightsquigarrow w&amp;lt;/math&amp;gt; folgt &amp;lt;math&amp;gt;w \rightsquigarrow v&amp;lt;/math&amp;gt;. In einem gerichteten Graphen ist dies im allgemeinen nicht der Fall.&lt;br /&gt;
&lt;br /&gt;
Bestimmte Wege haben spezielle Namen&lt;br /&gt;
&lt;br /&gt;
;Eulerweg: Ein Eulerweg ist ein Weg, der alle '''Kanten''' genau einmal enthält.&lt;br /&gt;
&lt;br /&gt;
Die eingangs erwähnte Frage des Königsberger Brückenproblems ist equivalent zu der Frage, ob der dazugehörige Graph einen Eulerweg besitzt (daher der Name). Ein anderes bekanntes Beispiel ist das &amp;quot;Haus vom Nikolaus&amp;quot;: Wenn man diesen Graphen in üblicher Weise in einem Zug zeichnet, erhält man gerade den Eulerweg. &lt;br /&gt;
&lt;br /&gt;
    O&lt;br /&gt;
   /  \&lt;br /&gt;
  O----O&lt;br /&gt;
  | \/ |&lt;br /&gt;
  | /\ |   &amp;quot;Das Haus vom Nikolaus&amp;quot;: Alle ''Kanten'' werden nur ''einmal'' passiert&lt;br /&gt;
  O----O&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
;Hamiltonweg: Ein Hamiltonweg ist ein Weg, der alle '''Knoten''' genau einmal enthält. Das &amp;quot;Haus vom Nikolaus&amp;quot; besitzt auch einen Hamiltonweg:&lt;br /&gt;
&lt;br /&gt;
    O&lt;br /&gt;
   /   &lt;br /&gt;
  O----O&lt;br /&gt;
     /  &lt;br /&gt;
    /      Alle ''Knoten'' werden nur ''einmal'' passiert&lt;br /&gt;
  O----O&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
;Hamiltonkreis: Ein Hamiltonkreis ist ein Kreis, der alle '''Knoten''' genau einmal enthält. Auch ein solches Gebilde ist im Haus von Nilolaus enthalten:&lt;br /&gt;
&lt;br /&gt;
    O&lt;br /&gt;
   /  \&lt;br /&gt;
  O    O&lt;br /&gt;
  |    |   v0 = vn&lt;br /&gt;
  |    |   vi != vj   Für Alle i,j   i !=j; i,j &amp;gt;0; i,j &amp;lt; n&lt;br /&gt;
  O----O     &lt;br /&gt;
&lt;br /&gt;
Die folgende Skizze zeigt hingegen einen Zyklus: Der Knoten rechts unten sowie die untere Kante sind zweimal enthalten (die Kante einmal von links nach rechts und einmal von rechts nach links):&lt;br /&gt;
&lt;br /&gt;
    O&lt;br /&gt;
   /  \&lt;br /&gt;
  O    O&lt;br /&gt;
    \  |&lt;br /&gt;
     \ |   Zyklus&lt;br /&gt;
  O====O&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
;Zusammenhang, Zusammenhangskomponenten: Ein ungerichteter Graph G heißt ''zusammenhängend'', wenn für alle v,w ∈ V gilt:&lt;br /&gt;
:::&amp;lt;math&amp;gt;v \rightsquigarrow w&amp;lt;/math&amp;gt;&lt;br /&gt;
:Ein gerichteter Graph G ist zusammenhängend, wenn für alle v,w ∈ V gilt:&lt;br /&gt;
:::&amp;lt;math&amp;gt;v \rightsquigarrow w&amp;lt;/math&amp;gt; '''oder''' &amp;lt;math&amp;gt;w \rightsquigarrow v&amp;lt;/math&amp;gt;. &lt;br /&gt;
:Er ist ''stark zusammenhängend'', wenn für alle v,w ∈ V gilt:&lt;br /&gt;
:::&amp;lt;math&amp;gt;v \rightsquigarrow w&amp;lt;/math&amp;gt; '''und''' &amp;lt;math&amp;gt;w \rightsquigarrow v&amp;lt;/math&amp;gt;. &lt;br /&gt;
:Entsprechende Definitionen gelten für Teilgraphen G'. Ein Teilgraph G' heisst ''Zusammenhangskomponente'' von G, wenn er ein ''maximaler'' zusammenhängender Teilgraph ist, d.h. wenn G' zusammenhängend ist, und man keine Knoten und Kanten aus G mehr zu G' hinzufügen kann, so dass G' immer noch zusammenhängend bleibt. Entsprechend definiert man ''starke Zusammenhangskomponenten'' in einem gerichteten Graphen.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
;Planarer Graph, ebener Graph: Ein Graph heißt ''planar'', wenn er so in einer Ebene gezeichnet werden ''kann'', dass sich die Kanten nicht schneiden (außer an den Knoten). Ein Graph heißt ''eben'', wenn er tatsächlich so gezeichnet ''ist'', dass sich die Kanten nicht schneiden. Die Einbettung in die Ebene ist im allgemeinen nicht eindeutig.&lt;br /&gt;
&lt;br /&gt;
'''Beispiele:'''&lt;br /&gt;
&lt;br /&gt;
Der folgende Graph ist planar und eben:&lt;br /&gt;
 &lt;br /&gt;
      O&lt;br /&gt;
     /|\&lt;br /&gt;
    / O \&lt;br /&gt;
   / / \ \&lt;br /&gt;
   O     O&lt;br /&gt;
&lt;br /&gt;
Das &amp;quot;Haus vom Nikolaus&amp;quot; ist ebenfalls planar, wird aber üblicherweise nicht als ebener Graph gezeichnet, weil sich die Diagonalen auf der Wand überkreuzen:&lt;br /&gt;
 &lt;br /&gt;
    O&lt;br /&gt;
   /  \&lt;br /&gt;
  O----O&lt;br /&gt;
  | \/ |&lt;br /&gt;
  | /\ |   &lt;br /&gt;
  O----O&lt;br /&gt;
&lt;br /&gt;
Eine ebene Einbettung dieses Graphen wird erreicht, wenn man eine der Diagonalen ausserhalb des Hauses zeichnet. Der Graph (also die Menge der Knoten und Kanten) ändert sich dadurch nicht.&lt;br /&gt;
 &lt;br /&gt;
      O  &lt;br /&gt;
     /  \&lt;br /&gt;
  --O----O&lt;br /&gt;
 /  |  / |&lt;br /&gt;
 |  | /  |   &lt;br /&gt;
 |  O----O      Das &amp;quot;Haus vom Nikolaus&amp;quot; als ebener Graph gezeichnet.&lt;br /&gt;
  \     /&lt;br /&gt;
   -----&lt;br /&gt;
&lt;br /&gt;
Eine alternative Einbettung erhalten wir, wenn wir die andere Diagonale außerhalb des Hauses zeichnen:&lt;br /&gt;
 &lt;br /&gt;
      O  &lt;br /&gt;
     /  \&lt;br /&gt;
    O----O--|&lt;br /&gt;
    | \  |  |&lt;br /&gt;
    |  \ |  | &lt;br /&gt;
    O----O  |     Alternative Einbettung des &amp;quot;Haus vom Nikolaus&amp;quot;.&lt;br /&gt;
    |       |&lt;br /&gt;
    |-------|&lt;br /&gt;
&lt;br /&gt;
Jede Einbettung eines planaren Graphen (also jeder ebene Graph) definiert eine eindeutige Menge von ''Regionen'':&lt;br /&gt;
&lt;br /&gt;
 |----O   @&lt;br /&gt;
 |   /@ \&lt;br /&gt;
 |  O----O&lt;br /&gt;
 |  |@ / |&lt;br /&gt;
 |  | / @|   &lt;br /&gt;
 |  O----O        @ entspricht jeweils einer ''Region''. Auch ausserhalb der Figur ist eine Region (die sogenannte ''unendliche'' Region).&lt;br /&gt;
 |@      |&lt;br /&gt;
 |-------|&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Der vollständige Graph K5 ist kein planarer Graph, da sich zwangsweise Kanten schneiden, wenn man diesen Graphen in der Ebene zeichnet.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
;Dualer Graph: Jeder ebene Graph G = (V, E) hat einen ''dualen Graphen'' D = (V&amp;lt;sub&amp;gt;D&amp;lt;/sub&amp;gt;, E&amp;lt;sub&amp;gt;D&amp;lt;/sub&amp;gt;), dessen Knoten und Kanten wie folgt definiert sind:&lt;br /&gt;
:* V&amp;lt;sub&amp;gt;D&amp;lt;/sub&amp;gt; enthält einen Knoten für jede Region des Graphen G&lt;br /&gt;
:* Für jede Kante e ∈ E gibt es eine duale Kante e&amp;lt;sub&amp;gt;D&amp;lt;/sub&amp;gt; ∈ E&amp;lt;sub&amp;gt;D&amp;lt;/sub&amp;gt;, die die an e angrenzenden Regionen (genauer: die entsprechenden Knoten in D) verbindet.&lt;br /&gt;
&lt;br /&gt;
Die folgende Abbildung zeigt einen Graphen (grau) und seinen dualen Graphen (schwarz). Die Knoten des dualen Graphen sind mit Zahlen gekennzeichnet und entsprechen den Regionen des Originalgraphen. Jeder (grauen) Kante des Originalgraphen entspricht eine (schwarze) Kante des dualen Graphen.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
[[Image:dual-graphs.png]]&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für duale Graphen gilt: Wenn der Originalgraph zusammenhängend ist, enthält jede Region des dualen Graphen genau einen Knoten des Originalgraphen. Deshalb ist der duale Graph des dualen Graphen wieder der Originalgraph. Bei nicht-zusammenhängenden Graphen gilt dies nicht (vgl. das Fenster bei obigem Bild). In diesem Fall hat der duale Graph mehrere mögliche Einbettungen in die Ebene (man kann z.B. die rechte Kante zwischen Knoten 2 und 4 auch links vom Fenster einzeichnen), und man erhält nicht notwendigerweise den Originalgraphen, wenn man den dualen Graphen des dualen berechnet.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
;Baum: Ein ''Baum'' ist ein zusammenhängender, kreisfreier Graph.&lt;br /&gt;
&lt;br /&gt;
Beispiel: Binärer Suchbaum&lt;br /&gt;
&lt;br /&gt;
;Spannbaum: Ein ''Spannbaum'' eines zusammenhängenden Graphen G ist ein zusammenhängender, kreisfreier Teilgraph von G, der alle Knoten von G enthält&lt;br /&gt;
&lt;br /&gt;
Beispiel: Spannbaum für das &amp;quot;Haus des Nikolaus&amp;quot; &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    O   &lt;br /&gt;
   /       &lt;br /&gt;
  O    O&lt;br /&gt;
  |  /  &lt;br /&gt;
  | /   &lt;br /&gt;
  O----O&lt;br /&gt;
&lt;br /&gt;
Der Spannbaum eines Graphen mit |V| Knoten hat stets |V| - 1 Kanten.&lt;br /&gt;
&lt;br /&gt;
;Wald: Ein ''Wald'' ist ein unzusammenhängender, kreisfreier Graph.&lt;br /&gt;
: Jede Zusammenhangskomponente eines Waldes ist ein Baum.&lt;br /&gt;
&lt;br /&gt;
=== Repräsentation von Graphen ===&lt;br /&gt;
&lt;br /&gt;
Sei G = ( V, E ) gegeben und liege V in einer linearen Sortierung vor.&amp;lt;br/&amp;gt; &lt;br /&gt;
:::&amp;lt;math&amp;gt;V = \{ v_1, ...., v_n \}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
;Adjazenzmatrix: Ein Graph kann durch eine Adjazenzmatrix repräsentiert werden, die soviele Zeilen und Spalten enthält, wie der Graph Knoten hat. Die Elemente der Adjazenzmatrix sind &amp;quot;1&amp;quot;, falls eine Kante zwischen den zugehörigen Knoten existiert:&lt;br /&gt;
:::&amp;lt;math&amp;gt;\mathrm{\bold A} = a_{ij} = &lt;br /&gt;
\begin{cases}&lt;br /&gt;
1 &amp;amp; \mathrm{falls}\quad (v_i, v_j) \in E \\&lt;br /&gt;
0 &amp;amp; \mathrm{sonst}&lt;br /&gt;
\end{cases} &lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
:Die Indizes der Matrix entsprechen also den Indizes der Knoten gemäß der gegebenen Sortierung. Im Falle eines ungerichteten Graphen ist die Adjazenzmatrix stets symmetrisch (d.h. es gilt &amp;lt;math&amp;gt;a_{ij}=a_{ji}&amp;lt;/math&amp;gt;), bei einem gerichteten Graphen ist sie im allgemeinen unsymmetrisch.&lt;br /&gt;
&lt;br /&gt;
Beispiel für einen ungerichteten Graphen:&lt;br /&gt;
&lt;br /&gt;
 v = { a,b,c,d }     b      d&lt;br /&gt;
                     | \  / |&lt;br /&gt;
                     |  \/  |&lt;br /&gt;
                     |  /\  |&lt;br /&gt;
                     | /  \ |&lt;br /&gt;
                     a      c&lt;br /&gt;
 &lt;br /&gt;
       a b c d&lt;br /&gt;
      -----------&lt;br /&gt;
      (0 1 0 1) |a &lt;br /&gt;
  A = (1 0 1 0) |b&lt;br /&gt;
      (0 1 0 1) |c&lt;br /&gt;
      (1 0 1 0) |d&lt;br /&gt;
&lt;br /&gt;
Die Adjazenzmatrixdarstellung eignet sich besonders für dichte Graphen (d.h. wenn die Zahl der Kanten in O(|V|&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;) ist.&lt;br /&gt;
&lt;br /&gt;
;Adjazenzlisten: In der Adjazenzlistendarstellung wird der Graph als Liste von Knoten repräsentiert, die für jeden Knoten einen Eintrag enthält. Der Eintrag für jeden Knoten ist wiederum eine Liste, die die Nachbarknoten dieses Knotens enthält:&lt;br /&gt;
:* graph = {adjazencyList(v) | v ∈ V}&lt;br /&gt;
:* adjazencyList(v) = {v' ∈ V | (v, v') ∈ E}&lt;br /&gt;
&lt;br /&gt;
In Python implementieren wir Adjazenzlisten zweckmäßig als Array von Arrays:&lt;br /&gt;
&lt;br /&gt;
                   graph = [[...],[...],...,[...]]&lt;br /&gt;
 Adjazenzliste für Knoten =&amp;gt;  0     1         n&lt;br /&gt;
&lt;br /&gt;
Wenn wir bei dem Graphen oben die Knoten wie bei der Adjazenzmatrix indizieren (also &amp;lt;tt&amp;gt;a =&amp;gt; 0&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;b =&amp;gt; 1&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;c =&amp;gt; 2&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;d =&amp;gt; 3&amp;lt;/tt&amp;gt;), erhalten wir die Adjazenzlistendarstellung:&lt;br /&gt;
&lt;br /&gt;
 graph = [[b, d], [a, c],[b, d], [a, c]]&lt;br /&gt;
&lt;br /&gt;
Auf die Nachbarknoten eines durch seinen Index &amp;lt;tt&amp;gt;node&amp;lt;/tt&amp;gt; gegebenen Knotens können wir also wie folgt zugreifen:&lt;br /&gt;
&lt;br /&gt;
      for neighbors in graph[node]:&lt;br /&gt;
          ... # do something with neighbor&lt;br /&gt;
&lt;br /&gt;
Die Adjazenzlistendarstellung ist effizienter, wenn der Graph nicht dicht ist, so dass viele Einträge der Adjazenzmatrix Null wären.&lt;br /&gt;
&lt;br /&gt;
;Transponierter Graph: Den ''transponierten Graphen'' G&amp;lt;sup&amp;gt;T&amp;lt;/sup&amp;gt; eines gerichteten Graphen G erhält man, wenn man alle Kantenrichtungen umkehrt.&lt;br /&gt;
&lt;br /&gt;
Bei ungerichteten Graphen hat die Transposition offensichtlich keinen Effekt, weil alle Kanten bereits in beiden Richtungen vorhanden sind, so dass G&amp;lt;sup&amp;gt;T&amp;lt;/sup&amp;gt; = G gilt. Bei gerichteten Graphen ist die Transposition dann einfach, wenn der Graph als Adjazenzmatrix implementiert ist, weil man einfach die transponierte Adjazenzmatrix verwenden muss (beachte, dass sich die Reihenfolge der Indizes umkehrt):&lt;br /&gt;
:::A&amp;lt;sup&amp;gt;T&amp;lt;/sup&amp;gt; = a&amp;lt;sub&amp;gt;ji&amp;lt;/sub&amp;gt;&lt;br /&gt;
Ist der Graph hingegen durch eine Adjazenzliste repräsentiert, muss etwas mehr Aufwand getrieben werden:&lt;br /&gt;
&lt;br /&gt;
 def transpose(graph):&lt;br /&gt;
      gt = [[] for k in graph]   # zunächst leere Adjazenzlisten von G&amp;lt;sup&amp;gt;T&amp;lt;/sup&amp;gt;&lt;br /&gt;
      for node in range(len(graph)):&lt;br /&gt;
           for neighbor in graph[node]:&lt;br /&gt;
               gt[neighbor].append(node)  # füge die umgekehrte Kante in G&amp;lt;sup&amp;gt;T&amp;lt;/sup&amp;gt; ein&lt;br /&gt;
      return gt&lt;br /&gt;
&lt;br /&gt;
== Durchlaufen von Graphen ==&lt;br /&gt;
&lt;br /&gt;
=== Tiefensuche in Graphen ===&lt;br /&gt;
&lt;br /&gt;
Sei der Graph gegeben als Liste von Listen = g&lt;br /&gt;
&lt;br /&gt;
 def dfs (g,node,v=0):&lt;br /&gt;
   if v == 0:&lt;br /&gt;
     v = [0]*len(g) #visited-Liste&lt;br /&gt;
   v[node] = 1 #besuche node&lt;br /&gt;
   for t in g[node]: #gehe zu allen Nachbarn&lt;br /&gt;
     if v[t] == 0: #falls diese noch nicht besucht&lt;br /&gt;
       dfs(g,t,v) #Rekursion&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Image:Tiefens.jpg]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Aufruf dfs(g,1)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;Folge 1,2,4,3,6,7,5&lt;br /&gt;
&lt;br /&gt;
=== Breitensuche ===&lt;br /&gt;
&lt;br /&gt;
 from Queue import *&lt;br /&gt;
 def bfs(g,startnode)&lt;br /&gt;
   v = [0]*len(g)&lt;br /&gt;
   q = Queue()&lt;br /&gt;
   v[startnode] = 1 #besuche&lt;br /&gt;
   q.put(startnode) #in Schlange&lt;br /&gt;
   while not q.empty():&lt;br /&gt;
     node = q.get()&lt;br /&gt;
     for t in g[node]: &lt;br /&gt;
       if v[t] == 0:&lt;br /&gt;
         v[t] = 1&lt;br /&gt;
         q.put(t)&lt;br /&gt;
&lt;br /&gt;
Aufgrund von Problemen mit der Implementation von Queue eine neue Version:&lt;br /&gt;
&lt;br /&gt;
 def bfs(g,startnode)&lt;br /&gt;
   v = [0]*len(g)&lt;br /&gt;
   q = []&lt;br /&gt;
   v[startnode] = 1 #besuche&lt;br /&gt;
   q.append(startnode) #in Schlange&lt;br /&gt;
   while not len(q):&lt;br /&gt;
     node = q.pop(0)&lt;br /&gt;
     for t in g[node]:&lt;br /&gt;
       if v[t] == 0:&lt;br /&gt;
         v[t] = 1&lt;br /&gt;
         q.append(t)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Image:Breitens.jpg]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;Folge 1,2,3,4,5,6,7&lt;br /&gt;
&lt;br /&gt;
== Damenproblem ==&lt;br /&gt;
&lt;br /&gt;
  ---------------&lt;br /&gt;
 |   | X |   |   |&lt;br /&gt;
 |---|---|---|---| &lt;br /&gt;
 |   |   |   | X |&lt;br /&gt;
 |---|---|---|---|&lt;br /&gt;
 | X |   |   |   |&lt;br /&gt;
 |---|---|---|---|&lt;br /&gt;
 |   |   |   | X |&lt;br /&gt;
  ---------------&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4 Damen auf einem vereinfachten Schachbrett so Positionieren, dass sich keine bedroht.&lt;br /&gt;
&lt;br /&gt;
erster Durchlauf:&lt;br /&gt;
&lt;br /&gt;
[[Image:Suche1.jpg]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
zweiter Durchlauf:&lt;br /&gt;
&lt;br /&gt;
[[Image:Suche2.jpg]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Weitere Anwendungen (18.06.08) ==&lt;br /&gt;
&lt;br /&gt;
 def dfs(graph):&lt;br /&gt;
        &amp;amp;#39;&amp;amp;#39;&amp;amp;#39;&lt;br /&gt;
        Diese Tiefensuche tut so noch nichts weiter als zu traversieren&lt;br /&gt;
        + graph ist Array,&lt;br /&gt;
            i-ter Eintrag enthaelt Adjazenzliste (auch Array) des i-ten Knotens,&lt;br /&gt;
            wobei Knoten nummeriert von 0 ... v-i&lt;br /&gt;
        &amp;amp;#39;&amp;amp;#39;&amp;amp;#39;&lt;br /&gt;
        def visit(graph, node, visited):&lt;br /&gt;
                &amp;amp;#39;&amp;amp;#39;&amp;amp;#39;&lt;br /&gt;
                visited ist Array mit Flags fuer besuchte Knoten&lt;br /&gt;
                &amp;amp;#39;&amp;amp;#39;&amp;amp;#39;&lt;br /&gt;
                if visited[node]: return&lt;br /&gt;
                visited[node] = True&lt;br /&gt;
                for neighbor in graph[node]:&lt;br /&gt;
                        visit(graph, neighbor, visited)&lt;br /&gt;
        visited = [False]*len(graph)&lt;br /&gt;
        for node in range(len(graph)):&lt;br /&gt;
                visit(graph, node, visited)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Finden von Zusammenhangskomponenten ===&lt;br /&gt;
&lt;br /&gt;
Ein möglicher Einsatz des Verfahrens ist das Finden von Zusammenhangskomponenten (connected components).&lt;br /&gt;
&lt;br /&gt;
* Beispiel: ...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Definition: CC_i = {u_k, u_l e V: es gibt einen Pfad von u_k nach u_l (&amp;quot;u_l ist von u_k aus erreichbar&amp;quot;)&lt;br /&gt;
* für gerichtete Graphen gilt zusätzlich: es gibt einen Pfad von u_l nach u_k}&lt;br /&gt;
&lt;br /&gt;
Die Relation CC_i, also die Zusammenhangskomponenten (ZK) bilden eine Aequivalenzrelation,&lt;br /&gt;
also kann fuer jede ZK ein Repraesentant bestimmt werden (der sog. &amp;quot;Anker&amp;quot;). Kennt jeder&lt;br /&gt;
Knoten seinen Anker, so ist das ZK-Problem geloest.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Tiefensuchen-Algorithmus ====&lt;br /&gt;
&lt;br /&gt;
Unser erster Ansatz ist, den Anker mit Hilfe der Tiefensuche zu finden, wobei statt&lt;br /&gt;
Knotenbesuche Knotennummern fuer die schon gefundenen Anker gesetzt werden. Ein moeglicher&lt;br /&gt;
Algorithmus lautet damit wie folgt:&lt;br /&gt;
&lt;br /&gt;
 def connectedComponents(graph):&lt;br /&gt;
        def visit(graph, node, anchors, anchor):&lt;br /&gt;
                &amp;amp;#39;&amp;amp;#39;&amp;amp;#39;&lt;br /&gt;
                anchor ist Anker der aktuellen ZK&lt;br /&gt;
                &amp;amp;#39;&amp;amp;#39;&amp;amp;#39;&lt;br /&gt;
                if anchors[node] is not None: return # Anker von &amp;lt;node&amp;gt; schon bekannt&lt;br /&gt;
                anchors[node] = anchor&lt;br /&gt;
                for neighbor in graph[node]&lt;br /&gt;
                        visit(graph, neighbor, anchors, anchor)&lt;br /&gt;
        &lt;br /&gt;
        anchors = [None]*len(graph)&lt;br /&gt;
        for node in range(len(graph)):&lt;br /&gt;
                visit(graph, node, anchors, node) # node: Anker der naechste ZK = erster Knoten der ZK&lt;br /&gt;
        return anchors&lt;br /&gt;
&lt;br /&gt;
* Beispiel: ...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Union-Find-Algorithmus ====&lt;br /&gt;
&lt;br /&gt;
Eine Alternative (ohne Tiefensuche) waere z.B. ein Union-Find-Algorithmus. Idee dabei ist, dass eingangs jeder Knoten eine eigene ZK bildet, wobei in einer anschliessenden Rekursion Kanten gesucht werden, die zwischen den ZK bestehen.&lt;br /&gt;
&lt;br /&gt;
Initialisierung: jeder Knoten wird als 1 ZK behandelt&lt;br /&gt;
Rekursion: fasse ZK zusammen (Union) falls Kante zwischen ihnen existiert&lt;br /&gt;
Ergebnis: Array mit dem Anker jedes Knotens&lt;br /&gt;
&lt;br /&gt;
 def unionFindCC(graph):&lt;br /&gt;
        def findAnchor(anchors, k):&lt;br /&gt;
                &amp;amp;#39;&amp;amp;#39;&amp;amp;#39;&lt;br /&gt;
                #Prueft auf anchors[k]==k&lt;br /&gt;
                &amp;amp;#39;&amp;amp;#39;&amp;amp;#39;&lt;br /&gt;
                while anchors[k] != k:&lt;br /&gt;
                        k = anchors[k]&lt;br /&gt;
                return k&lt;br /&gt;
 &lt;br /&gt;
        def edges(graph):&lt;br /&gt;
                e = []&lt;br /&gt;
                for node in range(len(graph)):&lt;br /&gt;
                        for n in graph[node]:&lt;br /&gt;
                                if node &amp;lt; n:&lt;br /&gt;
                                        e.append((node, n))&lt;br /&gt;
                return e&lt;br /&gt;
 &lt;br /&gt;
        anchors = range(len(graph)) # jeder Knoten ist sein eigener Anker&lt;br /&gt;
        for edge in edges(graph):&lt;br /&gt;
                # diese Schleife ordnet die Anker so, dass&lt;br /&gt;
                #   der 1. Anker immer der kleinste ist&lt;br /&gt;
                a1, a2 = findAnchor(anchors, edge[0]), findAnchor(anchors, edge[1])&lt;br /&gt;
                if a2 &amp;lt; a1: a2,a1 = a1,a2&lt;br /&gt;
                if a1 != a2: anchors[a2] = a1&lt;br /&gt;
        for node in range(len(graph)):&lt;br /&gt;
                # diese Schleife raeumt mit Indirektionen auf (s. Bsp. (#))&lt;br /&gt;
                anchors[node] = findAnchor(anchors, node)&lt;br /&gt;
        return anchors&lt;br /&gt;
&lt;br /&gt;
* Beispiel (#): ...&lt;br /&gt;
&lt;br /&gt;
Eine verbreitete Anwendung fuer dieses Verfahren gibt es in der Bildverarbeitung:&lt;br /&gt;
&lt;br /&gt;
* Beispiel: ...&lt;br /&gt;
&lt;br /&gt;
== Variationen der Tiefensuche (19.06.2008) ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Wichtige Algorithmen, die in der Vorlesung nicht behandelt werden ===&lt;br /&gt;
&lt;br /&gt;
* Max Flow (zur Bestimmung des maximalen Flusses durch ein Netzwerk, z.B. bei Ölpipelines)&lt;br /&gt;
* Matching (auch ''Paarung'' genannt): Teilmenge der Kanten eines Graphen, wobei keine zwei Kanten einen gleichen Knoten besitzen&lt;br /&gt;
*:Anwendungsbereiche: Zuordnung von Gruppen, z.B. Arbeitsamt (Zuordnung Arbeitssuchender - Stellenangebot), Universität (Zuordnung Studenten - Übungsgruppen) &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Vereinfachte Lösung für den ''acyclic''-Algorithmus ===&lt;br /&gt;
Zum Finden von Zyklen, bzw. der Feststellung, ob ein Graph azyklisch ist, verwenden wir&lt;br /&gt;
wieder eine modifizierte Version der Tiefensuche: Die Knoten werden wieder nach dem System der Tiefensuche besucht, und alle besuchten Knoten in einem Array visited abgespeichert. Es gibt einen Zyklus genau dann, wenn man zu&lt;br /&gt;
einem früheren Knoten (außer zum direkten Vorgaenger) zurückkommt.&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;code python&amp;gt;&lt;br /&gt;
     def acyclic(graph):&lt;br /&gt;
         def visit(graph, node, fromNode, visited):&lt;br /&gt;
             if visited[node]:			# Zyklus entdeckt&lt;br /&gt;
                 return False&lt;br /&gt;
             visited[node] = True&lt;br /&gt;
             for neighbor in graph[node]:&lt;br /&gt;
                 if neighbor == fromNode:	# überspringe Nachbar, von dem du gekommen bist&lt;br /&gt;
                     continue&lt;br /&gt;
                 if not visit(graph, neighbor, node, visited):&lt;br /&gt;
                     return False		# der Graph ist zyklisch&lt;br /&gt;
             return True			# kein Zyklus&lt;br /&gt;
         visited = [False]*len(graph)&lt;br /&gt;
         for node in range(len(graph)):&lt;br /&gt;
             if visited[node]:	# schließt aus, dass Knoten besucht wird, der schon besucht war&lt;br /&gt;
                 continue&lt;br /&gt;
             if not visit(graph, node, None, visited):&lt;br /&gt;
                 return False&lt;br /&gt;
         return True&lt;br /&gt;
   &amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Anmerkungen zum Code:'''&lt;br /&gt;
&lt;br /&gt;
* Wenn ein Knoten bereits besucht ist, dann gehört er zur gleichen Zusammenhangskomponente - dies hat allerdings nichts mit einem Zyklus zu tun.&lt;br /&gt;
* Ein Graph der einmal zyklisch war wird nie wieder azyklisch.&lt;br /&gt;
* Der obige Algorithmus weist Ähnlichkeiten mit den bereits behandelten Algorithmen auf: '''ein guter Algorithmus zeichnet sich dadurch aus, dass mit kleinen Code-Variationen ganz andere Probleme gelöst werden können'''.&lt;br /&gt;
&lt;br /&gt;
=== Kürzeste Wege (Pfade) ===&lt;br /&gt;
&lt;br /&gt;
* Definition: gewichteter Graph&lt;br /&gt;
&lt;br /&gt;
 Jeder Kante e ist eine reelle oder natürliche Zahl w&amp;lt;sub&amp;gt;e&amp;lt;/sub&amp;gt; zugeordnet (wird auch als&lt;br /&gt;
 ''Kantengewicht'' bezeichnet).&lt;br /&gt;
&lt;br /&gt;
z.B. &lt;br /&gt;
* Abstand der Anfangs- und Endknoten&lt;br /&gt;
&lt;br /&gt;
* Durchflusskapazität eines Rohres (für max-Flussprobleme)&lt;br /&gt;
&lt;br /&gt;
* Wechselkurse (Darstellung in einem gerichteten Graph, da jede Kante auch eine Richtung hat. Die Knoten sind die Währungen, die Kanten sind die Wechselkurse. Auf diese Weise lassen sich unterschiedliche Wechselkurse + Bankgebühren darstellen.)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Definition''': Problem des kürzesten Weges&lt;br /&gt;
&lt;br /&gt;
Sei P die Menge aller Wege von u nach v&lt;br /&gt;
&lt;br /&gt;
 P&amp;lt;sub&amp;gt;uv&amp;lt;/sub&amp;gt; = {u_v}&lt;br /&gt;
&lt;br /&gt;
und der Weg gegeben durch&lt;br /&gt;
&lt;br /&gt;
 u &amp;amp;rarr; x&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; &amp;amp;rarr; x&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; &amp;amp;rarr; ... &amp;amp;rarr; v&lt;br /&gt;
&lt;br /&gt;
dann sind die Kosten eines Weges definiert durch&lt;br /&gt;
&lt;br /&gt;
 Kosten (P&amp;lt;sub&amp;gt;uv&amp;lt;/sub&amp;gt;) = &amp;lt;math&amp;gt;\sum\limits_{l \in Pv}&amp;lt;/math&amp;gt; w&amp;lt;sub&amp;gt;e&amp;lt;/sub&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* gesucht: Pfad u_v, so dass Kosten (u_v) minimal sind&lt;br /&gt;
&lt;br /&gt;
* Lösung: Algorithmus von Dijkstra&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Algorithmus von Dijkstra ===&lt;br /&gt;
&lt;br /&gt;
==== Edsger Wybe Dijkstra ====&lt;br /&gt;
&lt;br /&gt;
geb. 11. Mai 1930 in Rotterdam&lt;br /&gt;
&lt;br /&gt;
ges. 06. August 2002&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Dijkstra war ein niederländischer Informatiker und Wegbereiter der strukturierten Programmierung. 1972 erhielt er für seine Leistung in der Technik und Kunst der Programmiersprachen den Turing Award, der jährlich von der Association for Computing Machinery (ACM) an Personen verliehen wird, die sich besonders um die Entwicklung der Informatik verdient gemacht haben. Zu seinen Beiträgen zur Informatik gehören unter anderem der Dijkstra-Algorithmus zur Berechnung des kürzesten Weges in einem Graphen sowie eine Abhandlung über den go-to-Befehl und warum er nicht benutzt werden sollte. Der go-to-Befehl war in den 60er und 70er Jahren weit verbreitet, führte aber zu Spaghetti-Code. In seinem berühmten Paper &amp;quot;A Case against the GO TO Statement&amp;quot;[http://www.cs.utexas.edu/users/EWD/ewd02xx/EWD215.PDF], das als Brief mit dem Titel &amp;quot;Go-to statement considered harmful&amp;quot; veröffentlicht wurde, argumentiert Dijkstra, dass es umso schwieriger ist, dem Quellcode eines Programmes zu folgen, je mehr go-to-Befehle darin enthalten sind und zeigt, dass man auch ohne diesen Befehl gute Programme schreiben kann.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Algorithmus ====&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;code python&amp;gt;&lt;br /&gt;
    import heapq	# heapq ist ein Modul von Python&lt;br /&gt;
    def dijkstra(graph, start, ziel):	# graph: gewichtete Adjazenzliste&lt;br /&gt;
        heap = []&lt;br /&gt;
        visited = [None]*len(graph)&lt;br /&gt;
        visited[start] = start&lt;br /&gt;
        for neighbor in graph[start]:&lt;br /&gt;
            heapq.heappush(heap, (neighbor[1], start, neighbor[0])) # neighbor[1]:Kantengewicht,neighbor[0]:Endpunkt d. K.&lt;br /&gt;
        while len(heap) &amp;gt; 0:	# solange der heap nicht leer ist&lt;br /&gt;
            w, fromNode, node = heapq.heappop(heap)&lt;br /&gt;
            if visited[node] is not None:	# wenn der kürzeste Pfad bereits bekannt ist, überspringe ihn&lt;br /&gt;
                continue&lt;br /&gt;
            visited[node] = fromNode    # baue Vorgänger-Baum&lt;br /&gt;
            if node == ziel:	# da der heap noch nicht leer ist, wird an dieser Stelle ein break benötigt&lt;br /&gt;
                break&lt;br /&gt;
            for neighbor in graph[node]:&lt;br /&gt;
                if visited[neighbor[0]] is not None:	# wenn der kürzeste Pfad bereits bekannt ist, überspringe ihn&lt;br /&gt;
                    continue&lt;br /&gt;
                heapq.heappush(heap, (neighbor[1]+w, node, neighbor[0]))&lt;br /&gt;
        bestPath = []&lt;br /&gt;
        t = ziel&lt;br /&gt;
        while t != visited[t]:		# Array wird durchlaufen bis der Anker des Pfades gefunden ist, vgl. Union-Search&lt;br /&gt;
            bestPath.append(t)&lt;br /&gt;
            t=visited[t]&lt;br /&gt;
        bestPath.append(start)&lt;br /&gt;
        return bestPath			# bestPath.reverse()&lt;br /&gt;
  &amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Anmerkungen zum Code:'''&lt;br /&gt;
* der graph ist eine gewichtete Adjazenzliste&lt;br /&gt;
&lt;br /&gt;
{| &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
&lt;br /&gt;
| Knoten || style=&amp;quot;background:silver; color:white&amp;quot; | 0 || &amp;amp;rarr; || style=&amp;quot;background:silver; color:white&amp;quot; | Endknoten || &amp;amp;rarr; || style=&amp;quot;background:silver; color:white&amp;quot; | Endknoten || (Nr. der Nachbarn des Knoten 0)&lt;br /&gt;
&lt;br /&gt;
|- &lt;br /&gt;
&lt;br /&gt;
| || style=&amp;quot;background:silver; color:white&amp;quot; | 1 ||  || style=&amp;quot;background:silver; color:white&amp;quot; | Gewicht || || style=&amp;quot;background:silver; color:white&amp;quot; | Gewicht || (Gewicht der jeweiligen Kante)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
&lt;br /&gt;
| || style=&amp;quot;background:silver; color:white&amp;quot; | 2 ||&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
&lt;br /&gt;
| || style=&amp;quot;background:silver; color:white&amp;quot; | 3 ||&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
* Eingabe z.B.:&lt;br /&gt;
{| &lt;br /&gt;
|-&lt;br /&gt;
| Knoten || style=&amp;quot;background:silver; color:white&amp;quot; | 0 || &amp;amp;rarr; || style=&amp;quot;background:silver; color:white&amp;quot; | (1, 0.3) || style=&amp;quot;background:silver; color:white&amp;quot; | (3, 0.1) || style=&amp;quot;background:silver; color:white&amp;quot; | (5, 1.2) ||&lt;br /&gt;
|- &lt;br /&gt;
| || style=&amp;quot;background:silver; color:white&amp;quot; | 1 || &amp;amp;rarr; || style=&amp;quot;background:silver; color:white&amp;quot; | || style=&amp;quot;background:silver; color:white&amp;quot; |  || style=&amp;quot;background:silver; color:white&amp;quot; |  ||&lt;br /&gt;
|-&lt;br /&gt;
| || style=&amp;quot;background:silver; color:white&amp;quot; | 2 ||&lt;br /&gt;
|-&lt;br /&gt;
| || style=&amp;quot;background:silver; color:white&amp;quot; | 3 ||&lt;br /&gt;
|-&lt;br /&gt;
| || style=&amp;quot;background:silver; color:white&amp;quot; | 4 ||&lt;br /&gt;
|-&lt;br /&gt;
| || style=&amp;quot;background:silver; color:white&amp;quot; | 5 ||&lt;br /&gt;
|-&lt;br /&gt;
| || style=&amp;quot;background:silver; color:white&amp;quot; | 6 ||&lt;br /&gt;
|}&lt;br /&gt;
* heapq() verwendet den 1. Eintrag des Tupels zum sortieren des heap&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Prinzip des Dijkstra-Algorithmus ====&lt;br /&gt;
&lt;br /&gt;
* Algorithmus ist Tiefensuche mit Prioritätswarteschlange (Heap) statt eines Stapelspeichers (Stack) &amp;amp;rarr; vgl. Übung 8&lt;br /&gt;
&lt;br /&gt;
* Die Prioritätswarteschlange speichert die kürzesten Wege, die bereits gefunden worden sind.&lt;br /&gt;
&lt;br /&gt;
* Wenn man die Prioritätswarteschlange (Heap) durch eine Warteschlange (Queue) ersetzt, erhält man Breitensuche.&lt;br /&gt;
&lt;br /&gt;
* Wenn man die Prioritätswarteschlange (Heap) durch einen Stapelspeicher (Stack) ersetzt, erhält man Tiefensuche.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Beispiel ====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Image:Bsp.jpg]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* An der Stelle &amp;quot;neighbor[1]&amp;quot; wird eine Zählvariable ''count'' eingefügt, die hoch (Breitensuche) oder runter (Tiefensuche) zählt.&lt;br /&gt;
&lt;br /&gt;
* Die Gewichte werden hoch- oder runtergezählt, so wie die Kanten gesehen wurden.&lt;br /&gt;
&lt;br /&gt;
* Wenn man rückwärts zählt (von 0 abziehen), werden die zuletzt hinzugefügten Kanten expandiert.&lt;br /&gt;
&lt;br /&gt;
* '''Algorithmus von Dijkstra funktioniert &amp;lt;u&amp;gt;nur&amp;lt;/u&amp;gt; für &amp;lt;u&amp;gt;positive&amp;lt;/u&amp;gt; Kantengewichte&lt;br /&gt;
*:&amp;lt;math&amp;gt;\forall&amp;lt;/math&amp;gt; w&amp;lt;sub&amp;gt;e&amp;lt;/sub&amp;gt; &amp;gt; 0'''&lt;br /&gt;
&lt;br /&gt;
* Bei negativen Kantengewichten könnte es Zyklen geben, die negative Kosten für den ganzen Zyklus haben:&lt;br /&gt;
&lt;br /&gt;
     /\		1. Durchlauf: Kosten -1&lt;br /&gt;
  1 /  \ -4	2. Durchlauf: Kosten -2&lt;br /&gt;
   /____\	etc.&lt;br /&gt;
      2&lt;br /&gt;
&lt;br /&gt;
* Verwendung bei arbitragen Geschäften (Börsengeschäfte, die die Preis-, Kurs- und Zinsunterschiede auf verschiedenen Märkten ausnutzen):&lt;br /&gt;
*:EURO wurden in YEN, YEN in DOLLAR gewechselt und das Geld hat sich dadurch vermehrt&lt;br /&gt;
* Für negative Kantengewichte verwendet man den Bellman-Ford-Allgorithmus, der allerdings langsamer ist, als der Dijkstra-Algorithmus.&lt;br /&gt;
&lt;br /&gt;
==== Komplexität von Dijkstra ====&lt;br /&gt;
&lt;br /&gt;
* Jeder Knoten wird höchstens 1x expandiert (Iteration über die Nachbarn des Knotens).&lt;br /&gt;
&lt;br /&gt;
* Jeder Knoten kann mehrmals im Heap enthalten sein.&lt;br /&gt;
&lt;br /&gt;
* Es sind aber höchstens E (Anzahl der Kanten) Heap-Einträge möglich, da jede Kante höchstens 1 Heap-Eintrag generiert (ein Knoten ist nur dann im Heap, wenn man ihn über eine Kante erreicht hat, die man vorher noch nicht besucht hatte). Deshalb können nie mehr Einträge im Heap sein, als es Kanten gibt. Die Komplexität von heappush(), heappop() ist&lt;br /&gt;
 O(log E) = O(2 log v) = O(log v) &lt;br /&gt;
wenn alle Kanten einen Heap-Eintrag generiert haben.&lt;br /&gt;
* Die while-Schleife wird im schlimmsten Fall E mal durchlaufen, deshalb ist die Komplexität von Dijkstra O(E log v).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Korrektheit von Dijkstra ====&lt;br /&gt;
&lt;br /&gt;
* Falls &lt;br /&gt;
 visited[node] (Schleifen-Invariante von while) != None &lt;br /&gt;
ist, dann liefert Zurückverfolgen des Pfades von node nach start den kürzesten Pfad von start nach node (gilt für alle Knoten, für die das visited-Feld gesetzt ist).&lt;br /&gt;
* Induktionsanfang: visited[start] ist einziger not-None-Fall &amp;amp;rarr; Bedingung erfüllt&lt;br /&gt;
* Induktionsschritt: wenn visited[node] gesetzt wird, ist es ein kürzester Pfad&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Indirekter Beweis ====&lt;br /&gt;
&lt;br /&gt;
Set S = {node | visited[node] != None} (alle Knoten, von denen wir den kürzesten Pfad schon kennen)&lt;br /&gt;
&lt;br /&gt;
* u ist der Knoten an der Spitze des Heaps&lt;br /&gt;
* fromNode &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; S (ein Nachbar von node kommt erst dann in den Heap, wenn visited[node] vorher gesetzt wurde)&lt;br /&gt;
* falls u &amp;amp;rarr; fromNode &amp;amp;rarr; start kein kürzester Pfad wäre, müsste u's Vorgänger in V\S sein&lt;br /&gt;
* sei dieser Vorgänger x &amp;lt;math&amp;gt;\notin&amp;lt;/math&amp;gt; S, x &amp;lt;math&amp;gt;\not=&amp;lt;/math&amp;gt; u&lt;br /&gt;
* sei w&amp;lt;sub&amp;gt;x&amp;lt;/sub&amp;gt; das Gewicht der Kante x &amp;amp;rarr; u, dann sind die Kosten für start nach u gleich&lt;br /&gt;
&lt;br /&gt;
  Kosten(start_u) = Kosten(start_x) + wx&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Annahme des indirekten Beweises:&lt;br /&gt;
&lt;br /&gt;
  Kosten(start_fromNode) + w&amp;lt;sub&amp;gt;fromNode&amp;lt;/sub&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Behauptung des indirekten Beweises:&lt;br /&gt;
 Es gibt einen anderen Pfad x, so dass die Kosten von start nach x geringer sind&lt;br /&gt;
&lt;br /&gt;
* Da aber gilt:&lt;br /&gt;
 fromNode &amp;lt;math&amp;gt;\in&amp;lt;/math&amp;gt; S und x &amp;lt;math&amp;gt;\notin&amp;lt;/math&amp;gt; S&lt;br /&gt;
&lt;br /&gt;
* gilt (Induktionsvoraussetzung):&lt;br /&gt;
  Kosten(start_fromNode) &amp;lt; Kosten(start_x)&lt;br /&gt;
&lt;br /&gt;
* Falls Kosten(start_x) &amp;lt; Kosten(start_u) müsste x im Heap vor u kommen; daraus folgt, dass u nicht an der Spitze des Heaps sein kann&lt;br /&gt;
&lt;br /&gt;
&amp;amp;rarr; Widerspruch!&lt;br /&gt;
&lt;br /&gt;
&amp;amp;rarr; Die Behauptung, der Weg über x ist besser, kann nicht stimmen.&lt;br /&gt;
&lt;br /&gt;
&amp;amp;rarr; Korrektheit von Dijkstra ist somit bewiesen.&lt;br /&gt;
&lt;br /&gt;
==== Wie kann man Dijkstra noch verbessern? ====&lt;br /&gt;
&lt;br /&gt;
===== A*-Algorithmus =====&lt;br /&gt;
&lt;br /&gt;
* Verbesserung von Dijkstra im typischen Fall, aber die Komplexität ist immer noch =(Elog v) im schlechtesten Fall (die Komplexität kann man nicht verbessern, aber die Laufzeit im typischen Fall).&lt;br /&gt;
* &amp;lt;u&amp;gt;Schätzung&amp;lt;/u&amp;gt; für jeden Knoten für den restlichen Weg: &lt;br /&gt;
geschätzte Gesamtkosten: Kosten(start_node) + Restschätzung(node_ziel)&lt;br /&gt;
(exakte Kosten werden durch Dijkstra ermittelt)&lt;br /&gt;
&lt;br /&gt;
'''Idee:'''&lt;br /&gt;
* Sortiere den Heap nach geschätzten Gesamtkosten.&lt;br /&gt;
* Satz: &lt;br /&gt;
 Falls jede Schätzung den exakten Weg &amp;lt;u&amp;gt;unterschätzt&amp;lt;/u&amp;gt;, werden die gleichen Pfade gefunden, wie &lt;br /&gt;
 bei Dijkstra (also die korrekten kürzesten Pfade).&lt;br /&gt;
(Die Schätzung für den restlichen Weg muss man immer so einrichten, dass der tatsächliche Weg unterschätzt wird. Da keine Straße kürzer sein kann als die Luftlinie, ist die Luftlinie eine geeignete Annahme für A*.)&lt;br /&gt;
* Falls der falsche Pfad im Heap eher an die Spitze kommt als der richtige Pfad, findet der A*-Algorithmus den falschen Pfad.&lt;br /&gt;
* Wenn der Pfad zum Ziel an der Spitze des Heap ist, dann wird keine Restschätzung mehr benötigt, denn wenn der Zielknoten aus dem Heap herrauskommt, dann hat man die exakte Berechnung. Die Restschätzung ist in diesem Fall 0. Wenn die Schätzung zu klein ist, wird der exakte Weg immer größer sein und zuerst aus dem Heap herauskommen.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Image:Minimum_spanning_tree.png‎ |thumb|200px|right|Ein minimal aufspannender Baum verbindet alle Punkte eines Graphen bei minimaler Kantenlänge ([http://de.wikipedia.org/wiki/Spannbaum Quelle])]]&lt;br /&gt;
=='''Minimaler Spannbaum'''==&lt;br /&gt;
'''(engl.: minimum spanning tree; abgekürzt: MST)'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;u&amp;gt;''gegeben''&amp;lt;/u&amp;gt;: gewichteter Graph, zusammenhängend&amp;lt;br/&amp;gt;&lt;br /&gt;
:&amp;lt;u&amp;gt;''gesucht''&amp;lt;/u&amp;gt;: Untermenge &amp;lt;math&amp;gt;E'\subseteq E&amp;lt;/math&amp;gt;, so dass &amp;lt;math&amp;gt;\sum_{e\in E} w_e&amp;lt;/math&amp;gt; minimal und G' zusammenhängend ist. &amp;lt;br/&amp;gt;&lt;br /&gt;
* G'definiert dann einen Baum, denn andernfalls könnte man &amp;lt;math&amp;gt;\sum_{E'}&amp;lt;/math&amp;gt;verringern (eine Kante weglassen) ohne die Zusammenhangskomponente zu verletzen. &amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Wenn der Graph nicht zusammenhängend ist, würde man den Spannbaum für jede Zusammenhangskomponente getrennt ausrechnen. &lt;br /&gt;
* Der MST ist ähnlich wie der Dijkstra-Algorithmus: Dort ist ein Pfad gesucht bei dem die Summe der Gewicht über den Pfad minimal ist. &lt;br /&gt;
* Beim MST suchen wir eine Lösung bei der die Summe der Gewichte über den ganzen Graphen minimal ist. &lt;br /&gt;
&lt;br /&gt;
* Das Problem des MST ist nahe verwandt mit der Bestimmung der Zusammenhangskomponente, z.B. über den Tiefensuchbaum, wobei ein beliebiger Baum für die Zusammenhangskomponente und beim MST ein minimaler Baum gesucht ist.&lt;br /&gt;
&lt;br /&gt;
;Anwendungen&lt;br /&gt;
* '''Wie verbindet man ''n'' Punkte mit möglichst wenigen (kurzen) Straßen (Eisenbahnen, Drähten (bei Schaltungen) usw.)?'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center&amp;quot; border=&amp;quot;0&amp;quot; cellpadding=&amp;quot;5&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
|MST minimale Verbindung (Abb.1)&lt;br /&gt;
|MST = 2 (Länge = Kantengewicht)(Abb.2)&lt;br /&gt;
|- valign=&amp;quot;top&amp;quot;&lt;br /&gt;
| [[Image:mst.png]] &lt;br /&gt;
| [[Image:Gleichseitigesdreieck.png]]&lt;br /&gt;
|}&lt;br /&gt;
*In der Praxis: Die Festlegung, dass man nur die gegebenen Punkte verwenden darf, ist eine ziemliche starke Einschränkung. &lt;br /&gt;
&lt;br /&gt;
* Wenn man sich vorstellt, es sind drei Punkte gegeben, die als gleichseitiges Dreieck angeordnet sind, dann ist der MST (siehe Abb.2, schwarz gezeichnet) und hat die Länge 2. Man kann hier die Länge als Kantengewicht verwenden. &lt;br /&gt;
&lt;br /&gt;
* Wenn es erlaubt ist zusätzliche Punkte einzufügen, dann kann man in der Mitte einen neuen Punkt setzen &amp;lt;math&amp;gt;\rightarrow&amp;lt;/math&amp;gt; neuer MST (siehe Abb.2, orange gezeichnet).&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Höhe = &amp;lt;math&amp;gt;\frac{1}{2}\sqrt{3}&amp;lt;/math&amp;gt;, Schwerpunkt: teilt die Höhe des Dreiecks im Verhältnis 2:1; der Abstand von obersten Punkt bis zum neu eingeführten Punkt: &amp;lt;math&amp;gt;\frac{2}{3}h = \frac{\sqrt{3}}{3}&amp;lt;/math&amp;gt;, davon insgesamt 3 Stück, damit (gilt für den MST in orange eingezeichnet): MST = &amp;lt;math&amp;gt;3\left(\frac{1}{3}\right) \sqrt{3} = \sqrt{3} \approx 1,7&amp;lt;/math&amp;gt;&amp;lt;br\&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Damit ist der MST in orange kürzer als der schwarz gezeichnete MST. &amp;lt;br\&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\Rightarrow&amp;lt;/math&amp;gt;Folgerung: MST kann kürzer werden, wenn man einen Punkt dazu nimmt. &lt;br /&gt;
* Umgekehrt kann der MST auch kürzer werden, wenn man einen Punkt aus dem Graphen entfernt, aber wie das Beipiel des gleichseitigen Dreiecks zeigt, ist dies nicht immer der Fall.&lt;br /&gt;
&lt;br /&gt;
[[Image: bahn.png|Bahnstrecke Verbindung (Abb.3)]]&lt;br /&gt;
&lt;br /&gt;
* Methode der zusätzlichen Punkteinfügung hat man früher beim Bahnstreckenbau verwendet. Durch Einführung eines Knotenpunktes kann die Streckenlänge verkürzt werden (Dreiecksungleichung).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''Bestimmung von Datenclustern'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Image:cluster.png]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Daten (in der Abb.: Punkte) bilden Gruppen. &lt;br /&gt;
&lt;br /&gt;
* In der Abbildung hat man 2 verschiedene Messungen gemacht (als x- und y-Achse aufgetragen), bspw. Größe und Gewicht von Personen. Für jede Person i wird ein Punkt an der Koordinate (Größe&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;, Gewicht&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;) gezeichnet (siehe Bild a). Dies bezeichnet man als ''Scatter Plot''. Wenn bestimmte Wertkombinationen häufiger auftreten als andere, bilden sich mitunter Gruppen aus, bspw. eine Gruppe für &amp;quot;klein und schwer&amp;quot; etc.&lt;br /&gt;
&lt;br /&gt;
* Durch Verbinden der Punkte mittels eines MST (siehe Abbildung (b)) sieht man, dass es kurze (innerhalb der Gruppen) und lange Kanten (zwischen den Gruppen) gibt. &lt;br /&gt;
&lt;br /&gt;
* Wenn man geschickt eine Schwelle einführt und alle Kanten löscht, die länger sind als die Schwelle, dann bekommt man als Zusammenhangskomponente die einzelnen Gruppen. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Zwei Algorithmen für dieses Problem &lt;br /&gt;
(im Vergleich zu Algorithmen für die Zusammenhangskomponente nur leicht verbesserte Algorithmen)&lt;br /&gt;
&lt;br /&gt;
====Algorithmus von Prim====&lt;br /&gt;
[http://de.wikipedia.org/wiki/Algorithmus_von_Prim#Hashing_mit_offener_Adressierung Wikipedia (de)]&lt;br /&gt;
[http://en.wikipedia.org/wiki/Prim%27s_algorithm (en)]&lt;br /&gt;
&lt;br /&gt;
:Idee: starte an der Wurzel (willkürlich gewählter Knoten) und füge jeweils die günstigste Kante hinzu (&amp;lt;math&amp;gt;\rightarrow&amp;lt;/math&amp;gt; genau wie beim Dijsktra-Algorithmus, aber die Definitionen, welche Kante die günstigste ist, unterscheiden sich.)&lt;br /&gt;
&lt;br /&gt;
 import heapq&lt;br /&gt;
 def prim(graph):  #Graphdatenstruktur ist wie bei Dijsktra  &lt;br /&gt;
 	heap = []                                       &lt;br /&gt;
 	visited = [False]*len(graph) &lt;br /&gt;
 	sum = 0  #wird später das Gewicht des Spannbaums sein&lt;br /&gt;
 	r = []  #r ist die Lösung&lt;br /&gt;
 	visited[0] = True #fixed&lt;br /&gt;
 	for neighbor in graph[0]:  #willkürlich 0 als Wurzel gewählt&lt;br /&gt;
 		heapq.heappush(heap, (neighbor[1], 0, neighbor[0]))  #Heap wird gefüllt &lt;br /&gt;
 	while len(heap):&lt;br /&gt;
 		wn, start, ziel = heapq.heappop(heap) &lt;br /&gt;
 		if visited[ziel]: continue&lt;br /&gt;
 		visited[ziel] = True  #wenn visited noch nicht besetzt&lt;br /&gt;
 		sum += wn  #Addition des Gewichts der aktuellen Kante&lt;br /&gt;
 		r.append([start, ziel])  #Kante wird an die Lsg. angehängt&lt;br /&gt;
 		for neighbor in graph[ziel]:&lt;br /&gt;
 			if visited[neighbor[0]]: continue&lt;br /&gt;
 			heapq.heappush(heap, (neighbor[1], ziel, neighbor[0]))&lt;br /&gt;
 	return sum, r&lt;br /&gt;
&lt;br /&gt;
====Algorithmus von Kruskal====&lt;br /&gt;
[http://de.wikipedia.org/wiki/Algorithmus_von_Kruskal Wikipedia (de)]&lt;br /&gt;
[http://en.wikipedia.org/wiki/Kruskal%27s_algorithm (en)]&lt;br /&gt;
&lt;br /&gt;
Eine andere Vorgehensweise zur Bestimmung des minimalen Spannbaums besteht darin, einfach Kanten nacheinander hinzuzufügen und hierbei bei jedem Schritt die kürzeste Kante zu verwenden, die keinen Zyklus bildet. Anders ausgedrückt: Der Algorithmus beginnt mit ''N'' Bäumen; in (''N''-1) Schritten kombiniert er jeweils zwei Bäume (unter Verwendung der kürzesten möglichen Kante), bis nur noch ein Baum übrig bleibt. &lt;br /&gt;
Der Algorithmus von J.Kruskal ist seit 1956 bekannt. &lt;br /&gt;
&lt;br /&gt;
* Idee: wie beim Union-Find-Algorithmus für Zusammenhangskomponenten&lt;br /&gt;
&lt;br /&gt;
# Behandle jeden Knoten als Baum für sich&lt;br /&gt;
# Fasse zwei Bäume zu einem neuen Baum zusammen&lt;br /&gt;
&lt;br /&gt;
* für MST (im Unterschied zu Union-Find): betrachte dazu die Kanten in aufsteigender Reihenfolge der Gewichte&lt;br /&gt;
(priority queue; ignoriere Kanten zwischen Knoten, die sich bereits im gleichem Baum befinden, was sich leicht daran erkennen läßt, dass ihre Anker gleich sind)&lt;br /&gt;
&lt;br /&gt;
* Algorithmus eignet sich besser für das Clusteringproblem, da der Schwellwert von vornerein über die Kantenlänge an den Algorithmus übergeben werden kann. Man hört mit dem Vereinigen auf, wenn die Kantenlänge den Schwellwert überschreitet. &lt;br /&gt;
*Es kann keine kürzere Kante als der Schwellwert mehr kommen, da die Kanten vorher sortiert worden sind. &lt;br /&gt;
&lt;br /&gt;
''Komplexität:'' gleich wie beim Dijkstra-Algorithmus, weil jede Kante höchstens einmal in den Heap kommt.&lt;br /&gt;
* Aufwand für Heap ist max. &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; Einträge, da jede Kante nur einmal im Heap sein kann, d.h. Heap hat den Aufwand: &amp;lt;math&amp;gt;O\left(E\log E\right)&amp;lt;/math&amp;gt;, falls keine Mehrfachkanten vorhanden: &amp;lt;math&amp;gt;v^2 &amp;gt; E&amp;lt;/math&amp;gt; und deshalb: log E &amp;lt; 2 log v. &lt;br /&gt;
* Daraus folgt, dass das dasselbe ist wie &amp;lt;math&amp;gt;O \left(E\log v\right)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=&amp;gt; geeignet für Übungsaufgabe&lt;br /&gt;
&lt;br /&gt;
== Problem des Handlungsreisenden ==&lt;br /&gt;
'''(engl.: Traveling Salesman Problem; abgekürzt: TSP)'''&amp;lt;br\&amp;gt;&lt;br /&gt;
[http://de.wikipedia.org/wiki/Problem_des_Handlungsreisenden Wikipedia (de)]&lt;br /&gt;
[http://en.wikipedia.org/wiki/Prim%27s_algorithm (en)]&lt;br /&gt;
[[Image:TSP_Deutschland_3.PNG|thumb|200px|right|Optimaler Reiseweg eines Handlungsreisenden([http://de.wikipedia.org/w/index.php?title=Bild:TSP_Deutschland_3.PNG&amp;amp;filetimestamp=20070110124506 Quelle])]]&lt;br /&gt;
&lt;br /&gt;
*Eine der wohl bekanntesten Aufgabenstellungen im Bereich der Graphentheorie ist das Problem des Handlungsreisenden. &lt;br /&gt;
*Hierbei soll ein Handlungsreisender nacheinander ''n'' Städte besuchen und am Ende wieder an seinem Ausgangspunkt ankommen. Dabei soll jede Stadt nur einmal besucht werden und der Weg mit den minimalen Kosten gewählt werden. &lt;br /&gt;
*Alternativ kann auch ein Weg ermittelt werden, dessen Kosten unter einer vorgegebenen Schranke liegen.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;u&amp;gt;''gegeben''&amp;lt;/u&amp;gt;: zusammenhängender, gewichteter Graph (oft vollständiger Graph)&lt;br /&gt;
:&amp;lt;u&amp;gt;''gesucht''&amp;lt;/u&amp;gt;: kürzester Weg, der alle Knoten genau einmal (falls ein solcher Pfad vorhanden) besucht (und zum Ausgangsknoten zurückkehrt)&amp;lt;br\&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:auch genannt: kürzester Hamiltonkreis &lt;br /&gt;
::- durch psychologische Experimente wurde herausgefunden, dass Menschen (in 2D) ungefähr proportionale Zeit zur Anzahl der Knoten brauchen, um einen guten Pfad zu finden, der typischerweise nur &amp;lt;math&amp;gt;\lesssim 5%&amp;lt;/math&amp;gt; länger als der optimale Pfad ist&amp;lt;br\&amp;gt;&lt;br /&gt;
:&amp;lt;u&amp;gt;''vorgegeben''&amp;lt;/u&amp;gt;: Startknoten (kann willkürlich gewählt werden), vollständiger Graph&lt;br /&gt;
&lt;br /&gt;
::::: =&amp;gt; v-1 Möglichkeiten für den ersten Nachfolgerknoten =&amp;gt; je v-2 Möglichkeiten für dessen Nachfolger...&lt;br /&gt;
:::::also &amp;lt;math&amp;gt;\frac{(v-1)!}{2}&amp;lt;/math&amp;gt; mögliche Wege in einem vollständigen Graphen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*Ein naiver Ansatz zur Lösung des TSP Problems ist das erschöpfende Durchsuchen des Graphen, auch &amp;quot;brute force&amp;quot; Algorithmus (&amp;quot;mit roher Gewalt&amp;quot;), indem alle möglichen Rundreisen betrachtet werden und schließlich die mit den geringsten Kosten ausgewählt wird. &lt;br /&gt;
*Dieses Verfahren versagt allerdings bei größeren Graphen, aufgrund der hohen Komplexität.&lt;br /&gt;
&lt;br /&gt;
=== Approximationsalgorithmus === &lt;br /&gt;
&lt;br /&gt;
Für viele Probleme in der Praxis sind keine effizienten Algorithmen bekannt&lt;br /&gt;
(NP-schwer). Diese (z.B. TSP) werden mit Approximationsalgorithmen berechnet,&lt;br /&gt;
die effizient berechenbar sind, aber nicht unbedingt die optimale&lt;br /&gt;
Lösung liefern. Beispielsweise ist es relativ einfach, eine Tour zu finden, die höchstens um den Faktor zwei länger ist als die optimale Tour. Die Methode beruht darauf, dass einfach der minimale Spannbaum ermittelt wird. &lt;br /&gt;
&lt;br /&gt;
'''Approximationsalgorithmus für TSP'''&amp;lt;br\&amp;gt;&lt;br /&gt;
* TSP für ''n'' Knoten sei durch Abstandsmatrix D = &amp;lt;math&amp;gt;(d_{ij}) 1 \le i, j \le n&amp;lt;/math&amp;gt; &lt;br /&gt;
:gegeben (vollständiger Graph mit ''n'' Knoten, &amp;lt;math&amp;gt;d_{ij}&amp;lt;/math&amp;gt; = Kosten der Kante (i,j)) &amp;lt;br\&amp;gt;&lt;br /&gt;
:''gesucht:'' Rundreise mit minimalen Kosten. Dies ist NP-schwer!&amp;lt;br\&amp;gt;&lt;br /&gt;
* D erfüllt die Dreiecksungleichung  &amp;lt;math&amp;gt; \Leftrightarrow d_{ij} + d_{jk} \geq d_{ik} \text{ fuer } \forall{i, j, k} \in \lbrace 1, ..., n  \rbrace&amp;lt;/math&amp;gt; &amp;lt;br\&amp;gt; &lt;br /&gt;
* Dies ist insbesondere dann erfüllt, wenn D die Abstände bezüglich einer Metrik darstellt oder D Abschluss einer beliebigen Abstandsmatrix C ist, d.h. :&amp;lt;math&amp;gt;d_{ij}&amp;lt;/math&amp;gt; = Länge des kürzesten Weges (bzgl. C) von i nach j.&lt;br /&gt;
&lt;br /&gt;
*Die ”Qualität”der Lösung mit einem Approximationsalgorithmus ist höchstens um einen konstanten Faktor schlechter ist als die des Optimums.&lt;br /&gt;
&lt;br /&gt;
=== Systematisches Erzeugen aller Permutationen === &lt;br /&gt;
*Allgemeines Verfahren, wie man von einer gegebenen Menge verschiedene Schlüssel - in diesem Fall: Knotennummern - sämtliche Permutationen systematisch erzeugen kann. &amp;lt;br\&amp;gt;&lt;br /&gt;
*'''Trick''': interpretiere jede Permutation als Wort und betrachte dann deren lexikographische (&amp;quot;wie im Lexikon&amp;quot;) Ordnung.&amp;lt;br\&amp;gt;&lt;br /&gt;
*Der erste unterschiedliche Buchstabe unterscheidet. Wenn die Buchstaben gleich sind, dann kommt das kürzere Wort zuerst. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;''gegeben''&amp;lt;/u&amp;gt;: zwei Wörter a, b der Länge n=len(a) bzw. m=len(b). Sei k = min(n,m) (im Spezialfall des Vergleichs von Permutationen gilt k = n = m)&amp;lt;br\&amp;gt;&lt;br /&gt;
Mathematische Definition, wie die Wörter im Wörterbuch sortiert sind: &amp;lt;br\&amp;gt;&lt;br /&gt;
:::&amp;lt;math&amp;gt;a&amp;lt;b \Leftrightarrow &lt;br /&gt;
\begin{cases}&lt;br /&gt;
n &amp;lt; m &amp;amp; \text{ falls fuer } 0 \le i \le k-1 \text{ gilt: } a[i] = b[i] \\&lt;br /&gt;
a[j] &amp;lt; b[j] &amp;amp; \text{ falls fuer } 0 \le i \le j-1 \text{ gilt: } a[i] = b[i], \text{ aber fuer ein } j&amp;lt;k: a[j] \ne b[j]&lt;br /&gt;
\end{cases}&amp;lt;/math&amp;gt;&amp;lt;br\&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Algorithmus zur Erzeuguung aller Permutationen:&lt;br /&gt;
# beginne mit dem kleinsten Wort bezüglich der lexikographischen Ordnung =&amp;gt; das ist das Wort, wo a aufsteigend sortiert ist&lt;br /&gt;
# definiere Funktion &amp;quot;next_permutation&amp;quot;, die den Nachfolger in lexikographischer Ordnung erzeugt&lt;br /&gt;
&lt;br /&gt;
Beispiel: Die folgenden Permutationen der Zahlen 1,2,3 sind lexikographisch geordnet&lt;br /&gt;
&lt;br /&gt;
 1 2 3    6 Permutationen, da 3! = 6&lt;br /&gt;
 1 3 2&lt;br /&gt;
 2 1 3&lt;br /&gt;
 2 3 1&lt;br /&gt;
 3 1 2&lt;br /&gt;
 3 2 1&lt;br /&gt;
 -----&lt;br /&gt;
 0 1 2 Position&lt;br /&gt;
&lt;br /&gt;
Die lexikographische Ordnung wird deutlicher, wenn wir statt dessen die Buchstaben a,b,c verwenden:&lt;br /&gt;
&lt;br /&gt;
 abc&lt;br /&gt;
 acb&lt;br /&gt;
 bac&lt;br /&gt;
 bca&lt;br /&gt;
 cab&lt;br /&gt;
 cba&lt;br /&gt;
&lt;br /&gt;
Eine Funktion, die aus einer gegebenen Permutation die in lexikographischer Ordnung nächst folgende erzeugt, kann wie folgt implementiert werden:&lt;br /&gt;
&lt;br /&gt;
 def next_permutation(a):&lt;br /&gt;
 	i = len(a) -1  #letztes Element; man arbeitet sich von hinten nach vorne durch&lt;br /&gt;
 	while True:  # keine Endlosschleife, da i dekrementiert wird und damit irgendwann 0 wird&lt;br /&gt;
 		if i &amp;lt;= 0: return False  # a ist letzte Permutation&lt;br /&gt;
 		i -= 1&lt;br /&gt;
 		if a[i]&amp;lt;a[i+1]: break&lt;br /&gt;
 	#lexikogr. Nachfolger hat größeres a[i]&lt;br /&gt;
 	j = len(a)&lt;br /&gt;
 	while True:&lt;br /&gt;
 		j -= 1&lt;br /&gt;
 		if a[i] &amp;lt; a[j]: break&lt;br /&gt;
 	a[i], a[j] = a[j], a[i] #swap a[i], a[j]&lt;br /&gt;
 	#sortiere aufsteigend zwischen a[i] und Ende&lt;br /&gt;
 	#zur Zeit absteigend sortiert =&amp;gt; invertieren&lt;br /&gt;
 	i += 1&lt;br /&gt;
 	j = len(a) -1&lt;br /&gt;
 	while i &amp;lt; j:&lt;br /&gt;
 		a[i], a[j] = a[j], a[i]&lt;br /&gt;
 		i += 1&lt;br /&gt;
 		j-= 1&lt;br /&gt;
 	return True  # eine weitere Permutation gefunden&lt;br /&gt;
  	&lt;br /&gt;
  def naiveTSP(graph):&lt;br /&gt;
 	start = 0&lt;br /&gt;
 	result = range(len(graph))+[start]&lt;br /&gt;
 	rest = range(1,len(graph))&lt;br /&gt;
 	c = pathCost(result, graph)&lt;br /&gt;
 	while next_permutation(rest):&lt;br /&gt;
 		r = [start]+rest+[start]&lt;br /&gt;
 		cc = pathCost(r, graph)&lt;br /&gt;
 		if cc &amp;lt; c:&lt;br /&gt;
 			c = cc&lt;br /&gt;
 			result = r&lt;br /&gt;
 		return c, result&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Komplexität''': &amp;lt;math&amp;gt;(v-1)!&amp;lt;/math&amp;gt; Schleifendurchläufe (=Anzahl der Permutationen, da die Schleife abgebrochen wird, sobald es keine weiteren Permutationen mehr gibt), also &lt;br /&gt;
	&amp;lt;math&amp;gt;O(v!) = O(v^v)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
;Beispiel:&lt;br /&gt;
{| &lt;br /&gt;
|- &lt;br /&gt;
| | i = 0 || |  |||  ||| j = 3 ||&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|| &amp;amp;darr; || || || &amp;amp;darr; ||&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;background:silver; color:white&amp;quot; | 1 ||style=&amp;quot;background:silver; color:white&amp;quot; | 4 ||style=&amp;quot;background:silver; color:white&amp;quot;| 3 ||style=&amp;quot;background:silver; color:white&amp;quot; | 2 || #input für next_permutation&lt;br /&gt;
|-&lt;br /&gt;
|-&lt;br /&gt;
&lt;br /&gt;
||  || i = 2 || ||  j = 3 ||&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
||  || &amp;amp;darr;|| || &amp;amp;darr; ||&lt;br /&gt;
|-&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
|- &lt;br /&gt;
| style=&amp;quot;background:silver; color:white&amp;quot; | 2 ||style=&amp;quot;background:silver; color:white&amp;quot; | 4 ||style=&amp;quot;background:silver; color:white&amp;quot;| 3 ||style=&amp;quot;background:silver; color:white&amp;quot; | 1|| # vertauschen der beiden Elemente &lt;br /&gt;
|-&lt;br /&gt;
|-&lt;br /&gt;
&lt;br /&gt;
||  ||  ||i = 2 ||   ||&lt;br /&gt;
|-&lt;br /&gt;
||  ||  ||j = 2 ||   ||&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
||  || || &amp;amp;darr;|| ||&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
|- &lt;br /&gt;
| style=&amp;quot;background:silver; color:white&amp;quot; | 2 ||style=&amp;quot;background:silver; color:white&amp;quot; | 1 ||style=&amp;quot;background:silver; color:white&amp;quot;| 3 ||style=&amp;quot;background:silver; color:white&amp;quot; | 4|| #absteigend sortiert&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Stirling'sche Formel ===&lt;br /&gt;
[http://de.wikipedia.org/wiki/Stirling-Formel Wikipedia (de)]&lt;br /&gt;
[http://en.wikipedia.org/wiki/Stirling%27s_approximation (en)]&lt;br /&gt;
&lt;br /&gt;
Die Stirling-Formel ist eine mathematische Formel, mit der man für große Fakultäten Näherungswerte berechnen kann. Die Stirling-Formel findet überall dort Verwendung, wo die exakten Werte einer Fakultät nicht von Bedeutung sind. Damit lassen sich durch die Stirling'sche Formel z.T. starke Vereinfachungen erzielen. &lt;br /&gt;
&amp;lt;math&amp;gt;v! \approx \sqrt{2 \pi v} \left(\frac{v}{e}\right)^v&amp;lt;/math&amp;gt;&lt;br /&gt;
: &amp;lt;math&amp;gt;O(v!) = O\left(\sqrt{v}\left(\frac{v}{e}\right)^v\right) \approx O(v^v)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= [http://de.wikipedia.org/wiki/Erfüllbarkeitsproblem_der_Aussagenlogik Erfüllbarkeitsproblem] =&lt;br /&gt;
&lt;br /&gt;
geg.: &lt;br /&gt;
* n Boolsche Variablen &amp;lt;math&amp;gt;x_i \in \{True,False\}&amp;lt;/math&amp;gt; und deren Negation &amp;lt;math&amp;gt;\neg x_i (i=1..n)&amp;lt;/math&amp;gt;&lt;br /&gt;
* Logischer Ausdruck in &amp;lt;math&amp;gt;x_i,\neg x_i&amp;lt;/math&amp;gt;&lt;br /&gt;
** zB &amp;lt;math&amp;gt;(x_1 \vee x_2) \wedge (x_3 \vee x_4)&amp;lt;/math&amp;gt; ...&lt;br /&gt;
&lt;br /&gt;
    Grammatik eines logischen Ausdrucks(in [http://de.wikipedia.org/wiki/Backus-Naur-Form BNF]):&lt;br /&gt;
    &amp;amp;lt;EXP&amp;amp;gt;  ::= &amp;amp;lt;DISJ&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;DISJ&amp;amp;gt; ::= &amp;amp;lt;CONJ&amp;amp;gt; | &amp;amp;lt;DISJ&amp;amp;gt; &amp;lt;math&amp;gt;\vee&amp;lt;/math&amp;gt; &amp;amp;lt;CONJ&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;CONJ&amp;amp;gt; ::= &amp;amp;lt;TERM&amp;amp;gt; | &amp;amp;lt;CONJ&amp;amp;gt; &amp;lt;math&amp;gt;\wedge&amp;lt;/math&amp;gt; &amp;amp;lt;TERM&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;TERM&amp;amp;gt; ::= ( &amp;amp;lt;EXPR&amp;amp;gt; ) | &amp;amp;not;( &amp;amp;lt;EXPR&amp;amp;gt; ) | &amp;amp;lt;VAR&amp;amp;gt; | &amp;amp;not;&amp;amp;lt;VAR&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;VAR&amp;amp;gt;  ::= &amp;lt;math&amp;gt;x_1&amp;lt;/math&amp;gt; | ... | &amp;lt;math&amp;gt;x_n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ges.: Eine Belegung der &amp;lt;math&amp;gt;x_i&amp;lt;/math&amp;gt;, so dass der gegebene Ausdruck &amp;quot;True&amp;quot; wird&lt;br /&gt;
&lt;br /&gt;
=== Naive Lösung ===&lt;br /&gt;
Probiere alle Bedingungen aus &amp;lt;math&amp;gt;\to&amp;lt;/math&amp;gt; Komplexität &amp;lt;math&amp;gt;\mathcal{O}(2^{n}) \!&amp;lt;/math&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
'''Im Allgemeinen ist das der effizienteste bekannte Algorithmus'''&lt;br /&gt;
&lt;br /&gt;
== '''Normalformen''' von logischen Ausdrücken ==&lt;br /&gt;
&lt;br /&gt;
=== k-Konjunktionen-Normalform(k-CNF) ===&lt;br /&gt;
&lt;br /&gt;
* ein &amp;quot;Literal&amp;quot; ist eine Variable &amp;lt;math&amp;gt;x_i&amp;lt;/math&amp;gt; oder deren Negation&lt;br /&gt;
* jeweils ''k'' Literale werden mit &amp;lt;math&amp;gt;\vee&amp;lt;/math&amp;gt; in einer '''Disjunktion''' verknüpft&lt;br /&gt;
* Disjunktionen werden mit &amp;lt;math&amp;gt;\wedge&amp;lt;/math&amp;gt; in einer '''Konjunktion''' verbunden&lt;br /&gt;
&lt;br /&gt;
    Grammatik eines Ausdrucks in k-CNF(wieder in [http://de.wikipedia.org/wiki/Backus-Naur-Form BNF]):&lt;br /&gt;
    &amp;amp;lt;EXP&amp;amp;gt;  ::= &amp;amp;lt;CONJ&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;CONJ&amp;amp;gt; ::= &amp;amp;lt;DISJ&amp;amp;gt; | &amp;amp;lt;CONJ&amp;amp;gt; &amp;lt;math&amp;gt;\wedge&amp;lt;/math&amp;gt; &amp;amp;lt;DISJ&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;DISJ&amp;amp;gt; ::= ( &amp;amp;lt;LIT&amp;amp;gt; &amp;lt;math&amp;gt;\vee&amp;lt;/math&amp;gt; ... &amp;lt;math&amp;gt;\vee&amp;lt;/math&amp;gt; &amp;amp;lt;LIT&amp;amp;gt; ) &amp;amp;lt;!-- k Literale --&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;LIT&amp;amp;gt;  ::=  &amp;amp;lt;VAR&amp;amp;gt; | &amp;lt;math&amp;gt;\neg&amp;lt;/math&amp;gt;&amp;amp;lt;VAR&amp;amp;gt; &lt;br /&gt;
    &amp;amp;lt;VAR&amp;amp;gt;  ::= &amp;lt;math&amp;gt;x_1&amp;lt;/math&amp;gt; | ... | &amp;lt;math&amp;gt;x_n&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Beispiele:&lt;br /&gt;
* 3-CNF: &amp;lt;math&amp;gt;(x_1 \vee \neg x_2 \vee x_4) \wedge (x_2 \vee x_3 \vee \neg x_4) \wedge (\neg x_1 \vee x_4 \vee \neg x_5)&amp;lt;/math&amp;gt;&lt;br /&gt;
* 2-CNF: &amp;lt;math&amp;gt;(x_1 \vee \neg x_2) \wedge (x_3 \vee x_4)&amp;lt;/math&amp;gt; ...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;border-bottom: 1px solid #000;&amp;quot;&amp;gt;Satz&amp;lt;/span&amp;gt;:&lt;br /&gt;
* Jeder logische Ausdruck kann in polynomieller Zeit in 3-CNF umgewandelt werden&lt;br /&gt;
* Im Allgemeinen kann ein logischer Ausdruck nicht in 2-CNF umgeschrieben werden&lt;br /&gt;
&lt;br /&gt;
=== Implikationen-Normalform(INF) ===&lt;br /&gt;
&lt;br /&gt;
Konjunktionen von Implikationen:&lt;br /&gt;
* zB &amp;lt;math&amp;gt;(x_1 \to x_2) \wedge (x_2 \to \neg x_3) \wedge (x_4 \to x_3)&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
    Grammatik eines Ausdrucks in INF(you know the drill ;)):&lt;br /&gt;
    &amp;amp;lt;EXP&amp;amp;gt;  ::= &amp;amp;lt;CONJ&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;CONJ&amp;amp;gt; ::= &amp;amp;lt;IMPL&amp;amp;gt; | &amp;amp;lt;CONJ&amp;amp;gt; &amp;lt;math&amp;gt;\wedge&amp;lt;/math&amp;gt; &amp;amp;lt;IMPL&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;IMPL&amp;amp;gt; ::= ( &amp;amp;lt;LIT&amp;amp;gt; &amp;lt;math&amp;gt;\to&amp;lt;/math&amp;gt; &amp;amp;lt;LIT&amp;amp;gt; )&lt;br /&gt;
    &amp;amp;lt;LIT&amp;amp;gt;  ::=  &amp;amp;lt;VAR&amp;amp;gt; | &amp;lt;math&amp;gt;\neg&amp;lt;/math&amp;gt;&amp;amp;lt;VAR&amp;amp;gt; &lt;br /&gt;
    &amp;amp;lt;VAR&amp;amp;gt;  ::= &amp;lt;math&amp;gt;x_1&amp;lt;/math&amp;gt; | ... | &amp;lt;math&amp;gt;x_n&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;border-bottom: 1px solid #000;&amp;quot;&amp;gt;Satz&amp;lt;/span&amp;gt;:&lt;br /&gt;
* jeder Ausdruck in 2-CNF kann in INF umgewandelt werden (siehe z.B. [http://en.wikipedia.org/wiki/2-satisfiability#Conjunctive_normal_form_and_implicative_normal_form hier]): &lt;br /&gt;
*: &amp;lt;math&amp;gt; (x_i \vee x_j) \Leftrightarrow (\neg x_i \to x_j) \Leftrightarrow (\neg x_j \to x_i) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Außerdem kann jeder Ausdruck in INF als gerichteter Graph dargestellt werden&lt;br /&gt;
# Jede Variable und ihre Negation sind 1 Knoten(dh insgesamt 2 Knoten)&lt;br /&gt;
# Jede Implikation ist eine gerichtete Kante&lt;br /&gt;
&lt;br /&gt;
== Stark zusammenhängende Komponenten ==&lt;br /&gt;
&lt;br /&gt;
geg.: gerichteter Graph&lt;br /&gt;
&lt;br /&gt;
    1. Bestimme die post Order Time (mit Tiefensuche)&lt;br /&gt;
    2. Transponieren des Graphen &amp;lt;math&amp;gt;G^T&amp;lt;/math&amp;gt;&lt;br /&gt;
    3. Bestimme ConnComp &amp;lt;math&amp;gt;G^T&amp;lt;/math&amp;gt; mit bekannten CC Algorithmen, aber so, dass Knoten in absteigender post Order behandelt werden&lt;br /&gt;
&lt;br /&gt;
[[Image:Curva.png|thumb|250px|none]]    Beweis: 1.Bilde Komponentengraphen:&lt;br /&gt;
    '''Knoten:''' jede SCC &amp;lt;math&amp;gt;C_i&amp;lt;/math&amp;gt; ist ein Knoten&lt;br /&gt;
    '''Kanten:''' &amp;lt;math&amp;gt;C_i \rightarrow C_j \Leftrightarrow U_k \rightarrow U_l&amp;lt;/math&amp;gt; mit &amp;lt;math&amp;gt;U_k \in C_i&amp;lt;/math&amp;gt; und &amp;lt;math&amp;gt;U_l \in C_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    '''*Eigenschaft 1:''' der Komponentengraph ist :&amp;lt;u&amp;gt;''azyklisch''&amp;lt;/u&amp;gt;:&lt;br /&gt;
     &amp;lt;math&amp;gt;pot \left(C_i\right) = max_{U_k \in C_i}  pot\left(U_k\right)&amp;lt;/math&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    '''*Eigenschaft 2:''' falls &amp;lt;math&amp;gt;C_i \rightsquigarrow C_j&amp;lt;/math&amp;gt; dann &amp;lt;math&amp;gt;pot \left(C_i\right) &amp;gt; pot \left(C_j\right)&amp;lt;/math&amp;gt;&lt;br /&gt;
     (ausserdem gilt: es gibt keinen Weg &amp;lt;math&amp;gt;C_j \rightsquigarrow C_i&amp;lt;/math&amp;gt; )&lt;br /&gt;
     aber: in transponierten Graphen sind alle Kanten umgedreht&lt;br /&gt;
    &lt;br /&gt;
    '''*Eigenschaft 3:''' falls &amp;lt;math&amp;gt;{C_j}^T \rightsquigarrow {C_i}^T&amp;lt;/math&amp;gt; , dann gilt &amp;lt;math&amp;gt;pot \left({C_i}^T\right) &amp;gt; pot \left({C_j}^T\right)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Eigenschaft 2-3 &amp;lt;math&amp;gt;\Longrightarrow&amp;lt;/math&amp;gt; im transponierten Graphen gibt es nie einen Pfad &amp;lt;math&amp;gt;{C_i}^T \rightsquigarrow {C_j}^T&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Falls &amp;lt;math&amp;gt;pot \left({C_i}^T\right) &amp;gt; pot \left({C_j}^T\right)&amp;lt;/math&amp;gt;  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Longrightarrow&amp;lt;/math&amp;gt; Schritt 3 des Algorithmus kann von einem geg. Startknoten &amp;lt;u&amp;gt;''nur''&amp;lt;/u&amp;gt; die Knoten derselben SCC erreichen&lt;br /&gt;
 &lt;br /&gt;
q.e.d.&lt;br /&gt;
&lt;br /&gt;
=== postOrderTime ===&lt;br /&gt;
&lt;br /&gt;
    ## In einem Baum: besuche erst die Kinder, dann die Wurzel&lt;br /&gt;
    def postOrderTime(graph):&lt;br /&gt;
        visited = [None] * len(graph) &lt;br /&gt;
        def visit(node, count):&lt;br /&gt;
            #markiert, dass 'node' besucht wurde, aber noch nicht fertig ist&lt;br /&gt;
            visited[node] = -1&lt;br /&gt;
            for  neighbor in graph[node]:&lt;br /&gt;
                if visited[neighbor] is not None: continue&lt;br /&gt;
                count = visit(neighbor, count)&lt;br /&gt;
            visited[node] = count&lt;br /&gt;
            count += 1&lt;br /&gt;
            return count&lt;br /&gt;
        count = 0&lt;br /&gt;
        for node in range(len(graph)):&lt;br /&gt;
            if visited[node] is not None: continue&lt;br /&gt;
            count = visit(node, count)&lt;br /&gt;
        return visited&lt;br /&gt;
&lt;br /&gt;
=== transpose ===&lt;br /&gt;
&lt;br /&gt;
    ## Kehre die Richtung der Pfeile in einem Graphen um (tut nichts fuer ungerichtete Pfeile und Graphen).&lt;br /&gt;
    def transpose(graph):&lt;br /&gt;
        grapht = [[] for k in range(len(graph))]&lt;br /&gt;
        for node in range(len(graph)):&lt;br /&gt;
            for neighbor in graph[node]:&lt;br /&gt;
                grapht[neighbor].append(node)&lt;br /&gt;
        return grapht&lt;br /&gt;
&lt;br /&gt;
=== strongCC ===&lt;br /&gt;
&lt;br /&gt;
    ## Jede Komponente durch e. Ankerknoten repräsentiert&lt;br /&gt;
    ## Jedes SCC ist die Menge aller Knoten mit identischem Ankterknoten&lt;br /&gt;
    def strongCC(graph):&lt;br /&gt;
        # Prinzip: Tiefensuche mit absteigender Post-Order-Time&lt;br /&gt;
        postOrder = postOrderTime(graph)&lt;br /&gt;
        # ordered = [(knotenindex, POT), ...]&lt;br /&gt;
        ordered = zip(range(len(graph)), postOrder)&lt;br /&gt;
        ordered.sort(key=lambda x: x[1], reverse=True)&lt;br /&gt;
        &lt;br /&gt;
        grapht = transpose(graph)&lt;br /&gt;
        anchors = [None] * len(graph)&lt;br /&gt;
        def visit(node, anchor):&lt;br /&gt;
            if anchors[node] is not None: return&lt;br /&gt;
            anchors[node] = anchor&lt;br /&gt;
            for neighbor in grapht[node]:&lt;br /&gt;
                visit(neighbor, anchor)&lt;br /&gt;
        &lt;br /&gt;
        for node in ordered:&lt;br /&gt;
            visit(node[0], node[0])&lt;br /&gt;
        return anchors&lt;br /&gt;
&lt;br /&gt;
== Anwendung auf 2-SAT Problem ==&lt;br /&gt;
&lt;br /&gt;
geg.: Implikationen-Normalform, dargestellt als gerichteter Graph.&lt;br /&gt;
&lt;br /&gt;
Eigenschaft: alle Variablen in derselben SCC müssen den gleichen Wert haben, weil &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\underbrace{x_i \rightsquigarrow x_j \stackrel{\wedge}{=} x_i \rightarrow x_j; \;\;\;     x_j \rightsquigarrow x_i \stackrel{\wedge}{=} x_j \rightarrow x_i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:::::&amp;lt;math&amp;gt;\;\;\;x_i == x_j&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\rightarrow \; x_i  \; und \; \neg x_i&amp;lt;/math&amp;gt; dürfen nie in derselben SCC sein, weil &amp;lt;math&amp;gt;x_i == \neg x_i&amp;lt;/math&amp;gt; ein Widerspruch ist&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\Longrightarrow&amp;lt;/math&amp;gt; Algorithmus für Erfüllbarkeit von INF: teste diese Eigenschaft für jede stark zusammenhängende Komponente&lt;br /&gt;
des Implikationengraphen&lt;br /&gt;
&lt;br /&gt;
'''Das funktioniert leider nicht für k-SAT mit &amp;lt;math&amp;gt;k&amp;gt;2&amp;lt;/math&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
[[Randomisierte Algorithmen|Nächstes Thema]]&lt;/div&gt;</summary>
		<author><name>Stephan.meister</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Main_Page&amp;diff=5061</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Main_Page&amp;diff=5061"/>
				<updated>2012-05-29T12:20:45Z</updated>
		
		<summary type="html">&lt;p&gt;Stephan.meister: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Vorlesung Algorithmen und Datenstrukturen ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Dr. Ullrich Köthe, Universität Heidelberg, Sommersemester 2012&lt;br /&gt;
&lt;br /&gt;
Die Vorlesung findet '''dienstags''' und '''donnerstags''' jeweils um 14:15 Uhr in INF 227 (KIP), HS 2 statt. &lt;br /&gt;
&lt;br /&gt;
=== Klausur und Nachprüfung ===&lt;br /&gt;
&lt;br /&gt;
Die '''Abschlussklausur''' findet am Dienstag, dem 31.7.2012 von 9:30 bis 12:30 Uhr im HS 1 in INF 306 statt. Zur Klausur wird zugelassen, wer mindestens 50% der Übungspunkte erreicht. (Hinweis: Sie benötigen einen Lichtbildausweis, um sich bei der Klausur zu indentifizieren!)&lt;br /&gt;
&amp;lt;!---&lt;br /&gt;
* '''[[Media:Prüfungsteilnehmer.pdf|Liste der Studenten]], die sich verbindlich zur Klausur angemeldet und die notwendige Übungspunktzahl erreicht haben.'''&lt;br /&gt;
* '''[[Media:Ergebnis-Klausur-23-07-2008.pdf|Ergebnis der Klausur vom 23.7.2008]]''' (anonymisiert)&lt;br /&gt;
* '''Scheine''' können ab 1.9.2008 im Sekretariat Informatik bei Frau Tenschert abgeholt werden.&lt;br /&gt;
* Die '''Wiederholungsklausur''' findet am 1.10.2008 um 9:00 Uhr im Seminarraum des [http://hci.iwr.uni-heidelberg.de/contact.php HCI, Speyerer Str. 4], statt.&lt;br /&gt;
* '''[[Media:Ergebnis-Klausur-01-10-2008.pdf|Ergebnis der Wiederholungsklausur vom 1.10.2008]]''' (anonymisiert)&lt;br /&gt;
---&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Leistungsnachweise ===&lt;br /&gt;
Für alle Leistungsnachweise ist die erfolgreiche Teilnahme an den Übungen erforderlich. Für Leistungspunkte bzw. den Klausurschein muss außerdem die schriftliche Prüfung bestanden werden. Einzelheiten werden noch bekanntgegeben.&lt;br /&gt;
&amp;lt;!-------&lt;br /&gt;
Im einzelnen können erworben werden:&lt;br /&gt;
* ein unbenoteter Übungsschein, falls jemand nicht an der Klausur teilnimmt bzw. die Klausur nicht bestanden wurde.&lt;br /&gt;
* ein benoteter Übungsschein (Magister mit Computerlinguistik im ''Nebenfach'', Physik Diplom)&lt;br /&gt;
* ein Klausurschein (Magister mit Computerlinguistik im ''Hauptfach'')&lt;br /&gt;
* ein Leistungsnachweis über 9 Leistungspunkte (B.A. Computerlinguistik - alte Studienordnung) &lt;br /&gt;
* ein Leistungsnachweis über 8 Leistungspunkte (B.Sc. Informatik, B.A. Computerlinguistik - neue Studienordnung) &lt;br /&gt;
* ein Leistungsnachweis über 7 Leistungspunkte (B.Sc. Physik).&lt;br /&gt;
---------&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Übungsbetrieb ===&lt;br /&gt;
* Termine und Räume: &lt;br /&gt;
** Mo 14:00 - 16:00 Uhr, INF 227 (KIP), Seminarraum 2.402 (Tutor: Sven Ebser [mailto:sven@ebsers.de sven AT ebsers.de])&lt;br /&gt;
** Di  9:00 - 11:00 Uhr, INF 227 (KIP), Seminarraum 2.403 (Tutor: Christoph Koke [mailto:koke@kip.uni-heidelberg.de koke AT kip.uni-heidelberg.de])&lt;br /&gt;
** Di 11:00 - 13:00 Uhr, INF 227 (KIP), Seminarraum 2.403 (Tutor: Kai Karius [mailto:kai.karius@googlemail.com kai.karius AT googlemail.com])&lt;br /&gt;
** Mi 14:00 - 16:00 Uhr, INF 227 (KIP), Seminarraum 2.401 (Tutor: Stephan Meister [mailto:stephan.meister@iwr.uni-heidelberg.de stephan.meister AT iwr.uni-heidelberg.de])  &lt;br /&gt;
* Die Übungsgruppen werden über &amp;lt;b&amp;gt;[https://www.mathi.uni-heidelberg.de/muesli/lecture/view/169 MÜSLI]&amp;lt;/b&amp;gt; verwaltet. Dort erfolgt auch die &amp;lt;b&amp;gt;Anmeldung&amp;lt;/b&amp;gt;.&lt;br /&gt;
&amp;lt;!------&lt;br /&gt;
* [[Media:Punktestand.pdf|aktueller Punktestand]] (PDF, anonymisiert, so aktuell, wie von den Tutoren an mich übermittelt -- UK)&lt;br /&gt;
-------&amp;gt;&lt;br /&gt;
* &amp;lt;b&amp;gt;[[Main Page#Übungsaufgaben|Übungsaufgaben]]&amp;lt;/b&amp;gt; (Übungszettel mit Abgabetermin, Musterlösungen). Lösungen bitte per Email an den jeweiligen Übungsgruppenleiter.&lt;br /&gt;
* Zur Klausur wird zugelassen, wer mindestens 50% der Übungspunkte erreicht. Außerdem muss jeder Teilnehmer eine Lösung (bzw. einen Teil davon) in der Übungsgruppe vorrechnen. &lt;br /&gt;
* Durch das Lösen von Bonusaufgaben und gute Mitarbeit in den Übungen können Sie Zusatzpunkte erlangen. Zusatzpunkte werden auch vergeben, wenn Sie größere Verbesserungen an diesem Wiki vornehmen. Damit solche Verbesserungen der richtigen Person zugeordnet werden, sollten Sie dafür ein eigenes Wiki-Login verwenden, das Ihnen Stephan Meister oder Ullrich Köthe auf Anfrage gerne einrichten.&lt;br /&gt;
&lt;br /&gt;
=== Prüfungsvorbereitung ===&lt;br /&gt;
&lt;br /&gt;
Zur Hilfe bei der Prüfungsvorbereitung hat Andreas Fay [http://de.neemoy.com/quizcategories/31/ Quizfragen] erstellt.&lt;br /&gt;
&lt;br /&gt;
=== Literatur ===&lt;br /&gt;
&lt;br /&gt;
* R. Sedgewick: Algorithmen (empfohlen für den ersten Teil, bis einschließlich Graphenalgorithmen)&lt;br /&gt;
* J. Kleinberg, E.Tardos: Algorithm Design (empfohlen für den zweiten Teil, einschließlich Graphenalgorithmen)&lt;br /&gt;
* T. Cormen, C. Leiserson, R.Rivest: Algorithmen - eine Einführung (empfohlen zum Thema Komplexität)&lt;br /&gt;
* Wikipedia und andere Internetseiten (sehr gute Seiten über viele Algorithmen und Datenstrukturen)&lt;br /&gt;
&lt;br /&gt;
=== Gliederung der Vorlesung ===&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Einführung]] (17.4.2012) &lt;br /&gt;
#* Definition von Algorithmen und Datenstrukturen, Geschichte&lt;br /&gt;
#* Fundamentale Algorithmen: create, assign, copy, swap, compare etc.&lt;br /&gt;
#* Fundamentale Datenstrukturen: Zahlen, Container, Handles&lt;br /&gt;
#* Python-Grundlagen&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Container]] (19.4.2012)&lt;br /&gt;
#* Anforderungen von Algorithmen an Container&lt;br /&gt;
#* Einteilung der Container&lt;br /&gt;
#* Grundlegende Container: Array, verkettete Liste, Stack und Queue&lt;br /&gt;
#* Sequenzen und Intervalle (Ranges)&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Sortieren]] (24. und 26.4.2012)&lt;br /&gt;
#* Spezifikation des Sortierproblems&lt;br /&gt;
#* Selection Sort und Insertion Sort&lt;br /&gt;
#* Merge Sort&lt;br /&gt;
#* Quick Sort und seine Varianten&lt;br /&gt;
#* Vergleich der Anzahl der benötigten Schritte&lt;br /&gt;
#* Laufzeitmessung in Python&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Korrektheit]] (3. und 8.5.2012)&lt;br /&gt;
#* Definition von Korrektheit, Algorithmen-Spezifikation&lt;br /&gt;
#* Korrektheitsbeweise versus Testen&lt;br /&gt;
#* Vor- und Nachbedingungen, Invarianten, Programming by contract&lt;br /&gt;
#* Testen, Execution paths, Unit Tests in Python&lt;br /&gt;
#* Ausnahmen (exceptions) und Ausnahmebehandlung in Python&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Effizienz]] (10. und 15.5.2012)&lt;br /&gt;
#* Laufzeit und Optimierung: Innere Schleife, Caches, locality of reference&lt;br /&gt;
#* Laufzeit versus Komplexität&lt;br /&gt;
#* Landausymbole (O-Notation, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;-Notation, &amp;lt;math&amp;gt;\Theta&amp;lt;/math&amp;gt;-Notation), Komplexitätsklassen&lt;br /&gt;
#* Bester, schlechtester, durchschnittlicher Fall&lt;br /&gt;
#* Amortisierte Komplexität&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Suchen]] (22. und 24.5.2012)&lt;br /&gt;
#* Lineare Suche&lt;br /&gt;
#* Binäre Suche in sortierten Arrays, Medianproblem&lt;br /&gt;
#* Suchbäume, balancierte Bäume&lt;br /&gt;
#* selbst-balancierende Bäume, Rotationen&lt;br /&gt;
#* Komplexität der Suche&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Prioritätswarteschlangen]] (29.5.2012)&lt;br /&gt;
#* Heap-Datenstruktur&lt;br /&gt;
#* Einfüge- und Löschoperationen&lt;br /&gt;
#* Heapsort&lt;br /&gt;
#* Komplexität des Heaps&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Hashing und assoziative Arrays]] (31.5.und 5.6.2012)&lt;br /&gt;
#* Implementation assoziativer Arrays mit Bäumen&lt;br /&gt;
#* Hashing und Hashfunktionen&lt;br /&gt;
#* Implementation assoziativer Arrays als Hashtabelle mit linearer Verkettung bzw. mit offener Adressierung&lt;br /&gt;
#* Anwendung des Hashing zur String-Suche: Rabin-Karp-Algorithmus&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Iteration versus Rekursion]] (12.6.2012)&lt;br /&gt;
#* Typen der Rekursion und ihre Umwandlung in Iteration&lt;br /&gt;
#* Auflösung rekursiver Formeln mittels Master-Methode und Substitutionsmethode&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Generizität]] (14.6.2012)&lt;br /&gt;
#* Abstrakte Datentypen, Typspezifikation&lt;br /&gt;
#* Required Interface versus Offered Interface&lt;br /&gt;
#* Adapter und Typattribute, Funktoren&lt;br /&gt;
#* Beispiel: Algebraische Konzepte und Zahlendatentypen&lt;br /&gt;
#* Operator overloading in Python&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Graphen und Graphenalgorithmen]] (19. bis 28.6.2012)&lt;br /&gt;
#* Einführung&lt;br /&gt;
#* Graphendatenstrukturen, Adjazenzlisten und Adjazenzmatrizen&lt;br /&gt;
#* Gerichtete und ungerichtete Graphen&lt;br /&gt;
#* Vollständige Graphen&lt;br /&gt;
#* Planare Graphen, duale Graphen&lt;br /&gt;
#* Pfade, Zyklen&lt;br /&gt;
#* Tiefensuche und Breitensuche&lt;br /&gt;
#* Zusammenhang, Komponenten&lt;br /&gt;
#* Gewichtete Graphen&lt;br /&gt;
#* Minimaler Spannbaum&lt;br /&gt;
#* Kürzeste Wege, Best-first search (Dijkstra)&lt;br /&gt;
#* Most-Promising-first search (A*)&lt;br /&gt;
#* Problem des Handlungsreisenden, exakte Algorithmen (erschöpfende Suche, Branch-and-Bound-Methode) und Approximationen&lt;br /&gt;
#* Erfüllbarkeitsproblem, Darstellung des 2-SAT-Problems durch gerichtete Graphen, stark zusammenhängende Komponenten&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
&amp;lt;!---#* Repetition---&amp;gt;&lt;br /&gt;
&amp;lt;!---#* Orthogonale Zerlegung des Problems---&amp;gt;&lt;br /&gt;
&amp;lt;!---#* Hierarchische Zerlegung der Daten (Divide and Conquer)---&amp;gt;&lt;br /&gt;
&amp;lt;!---#* Randomisierung---&amp;gt;&lt;br /&gt;
&amp;lt;!---#* Optimierung, Zielfunktionen---&amp;gt;&lt;br /&gt;
&amp;lt;!---#* Systematisierung von Algorithmen aus der bisherigen Vorlesung---&amp;gt;&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
&amp;lt;!---# [[Analytische Optimierung]] (25.6.2008)---&amp;gt;&lt;br /&gt;
&amp;lt;!---#* Methode der kleinsten Quadrate---&amp;gt;&lt;br /&gt;
&amp;lt;!---#* Approximation von Geraden---&amp;gt;&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Randomisierte Algorithmen]] (3. und 5.7.2012)&lt;br /&gt;
#* Zufallszahlen, Zyklenlänge, Pitfalls&lt;br /&gt;
#* Zufallszahlengeneratoren: linear congruential generator, Mersenne Twister&lt;br /&gt;
#* Randomisierte vs. deterministische Algorithmen&lt;br /&gt;
#* Las Vegas vs. Monte Carlo Algorithmen&lt;br /&gt;
#* Beispiel für Las Vegas: Randomisiertes Quicksort&lt;br /&gt;
#* Beispiele für Monte Carlo: Randomisierte Lösung des k-SAT Problems &lt;br /&gt;
#* RANSAC-Algorithmus, Erfolgswahrscheinlichkeit, Vergleich mit analytischer Optimierung (Methode der kleinsten Quadrate)&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Greedy-Algorithmen und Dynamische Programmierung]] (10. und 12.7.2012)&lt;br /&gt;
#* Prinzipien, Aufwandsreduktion in Entscheidungsbäumen&lt;br /&gt;
#* bereits bekannte Algorithmen: minimale Spannbäume nach Kruskal, kürzeste Wege nach Dijkstra&lt;br /&gt;
#* Beispiel: Interval Scheduling Problem und Weighted Interval Scheduling Problem&lt;br /&gt;
#* Beweis der Optimalität beim Scheduling Problem: &amp;quot;greedy stays ahead&amp;quot;-Prinzip, Directed Acyclic Graph bei dynamischer Programmierung&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[NP-Vollständigkeit]] (17. und 19.7.2012)&lt;br /&gt;
#* die Klassen P und NP&lt;br /&gt;
#* NP-Vollständigkeit und Problemreduktion&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# Reserve und/oder Wiederholung (24. und 26.7.2012)&lt;br /&gt;
&lt;br /&gt;
== Übungsaufgaben ==&lt;br /&gt;
&lt;br /&gt;
(im PDF Format). Die Abgabe erfolgt am angegebenen Tag bis 14:00 Uhr per Email an den jeweiligen Übungsgruppenleiter. Bei Abgabe bis zum folgenden Montag 11:00 Uhr werden noch 50% der erreichten Punkte angerechnet. Danach wird die Musterlösung freigeschaltet. &lt;br /&gt;
&lt;br /&gt;
# [[Media:Übung-1.pdf|Übung]] (Abgabe 24.4.2012) und [[Media:Uebung-1-Musterloesung.pdf|Musterlösung]]&lt;br /&gt;
#* Python-Tutorial&lt;br /&gt;
#* Sieb des Eratosthenes&lt;br /&gt;
#* Wert- und Referenzsemantik&lt;br /&gt;
#* Dynamisches Array&lt;br /&gt;
# [[Media:Uebung-2.pdf|Übung]] (Abgabe 3.5.2012) und [[Media:Uebung-2-Musterloesung.pdf|Musterlösung]]&lt;br /&gt;
#* Sortieren: Implementation und Geschwindigkeitsvergleich (Diagramme in Abhängigkeit von der Problemgröße)&lt;br /&gt;
#* Entwicklung eines Gewinnalgorithmus für ein Spiel&lt;br /&gt;
#* Bonus: Dynamisches Array mit verringertem Speicherverbrauch&lt;br /&gt;
# [[Media:Uebung-3.pdf|Übung]] (Abgabe 10.5.2012) und [[Media:Uebung-3-Musterlösung.pdf|Musterlösung]]&lt;br /&gt;
#* Experimente zur Effektivität von Unit Tests&lt;br /&gt;
#* Bestimmung von Pi mit dem Algorithmus von Archimedes&lt;br /&gt;
#* Deque-Datenstruktur: Vor- und Nachbedingungen der Operationen, Implementation und Unit Tests&lt;br /&gt;
# [[Media:Uebung-4.pdf|Übung]] (Abgabe '''Montag''' 21.5.2012) &amp;lt;!------------ und [[Media:Musterloesung_4.pdf|Musterlösung]] ----&amp;gt;&lt;br /&gt;
#* Theoretische Aufgaben zur Komplexität&lt;br /&gt;
#* Amortisierte Komplexität von array.append()&lt;br /&gt;
#* Optimierung der Matrizenmultiplikation&lt;br /&gt;
# [[Media:Uebung-5.pdf|Übung]] (31.5.2012) &amp;lt;!------ und [[Media:muster_blatt5.pdf|Musterlösung]] ----&amp;gt;&lt;br /&gt;
#* Implementation und Analyse eines Binärbaumes&lt;br /&gt;
#* Anwendung: einfacher Taschenrechner&lt;br /&gt;
&amp;lt;!------------&lt;br /&gt;
# [[Media:Übung-6.pdf|Übung]] (Abgabe 5.6.2012) und [[Media:muster_blatt6.pdf|Musterlösung]]&lt;br /&gt;
#* Treap-Datenstruktur: Verbindung von Suchbaum und Heap&lt;br /&gt;
#* Anwendung: Worthäufigkeiten (Dazu benötigen Sie das File  [http://klimt.iwr.uni-heidelberg.de/mip/people/ukoethe/download/die-drei-musketiere.txt die-drei-musketiere.txt]. Die Zeichenkodierung in diesem File ist Latin-1.)&lt;br /&gt;
#* Suche mit linearer Komplexität&lt;br /&gt;
&amp;lt;!------------&lt;br /&gt;
# [[Media:Übung-7.pdf|Übung]] (Abgabe 12.6.2012) und [[Media:muster_blatt7.pdf|Musterlösung]]&lt;br /&gt;
#* Übungen zu Rekursion und Iteration: Fakultät, Koch-Schneeflocke, Komplexität rekursiver Algorithmen, Umwandlung von Rekursion in Iteration&lt;br /&gt;
&amp;lt;!------------&lt;br /&gt;
# [[Media:Übung-8.pdf|Übung]] (Abgabe 19.6.2012) und [[Media:muster_blatt8.pdf|Musterlösung]]&lt;br /&gt;
#* Elementare Graphenaufgaben: Aufstellen von Adjazenzmatrizen und Adjazenzlisten, planare Graphen&lt;br /&gt;
#* Übungen zur Generizität: Sortieren mit veränderter Ordnung, Iterator für Tiefensuche&lt;br /&gt;
&amp;lt;!------------&lt;br /&gt;
# [[Media:Übung-9.pdf|Übung]] (Abgabe 26.6.2012)&lt;br /&gt;
#* Fortgeschrittene Graphenaufgaben: Erzeugen einer perfekten Hashfunktion, Routenplaner (Dazu benötigen Sie das File  [http://klimt.iwr.uni-heidelberg.de/mip/people/ukoethe/download/entfernungen.txt entfernungen.txt]. Die Zeichenkodierung in diesem File ist Latin-1.)&lt;br /&gt;
&amp;lt;!------------&lt;br /&gt;
# [[Media:Übung-10.pdf|Übung]] (Abgabe 3.7.2012) und [[Media:loesung_blatt10.pdf|Musterlösung]] sowie schöne [[Media:ballungsgebiete.pdf|Visualisierung der Ballungsgebiete]] von Thorben Kröger&lt;br /&gt;
#* Fortgeschrittene Graphenaufgaben 2: Clusterung mittels minimaler Spannbäume, Problem des Handelsreisenden (Eine &amp;lt;font color=red&amp;gt;neue Version&amp;lt;/font&amp;gt; der Datei [http://klimt.iwr.uni-heidelberg.de/mip/people/ukoethe/download/entfernungen.txt entfernungen.txt] ist verfügbar. Dank an Sven Ebser, Joachim Schleicher und Thorben Kröger für Hilfe bei der Verbesserung der Datei.)&lt;br /&gt;
&amp;lt;!------------&lt;br /&gt;
# [[Media:Übung-11.pdf|Übung]] (Abgabe 10.7.2012)&lt;br /&gt;
#* Erfüllbarkeitsproblem, Anwendung: Heim- und Auswärtsspiele im Fussball (Dazu benötigen sie das File [http://klimt.iwr.uni-heidelberg.de/mip/people/ukoethe/download/bundesliga-paarungen-08-09.txt bundesliga-paarungen-08-09.txt].)&lt;br /&gt;
#* Randomisierte Algorithmen: RANSAC für Kreise (Dazu benötigen sie das File [http://klimt.iwr.uni-heidelberg.de/mip/people/ukoethe/download/noisy-circles.txt noisy-circles.txt].)&lt;br /&gt;
&amp;lt;!-------------&lt;br /&gt;
# [[Media:Übung-12.pdf|Übung]] (&amp;lt;font color=red&amp;gt;Achtung: Abgabe bereits am Mittwoch, 16.7.2012&amp;lt;/font&amp;gt;)&lt;br /&gt;
#* Greedy-Algorithmen und Dynamische Programmierung&lt;br /&gt;
&amp;lt;!----------------&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Sonstiges ==&lt;br /&gt;
* [[Gnuplot| Gnuplot Kurztutorial]]&lt;br /&gt;
* [[Git Kurztutorial]]&lt;br /&gt;
* [[neue Startseite|mögliche neue Startseite]]&lt;/div&gt;</summary>
		<author><name>Stephan.meister</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Git_Kurztutorial&amp;diff=5060</id>
		<title>Git Kurztutorial</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Git_Kurztutorial&amp;diff=5060"/>
				<updated>2012-05-29T12:20:04Z</updated>
		
		<summary type="html">&lt;p&gt;Stephan.meister: Kurztoturial angelegt&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Anleitung zur Abgabe der Übungszettel über Git und Bitbucket =&lt;br /&gt;
&lt;br /&gt;
Übungszettel können in Zukunft auch das Online Repository Bitbucket (https://bitbucket.org/) abgegeben werden.&lt;br /&gt;
Dazu nutzen wir das Versionverwaltungstool Git (http://git-scm.com/).&lt;br /&gt;
&lt;br /&gt;
= Terminologie =&lt;br /&gt;
Die genauen Bezeichnungen können sich zwischen den verschiedenen Versionsverwaltungssystemem unterscheiden (z.B. Git, Bazaar, SVN, etc.)&lt;br /&gt;
; Revision : Eine konkrekte Version einer Datei, eines Ordners oder einer ganzen Ordnerstruktur die im Versionsverwaltungssystem enthalten ist&lt;br /&gt;
; Branch : i.d.R. die aktuellste Revision. In der Softwareentwicklung nutz man häufig mehrere branches parallel, z.B. einen Hauptentwicklungszweig (branch) und mehrere Nebenzweige für Experimente oder neue Programfeatures.&lt;br /&gt;
; working copy : Die Dateien und Ordner die man gerade bearbeitet und deren Änderungen i.d.R. noch nicht im Versionsverwaltungssystem enthalten sind&lt;br /&gt;
; Repository : Eine Sammlung an Revisionen mit zeitlicher und/oder logischer Ordnung. &lt;br /&gt;
; commit : Das einpflegen der aktuellen working copy in das Repository (dabei wird eine neue Revision erzeugt)&lt;br /&gt;
&lt;br /&gt;
= Kurzanleitung =&lt;br /&gt;
# Anlegen eines Accounts bei Bitbucket&lt;br /&gt;
# Erzeugen eines Repositories (Repositories -&amp;gt; create Repository)&lt;br /&gt;
## Sinnvollen Namen für das Repository wählen, z.B. ALDA&lt;br /&gt;
## Repository Type: Git&lt;br /&gt;
## Language: Python&lt;br /&gt;
# Setzen der Zugriffsrechte&lt;br /&gt;
## (Repository -&amp;gt; &amp;lt;eigenes Repository&amp;gt; -&amp;gt; Admin -&amp;gt; Access Managment)&lt;br /&gt;
## Hier kann man dem Übungspartner Schreibrechte zuweisen, bzw. dem Übungsgruppenleiter Leserechte&lt;br /&gt;
# Klonen des Repositories auf den eigenen Rechner (damit erzeugt man auch eine working copy)&lt;br /&gt;
## In der Kommandozeile: &amp;lt;nowiki&amp;gt;git clone https://&amp;lt;nutzername&amp;gt;@bitbucket.org/&amp;lt;nutzername&amp;gt;/&amp;lt;repository&amp;gt;.git&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
# Übungszettel lösen&lt;br /&gt;
# Für die Übung relevante Dateien einchecken&lt;br /&gt;
## Es empfiehlt sich Unterordner für die verschiedenen Übungszettel anzulegen, z.b. 01,02,etc.&lt;br /&gt;
## &amp;lt;nowiki&amp;gt;git add &amp;lt;dateiname&amp;gt;&amp;lt;/nowiki&amp;gt;  bzw. git add &amp;lt;ordnername&amp;gt;  (fügt den ordner und alle enthaltenen Dateien hinzu)&lt;br /&gt;
## &amp;lt;nowiki&amp;gt;git commit -a -m &amp;quot;Statusnachricht&amp;quot;&amp;lt;/nowiki&amp;gt; (Erzeugt eine neue Revision mit allen hinzugefügten neuen Dateien sowie den Änderungen an bereits bestehenden Dateien)&lt;br /&gt;
## &amp;lt;nowiki&amp;gt;git push -u origin master&amp;lt;/nowiki&amp;gt; (Übertragen der Daten zum Server)&lt;br /&gt;
&lt;br /&gt;
* Erst nach diesem letzem Schritt können die Übungsleiter auch die Lösungen einsehen. Es empfiehlt sich auf der bitbucket Homepage im eigenen Repository unter &amp;quot;Code&amp;quot; zu überprüfen ob alle wichtigen Dateien vorhanden sind.&lt;br /&gt;
* Es genügt ein Repository pro Team. Einzelne Übungszettel sollten in Unterordnern organisiert sein.&lt;br /&gt;
&lt;br /&gt;
= Nützliche Links =&lt;br /&gt;
* Bitbucket Tutorial[https://confluence.atlassian.com/display/BITBUCKET/Create+an+Account+and+a+Git+Repo 1], [https://confluence.atlassian.com/display/BITBUCKET/Clone+Your+Git+Repo+and+Add+Source+Files 2]&lt;/div&gt;</summary>
		<author><name>Stephan.meister</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Main_Page&amp;diff=4939</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Main_Page&amp;diff=4939"/>
				<updated>2012-05-08T11:43:11Z</updated>
		
		<summary type="html">&lt;p&gt;Stephan.meister: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Vorlesung Algorithmen und Datenstrukturen ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Dr. Ullrich Köthe, Universität Heidelberg, Sommersemester 2012&lt;br /&gt;
&lt;br /&gt;
Die Vorlesung findet '''dienstags''' und '''donnerstags''' jeweils um 14:15 Uhr in INF 227 (KIP), HS 2 statt. &lt;br /&gt;
&lt;br /&gt;
=== Klausur und Nachprüfung ===&lt;br /&gt;
&lt;br /&gt;
Die '''Abschlussklausur''' findet am Dienstag, dem 31.7.2012 von 9:30 bis 12:30 Uhr im HS 1 in INF 306 statt. Zur Klausur wird zugelassen, wer mindestens 50% der Übungspunkte erreicht. (Hinweis: Sie benötigen einen Lichtbildausweis, um sich bei der Klausur zu indentifizieren!)&lt;br /&gt;
&amp;lt;!---&lt;br /&gt;
* '''[[Media:Prüfungsteilnehmer.pdf|Liste der Studenten]], die sich verbindlich zur Klausur angemeldet und die notwendige Übungspunktzahl erreicht haben.'''&lt;br /&gt;
* '''[[Media:Ergebnis-Klausur-23-07-2008.pdf|Ergebnis der Klausur vom 23.7.2008]]''' (anonymisiert)&lt;br /&gt;
* '''Scheine''' können ab 1.9.2008 im Sekretariat Informatik bei Frau Tenschert abgeholt werden.&lt;br /&gt;
* Die '''Wiederholungsklausur''' findet am 1.10.2008 um 9:00 Uhr im Seminarraum des [http://hci.iwr.uni-heidelberg.de/contact.php HCI, Speyerer Str. 4], statt.&lt;br /&gt;
* '''[[Media:Ergebnis-Klausur-01-10-2008.pdf|Ergebnis der Wiederholungsklausur vom 1.10.2008]]''' (anonymisiert)&lt;br /&gt;
---&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Leistungsnachweise ===&lt;br /&gt;
Für alle Leistungsnachweise ist die erfolgreiche Teilnahme an den Übungen erforderlich. Für Leistungspunkte bzw. den Klausurschein muss außerdem die schriftliche Prüfung bestanden werden. Einzelheiten werden noch bekanntgegeben.&lt;br /&gt;
&amp;lt;!-------&lt;br /&gt;
Im einzelnen können erworben werden:&lt;br /&gt;
* ein unbenoteter Übungsschein, falls jemand nicht an der Klausur teilnimmt bzw. die Klausur nicht bestanden wurde.&lt;br /&gt;
* ein benoteter Übungsschein (Magister mit Computerlinguistik im ''Nebenfach'', Physik Diplom)&lt;br /&gt;
* ein Klausurschein (Magister mit Computerlinguistik im ''Hauptfach'')&lt;br /&gt;
* ein Leistungsnachweis über 9 Leistungspunkte (B.A. Computerlinguistik - alte Studienordnung) &lt;br /&gt;
* ein Leistungsnachweis über 8 Leistungspunkte (B.Sc. Informatik, B.A. Computerlinguistik - neue Studienordnung) &lt;br /&gt;
* ein Leistungsnachweis über 7 Leistungspunkte (B.Sc. Physik).&lt;br /&gt;
---------&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Übungsbetrieb ===&lt;br /&gt;
* Termine und Räume: &lt;br /&gt;
** Mo 14:00 - 16:00 Uhr, INF 227 (KIP), Seminarraum 2.402 (Tutor: Sven Ebser [mailto:sven@ebsers.de sven AT ebsers.de])&lt;br /&gt;
** Di  9:00 - 11:00 Uhr, INF 227 (KIP), Seminarraum 2.403 (Tutor: Christoph Koke [mailto:koke@kip.uni-heidelberg.de koke AT kip.uni-heidelberg.de])&lt;br /&gt;
** Di 11:00 - 13:00 Uhr, INF 227 (KIP), Seminarraum 2.403 (Tutor: Kai Karius [mailto:kai.karius@googlemail.com kai.karius AT googlemail.com])&lt;br /&gt;
** Mi 14:00 - 16:00 Uhr, INF 227 (KIP), Seminarraum 2.401 (Tutor: Stephan Meister [mailto:stephan.meister@iwr.uni-heidelberg.de stephan.meister AT iwr.uni-heidelberg.de])  &lt;br /&gt;
* Die Übungsgruppen werden über &amp;lt;b&amp;gt;[https://www.mathi.uni-heidelberg.de/muesli/lecture/view/169 MÜSLI]&amp;lt;/b&amp;gt; verwaltet. Dort erfolgt auch die &amp;lt;b&amp;gt;Anmeldung&amp;lt;/b&amp;gt;.&lt;br /&gt;
&amp;lt;!------&lt;br /&gt;
* [[Media:Punktestand.pdf|aktueller Punktestand]] (PDF, anonymisiert, so aktuell, wie von den Tutoren an mich übermittelt -- UK)&lt;br /&gt;
-------&amp;gt;&lt;br /&gt;
* &amp;lt;b&amp;gt;[[Main Page#Übungsaufgaben|Übungsaufgaben]]&amp;lt;/b&amp;gt; (Übungszettel mit Abgabetermin, Musterlösungen). Lösungen bitte per Email an den jeweiligen Übungsgruppenleiter.&lt;br /&gt;
* Zur Klausur wird zugelassen, wer mindestens 50% der Übungspunkte erreicht. Außerdem muss jeder Teilnehmer eine Lösung (bzw. einen Teil davon) in der Übungsgruppe vorrechnen. &lt;br /&gt;
* Durch das Lösen von Bonusaufgaben und gute Mitarbeit in den Übungen können Sie Zusatzpunkte erlangen. Zusatzpunkte werden auch vergeben, wenn Sie größere Verbesserungen an diesem Wiki vornehmen. Damit solche Verbesserungen der richtigen Person zugeordnet werden, sollten Sie dafür ein eigenes Wiki-Login verwenden, das Ihnen Stephan Meister oder Ullrich Köthe auf Anfrage gerne einrichten.&lt;br /&gt;
&lt;br /&gt;
=== Prüfungsvorbereitung ===&lt;br /&gt;
&lt;br /&gt;
Zur Hilfe bei der Prüfungsvorbereitung hat Andreas Fay [http://de.neemoy.com/quizcategories/31/ Quizfragen] erstellt.&lt;br /&gt;
&lt;br /&gt;
=== Literatur ===&lt;br /&gt;
&lt;br /&gt;
* R. Sedgewick: Algorithmen (empfohlen für den ersten Teil, bis einschließlich Graphenalgorithmen)&lt;br /&gt;
* J. Kleinberg, E.Tardos: Algorithm Design (empfohlen für den zweiten Teil, einschließlich Graphenalgorithmen)&lt;br /&gt;
* T. Cormen, C. Leiserson, R.Rivest: Algorithmen - eine Einführung (empfohlen zum Thema Komplexität)&lt;br /&gt;
* Wikipedia und andere Internetseiten (sehr gute Seiten über viele Algorithmen und Datenstrukturen)&lt;br /&gt;
&lt;br /&gt;
=== Gliederung der Vorlesung ===&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Einführung]] (17.4.2012) &lt;br /&gt;
#* Definition von Algorithmen und Datenstrukturen, Geschichte&lt;br /&gt;
#* Fundamentale Algorithmen: create, assign, copy, swap, compare etc.&lt;br /&gt;
#* Fundamentale Datenstrukturen: Zahlen, Container, Handles&lt;br /&gt;
#* Python-Grundlagen&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Container]] (19.4.2012)&lt;br /&gt;
#* Anforderungen von Algorithmen an Container&lt;br /&gt;
#* Einteilung der Container&lt;br /&gt;
#* Grundlegende Container: Array, verkettete Liste, Stack und Queue&lt;br /&gt;
#* Sequenzen und Intervalle (Ranges)&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Sortieren]] (24. und 26.4.2012)&lt;br /&gt;
#* Spezifikation des Sortierproblems&lt;br /&gt;
#* Selection Sort und Insertion Sort&lt;br /&gt;
#* Merge Sort&lt;br /&gt;
#* Quick Sort und seine Varianten&lt;br /&gt;
#* Vergleich der Anzahl der benötigten Schritte&lt;br /&gt;
#* Laufzeitmessung in Python&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Korrektheit]] (3. und 8.5.2012)&lt;br /&gt;
#* Definition von Korrektheit, Algorithmen-Spezifikation&lt;br /&gt;
#* Korrektheitsbeweise versus Testen&lt;br /&gt;
#* Vor- und Nachbedingungen, Invarianten, Programming by contract&lt;br /&gt;
#* Testen, Execution paths, Unit Tests in Python&lt;br /&gt;
#* Ausnahmen (exceptions) und Ausnahmebehandlung in Python&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Effizienz]] (10. und 15.5.2012)&lt;br /&gt;
#* Laufzeit und Optimierung: Innere Schleife, Caches, locality of reference&lt;br /&gt;
#* Laufzeit versus Komplexität&lt;br /&gt;
#* Landausymbole (O-Notation, &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;-Notation, &amp;lt;math&amp;gt;\Theta&amp;lt;/math&amp;gt;-Notation), Komplexitätsklassen&lt;br /&gt;
#* Bester, schlechtester, durchschnittlicher Fall&lt;br /&gt;
#* Amortisierte Komplexität&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Suchen]] (22. und 24.5.2012)&lt;br /&gt;
#* Lineare Suche&lt;br /&gt;
#* Binäre Suche in sortierten Arrays, Medianproblem&lt;br /&gt;
#* Suchbäume, balancierte Bäume&lt;br /&gt;
#* selbst-balancierende Bäume, Rotationen&lt;br /&gt;
#* Komplexität der Suche&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Prioritätswarteschlangen]] (29.5.2012)&lt;br /&gt;
#* Heap-Datenstruktur&lt;br /&gt;
#* Einfüge- und Löschoperationen&lt;br /&gt;
#* Heapsort&lt;br /&gt;
#* Komplexität des Heaps&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Hashing und assoziative Arrays]] (31.5.und 5.6.2012)&lt;br /&gt;
#* Implementation assoziativer Arrays mit Bäumen&lt;br /&gt;
#* Hashing und Hashfunktionen&lt;br /&gt;
#* Implementation assoziativer Arrays als Hashtabelle mit linearer Verkettung bzw. mit offener Adressierung&lt;br /&gt;
#* Anwendung des Hashing zur String-Suche: Rabin-Karp-Algorithmus&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Iteration versus Rekursion]] (12.6.2012)&lt;br /&gt;
#* Typen der Rekursion und ihre Umwandlung in Iteration&lt;br /&gt;
#* Auflösung rekursiver Formeln mittels Master-Methode und Substitutionsmethode&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Generizität]] (14.6.2012)&lt;br /&gt;
#* Abstrakte Datentypen, Typspezifikation&lt;br /&gt;
#* Required Interface versus Offered Interface&lt;br /&gt;
#* Adapter und Typattribute, Funktoren&lt;br /&gt;
#* Beispiel: Algebraische Konzepte und Zahlendatentypen&lt;br /&gt;
#* Operator overloading in Python&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Graphen und Graphenalgorithmen]] (19. bis 28.6.2012)&lt;br /&gt;
#* Einführung&lt;br /&gt;
#* Graphendatenstrukturen, Adjazenzlisten und Adjazenzmatrizen&lt;br /&gt;
#* Gerichtete und ungerichtete Graphen&lt;br /&gt;
#* Vollständige Graphen&lt;br /&gt;
#* Planare Graphen, duale Graphen&lt;br /&gt;
#* Pfade, Zyklen&lt;br /&gt;
#* Tiefensuche und Breitensuche&lt;br /&gt;
#* Zusammenhang, Komponenten&lt;br /&gt;
#* Gewichtete Graphen&lt;br /&gt;
#* Minimaler Spannbaum&lt;br /&gt;
#* Kürzeste Wege, Best-first search (Dijkstra)&lt;br /&gt;
#* Most-Promising-first search (A*)&lt;br /&gt;
#* Problem des Handlungsreisenden, exakte Algorithmen (erschöpfende Suche, Branch-and-Bound-Methode) und Approximationen&lt;br /&gt;
#* Erfüllbarkeitsproblem, Darstellung des 2-SAT-Problems durch gerichtete Graphen, stark zusammenhängende Komponenten&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
&amp;lt;!---#* Repetition---&amp;gt;&lt;br /&gt;
&amp;lt;!---#* Orthogonale Zerlegung des Problems---&amp;gt;&lt;br /&gt;
&amp;lt;!---#* Hierarchische Zerlegung der Daten (Divide and Conquer)---&amp;gt;&lt;br /&gt;
&amp;lt;!---#* Randomisierung---&amp;gt;&lt;br /&gt;
&amp;lt;!---#* Optimierung, Zielfunktionen---&amp;gt;&lt;br /&gt;
&amp;lt;!---#* Systematisierung von Algorithmen aus der bisherigen Vorlesung---&amp;gt;&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
&amp;lt;!---# [[Analytische Optimierung]] (25.6.2008)---&amp;gt;&lt;br /&gt;
&amp;lt;!---#* Methode der kleinsten Quadrate---&amp;gt;&lt;br /&gt;
&amp;lt;!---#* Approximation von Geraden---&amp;gt;&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Randomisierte Algorithmen]] (3. und 5.7.2012)&lt;br /&gt;
#* Zufallszahlen, Zyklenlänge, Pitfalls&lt;br /&gt;
#* Zufallszahlengeneratoren: linear congruential generator, Mersenne Twister&lt;br /&gt;
#* Randomisierte vs. deterministische Algorithmen&lt;br /&gt;
#* Las Vegas vs. Monte Carlo Algorithmen&lt;br /&gt;
#* Beispiel für Las Vegas: Randomisiertes Quicksort&lt;br /&gt;
#* Beispiele für Monte Carlo: Randomisierte Lösung des k-SAT Problems &lt;br /&gt;
#* RANSAC-Algorithmus, Erfolgswahrscheinlichkeit, Vergleich mit analytischer Optimierung (Methode der kleinsten Quadrate)&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[Greedy-Algorithmen und Dynamische Programmierung]] (10. und 12.7.2012)&lt;br /&gt;
#* Prinzipien, Aufwandsreduktion in Entscheidungsbäumen&lt;br /&gt;
#* bereits bekannte Algorithmen: minimale Spannbäume nach Kruskal, kürzeste Wege nach Dijkstra&lt;br /&gt;
#* Beispiel: Interval Scheduling Problem und Weighted Interval Scheduling Problem&lt;br /&gt;
#* Beweis der Optimalität beim Scheduling Problem: &amp;quot;greedy stays ahead&amp;quot;-Prinzip, Directed Acyclic Graph bei dynamischer Programmierung&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# [[NP-Vollständigkeit]] (17. und 19.7.2012)&lt;br /&gt;
#* die Klassen P und NP&lt;br /&gt;
#* NP-Vollständigkeit und Problemreduktion&lt;br /&gt;
&amp;lt;!-------------&amp;gt;&lt;br /&gt;
# Reserve und/oder Wiederholung (24. und 26.7.2012)&lt;br /&gt;
&lt;br /&gt;
== Übungsaufgaben ==&lt;br /&gt;
&lt;br /&gt;
(im PDF Format). Die Abgabe erfolgt am angegebenen Tag bis 14:00 Uhr per Email an den jeweiligen Übungsgruppenleiter. Bei Abgabe bis zum folgenden Montag 11:00 Uhr werden noch 50% der erreichten Punkte angerechnet. Danach wird die Musterlösung freigeschaltet. &lt;br /&gt;
&lt;br /&gt;
# [[Media:Übung-1.pdf|Übung]] (Abgabe 24.4.2012) und [[Media:Uebung-1-Musterloesung.pdf|Musterlösung]]&lt;br /&gt;
#* Python-Tutorial&lt;br /&gt;
#* Sieb des Eratosthenes&lt;br /&gt;
#* Wert- und Referenzsemantik&lt;br /&gt;
#* Dynamisches Array&lt;br /&gt;
# [[Media:Uebung-2.pdf|Übung]] (Abgabe 3.5.2012) &amp;lt;!--- sowie Musterlösungen für [[Media:muster_blatt2-aufgabe1.pdf|Aufgabe 1]] und [[Media:muster_blatt2-aufgabe2.pdf|Aufgabe 2]] ---&amp;gt;&lt;br /&gt;
#* Sortieren: Implementation und Geschwindigkeitsvergleich (Diagramme in Abhängigkeit von der Problemgröße)&lt;br /&gt;
#* Entwicklung eines Gewinnalgorithmus für ein Spiel&lt;br /&gt;
#* Bonus: Dynamisches Array mit verringertem Speicherverbrauch&lt;br /&gt;
# [[Media:Uebung-3.pdf|Übung]] (Abgabe 10.5.2012) &amp;lt;!----------- und [[Media:Übung-3-Musterlösung.pdf|Musterlösung]] -----&amp;gt;&lt;br /&gt;
#* Experimente zur Effektivität von Unit Tests&lt;br /&gt;
#* Bestimmung von Pi mit dem Algorithmus von Archimedes&lt;br /&gt;
#* Deque-Datenstruktur: Vor- und Nachbedingungen der Operationen, Implementation und Unit Tests&lt;br /&gt;
&amp;lt;!------------&lt;br /&gt;
# [[Media:Übung-4.pdf|Übung]] (Abgabe 15.5.2008) und [[Media:Musterloesung_4.pdf|Musterlösung]]&lt;br /&gt;
#* Theoretische Aufgaben zur Komplexität&lt;br /&gt;
#* Amortisierte Komplexität von array.append()&lt;br /&gt;
#* Optimierung der Matrizenmultiplikation&lt;br /&gt;
&amp;lt;!------------&lt;br /&gt;
# [[Media:Übung-5.pdf|Übung]] ('''neuer Abgabetermin''' 29.5.2008) und [[Media:muster_blatt5.pdf|Musterlösung]]&lt;br /&gt;
#* Implementation und Analyse eines Binärbaumes&lt;br /&gt;
#* Anwendung: einfacher Taschenrechner&lt;br /&gt;
&amp;lt;!------------&lt;br /&gt;
# [[Media:Übung-6.pdf|Übung]] (Abgabe 5.6.2008) und [[Media:muster_blatt6.pdf|Musterlösung]]&lt;br /&gt;
#* Treap-Datenstruktur: Verbindung von Suchbaum und Heap&lt;br /&gt;
#* Anwendung: Worthäufigkeiten (Dazu benötigen Sie das File  [http://klimt.iwr.uni-heidelberg.de/mip/people/ukoethe/download/die-drei-musketiere.txt die-drei-musketiere.txt]. Die Zeichenkodierung in diesem File ist Latin-1.)&lt;br /&gt;
#* Suche mit linearer Komplexität&lt;br /&gt;
&amp;lt;!------------&lt;br /&gt;
# [[Media:Übung-7.pdf|Übung]] (Abgabe 12.6.2008) und [[Media:muster_blatt7.pdf|Musterlösung]]&lt;br /&gt;
#* Übungen zu Rekursion und Iteration: Fakultät, Koch-Schneeflocke, Komplexität rekursiver Algorithmen, Umwandlung von Rekursion in Iteration&lt;br /&gt;
&amp;lt;!------------&lt;br /&gt;
# [[Media:Übung-8.pdf|Übung]] (Abgabe 19.6.2008) und [[Media:muster_blatt8.pdf|Musterlösung]]&lt;br /&gt;
#* Elementare Graphenaufgaben: Aufstellen von Adjazenzmatrizen und Adjazenzlisten, planare Graphen&lt;br /&gt;
#* Übungen zur Generizität: Sortieren mit veränderter Ordnung, Iterator für Tiefensuche&lt;br /&gt;
&amp;lt;!------------&lt;br /&gt;
# [[Media:Übung-9.pdf|Übung]] (Abgabe 26.6.2008)&lt;br /&gt;
#* Fortgeschrittene Graphenaufgaben: Erzeugen einer perfekten Hashfunktion, Routenplaner (Dazu benötigen Sie das File  [http://klimt.iwr.uni-heidelberg.de/mip/people/ukoethe/download/entfernungen.txt entfernungen.txt]. Die Zeichenkodierung in diesem File ist Latin-1.)&lt;br /&gt;
&amp;lt;!------------&lt;br /&gt;
# [[Media:Übung-10.pdf|Übung]] (Abgabe 3.7.2008) und [[Media:loesung_blatt10.pdf|Musterlösung]] sowie schöne [[Media:ballungsgebiete.pdf|Visualisierung der Ballungsgebiete]] von Thorben Kröger&lt;br /&gt;
#* Fortgeschrittene Graphenaufgaben 2: Clusterung mittels minimaler Spannbäume, Problem des Handelsreisenden (Eine &amp;lt;font color=red&amp;gt;neue Version&amp;lt;/font&amp;gt; der Datei [http://klimt.iwr.uni-heidelberg.de/mip/people/ukoethe/download/entfernungen.txt entfernungen.txt] ist verfügbar. Dank an Sven Ebser, Joachim Schleicher und Thorben Kröger für Hilfe bei der Verbesserung der Datei.)&lt;br /&gt;
&amp;lt;!------------&lt;br /&gt;
# [[Media:Übung-11.pdf|Übung]] (Abgabe 10.7.2008)&lt;br /&gt;
#* Erfüllbarkeitsproblem, Anwendung: Heim- und Auswärtsspiele im Fussball (Dazu benötigen sie das File [http://klimt.iwr.uni-heidelberg.de/mip/people/ukoethe/download/bundesliga-paarungen-08-09.txt bundesliga-paarungen-08-09.txt].)&lt;br /&gt;
#* Randomisierte Algorithmen: RANSAC für Kreise (Dazu benötigen sie das File [http://klimt.iwr.uni-heidelberg.de/mip/people/ukoethe/download/noisy-circles.txt noisy-circles.txt].)&lt;br /&gt;
&amp;lt;!-------------&lt;br /&gt;
# [[Media:Übung-12.pdf|Übung]] (&amp;lt;font color=red&amp;gt;Achtung: Abgabe bereits am Mittwoch, 16.7.2008&amp;lt;/font&amp;gt;)&lt;br /&gt;
#* Greedy-Algorithmen und Dynamische Programmierung&lt;br /&gt;
&amp;lt;!----------------&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Sonstiges ==&lt;br /&gt;
* [[Gnuplot| Gnuplot Kurztutorial]]&lt;/div&gt;</summary>
		<author><name>Stephan.meister</name></author>	</entry>

	<entry>
		<id>https://alda.iwr.uni-heidelberg.de/index.php?title=Gnuplot&amp;diff=4938</id>
		<title>Gnuplot</title>
		<link rel="alternate" type="text/html" href="https://alda.iwr.uni-heidelberg.de/index.php?title=Gnuplot&amp;diff=4938"/>
				<updated>2012-05-08T11:41:00Z</updated>
		
		<summary type="html">&lt;p&gt;Stephan.meister: Created page with '= Gnuplot Kurztutorial =  == Plotten von Daten aus einer Datei ==  Bsp. data.txt mit 4 x/y1/y2 Wertepaaren:     1  2.0  1.0     2  3.0  4.0     3  5.5  7.9     4  3.1  16.1  Plot…'&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Gnuplot Kurztutorial =&lt;br /&gt;
&lt;br /&gt;
== Plotten von Daten aus einer Datei ==&lt;br /&gt;
&lt;br /&gt;
Bsp. data.txt mit 4 x/y1/y2 Wertepaaren:&lt;br /&gt;
    1  2.0  1.0&lt;br /&gt;
    2  3.0  4.0&lt;br /&gt;
    3  5.5  7.9&lt;br /&gt;
    4  3.1  16.1&lt;br /&gt;
&lt;br /&gt;
Plotten der Punkte x/y1:&lt;br /&gt;
 plot 'data.txt' using 1:2 # 1:2 gibt die 1. und 2. Spalte in der Datei an&lt;br /&gt;
Plotten der Punkte x/y2 mit Verbindungslinien und Legendeneintrag:&lt;br /&gt;
 plot 'data.txt' using 1:3 with lines title 'some Data'&lt;br /&gt;
&lt;br /&gt;
== Fitten einer Funktion ==&lt;br /&gt;
 f(x) = a*x**2+b*x+c #Definieren der Funktion:&lt;br /&gt;
 fit f(x) 'data.txt' using 1:3 via a,b,c #Fit der x/y2 Daten:&lt;br /&gt;
 plot 'data.txt' using 1:3, f(x)&lt;br /&gt;
&lt;br /&gt;
== Speichern des Plots als Bilddatei ==&lt;br /&gt;
 set term png size 800,600 #Ausgabeformat auf png Bilddatei mit 800x600 pixeln umstellen&lt;br /&gt;
 set output 'plot.png' #Dateiname für Ausgabe festlegen&lt;br /&gt;
 replot #vorherigen plotbefehl wiederholen&lt;br /&gt;
 set output #offene Bilddatei schließen&lt;br /&gt;
 set term wxt #Ausgabe wieder auf Bildschirmausgabe zurückschalten, Alternativ auch: set term x11 oder set term windows&lt;br /&gt;
&lt;br /&gt;
== Nützliche Kommandos ==&lt;br /&gt;
 set xlabel 'Something' #Setzen der Achsenbeschriftung&lt;br /&gt;
 set title 'Bla' #Plot Überschrift&lt;br /&gt;
 set logscale y #Y-Achse logarithmisch skalieren&lt;br /&gt;
 set xrange [0:10] #Plotbereich auf der X-Achse festlegen&lt;br /&gt;
 set grid #Raster anzeigen&lt;br /&gt;
 pwd #Anzeige des aktuellen Arbeitsverzeichnisses&lt;br /&gt;
 cd 'pfad' #wechsel des aktuellen Arbeitsverzeichnisses&lt;br /&gt;
&lt;br /&gt;
== Hinweise ==&lt;br /&gt;
* Gnuplot benutzt entweder . oder , als Dezimaltrennzeichen (Abhängig von der Spracheinstellung des Systems). Wenn die Werte abgeschnitten werden muss in der Datei mit den Werten . durch , oder anders herum ersetzt werden.&lt;br /&gt;
* Alle Plotbefehle können auch in eine Datei (z.B. plot.txt) geschrieben und dann mit load('plot.txt') ausgeführt werden.&lt;br /&gt;
* Weitere Informationen und Tipps findet man z.B. bei [http://t16web.lanl.gov/Kawano/gnuplot/index-e.html Gnuplot Not-so-FAQ] oder [http://www.gnuplot.info/documentation.html]&lt;br /&gt;
* Lange Plotbefehle (mit mehreren Kurven/Datensätzen in einem Graph) können mit \ in mehrere Zeilen aufgebrochen werden&lt;/div&gt;</summary>
		<author><name>Stephan.meister</name></author>	</entry>

	</feed>